用 Hermes Agent 搭了个全自动内容发布系统,三天里推翻了两次架构。最后想明白一件事:Harness 就两个维度——控制流决定"谁来做决策",数据流决定"数据长什么样"。
大模型本质上就是一个函数——你扔一句话进去,它吐一句话出来。但它自己决定不了先做什么、后做什么、出了错怎么办。这些控制流的事,得交给代码来管。
如果你让大模型自己管流程,就是用一个不靠谱的东西去管另一个不靠谱的东西。让代码管流程、把大模型压成一个只管输入输出的函数,才是正道。
从一个具体问题说起
我有一个英文 WordPress 站,内容来源是中文平台。手动流程就是:找文章、读原文、改写英文、登录后台发布。每天重复,枯燥耗时。
需求拆开很简单:采集、改写、发布。三步。
目标是用 Hermes Agent 的定时任务实现全部自动化。但这个过程中,我真正解决的不是"怎么采集"、"怎么改写"这些技术问题,而是反复在想一个更底层的问题:
整个流程中,谁在做决定?
这不是一个工程问题,这是一个关于编排模式的问题。
什么是 Harness
当你把 LLM 引入一个系统,不管是做内容改写、代码生成还是流程自动化,你都绕不开一个核心问题:怎么驾驭它?
LLM 不是普通函数。你给它同样的输入,它可能给你不同的输出。它有理解力,但理解力不等于可靠性。它有创造力,但创造力不等于可重复性。
Harness 就是解决这个问题的工程实践:在系统中,以什么方式使用 LLM,让它既能干活,又不失控。
这不是一个新问题。每次有新技术进来,都会面对类似的 harness 问题——怎么驾驭数据库(ORM),怎么驾驭微服务(编排),怎么驾驭分布式(一致性协议)。LLM 只是给了这个问题一个新的维度。
三天的实践让我走过了三种 harness 模式。每一种都是对"怎么驾驭 LLM"的不同回答。
第一种 Harness:让 LLM 驾驭一切
最初的方案非常"AI 原生"。
我写了一个 SKILL.md,用自然语言描述整个流程——"先从信息源采集文章,提取正文,检查是不是广告,如果不是,用 Keystem 模型分析视角,再用 Mimeng 风格改写成英文,最后发布到 WordPress"。然后让 Hermes Agent 读这个文件,自己决定每一步调用哪个 Python 脚本。
这个 harness 模式的隐含假设是:LLM 是驾驭者,Markdown 是缰绳,Python 是马。
SKILL.md(意图声明)
↓
LLM(驾驭者)
↓
Python 脚本(执行工具)
然后频繁跟 Agent 沟通——提需求、测试、改。大部分时候能跑通。但不对劲的地方逐渐暴露:
控制流失控: 同一篇文章,LLM 有时候先采集再检查质量,有时候先检查质量再采集。有时候它会跳过某个步骤,理由是"我认为不需要"。有时候它多调用了一次 LLM,因为它"忘记了之前已经分析过"。
数据流失控: LLM 的输出是自由格式的自然语言。下游的 LLM 需要"理解"上一步的输出,才能决定下一步做什么。但"理解"本身是概率性的——有时候它把标题当成了正文,有时候它忽略了 FAQ 部分,有时候输出的 JSON 格式不对。
由于需要调用改写风格的 SKILL.md,上下文爆炸,质量不可控,格式不可控。
当 LLM 同时驾驭控制流和数据流,系统行为就不可预测。
更深的问题是成本。每次执行,LLM 都要把整份流程文档读一遍、理解一遍、决策一遍。定时任务每天跑 24 次,每次都要花 token 让 LLM "想清楚要做什么"——而这个"什么"从来没有变过。
我在为一个确定性的流程,反复支付"理解"的成本。
这种 harness 模式适合什么场景?适合探索期。当你还不知道流程是什么的时候,你需要 LLM 的"理解力"帮你试错。事实上,用 Hermes Agent 和 SKILL.md 快速原型的方式,让我在第一天就跑通了整个链路。没有这个阶段,我根本不知道 Python 代码应该编排什么。
但它不适合生产。让 LLM 驾驭一切的前提是:流程还不确定。一旦确定了,就应该把驾驭权收回来。
第二种 Harness:用代码驾驭 LLM
意识到问题后,我做了激进的改变:用 Python 编排全流程,LLM 只在具体任务中被调用。
Python(驾驭者)
↓ 决定执行顺序
↓ 决定是否跳过/重试
↓ 决定错误处理
↓
LLM(被驾驭的工具)
↓ 只负责:判断质量、分析视角、生成内容
这个 harness 模式完全反转了:代码是驾驭者,LLM 是被驾驭的工具。
我用 Python 定义了明确的流程:先采集,逐条去重,通过去重的做质量检查,通过检查的做改写,改写完的发布。每一步的输入输出都是强类型。每一步的错误处理都是确定的。失败的留在缓冲区,下一轮自动重试。
同时,我也开始给数据流加上结构约束:
- 采集器必须输出 FetchResult(title/url/content/sync_id)
- LLM 的输入是结构化 prompt,输出必须是 JSON(title/content/faq/tags)
- publisher 的输入必须符合 WordPress API 规范
效果立竿见影:
- 可靠性从"看运气"变成"每次都一样"
- LLM 调用从 4 次压缩到 2 次(合并 prompt)
- token 消耗减半
- 定时任务可以稳定跑了
但代价也很清楚:灵活性没了。
加一个新平台要写代码。改一个流程要改代码。调整策略要改代码。所有"变化"都变成了"改代码→测试→部署"这个链条。
而实际上,有些东西变的频率是不一样的。模型会换,源会加,标签策略会调整——这些是高频变化。采集→去重→检查→改写→发布——这个主流程从确定之后就没变过。
用同一种 harness 方式处理不同频率的变化,是第二种模式的根本问题。
第三种 Harness:分层驾驭
第三种模式不是折中,而是真正想清楚之后的结果。
核心洞察:不同层次的决策,应该用不同的介质来驾驭;不同层次的数据,应该用不同的约束来管理。
控制流(谁来驾驭) 数据流(什么约束) ┌─────────────────┐ ┌─────────────────┐ │ Python 代码 │ │ FetchResult schema│ │ 流程编排,几乎不变│ │ JSON 结构约束 │ ├─────────────────┤ ├─────────────────┤ │ config.json │ │ config schema │ │ 策略配置,经常变 │ │ 字段定义 │ ├─────────────────┤ ├─────────────────┤ │ LLM │ │ prompt + JSON │ │ 内容智能,每篇不同│ │ 输入输出结构化 │ ├─────────────────┤ ├─────────────────┤ │ SKILL.md │ │ 设计文档 │ │ 设计意图,偶尔更新│ │ 记录数据契约 │ └─────────────────┘ └─────────────────┘
换模型?改 config.json。加一个新信息源?写一个采集器模块 + config.json 加一行配置,路由自动识别。改写风格变了?改 prompt 配置。主流程变了?这很少发生,改代码。
该确定的确定,该灵活的灵活,该智能的智能。
这种 harness 模式吸收了第一种的"声明式"优点(配置驱动),保留了第二种的"确定性"优势(代码编排),同时把 LLM 的能力严格限定在它真正擅长的范围内。
数据流转中的 Harness 契约
决策权分配解决的是控制流问题——谁来驾驭下一步。但还有一个同等重要的问题:数据以什么形态在系统中流动。
这个认知来自一个具体的教训:去重。
最初没有去重,同一篇文章被采集、改写、发布了三次。解决方案是给每篇文章一个唯一标识(sync_id)。但这个标识不是随便起的——它必须满足一个约束:在数据流转的全生命周期中,同一份内容始终映射到同一个 ID。
| 平台 | sync_id 格式 | 生成规则 |
|---|---|---|
| Hacker News | hn-{item_id} | 从 URL 中的 item ID 提取 |
| 信息源 A | wspm-{category}-{id} | 从文章 URL 结构化提取 |
| 信息源 B | zhihu-hot-{qid} | 从热榜链接的 qid 提取 |
| 信息源 C | zhihu-daily-{story_id} | 从日报 API 的 story_id 提取 |
sync_id 同时承担了三个角色:
- 去重数据库的主键——判断这篇文章是否已经处理过
- 缓冲区文件的文件名——采集和发布解耦的桥梁
- WordPress meta field 的值——发布后反查的依据
一个标识,三处复用。如果这三个环节用了不同的标识方式,系统就会分裂——去重认为是新文章,缓冲区里其实已经有了,WordPress 上已经发布过了。
sync_id 不是一个技术细节。它是数据流转的 harness 契约。
再看其他环节,每一个交接点都有类似的结构约束:
采集器输出 → 必须是 FetchResult(title/url/content/sync_id)
↓
去重检查 → sync_id 必须唯一
↓
LLM 输出 → 必须是 JSON(title/content/faq/tags)
↓
publisher 输入 → tags 必须是 ID 不是 name
↓
WordPress → meta field 必须在主题中注册
任何一环的结构不匹配,下游就会报错。而这类错误往往是最难调试的——因为数据"看起来是对的",但格式或类型不对。
我踩过几个坑:
采集器输出没有 content 字段。 下游的质量检查和改写都需要正文内容,但采集器默认只返回了标题和 URL。结果 LLM 收到空内容,生成了一篇"基于标题猜测"的文章。
LLM 输出的 JSON 引号未转义。 内容里有双引号,破坏了 JSON 结构。直接 json.loads() 失败。需要多层 fallback 解析。
publisher 传了 tag name 而不是 ID。 WordPress Posts API 的 tags 字段只接受 integer ID。传了 ["AI", "Tech"] 进去,API 静默忽略,标签全丢了。
这些都不是逻辑错误,都是结构错误。逻辑错误会报异常,结构错误往往静默失败——数据流转过去了,但结果是错的,而且你不知道哪里错了。
Harness 的两个正交维度
回过头看,整个系统的设计其实是两个正交维度的交织:
控制流 数据流
(谁来做决定) (数据长什么样)
模式一 LLM 驾驭 自由格式
模式二 Python 驾驭 强类型约束
模式三 分层驾驭 分层约束
模式一不仅让 LLM 驾驭控制流,也放任数据以自由格式流动——LLM 的输出不需要符合任何 schema,下游的 LLM 自己"理解"就好。这在探索阶段没问题,但到了生产阶段,"理解"本身就是不确定性。
模式三不仅分层分配了驾驭权,也在每一层的边界上定义了数据契约:
config.json → 定义了 source 的 schema(name/type/url/platform) 采集器 → 必须输出 FetchResult 缓冲区 → JSON 文件,字段固定 LLM → 输入输出都是结构化 prompt + JSON publisher → 输入必须符合 WP API 规范
控制流的可靠性来自驾驭权的确定性分配。数据流的可靠性来自每一层边界的结构约束。两者缺一不可。
单独做好一个不够。我见过只有控制流约束、没有数据流约束的系统——流程是对的,但每一步的数据格式全靠"默契",一旦 LLM 输出变了一点,整条链路就断。也见过只有数据流约束、没有控制流约束的系统——每个接口都有 schema,但调用顺序完全由 LLM 决定,今天走 A→B→C,明天走 B→A→C。
Harness 的本质是同时驾驭控制流和数据流。
Harness 的演进路径
三种模式不是三种技术方案,而是三个认知阶段:
模式一 模式二 模式三 LLM 驾驭一切 代码驾驭 LLM 分层驾驭 ←————————————————————————————→ 灵活但不可靠 可靠但不灵活 分层可控
大多数自动化项目的 harness 演进路径是:
先让 LLM 驾驭一切(探索),再用代码驾驭 LLM(固化),最后用分层驾驭平衡确定性与灵活性(生产)。
走完这三个阶段,你就完成了从"探索"到"生产"的跨越。而这个跨越的本质,是驾驭权的一次次重新分配。
一个更深的问题
走完三种模式之后,我开始想一个更根本的问题:
当 LLM 变得越来越强大,模式一会不会重新成为最优解?
如果 LLM 的理解力足够强、输出足够稳定、成本足够低——我们还需要用代码来驾驭它吗?还需要结构化约束来管理数据流吗?
我觉得短期内答案还是"需要"。原因不是 LLM 不够强,而是确定性是一种独立于智能的属性。
一个足够聪明的人,也不适合做流水线——不是因为他做不好,而是因为他每次做出来的东西不完全一样。而自动化系统需要的恰恰是:同样的输入,永远是同样的输出。
LLM 擅长的是理解和创造。代码擅长的是确定性和可重复性。配置擅长的是隔离变化。文档擅长的是传递意图。
它们解决的是不同的问题。不是能力高低的问题,是性质不同的问题。
Harness 的目标从来不是"让 LLM 做最多的事",而是"让 LLM 在对的地方做对的事"。
写在最后
三天时间,两次推翻,从 SKILL.md 驱动到 Python 编排,最终落地到分层 harness 架构。这个过程教会我的不是怎么写代码,而是怎么思考两个正交的问题:
控制流:谁来驾驭? 把驾驭权分配给合适的层——流程交给代码,策略交给配置,理解力交给 LLM。
数据流:什么约束数据? 在每一层的边界上定义结构契约——输入是什么格式,输出是什么格式,标识怎么生成,失败怎么处理。
如果你也在用 Hermes Agent 或类似的 AI Agent 平台构建自动化系统,建议在动手之前先问自己两个问题:
在这个流程里,你希望谁来驾驭?数据在每一层之间流动时,长什么样?
把这两个问题想清楚,Harness 自然就清楚了。
附:WordPress 自动化内容发布 Agent 开发心得
一、项目背景
构建一个自动化内容发布系统:从多个中文内容源采集文章 → AI 改写为英文 → 发布到 WordPress 站点。整个流程需要全自动运行(定时任务),不用人盯。
二、架构演进过程
最初的形态:Markdown 驱动
最早的方案是用 SKILL.md 定义流程意图,让大模型读完之后理解"要干嘛",然后调用对应的 Python 脚本。
SKILL.md(流程定义)
↓ 大模型读取理解
↓ 决定调用哪个脚本
Python 脚本(工具)
本质:大模型是操作系统,markdown 是程序,Python 是系统调用。
好处是灵活——改 markdown 就能改流程。但问题一堆:
- 不靠谱 — 大模型每次都要重新理解流程,可能理解错
- 费钱 — 每次执行都要 LLM 读完整个流程文档
- 不适合定时任务 — 自动化场景不能依赖 LLM 的"理解力"
演进为:代码驱动
最终方案改成 Python 编排全流程,LLM 只在具体环节被调用:
main.py(Python 编排)
↓ Python 决定流程
↓ 调 LLM 做内容改写
↓ 调 WordPress API 发布
LLM 只是被调用的组件
本质:Python 是操作系统,LLM 是一个函数。
两种模式的对比
| 维度 | Markdown 驱动 | 代码驱动 |
|---|---|---|
| 谁做决策 | 大模型 | Python 逻辑 |
| 可靠性 | 取决于 LLM 理解力 | 确定性执行 |
| 灵活性 | 高(改 md 即改流程) | 低(改代码) |
| 成本 | 高(每次 LLM 理解全流程) | 低(LLM 只做具体任务) |
| 适合场景 | 探索期、流程未定型 | 生产环境、流程已定型 |
最终选择代码驱动的原因:
- 定时任务要可靠——LLM 不能每次都重新理解流程
- 成本要可控——4 次 LLM 调用压缩到 2 次
- 流程已定型——采集→改写→发布,不需要 LLM 决定"下一步做什么"
但保留了配置驱动的灵活性——换模型、换源、换标签策略,只改 config.json,不动代码。这吸收了 markdown 驱动的"声明式"优点。
三、最终架构
wp-agent/
├── config.json # 配置(凭证/分类/LLM/常量)
├── main.py # 入口(--fetch / --publish / --status)
├── readme.md # 说明
├── SKILL.md # Hermes 技能文档
├── core/ # 基础模块
│ ├── config.py # 配置加载器(读 JSON + .env)
│ ├── models.py # FetchResult 数据模型
│ ├── dedup.py # SQLite 去重 + 状态追踪
│ ├── buffer.py # 中间缓冲区(raw/)
│ └── lock.py # 进程锁(防并发)
├── fetchers/ # 采集层
│ ├── router.py # 路由(配置驱动,动态加载)
│ ├── discover.py # 自动发现(RSS/API)
│ ├── extractor.py # 正文提取(trafilatura)
│ ├── hackernews.py # HN 采集器
│ ├── woshipm.py # 人人都是产品经理采集器
│ └── zhihu.py # 知乎采集器(热榜+日报)
├── transformer/ # 改写层
│ └── ai_trans.py # Keystem + Mimeng 双 skill 改写
├── publisher/ # 发布层
│ └── publisher.py # WordPress 发布
└── data/ # 运行数据
├── dedup.db # 去重数据库
└── raw/ # 采集缓冲区
核心设计原则
- 配置驱动 — 加新平台只改 config.json,不改代码
- 契约明确 — 每个函数的输入输出都是强类型
- 解耦优先 — 采集和发布通过 raw/ 缓冲区隔开
- 失败隔离 — 单条失败不影响其他,留在 raw/ 下次重试
- 零人工干预 — 定时任务全自动,锁防并发,限额可配
- LLM 尽量少调 — 4→2 次,合并 prompt 省延迟和 token
四、关键决策与思考
配置格式:YAML → JSON
最初用 YAML,需要 PyYAML 依赖。系统权限问题装不上,改成 JSON。Python 标准库直接解析,零外部依赖。
采集和发布解耦
最初是串行流程:discover → fetch → transform → publish,全在一个进程里。
问题:采集快(秒级),改写慢(分钟级),串行导致采集被改写拖住了。
解决方案:加 raw/ 缓冲区
之前:fetch → transform → publish(串行,一个卡全部卡) 现在:fetch → raw/ → publish(解耦,各自独立调度)
LLM 调用优化:4 次 → 2 次
最初每篇文章需要 4 次 LLM 调用:
| 次数 | 调用 | 作用 |
|---|---|---|
| 1 | quality_check | 判断是否广告/新闻稿 |
| 2 | KEYSYSTEM_PROMPT | 分析视角和框架 |
| 3 | MIMENG_PROMPT | 写文章 |
| 4 | _generate_faq | 生成 FAQ |
优化后合并为 2 次:
| 次数 | 合并内容 | 作用 |
|---|---|---|
| 1 | 质量检查 + Keystem 分析 | 一次调用搞定判断+分析 |
| 2 | Mimeng 写作 + FAQ 生成 | 一次调用搞定写作+FAQ |
效果:速度提升约 50%,token 消耗减半。
Keystem + Mimeng 双 Skill 联动
改写流程是两个 skill 的协作:
- Keystem(思考内核):选模型/视角,提供分析框架和深度观点
- Mimeng(写作引擎):造爆点标题,情绪驱动行文,输出最终 HTML
流程:Keystem 先想(选哪个模型、从什么角度分析)→ Mimeng 再写(用爆文风格把分析包装成能传播的内容)。
Mimeng 的 6 条写作铁律被完整保留在 prompt 里:
- 首段情绪钩子——第一段就要抓住读者
- 现象命名——给现象起一个能传播的名字
- 每 2-3 段一个
<strong>金句 - 态度鲜明,不中立
- 情绪曲线完整:低→中→高→爆发→收尾
- 站在读者立场
标签策略:随机抽 → 从已有标签选
最初是随机从固定池里抽标签,跟文章内容完全无关。
问题:标签不一致,WordPress 标签越来越多越来越乱。
最终方案:LLM 从 WordPress 已有标签里选 2-4 个,没有合适的才创建新的。
已有标签 → 优先选(保持一致性) 没有合适的 → 允许新建(SEO 增长) publisher 自动创建 WP 里不存在的标签
正文提取:HTML 正则 → trafilatura
最初用正则表达式从 HTML 提取正文,大部分网站抓不到内容。
解决方案:用 trafilatura(专门做正文提取的库),提取质量远超正则。
# 之前(正则,不可靠) title_match = re.search(r'<h1[^>]*>(.*?)</h1>', html, re.DOTALL) # 现在(trafilatura,可靠) result = trafilatura.extract(html, include_comments=False, include_tables=True)
sync_id 设计:去重主键
每篇文章有唯一标识,格式按平台区分:
| 平台 | 格式 | 示例 |
|---|---|---|
| Hacker News | hn-{item_id} | hn-48714529 |
| 人人都是产品经理 | wspm-{category}-{article_id} | wspm-ai-6422934 |
| 知乎热榜 | zhihu-hot-{qid} | zhihu-hot-2056091282 |
| 知乎日报 | zhihu-daily-{story_id} | zhihu-daily-9790812 |
sync_id 同时作为:
- dedup.db 的主键
- raw/ 缓冲区的文件名
- WordPress meta field
_site_sync_unique_id的值
WordPress Meta Field
发布时写入三个 meta field:
| Field | 用途 |
|---|---|
_site_sync_unique_id | 原文唯一标识,用于反查 |
_site_source_url | 原文链接 |
_site_faq_items | FAQ 结构化数据,single 页面模板读取渲染 |
注意:这些 meta field 得在 WordPress 主题里用 register_post_meta 注册,不然 API 不返回。
WordPress API 的 Tags 字段
Posts API 的 tags 字段只接受 integer ID,不接受 string name:
// ❌ 不支持
"tags": [{"name": "AI"}]
// ✅ 只支持
"tags": [17, 23]
所以 publisher 得做 name → ID 转换:GET /wp/v2/tags?search={name} 查是否已存在,不存在就 POST 创建。
Cloudflare 403 问题
Python 的 urllib 默认不带 User-Agent,被 Cloudflare 拦截(error code: 1010)。
解决方案:所有 HTTP 请求统一加浏览器 UA:
req.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36 Edg/149.0.0.0")
知乎源:热榜 + 日报
知乎有两个数据源,API 不一样:
| 源 | API | sync_id 格式 |
|---|---|---|
| 知乎热榜 | RSSHub RSS | zhihu-hot-{qid} |
| 知乎日报 | daily.zhihu.com API | zhihu-daily-{story_id} |
热榜的 RSSHub URL 是 XML 格式,得用 XML 解析器(不是 HTML 正则)。
进程锁:防并发
定时任务每小时触发一次,如果 publish 跑了 45 分钟,下一轮可能还没跑完就又触发了。
解决方案:锁文件机制
publish 开始 → 创建 data/publish.lock publish 运行中 → 新触发看到锁 → 跳过 publish 结束 → 删除锁 锁超过 30 分钟自动过期(防死锁)
配置驱动的路由
采集器路由不用硬编码,从 config.json 读取平台规则:
"platforms": {
"hn": { "domain": "ycombinator.com", "fetcher": "fetchers.hackernews" },
"woshipm": { "domain": "woshipm.com", "fetcher": "fetchers.woshipm" }
}
加新平台只需:
- 写一个
fetchers/new_platform.py(实现fetch(url) -> FetchResult) - 在 config.json 加一条平台配置
router.py 不用改任何代码,自动识别 + 动态加载。
自动发现层
定时任务得自动发现新内容,不能靠手动传 URL。
从 config.json 的 sources 配置自动拉取:
"sources": [
{ "name": "HN Front Page", "type": "hn_algolia", "url": "...", "platform": "hn" },
{ "name": "RSS Feed", "type": "rss", "url": "...", "platform": "woshipm" },
{ "name": "知乎热榜", "type": "zhihu_hot", "url": "...", "platform": "zhihu" },
{ "name": "知乎日报", "type": "zhihu_daily", "url": "...", "platform": "zhihu" }
]
支持的源类型:hn_algolia、rss、zhihu_hot、zhihu_daily。加新源类型只需在 discover.py 加一个解析函数。
质量检查:过滤广告/新闻稿
在改写前加一步 LLM 快速检查:
source_content → quality_check(只看前500字)
├─ REJECT → 跳过(广告/新闻稿/软文)
└─ PASS → 继续改写
一次 LLM 调用过滤广告,省掉了后面 3 次无用的改写调用。
JSON 解析容错
LLM 输出的 JSON 经常格式不标准(引号没转义、换行等)。多层 fallback 解析:
- 直接
json.loads() - 取
{到}之间的内容再解析 - 用正则提取 title/content/FAQ 字段
LLM 模型选择
测了 4 个免费模型:
| 模型 | 速度 | 质量 | 状态 |
|---|---|---|---|
| glm-5.2 | 中等 | ✅ 好 | 最终选用 |
| deepseek-v4-flash | 快 | ✅ 好 | 备选 |
| flash-lite | 快 | ❌ 差 | 不推荐 |
| u1-fast | - | - | 404 不可用 |
最终选定 glm-5.2,质量稳定。
五、踩过的坑
- 循环导入 — fetcherRouter 和 hackernews_fetcher 互相引用 → 抽出 models.py
- Cloudflare 403 — urllib 没有 User-Agent → 统一加浏览器 UA
- LLM 输出非标准 JSON — 内容里有引号/换行 → 多层 fallback 解析
- sync_id 假 ID — 测试时硬编码假 ID → 必须从 URL 生成
- 标签 name vs ID — WP API posts 只接受 tag ID → publisher 做转换
- meta field 名称 — 实际名称和预期不一致 → 必须跟 WP 主题一致
- cron 脚本路径 — 相对路径找不到 → 改成绝对路径
- 知乎热榜解析 — 当 HTML 解析 RSS XML → 改用 XML 解析器
- 批量归档误操作 — 手动移动所有 raw 文件 → 代码逻辑对但手动操作搞错了
- 模型质量差异 — flash-lite 输出内容短且差 → 选定 glm-5.2
六、定时任务设计
两个独立的定时任务,通过 raw/ 缓冲区解耦:
每小时整点 → fetch(采集,不涉及 LLM) 每小时15分 → publish(改写+发布,带锁防并发)
- fetch 秒级结束,publish 是分钟级
- 锁机制防止 publish 并发
- raw/ 只保留没处理的,处理完自动删
- 失败的留在 raw/ 下次重试
七、待优化
- 知乎热榜 Cloudflare 403 问题
- LLM 响应慢时的超时处理
- dedup.db 历史数据清理策略
- 多模型自动切换(主模型挂了自动切备选)
- 采集器并发(3 个源同时拉取)
- 更多内容源扩展(Twitter、Reddit、微博等)
八、核心结论
Markdown 驱动适合探索期,代码驱动适合生产期。
最好的做法是混合模式:
- Python 编排主流程(确定性、可靠)
- LLM 做具体任务(改写、判断)
- config.json 做策略配置(灵活性)
- SKILL.md 做文档说明(可读性)
四个各司其职,既可靠又灵活。
本文作者:ZKCOI
文章名称:Harness 的本质:驾驭控制流与数据流
文章链接:https://www.zkcoi.com/365up/ai-agent/4810.html
本站资源仅供个人学习和交流,如若转载,请注明出处,详见《免责声明》。
微信扫一扫
支付宝扫一扫 