给公众号装上双引擎:7-Agent 写作管线多账号分线改造实录

给公众号装上双引擎:7-Agent 写作管线多账号分线改造实录

给公众号装上"双引擎":一套 7-Agent AI 写作管线从单号到多账号分线的完整实操

一个人运营的微信公众号矩阵是怎么炼成的:git 基线 → 配置化分线改造 → 错峰定时 → 凭证验证 → 端到端实测 → 性能归因。这篇文章把整个过程的命令、配置和踩过的坑完整记录下来——所有代码都在生产环境跑通过。

先交代背景:我有一套每天自动运行的公众号写作管线,系统 crontab 触发后,依次调用 7 个 AI Agent 完成流水线:

选题 → 研究 → 风格策划 → 写作 → 配图 → 审核 → 推送(只进草稿箱,人工确认后发布)

以前它只服务一个号(社会民生类)。这次的目标是:同一个清晨,两个号各自收到一篇定位完全不同的文章——一号继续写民生追问体,二号"马见寻常"专注日常生活美学。

一、先立回滚点:给存量管线建 git

改造生产环境第一件事永远不是写代码,而是建立可回滚的基线:

cd /root/wechat_pipeline
git init -q
cat > .gitignore << 'EOF'
.env
accounts.json          # 含 appid/secret,绝不能进版本历史
run_daily.log
orchestrator_state.json
.style_state*.json
workspace/
backups/
__pycache__/
EOF
git add .gitignore orchestrator.py publish_article_cloud.py prompts/ themes/
git -c user.name=hermes -c user.email="hermes@local" \
    commit -q -m "基线:改造前快照"

此后每个逻辑变更单独提交,出问题 git checkout <sha> -- . 一条命令回滚。整个改造结束时 log 长这样:

a36eb8f Phase2: prompts 领域参数化 + 账号主题强制 + 单号定向推送
001ffcc Phase1: orchestrator 多线循环(run隔离/风格状态拆分/跨线查重)
dd6d8de 基线:单线管线(改造前快照)

踩坑记录:.gitignore 必须把 accounts.json、.env 挡在外面。公众号 appsecret 一旦进了 git 历史,删文件都没用,得改写历史。

二、配置即档案:vertical 字段设计

新架构的核心思想:加一个号 = 加一份内容档案,零代码改动。账号表 accounts.json 里每个号带一个 vertical 块:

{
  "name": "马见寻常",
  "enabled": true,
  "appid": "wx…",
  "secret": "***",
  "theme": "lifestyle",
  "author": "马见寻常",
  "vertical": {
    "category": "日常生活与生活美学",
    "topic_seeds": ["极简居家打理", "日常情绪养护", "低成本生活美学",
                     "作息与习惯养成", "城市轻生活方式"],
    "style_bias": "温和务实·落地感强·给出可直接上手的小方案,少鸡汤多真实体验",
    "theme": "lifestyle",
    "freshness_min": 4
  }
}

五个字段各管一段:

字段 作用 生效位置
category 领域边界,选题/防串味/审核依据 选题 Agent、审核 Agent 的 prompt 头部
topic_seeds 优先选题池,选题飘忽时的抓手 选题 Agent
style_bias 文风倾向,框定风格轮换的象限 风格策划 Agent + 写作 Agent
theme 排版主题;写死具体值=强制,auto=每篇自选 发布环节选择 CSS
freshness_min 时效门槛(1-10),低于它整条线跳过不发 orchestrator 校验闸门

民生号没有 vertical 时走 default_vertical() 缺省档案(category=中国社会民生、追问体、freshness_min=5),保证改造前行为完全等价——这是回归兼容的关键设计。

三、orchestrator:单线 main 重构为多线循环

原来 main() 直接跑 7 步;现在抽出一个 run_line(account),一个账号 = 一条完整生产线:

def run_line(account, ts):
    safe = re.sub(r"[^\w\u4e00-\u9fff-]+", "_", account["name"])
    run_id = f"{ts}-{safe}"              # 工作区按 日期-账号 隔离
    run_dir = WORKSPACE_DIR / run_id
    ctx = {"run_id": run_id, "step": 0, "account": account}
    for step_name, step_func in STEPS:
        ok, err = step_func(run_dir, ctx)
        if not ok:
            return {"status": "failed", "error": f"{step_name}: {err}", ...}
    ...
    return {"status": "success", "media_id": ..., ...}

def main():
    ts = new_run_id()
    records = []
    for account in load_pipeline_accounts():   # 读 accounts.json
        try:
            rec = run_line(account, ts)
        except Exception as e:                  # 单线崩溃不影响其他线
            rec = {"account": account["name"], "status": "failed", "error": str(e)}
        records.append(rec)
    save_state(records)
    return 0 if any(r["status"] == "success" for r in records) else 1

配套四个机制:

1. 注入账号上下文——每个 Agent 的 prompt 头部自动生成:

【当前账号】马见寻常
【内容定位】日常生活与生活美学
【风格倾向】温和务实·落地感强…
【本线要求】选题、写作、审核都必须服务于该账号定位;串味内容一律不合格。

2. 风格历史按账号拆分——.style_state_马见寻常.json,每号独立做 30 期风格轮换,互不污染:

def style_state_path(account_name):
    p = PIPELINE_DIR / f".style_state_{account_name}.json"
    if not p.exists() and LEGACY.exists():
        p.write_text(LEGACY.read_text())   # 旧数据自动迁移
    return p

3. 跨线防撞题——recent_topics.json 记录全矩阵近 14 天写过的话题,民生号写过"饭店宰客",生活号想写同一事件会被闸门直接打回换题:

for it in load_recent_topics(days=14):
    if topic in it["topic"] or it["topic"] in topic:
        return False, f"与 {it['date']} 账号[{it['account']}]撞题"

4. 发布定向——推送脚本带 --account 马见寻常,每号只进自己草稿箱;图片素材按号隔离上传(两号的 media 库互不相通)。

另外给马见寻常新建了一套 lifestyle 排版:themes/lifestyle.css,鼠尾草绿 #4a6b5d 主色、行距 2.0、圆润引用块、重点句浅绿荧光底——生活美学文章的视觉调性,并注册进风格策划 Agent 的主题库。

四、定时任务:改到夜间半价窗口

阿里云 Token Plan 夜间(22:00–08:00)token 半价,而管线凌晨跑完全不影响早上看稿:

# crontab -e:06:00 启动
0 6 * * * bash /root/wechat_pipeline/run_daily.sh >> /root/wechat_pipeline/run_daily.log 2>&1

# Hermes 内的对话总结任务:8:00 → 7:00
# (hermes cron update 615bc7a41ffe --schedule "0 7 * * *")

双线 45 分钟的跑批从 06:00 开始,07:00 前后两篇稿子全部进草稿箱,人起床即收稿——半价窗口整整 10 小时,只用掉 7%。

五、验证凭证:先测 token 再上线

新号凭证填完,别等明早跑批才发现 secret 错了。10 秒钟的预检:

r = requests.get("https://api.weixin.qq.com/cgi-bin/token", params={
    "grant_type": "client_credential", "appid": appid, "secret": secret})
if "access_token" in r.json():
    print("✅ 凭证有效,且 IP 白名单已包含本机")
else:
    print("❌", r.json()["errcode"], r.json()["errmsg"])

errcode 40164 就是忘配 IP 白名单(公众号后台把服务器 IP 加进白名单);41004/40013 则是 secret 填错。这一步同时验证了凭证和白名单两件事,只耗一个 GET。

六、端到端实测:一晚抓出三个真 bug

dry-run 全绿的代码,实跑第一次就崩了。这正是为什么要真人时段跑测试而不是直接等凌晨 cron:

Bug 1:import re 缺失。 run_line() 里用了 re.sub,但文件头没导入。py_compile 只查语法不查名字解析,静态检查看不见它,一运行就 NameError。修复:补一行 import,提交 git。

Bug 2:子进程用了错的 python。 orchestrator 调配图兜底脚本时写死 python3(系统解释器),而系统 python3 没装 requests——两条线全死在配图环节。修复是双保险:orchestrator 改用 venv 绝对路径 + 给系统 python3 补装 requests(防 cron 裸环境)。

Bug 3:theme 强制语义漏洞。 账号显式指定 theme: lifestyle,但代码仍让风格 Agent 的选择覆盖它。修复:显式指定即强制。

修复后的完整跑批结果(end lines=2 success=2):

=== 人间观察笔记(17分钟)===
  选题   《一秒点1000下抢专家号,他判了一年》   热度8 时效10 → 民生追问体
=== 马见寻常(28分钟)===
  选题   《收假第一天撞上寒露:咱把作息慢慢掰》 热度8 时效10 → 反差体·冷静克制
  排版   lifestyle(Agent 自选也是它,强制一致)

同一个早上、同一个热点池,两个号从选题、文风到排版完全分叉,且互相同题互斥——分线目标达成。

七、性能账本:两篇文章为什么跑了 44 分钟

逐 Agent 打点拆出来的时间线:

阶段 民生线 生活线 耗时都花在哪
选题 1'56" 3'11" 5-15 次 web 搜索 + 候选评估
研究 4'35" 5'37" 逐条搜证、打开来源页、交叉核实
风格 1'49" 2'12" 读历史 + 选风格 + 选主题
写作 3'17" 6'01" 长文生成,生活稿结构更复杂
配图 2'12" 2'27" 提取来源 og:image + 图库搜索兜底
审核 1'34" 7'23" 生活线自己去查了气象资料核实"寒露交节时间"
推送 0'32" 0'46" 上传素材 + draft/add

四个根因:

  1. 每步都是全量 Agent 会话:7 步 = 14 次进程冷启动 + system prompt 加载,固定开销约 3-5 分钟。最夸张的是 publisher——实际干活 10 秒,整个会话花 40 秒
  2. 步骤内部是多轮工具循环:researcher 一次要执行 20-40 个搜索/读页动作,这是"该花的时间"
  3. 线间串行是设计决定:为规避 API 限流和并发排障复杂度,两线总时长 = 17'+28'
  4. 质量换时间:生活线比民生线慢 11 分钟,慢在审核 Agent 对无权威来源的话题自发加深了核查

结论:旧单线跑一篇要 38 分钟,现在双线 44 分钟——每线的边际成本其实更低。而且它是无人值守凌晨跑的,44 分钟只占半价窗口的 7%,快慢根本不是矛盾点。真想压时间,线间并行(改 15 行代码,44'→28')和发布环节去 Agent 化(省 1.5')都是现成选项。

八、留给下次的命令备忘

# 回滚任意阶段
cd /root/wechat_pipeline && git checkout dd6d8de -- orchestrator.py prompts/

# 只重推某一号的草稿(先手动生成好 md+三图)
python publish_article_cloud.py --md article_daily_马见寻常.md \
  --cover … --body1 … --body2 … --account 马见寻常 --theme lifestyle

# 新增第三号 = 只在 accounts.json 加一段带 vertical 的配置,次日生效

# 查近 14 天全矩阵撞题库
cat recent_topics.json | python3 -m json.tool

整套改造实际写的核心代码不到 200 行,剩下的全是配置和 prompt 措辞。多 Agent 管线的可维护性大概就是这样:逻辑收敛在一个 orchestrator,变化全在数据文件里。

评论 (0)

发表评论