AI Agent Harness七子系统:从LLM集成到可观测性的工程架构
1. 别被“Harness”这个词唬住,它其实就是Agent的骨架
第一次听到“Harness”这个词,很多人会以为是某个新出的框架或者某个大厂开源的库。其实不是。Harness 在 AI Agent 语境里,指的是让大模型真正能“干活”的那套运行时骨架——它把 LLM 的文本生成能力,包装成一个能感知环境、能做决策、能调用工具、能记住上下文、能循环执行直到任务完成的系统。
你可以把 LLM 想象成一个极其聪明但被关在黑屋子里的人。你从门缝里递进去一张纸条,他能在纸条上写回复递出来。但他看不见外面、动不了手、记不住上一张纸条写了什么。Harness 就是那个把黑屋子改造成一间办公室的东西:装上眼睛(感知)、接上手脚(工具调用)、放上笔记本(记忆)、安排工作流程(循环控制)、再配一个监工(编排调度)。
所以当你看到“AI Agent 真正干活的 Harness 到底是啥”这个问题时,答案不是某个具体产品,而是一套由七个子系统协同工作的工程架构。这七个部分缺一个,Agent 就只能停留在“聊天机器人”的水平,永远没法真正替你完成任务。
我接触过不少团队,一开始都以为接个 LLM API 再加个 function calling 就算 Agent 了。结果一上真实场景就崩:要么循环停不下来,要么工具调错了参数,要么上下文爆了 token 限制,要么并发一上来整个系统卡死。问题从来不在模型本身,而在于 Harness 这层没搭好。
下面我就按这七个子系统,一个一个拆开讲。每个部分我都会说清楚它解决什么问题、核心机制是什么、实际搭建时容易踩什么坑。如果你正在从 0 到 1 搭建 AI Agent,或者想理解市面上那些 Agent 产品到底是怎么运转的,这篇内容应该能帮你把底层逻辑理顺。
2. Agent Loop:让模型从“说一句话”变成“做一件事”
2.1 为什么单次调用不够用
普通 LLM 调用是“一问一答”模式:你发一个 prompt,模型返回一个 completion,结束。但真实任务往往需要多步操作。比如“帮我查一下明天北京的天气,如果下雨就提醒我带伞,顺便把提醒写到我的日历里”——这里面至少涉及三个动作:查天气、条件判断、写日历。单次调用做不到,因为模型没法在执行完第一步之后根据结果决定下一步。
Agent Loop 就是解决这个问题的。它的核心逻辑是一个while 循环:只要任务没完成,就把当前状态喂给模型,让模型决定下一步做什么,执行,然后把结果追加到状态里,再循环。直到模型输出一个“完成”信号,或者达到最大迭代次数。
2.2 循环控制的关键参数
搭 Agent Loop 的时候,有几个参数必须认真设,不然后患无穷:
- 最大迭代次数(max_iterations):这是防止死循环的最后一道防线。我一般设 10 到 15 次。设太小,复杂任务做不完;设太大,一旦模型陷入循环,token 消耗会失控。实测下来,大部分日常任务 5 到 8 轮就能收敛。
- 单步超时(step_timeout):每个工具调用都要设超时。外部 API 可能挂掉,如果不设超时,整个 Loop 就卡死在那里。我通常设 30 秒,特殊场景可以放宽到 60 秒。
- 循环终止条件:不能只靠模型自己说“我完成了”。要加硬性判断,比如连续两轮输出相同内容、或者连续三轮没有调用任何工具,就强制终止。
2.3 循环中的状态管理
每一轮循环都需要把“到目前为止发生了什么”告诉模型。这个状态通常包括:原始任务描述、已经执行的动作列表、每个动作的返回结果、当前进度。状态管理做得好不好,直接决定 Agent 能不能处理长任务。
我的经验是,状态不要全量塞回去。随着轮次增加,上下文会越来越长,token 成本飙升,而且模型注意力会被稀释。正确做法是做状态压缩:只保留最近 N 轮的详细记录,更早的轮次用摘要代替。摘要可以由模型自己生成,也可以用规则提取关键信息。
注意:状态压缩的时机很关键。不要等到快爆 token 了才压缩,而是在每轮结束后判断当前上下文长度,超过阈值就触发压缩。我一般把阈值设在模型上下文窗口的 60% 左右,留出余量给后续轮次。
3. LLM Integration:模型不是随便接一个就行的
3.1 模型选型的实际考量
很多人搭 Agent 时随便选一个模型就上了,结果发现工具调用格式老是对不上、JSON 输出不稳定、长上下文性能骤降。LLM Integration 这层要解决的核心问题是:让模型的输出可被程序可靠解析。
选模型时我关注三个指标:工具调用准确率、结构化输出稳定性、长上下文一致性。工具调用准确率指的是模型能不能正确选择工具并填对参数;结构化输出稳定性指的是 JSON 格式是否可靠;长上下文一致性指的是当上下文很长时,模型还能不能记住前面的关键信息。
不同模型在这三个维度上差异很大。有些模型工具调用很强但长上下文拉胯,有些反过来。实际项目中,我通常会准备两个模型:一个主力模型负责核心决策,一个轻量模型负责摘要、分类等辅助任务。这样成本和效果都能兼顾。
3.2 输出解析的容错设计
模型输出 JSON 时经常出问题:多一个逗号、少一个引号、该用双引号的地方用了单引号、甚至前面加一段“好的,我来帮你...”的废话。如果解析器不够健壮,整个 Agent 就崩了。
我的做法是三层容错:
- 第一层:正则提取。先用正则把 JSON 块从输出里抠出来,忽略前后的自然语言。
- 第二层:宽松解析。用支持尾逗号、单引号的宽松 JSON 解析器,处理格式小问题。
- 第三层:模型自修复。如果前两层都失败,把原始输出和错误信息一起发给模型,让它重新输出正确格式。
这三层下来,解析成功率能到 99% 以上。剩下那 1% 就记录日志,人工排查。
3.3 Prompt 结构的工程化
Agent 的 system prompt 和普通聊天完全不一样。它需要包含:角色定义、可用工具列表及参数说明、输出格式要求、约束条件、当前状态。这些内容要结构化组织,不能一大段文字堆上去。
我习惯用 XML 标签或 Markdown 标题来分段,让模型能清晰区分不同模块。工具描述要写得极其精确,每个参数的类型、是否必填、取值范围都要说清楚。实测发现,工具描述写得好,工具调用错误率能降低一半以上。
4. Tool Use:Agent 的手脚,也是最容易出事的地方
4.1 工具注册与描述规范
工具是 Agent 和外部世界交互的唯一通道。每个工具需要定义:名称、功能描述、参数 schema、返回值格式、错误处理方式。名称要短且语义明确,描述要写清楚“什么时候该用这个工具”,而不只是“这个工具做什么”。
举个例子,一个查天气的工具,描述不应该只写“查询天气”,而应该写“当用户询问某地天气、需要根据天气做决策、或需要天气数据作为其他任务的输入时,调用此工具”。这样模型才能准确判断调用时机。
参数 schema 用 JSON Schema 定义,每个参数都要有 type、description、required 字段。枚举类型的参数要把所有可能值列出来。我见过太多因为参数没写清楚导致模型瞎填的案例。
4.2 工具执行的沙箱与安全
工具执行是风险最高的环节。Agent 可能会调用文件操作、网络请求、数据库查询等敏感操作。必须做沙箱隔离和权限控制。
我的做法是:每个工具声明自己需要的权限级别,执行前检查当前 Agent 是否有对应权限。文件操作限制在特定目录内,网络请求限制在白名单域名内,数据库操作只允许只读或特定表的写入。所有工具调用都记录完整日志,包括输入参数、执行时间、返回结果、是否成功。
提示:工具执行失败时,不要把原始错误堆栈直接返回给模型。要包装成模型能理解的错误信息,比如“天气查询服务暂时不可用,请稍后重试或改用其他方式获取天气信息”。原始错误写日志就行。
4.3 工具调用的并发与串行
有些工具调用可以并行,有些必须串行。比如同时查三个城市的天气可以并行,但“先创建文件再写入内容”必须串行。Agent Loop 默认是串行的,因为模型一次通常只输出一个工具调用。
但如果模型支持一次输出多个工具调用,就要判断这些调用之间有没有依赖关系。没有依赖的可以并发执行,有依赖的必须按顺序来。并发执行时要注意结果合并的顺序,不能乱。
5. Memory:没有记忆的 Agent 永远是个新手
5.1 短期记忆与长期记忆的分工
Memory 子系统分两层:短期记忆和长期记忆。短期记忆就是当前会话的上下文,存在内存里,会话结束就没了。长期记忆是跨会话的,存在数据库或向量库里,需要时检索出来。
短期记忆管理的关键是滑动窗口 + 摘要。最近几轮保留完整内容,更早的轮次压缩成摘要。摘要要保留关键决策、重要结果、未完成事项,丢弃中间过程细节。
长期记忆的核心是检索增强。把历史会话中的关键信息向量化存储,新会话开始时根据当前任务检索相关记忆。检索策略我一般用混合检索:向量相似度 + 关键词匹配 + 时间衰减。纯向量检索容易漏掉精确匹配的内容,加上关键词匹配能补上这个短板。
5.2 记忆写入的时机与策略
不是所有对话都值得写入长期记忆。我的策略是:任务完成后,让模型自己判断这次会话有没有值得记住的信息。如果有,生成一条结构化记忆记录,包含任务类型、关键结论、用户偏好等字段。
写入频率也要控制。每条记忆都写,检索时会噪音太大;写太少,又记不住东西。我一般按任务粒度写,一个完整任务写一条。如果任务特别长,中间有重要里程碑,也可以分段写。
5.3 记忆检索的排序与过滤
检索出记忆后,不能直接全塞给模型。要按相关性排序,取 top K 条。K 一般设 3 到 5 条,太多会干扰模型判断。排序时综合考虑相似度分数、时间新鲜度、记忆类型匹配度。
还要做去重和冲突检测。如果两条记忆内容矛盾,要标记出来让模型知道存在冲突,或者按时间取最新的。我遇到过用户偏好变了但旧记忆还在的情况,导致 Agent 行为不一致。后来加了冲突检测,问题就解决了。
6. Planning:让 Agent 学会“先想再做”
6.1 任务分解的粒度控制
Planning 子系统负责把复杂任务拆成可执行的步骤。拆得太粗,每步还是太复杂做不了;拆得太细,步骤太多循环次数爆炸。我的经验是拆到“每个步骤可以用一个工具调用或一次模型推理完成”的粒度。
比如“帮我策划一场团队活动”这种任务,可以拆成:确定参与人数和预算、选择活动类型、查找场地、比较场地、预订场地、发送通知。每个步骤都有明确的输入和输出,可以独立执行和验证。
6.2 动态重规划的必要性
计划赶不上变化。执行过程中可能发现某个步骤做不了、或者出现了新信息需要调整计划。这时候不能死守原计划,要允许动态重规划。
我的做法是每执行完一个步骤,就让模型评估一下:当前进度是否符合预期?是否需要调整后续计划?如果发现偏差,生成新的计划替换剩余步骤。重规划频率不能太高,否则会陷入“一直规划不执行”的怪圈。我一般设一个阈值,连续两次重规划之间至少执行三个步骤。
6.3 计划与执行的解耦
Planning 和 Execution 最好解耦。Planning 阶段只负责生成步骤列表,不执行具体操作。Execution 阶段按步骤列表逐个执行。这样好处是计划可以审查、可以修改、可以复用。
实现上,计划就是一个结构化列表,每个步骤包含:步骤编号、描述、预期输入、预期输出、依赖步骤。执行器按依赖关系拓扑排序后依次执行。执行结果回写到步骤状态里,供后续步骤和重规划使用。
7. Orchestration:多个 Agent 怎么不打架
7.1 单 Agent 与多 Agent 的取舍
不是所有场景都需要多 Agent。单 Agent 能搞定的事,不要上多 Agent,因为多 Agent 的协调成本很高。什么时候需要多 Agent?当任务可以明确分成不同角色、每个角色需要不同的工具集和 prompt、角色之间有清晰的输入输出接口时。
比如一个“研究报告生成”任务,可以分成:资料搜集 Agent、数据分析 Agent、报告撰写 Agent、审核 Agent。每个 Agent 专注自己的领域,通过消息传递协作。
7.2 通信协议与状态同步
多 Agent 之间怎么通信?最简单的是共享一个消息队列,每个 Agent 往队列里发消息,也从队列里读消息。消息格式要统一,包含发送者、接收者、消息类型、内容、时间戳。
状态同步是个难点。多个 Agent 可能同时修改同一份状态,需要加锁或版本控制。我一般用乐观锁:每个状态带版本号,修改时检查版本号是否变化,变了就重试。这样避免并发写冲突。
7.3 失败处理与任务回滚
多 Agent 系统中,一个 Agent 失败可能影响整个任务。需要设计失败处理机制:失败 Agent 重试、任务重新分配给其他 Agent、或者整个任务回滚。
回滚要记录每个步骤的副作用,失败时按相反顺序撤销。比如创建了文件就删除文件,发送了通知就发撤回通知。不是所有操作都能回滚,所以关键操作前要加确认机制。
8. Observability:Agent 跑起来之后你怎么知道它在干嘛
8.1 日志与追踪的粒度
Agent 系统不透明是最大的痛点。模型为什么做这个决策?工具为什么调错了?循环为什么停不下来?没有可观测性,这些问题都查不了。
我的做法是每个关键节点都打日志:模型输入、模型输出、工具调用参数、工具返回结果、循环轮次、状态变化、错误信息。日志要结构化,方便后续查询和分析。每条日志带 trace_id,能把一次完整任务的所有日志串起来。
8.2 关键指标监控
除了日志,还要监控关键指标:任务成功率、平均循环轮次、平均 token 消耗、工具调用错误率、响应延迟。这些指标能帮你发现系统性问题。
比如工具调用错误率突然上升,可能是某个外部服务挂了;平均循环轮次增加,可能是模型决策质量下降;token 消耗异常,可能是上下文管理出了问题。我一般设阈值告警,超过就排查。
8.3 回放与调试
出问题时,能回放整个执行过程是最有效的调试手段。把一次任务的所有输入输出按时间顺序存下来,调试时可以逐步回放,看每一步的决策依据。
我还会做一个“决策解释”功能:让模型在每次决策后附带一段简短解释,说明为什么选择这个工具、为什么填这些参数。这些解释在调试时非常有用,能快速定位是模型理解错了还是工具描述有问题。
9. 搭 Harness 时我踩过的那些坑
第一个坑是工具描述太模糊。早期我写工具描述就一句话“查询用户信息”,结果模型经常在不该调用的时候调用,该调用的时候又选错工具。后来把描述改成“当需要根据用户 ID 获取用户姓名、邮箱、注册时间等基本信息时调用此工具,不适用于查询用户订单或行为数据”,错误率立刻降下来了。
第二个坑是循环没有硬终止条件。有一次模型陷入了一个“调用工具A失败→重试→再失败→再重试”的死循环,因为没有设最大重试次数,跑了 200 多轮才被 token 限制拦住。后来加了“同一工具连续失败 3 次就跳过”的规则,再也没出现过这种情况。
第三个坑是上下文压缩太激进。为了省 token,我把历史记录压得很狠,结果模型忘了前面已经执行过的步骤,重复执行导致副作用叠加。后来调整了压缩策略,保留最近 5 轮的完整记录,更早的才压缩,问题就没了。
第四个坑是多 Agent 状态冲突。两个 Agent 同时修改同一个任务状态,后写的覆盖了先写的,导致部分工作丢失。后来加了版本号和重试机制,才解决。
第五个坑是没有可观测性。上线后用户反馈“Agent 有时候不回复”,查了半天查不出原因。后来补了全链路日志和指标监控,才发现是某个外部 API 偶尔超时导致循环卡住。加上超时和重试后,问题消失。
10. 从这七个子系统回头看 Agent 产品的选型
理解了这七个子系统,再看市面上的 Agent 产品就清晰多了。有些产品强在 LLM Integration,工具调用特别稳;有些强在 Memory,长任务表现好;有些强在 Orchestration,多 Agent 协作流畅。选型时不要只看模型能力,要看这七个子系统的完整度和成熟度。
自己搭的话,建议按这个顺序来:先把 Agent Loop 和 LLM Integration 跑通,让 Agent 能完成最简单的“调用一个工具并返回结果”;然后加 Tool Use 和 Memory,让它能处理多步任务;再加 Planning 和 Orchestration,处理复杂场景;最后补 Observability,保证系统可维护。
每个子系统不用一开始就做得很完美,先跑通再优化。我见过太多团队想一次性把七个都做到位,结果三个月过去了还在设计阶段。正确的做法是快速搭一个能跑的最小版本,然后在真实使用中逐步完善。
提示:搭 Harness 最容易被低估的是 Observability。很多人觉得这是后期才需要的东西,结果出了问题完全抓瞎。我的建议是从第一天就加日志,哪怕只是简单的 print,也比没有强。
这套架构不是理论推演,是我在实际项目中反复调整出来的。每个子系统的细节都经过真实场景验证,踩过的坑也都补上了对应的防护。如果你正在搭自己的 Agent,希望这些经验能帮你少走点弯路。