资讯详情

资讯详情

AI Agent 总跑不稳?拆解 Harness 的 7 大核心子系统

1. 从模型很聪明但活干不明白说起如果你最近半年在折腾 AI Agent大概率经历过这种落差模型在对话里表现得像个专家一旦让它去完成一个多步骤的真实任务——比如读一份本地文档、查一次数据库、改一个文件、再跑一遍测试——它就开始掉链子。要么忘了自己刚才做了什么要么把工具调用的参数拼错要么在第 5 步之后彻底跑偏把前面 4 步的成果全推翻。很多人第一反应是模型不行换个更强的。但换了几轮模型之后你会发现问题依旧。真正的原因往往不在模型本身而在于模型外面那层脚手架没搭好。这层脚手架业内现在有个越来越统一的叫法——Harness。Harness 这个词直译是马具、挽具你可以把它理解成套在模型这匹马身上的那套缰绳、鞍具和车架。模型负责提供智力和推理Harness 负责把这份智力约束在正确的轨道上让它能稳定地、可重复地、可观测地完成一件具体的事。没有 Harness模型就是一个只会聊天的天才有了 Harness它才变成一个能真正干活的 Agent。那 Harness 到底由哪些东西组成我把它拆成了 7 个子系统。这个拆法不是教科书上的标准答案而是我在实际搭建和调试多个 Agent 项目之后总结出来的一套能对上号的结构。你会发现几乎所有跑得稳的 Agent 框架都能在这 7 个子系统里找到对应物而几乎所有跑不稳的 Agent问题也都能定位到这 7 个里的某一个或某几个。这篇文章适合三类人一是刚开始接触 AI Agent、想知道除了调模型还要做什么的新手二是已经搭过 Demo、但发现一上真实任务就崩的开发者三是想评估某个 Agent 框架到底靠不靠谱的技术选型者。我会把每个子系统讲清楚它解决什么问题、核心机制是什么、实际搭建时容易踩什么坑尽量让你看完就能对照自己的项目做体检。2. 子系统一Agent Loop——整个 Harness 的心跳2.1 为什么需要一个循环而不是一次调用最朴素的 Agent 实现是这样的把用户的问题拼成 prompt发给模型拿到回复结束。这在单轮问答里没问题但一旦任务需要多步就彻底不够用了。因为模型没法在一次输出里既思考又行动又观察结果——它输出一个工具调用请求之后必须有人去真正执行这个工具把结果喂回去模型才能基于结果决定下一步。这个模型输出 → 执行工具 → 结果回填 → 模型再输出的过程就是Agent Loop。它是整个 Harness 的心跳没有它其他 6 个子系统都没有存在的意义。一个最小可用的 Agent Loop 大概长这样while not done: response llm.chat(messages, toolstool_schemas) if response.has_tool_call: result execute_tool(response.tool_call) messages.append(tool_result_message(result)) else: done True final_answer response.content看起来简单但魔鬼全在细节里。2.2 循环终止条件最容易写出死循环的地方我见过太多 Agent 卡在死循环里烧 token。模型调用同一个工具、拿到同样的结果、再调用一次无限重复。原因通常是终止条件设计得太粗糙——只判断模型有没有输出最终答案而没考虑模型是不是在原地打转。实际项目里我一般会同时设置三重终止条件正常终止模型输出了不含工具调用的最终回复。步数上限硬性限制最大循环次数比如 25 步。超过就强制中断把当前状态返回给用户。重复检测连续 N 次通常 3 次出现完全相同的工具调用和参数判定为卡死主动注入一条提示让模型换思路或者直接中断。提示步数上限不要设得太小。我一开始设成 10结果稍微复杂点的任务比如读三个文件然后汇总根本跑不完。后来调到 25 到 30 之间配合重复检测才比较平衡。2.3 上下文管理循环越长历史越臃肿Agent Loop 每转一圈messages 数组就长一截。转到第 20 圈的时候光历史记录可能就几万 token 了。这时候有两个问题一是成本飙升二是模型在超长上下文里反而容易迷失抓不住重点。我的做法是分层管理上下文。近期消息保留完整原文中期消息做摘要压缩把调用了 read_file参数是 xxx返回了 yyy压缩成读取了文件 xxx远期消息只保留关键结论。具体压缩策略要看任务类型但核心原则是模型当前决策最需要的信息必须完整保留已经消化过的过程性信息可以压缩。这里有个反直觉的经验不是所有工具返回结果都值得完整回填。比如一个返回 5000 行日志的工具如果你原样塞回上下文模型很可能被淹没。更好的做法是在工具层就做截断或摘要只把最相关的部分回填给模型。这个结果预处理的活儿属于工具子系统但它直接影响 Loop 的健康度。3. 子系统二LLM Integration——别把模型当成一个黑盒函数3.1 模型接入不只是填个 API Key很多人以为 LLM Integration 就是配个 API Key、选个模型名完事。但真正做过生产级 Agent 的人都知道这一层要处理的事情多得多多模型路由、流式输出、重试与降级、token 计数、结构化输出约束、以及不同模型对工具调用格式的差异。举个最实际的例子不同模型对工具调用的支持方式完全不同。有的模型有原生的 function calling 能力返回结构化的 tool_calls 字段有的模型只能靠 prompt 约定让它输出特定格式的 JSON然后你自己解析。如果你的 Harness 只适配了一种换个模型就得大改。我的建议是在这一层做一个统一的模型抽象层把不同模型的差异屏蔽掉对上层 Agent Loop 暴露统一的接口class LLMAdapter: def chat(self, messages, toolsNone, streamFalse): # 内部处理不同模型的格式差异 # 统一返回 {content, tool_calls, usage} ...3.2 结构化输出让模型的输出可被程序消费Agent 要干活模型的输出就必须是程序能解析的。但模型天生喜欢自由发挥你让它输出 JSON它可能给你包一层 markdown 代码块可能加一句好的这是结果可能字段名大小写不一致。处理这个问题有三条路我按可靠性从高到低排用模型原生的结构化输出能力如果支持比如强制 JSON schema。用解析器做容错正则提取 JSON、修复常见格式问题。失败后重试把解析错误信息回填给模型让它重新输出。实测下来最稳的是原生能力 容错解析 有限重试三件套。单靠任何一个都不够。我踩过的坑是只做了 JSON 解析没做重试结果模型偶尔输出带注释的 JSONJSON 标准不支持注释直接解析失败整个 Loop 崩掉。3.3 重试、降级与成本控制模型 API 不是 100% 可用的。超时、限流、偶发的服务错误都会发生。Harness 必须处理这些。我的做法是指数退避重试失败后等 1s、2s、4s 再试最多 3 次。模型降级主模型连续失败自动切到备用模型可以是更便宜或更稳定的。token 预算给每个任务设一个 token 上限接近上限时主动收尾而不是等它烧爆。注意降级模型的能力可能差很多切换后要相应调整 prompt 或降低任务复杂度预期否则降级后反而更容易出错。4. 子系统三Tool System——Agent 的手和脚4.1 工具不是越多越好新手最容易犯的错是恨不得把能想到的工具全塞给模型。结果模型面对 50 个工具选择困难经常调错。我做过一个对比实验同一个任务给模型 8 个精心设计的工具成功率和给 30 个工具相比前者明显更高。工具设计的核心原则是正交且语义清晰。每个工具解决一类明确的问题工具之间不要有功能重叠。比如你有了read_file就不要再搞一个read_text_file模型会困惑该用哪个。4.2 工具描述就是给模型的使用说明书工具的名字、描述、参数 schema本质上都是 prompt 的一部分。写得含糊模型就用得含糊。我见过一个工具描述写处理数据模型完全不知道什么时候该调它。改成读取指定 CSV 文件的前 N 行用于快速预览数据结构调用准确率立刻上来了。参数描述同样重要。每个参数要说明它是什么、格式是什么、有没有默认值、边界在哪。比如max_lines参数要写清楚最多返回多少行默认 100最大 1000而不是只写个行数。4.3 工具执行的安全边界工具是 Agent 真正动手的地方也是最危险的地方。一个能执行 shell 命令的工具如果没做限制模型可能删掉你的文件。所以工具层必须做权限和边界控制文件操作限制在指定目录内禁止路径穿越。命令执行做白名单或者至少在沙箱里跑。网络请求限制域名和频率。所有写操作考虑加确认机制或 dry-run 模式。这些不是以后再说的事而是从第一个工具开始就要设计的。我吃过亏早期图省事文件工具直接用了绝对路径结果模型在一次任务里把配置目录给改了排查了半天。4.4 工具结果的预处理前面提过工具返回结果不能原样回填。这一层要做的事包括截断超长输出、提取关键字段、把二进制或复杂结构转成模型能读的文本、错误信息标准化。一个实用技巧让工具返回结构化的{success, data, error}格式而不是裸的字符串。这样 Agent Loop 能统一判断工具是否成功失败时也能把标准化的错误信息回填给模型让它决定是重试还是换方案。5. 子系统四Memory State——让 Agent 记得住、接得上5.1 短期记忆和长期记忆是两回事Agent 的记忆经常被混为一谈其实至少要分两层短期记忆工作记忆当前任务内的上下文就是 Agent Loop 里那个 messages 数组。任务结束就清空。长期记忆跨任务、跨会话保留的信息。比如用户的偏好、之前任务的结论、领域知识。短期记忆的管理前面讲上下文时提过了。长期记忆才是真正考验设计的地方。最简单的长期记忆就是一个向量数据库把重要信息存进去需要时检索。但实际用起来检索的时机和内容筛选很讲究——检索太多会污染上下文检索太少又记不住关键信息。5.2 状态机复杂任务不能只靠自由发挥对于步骤明确的任务比如先查数据、再分析、再生成报告纯靠模型自由决策容易漏步骤。这时候引入一个轻量的状态机把任务拆成明确的阶段每个阶段有进入条件和退出条件会稳很多。状态机不需要很重。哪怕只是维护一个current_phase变量在 prompt 里告诉模型你现在处于数据收集阶段只能调用查询类工具效果就很明显。这本质上是用工程手段给模型的自由度加了个护栏。5.3 检查点与恢复长任务跑到一半失败了怎么办如果没有任何检查点机制只能从头再来浪费时间和 token。我的做法是在关键节点保存状态快照当前阶段、已完成步骤、中间结果。失败后可以从最近的检查点恢复而不是从零开始。这个机制在调试阶段尤其有用。你可以从任意检查点重放快速定位是哪一步开始出问题的。6. 子系统五Orchestration——多 Agent 协作的调度中枢6.1 什么时候需要多个 Agent单个 Agent 能搞定的事就别上多 Agent。多 Agent 带来的复杂度是成倍增长的通信开销、状态同步、冲突解决、调试难度。我见过不少项目明明一个 Agent 加几个工具就能解决非要拆成五个 Agent 互相调用结果稳定性一塌糊涂。真正需要多 Agent 的场景通常是任务可以清晰拆分成不同专业领域比如一个负责检索、一个负责写作、一个负责审核或者需要并行处理多个独立子任务。6.2 常见的编排模式实际项目里编排模式主要有这么几种模式适用场景复杂度我的评价顺序流水线步骤固定的任务低最稳优先考虑主管-工人任务可动态分派中灵活但主管容易成为瓶颈并行竞标需要多方案择优中成本高适合关键决策自由协作开放式复杂任务高最难调慎用对大多数项目我建议从顺序流水线开始不够用了再升级。别一上来就搞自由协作那是给自己找麻烦。6.3 通信与状态共享多 Agent 之间怎么传递信息最简单的是通过共享的上下文对象但要注意并发读写的问题。更规范的做法是定义明确的消息格式Agent 之间只通过消息通信不直接共享内存。状态共享的坑在于多个 Agent 同时修改同一份状态容易产生不一致。我的经验是尽量让状态单向流动——上游 Agent 产出结果下游 Agent 消费避免双向修改。7. 子系统六Observability——看不见的 Agent 最可怕7.1 为什么可观测性是刚需Agent 出问题时最怕的是不知道为什么。模型调了什么工具、传了什么参数、拿到什么结果、为什么做这个决策——如果这些都没有记录排查就是盲人摸象。可观测性不是锦上添花而是雪中送炭。我在项目里强制要求每一次 LLM 调用、每一次工具执行、每一次状态变更都要有结构化日志。日志里至少包含时间戳、步骤编号、输入、输出、耗时、token 消耗。7.2 追踪一次完整的 Agent 执行理想情况下你能把一次 Agent 执行还原成一条清晰的时间线Step 1: LLM 调用 (1.2s, 850 tokens) - 决定调用 search_docs Step 2: 工具执行 search_docs (0.3s) - 返回 3 条结果 Step 3: LLM 调用 (2.1s, 1200 tokens) - 决定调用 read_file Step 4: 工具执行 read_file (0.1s) - 返回文件内容 Step 5: LLM 调用 (1.8s, 900 tokens) - 输出最终答案有了这条时间线哪一步慢、哪一步 token 消耗异常、哪一步决策可疑一目了然。7.3 关键指标与告警除了单次追踪还要有聚合指标任务成功率、平均步数、平均 token 消耗、工具调用失败率、平均耗时。这些指标能帮你发现系统性的问题。比如工具失败率突然上升可能是某个外部服务挂了平均步数变多可能是模型变笨了或者 prompt 需要调整。提示别等出问题才加日志。从第一天就把可观测性做进去后面省下的排查时间远超前期投入。8. 子系统七Guardrails——给 Agent 装上刹车和护栏8.1 输入输出双向校验Guardrails 是最后一道防线负责在输入和输出两端做校验。输入端过滤恶意 prompt、检测越权请求、限制任务范围。输出端检查结果是否符合格式要求、是否包含敏感内容、是否真的完成了任务。这一层经常被忽略直到出事。我见过 Agent 被诱导去执行了不该执行的操作也见过 Agent 输出了一堆看起来像模像样但完全错误的结果。双向校验能拦住大部分这类问题。8.2 行为约束与预算控制除了内容校验还要约束 Agent 的行为边界单次任务最多调多少次工具、最多花多少 token、最多跑多长时间。这些预算控制是防止 Agent 失控的关键。预算控制要和 Agent Loop 的终止条件配合。Loop 负责正常情况下的收尾Guardrails 负责异常情况下的强制刹车。两者缺一不可。8.3 人工介入的时机不是所有决策都该让 Agent 自己做。涉及不可逆操作删除、支付、发送时应该设置人工确认节点。这个人在回路的设计是很多生产级 Agent 的标配。我的做法是给工具打上风险等级标签高风险工具在执行前必须经过确认。确认可以是同步的暂停等用户点确认也可以是异步的先记录事后审核。具体选哪种看业务对实时性的要求。9. 把这 7 个子系统串起来一次真实任务的完整走查光讲子系统容易割裂我们用一个具体任务把它们串一遍。假设任务是读取项目里的日志文件找出所有报错汇总成一份报告。第一步Guardrails 介入校验这个请求是否在允许范围内读文件、生成报告没问题设定预算最多 20 步、5 万 token。第二步Agent Loop 启动把任务和可用工具列表read_file、list_dir、write_file交给 LLM Integration 层。第三步LLM 决策模型决定先调用 list_dir 看看有哪些文件。Tool System 执行返回文件列表。结果经过预处理截断、格式化后回填。第四步Memory 记录当前阶段是文件探索已发现日志文件路径这些状态被记录下来。第五步继续 Loop模型调用 read_file 读取日志Tool System 返回内容可能很大做截断。模型分析内容找出报错行。第六步Orchestration如果用了多 Agent如果报告生成交给另一个专门的 Agent这里就是交接点。第七步Observability 记录全程每一步的耗时、token、决策都被记录形成完整时间线。第八步Guardrails 收尾校验检查生成的报告是否符合格式、是否真的包含了报错信息、有没有越权操作。第九步Loop 终止模型输出最终报告任务完成。你看一个看起来简单的任务背后是 7 个子系统在协同。任何一个环节薄弱任务都可能失败或质量下降。10. 搭建 Harness 时我踩过的几个真实坑坑一过早追求通用。一开始我想做一个能处理所有任务的通用 Harness结果每个子系统都做得很泛哪个任务都跑不好。后来改成针对具体场景做特化反而稳了。通用性是演化出来的不是设计出来的。坑二忽视工具结果的体量。有个工具返回了几万字的文本直接塞进上下文模型直接失忆后面的决策全乱。后来强制所有工具结果做截断和摘要问题解决。坑三日志记了但没人看。早期日志格式混乱出问题时根本没法快速定位。后来统一了结构化日志格式还写了个简单的可视化脚本排查效率提升明显。坑四终止条件太单一。只判断模型有没有输出最终答案结果遇到模型反复调用同一工具时死循环。加上步数上限和重复检测后才稳。坑五把 Guardrails 当摆设。觉得模型应该不会乱来结果一次测试中模型真的执行了超出预期的操作。从那以后所有高风险工具都加了确认机制。11. 关于 Harness 和 Agent 的关系再说几句经常有人问Harness 和 Agent 到底啥区别我的理解是Agent 是一个完整的概念指的是能自主完成任务的智能体Harness 是支撑这个智能体运转的工程基础设施。Agent 是人Harness 是人的神经系统、骨骼和工具。所以当你觉得我的 Agent 不好用时先别急着换模型先体检这 7 个子系统Loop 稳不稳模型接入有没有容错工具设计清不清晰记忆管理合不合理编排是不是过度设计可观测性够不够护栏有没有大部分问题都能在这 7 个里找到答案。模型的能力当然重要但决定一个 Agent 能不能真正干活的往往是模型外面这层看不见的 Harness。把 Harness 搭扎实了哪怕用中等能力的模型也能跑出稳定的结果Harness 搭得烂再强的模型也救不了。这套 7 子系统的拆法是我自己在多个项目里反复验证、不断修正后形成的。它不一定是最权威的但至少能对上号、能落地、能帮你快速定位问题。如果你正在搭自己的 Agent不妨拿这 7 条对照着过一遍看看哪块最薄弱先补哪块。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →