Agent Harness 这个词在 Agent 开发圈里出现频率越来越高,但很多人对它的理解还停留在“不就是给大模型套了个循环吗”。真正把 Harness 拆开看,你会发现它是一套由无数组件组成的运行时体系,比 Agent 本体复杂得多。这篇文章我想用一个比较省力的方式帮你建立整体认知:先找一个所有人都熟悉的类比,把它映射到工程组件上,再倒推回来。适合刚接触 Agent 开发的工程师,也适合一直搞不清 Harness 和 Agent 有什么区别、不知道一个生产级智能体到底需要哪些组件的技术负责人。
1. 先搞清楚:Harness 到底是什么,它和 Agent 有哪些区别
1.1 从“挽具”的本意聊起
Harness 这个英文词,原意是马的挽具、拉车的背带,后来引申为“把某种力量驾驭起来使用”。软件圈借这个词,其实就是形容“把模型、工具、记忆、外部服务绑在一起运行的那层支架”。
这个比喻很准确。你有一个很强的大模型,但它本质上只是“大脑”,它不负责吃饭喝水,不负责拿东西,也不负责记住自己讲过的话。真正能干活的东西,是把大脑架在一套系统里,让它能看资料、能调工具、能带着上下文跑完整个长任务。这套系统,就是 Agent Harness。
我见过不少朋友做 Agent 项目,第一版代码就是“while True: 调一次模型”,调完拿到结果就结束。这种玩意严格讲不叫 Harness,就是个脚本。等任务一复杂,你会发现缺的东西越来越多:谁来规划步骤?谁记上下文?工具调用失败怎么办?模型乱答怎么拦?这些问题全都要由 Harness 里的组件来回答。
1.2 Agent 和 Harness 的差别:赛车手与赛车
很多人把 Agent 和 Harness 混着用,我建议用赛车来理解:
- Agent 是坐在车里的赛车手,他有目标、有决策能力、知道下一步该做什么;
- Harness 是整辆赛车,包括引擎、变速箱、仪表盘、刹车、通讯设备,缺一个都没法开完整场比赛。
没有赛车的赛车手,再厉害也只能原地站着;没有赛车手的赛车,本质上就是一堆铁。实际工程里,Agent 是运行时里的决策节点,Harness 是让这个决策节点能持续运转的整个支撑层。
为了更直观,我列一个对比表:
| 维度 | Agent | Agent Harness |
|---|---|---|
| 核心角色 | 决策主体 | 支撑主体运转的整套系统 |
| 典型组成 | 模型调用 + 提示词 + 基本工具逻辑 | 循环调度、规划、记忆、工具注册、执行、状态、护栏、观测、沙箱等 |
| 类比 | 赛车手 | 一整辆赛车 |
| 常见实现 | AutoGPT 里的 AgentExecutor、OpenAI Assistants | LangGraph、CrewAI、Mastra、自研 Runtime 等 |
| 职责边界 | 决定“做什么” | 保证“做得动、做得完、出错能兜底、复盘有依据” |
实际项目里两者经常不分家,因为框架里一个 Agent 节点往往已经把循环和支撑逻辑耦合进去了。但我一直觉得,理解职责域比抠名词更重要。你心里能分清“哪些逻辑属于决策、哪些属于支撑”,后面设计任何架构都不会乱。
1.3 什么情况下你需要真正引入 Harness
如果你的功能就是“用户提问 -> 模型回答”,一次调用就结束,那真的不需要 Harness。但一旦出现下面这些信号,就得认真考虑:
- 任务需要多步才能完成,比如先调研再总结再发邮件;
- 需要调用外部工具,比如搜索、查数据库、操作文件、执行代码;
- 中间结果可能是错的,需要重试、修正或换一种方案;
- 需要跨会话或有长任务的“短期记忆”;
- 出问题后,你要能查清楚是哪一步导致的,而不是让 Agent 当黑盒。
客服机器人、代码智能体、数据分析 Agent 基本都属于这种情况。一个生产级的 Agent 背后,至少有一个能承接复杂流程的 Harness,只是你没意识到而已。
2. 选一个类比:把 Harness 看作一家公司的经营体系
2.1 为什么是公司,而不是流水线
我之前讲组件喜欢用“流水线”来比喻,后来发现不准确。流水线只能表达“按顺序处理”,没法解释 Agent 面对新情况时的动态调整,也不能体现“复盘、风控、容错”这些支撑职责。
公司不一样。一家公司天生就有分工、有流程、有风控、有总结复盘。董事会做决策、战略部做规划、行政管档案、采购管供应商、一线员工干活、法务做合规、后勤处理事故、监控中心记录一切。这跟 Agent Harness 的职责域高度相似。
所以下面的推导我全部用“公司”来指代 Harness。你只需要记住一个公式:Harness = 一家公司的经营体系,Agent = 这家公司的 CEO。
2.2 公司部门到 Harness 组件的总映射
先把总表放这里,后面逐个细讲。
| 生命周期阶段 | 公司角色 | Harness 组件 | 一句话职责 |
|---|---|---|---|
| 想(决策层) | CEO | 核心调度器 | 决定下一步做什么,驱动主循环 |
| 想(决策层) | 战略部 | 规划器 | 拆解目标、规划步骤、修订计划 |
| 想(决策层) | 董事会秘书 | 模型适配层 | 对接各种大模型,统一通信协议 |
| 备(准备层) | 档案室 | 记忆系统 | 存取短期工作记忆和长期事实 |
| 备(准备层) | 信息助理 | 上下文管理器 | 控制模型每轮真正看到的信息量 |
| 备(准备层) | 采购部 | 工具注册表 | 登记外部工具,公示调用方式和参数 |
| 做(执行层) | 一线员工 | 工具执行器 | 实际调用工具并取回结果 |
| 做(执行层) | 财务部 | 状态管理器 | 记录任务进度、变量、已完成的步骤 |
| 做(执行层) | 法务/风控 | 安全护栏 | 执行前后检查合规、权限、敏感内容 |
| 护(保障层) | 人事/培训 | 错误处理与恢复 | 捕获异常、重试、降级、换方案 |
| 护(保障层) | 监控中心 | 日志与可观测性 | 记录全部调用、追踪链路、统计成本 |
| 护(保障层) | 隔离车间 | 沙箱环境 | 把危险操作隔离在受限环境里跑 |
这张表其实是全文的骨架,后面就是围绕它展开。
2.3 “为什么是 12 个组件”的推导逻辑
你可能会问,为什么不是 10 个也不是 15 个?这里有一个非常实用的推导方法:把任何 Agent 的运行过程切成四个阶段——想、备、做、护。
每个阶段问三个问题:
- 谁是核心决策角色?
- 它依靠什么资源?
- 出了风险和错误谁来兜底?
每阶段 3 个组件,4 个阶段就是 12 个。这个推导逻辑比死记组件清单强多了。以后你往 Harness 里加任何东西,只需要问自己:这个组件属于哪个阶段、解决什么问题。如果某个阶段一个组件都没有,那你的系统一定有隐患。
3. 12 个组件逐个拆解:职责、类比和真实落地
3.1 决策层三件套:调度器、规划器、模型适配器
组件 1:核心调度器(Orchestrator)
这是整个 Harness 的心脏,对应公司的 CEO。它的职责是驱动主循环:拿到任务后,决定“现在该做什么”,调用相应的组件,收集结果,再决定“下一步做什么”,直到任务完成或触发终止条件。
真实落地时,常见的技术形态是 ReAct Loop(推理-行动-观察循环),或者是 LangGraph 里的 StateGraph 节点循环。你写 Agent 时那个最外层的 while 循环,本质上就是调度器。
我踩过的最大的坑是忘记设终止条件。模型在对话中很容易陷入“我再试一次”的循环,如果没有最大步数、成本上限或明确的结束信号,一次任务能把预算烧穿。所以调度器必须内置:最大迭代步数、单任务 token 预算、优先级规则,并且在模型重复输出同一动作时强制中断,而不是硬跑。
组件 2:规划器(Planner)
对应公司里的战略部,负责把一个大目标拆成若干可执行的小任务,并确定依赖关系。
比如“调研三家公司并输出报告”这个任务,规划器会拆成:搜索资料 -> 提取关键信息 -> 对比分析 -> 生成报告。技术实现上有 Plan-and-Execute(先规划再逐项执行)、思维树 ToT、以及 MetaGPT 那一类角色任务拆分。
规划器的设计有个取舍:一次性规划 vs 逐步规划。一次性规划对简单任务效率高,对复杂任务容易因为初始假设错误导致全线崩盘。我现在的习惯是混合式:先粗粒度规划,执行中动态调整细粒度步骤。如果你写死一个计划让 Agent 盲目执行,一旦中途环境变了,整个任务就废了。
组件 3:模型适配层(Model Adapter / LLM Gateway)
对应董事会秘书——CEO 不可能自己直接跟所有外部机构对接,需要一个统一传话的人。
技术上,模型适配层负责把 OpenAI、Anthropic、Gemini、本地 Ollama 等不同模型封装成统一接口,屏蔽消息格式、工具调用协议、token 计量方式的差异。现实中你会发现不同厂商的工具调用格式完全不一样:OpenAI 用tool_calls,Anthropic 用tool_use,解析逻辑无法直接复用。
这个组件被很多人忽略,直到切换模型才痛苦。我建议一开始就把模型调用抽象成统一接口,哪怕你当前只用一个厂商。反例是我见过有项目直接在业务代码里写 OpenAI SDK,后来要兼容本地模型,改到怀疑人生。
3.2 准备层三件套:记忆、上下文、工具注册
组件 4:记忆系统(Memory)
对应公司的档案室。Agent 在长任务里需要记住“刚才查到了什么”“用户偏好是什么”,否则每一步都像失忆了一样重来。
记忆可以分两层:短期工作记忆就是当前对话的上下文;长期记忆则把重要事实存入外部存储,比如向量数据库、摘要文件、结构化数据库。常见实现有向量检索(Chroma、PGVector)、摘要记忆链、mem0 这类记忆中间件。
记忆系统的关键不是“怎么存”,而是“什么该存、什么不该存”。我早期天真地以为把所有中间结果都存进长期记忆就是“记忆增强”,结果模型反复引用早期错误结论,这就是典型的记忆污染。现在我会在写入记忆前做一层判断:只有已经验证过的事实、明确的用户偏好、已完成任务的结论,才有资格进长期记忆。
组件 5:上下文管理器(Context Manager)
这个组件经常和记忆系统混淆,但两者职责确实不同。记忆系统管“存什么、怎么取”,上下文管理器管“这一轮模型到底能看到什么”。
大模型的上下文窗口是有限的,你不能把整个档案室都搬给 CEO 看。上下文管理器要做的事包括:滑动窗口截取最近几轮对话、对早期内容做摘要压缩、从长期记忆中检索 top-k 相关片段,并控制在模型输入的 token 预算内。
这里有一个很反直觉的经验:上下文不是越全越好。把历史全部塞进去,模型确实能看到更多信息,但它对关键指令的跟随能力会迅速劣化。你应该像给 CEO 准备汇报材料一样,只精选重点。压缩摘要时也要注意:不是简单地“把对话变短”,而是保留“动作 + 结果 + 结论”,否则压缩完的信息对后续决策毫无帮助。
组件 6:工具注册表(Tool Registry)
对应采购部的供应商名录。Agent 能调用哪些工具、每个工具的参数是什么、入参出参格式如何,都由工具注册表来管理。
真实落地时,工具注册表就是一份工具清单,每个工具包含名称、描述、参数 JSON Schema、执行权限等级。LangChain 的@tool装饰器、OpenAI Function Calling 的tools参数,本质上都是往这个表里塞记录。
工具注册表最容易被低估的是描述质量。描述写得模糊,模型就会乱用甚至不调用。比如“search”和“search_web”两个工具,如果描述都是“搜索”,模型根本不知道区别。你要写明“search_web 用于搜索公网网页内容;search_db 用于查询内部产品数据库”,模型才能做出正确路由。工具多了之后,还可以做分组——先让模型选组,再让模型选工具,这样比直接给 30 个扁平清单要稳得多。这跟前端“动态组件加载”“组件注册”的思路其实很像,提前把可用能力注册好,运行时按需取用。
3.3 执行层三件套:工具执行、状态管理、护栏
组件 7:工具执行器(Tool Executor)
对应一线员工。模型只是“说”要调用工具,真正去调用函数的、发 HTTP 请求的、开子进程的,是工具执行器。
执行器要处理几类脏活:参数校验(模型经常传错类型)、执行超时(外部接口卡死不能拖垮整个任务)、结果截断(工具返回 10 万字的 HTML,你不能全塞给模型)、并行执行(多个独立工具调用同时跑可以大幅提速)。
最痛的经验是结果回写。工具执行完拿到结果,必须结构化地写回上下文,同时标注工具名、执行状态和关键内容。很多初学者只调工具不存结果,下一步模型决策时根本看不到刚才查到了什么,于是反复调用同一个工具。这个我在第 4 部分会展开讲。
组件 8:状态管理器(State Manager)
对应财务部。整个任务执行到哪里了、已完成哪些步骤、哪些变量是当前值,都需要状态管理器记录,而不是散落在各个局部变量里。
LangGraph 里的 State 就是典型实现——每个节点执行完返回新的状态,下次节点读取最新状态。状态管理还支持断点续跑和人工介入:任务执行到某一步需要人工审批,先持久化状态,人批完再恢复。这对生产级 Agent 几乎是刚需,尤其是涉及支付、删除、对外发布等高风险动作。
踩坑提示:状态结构要尽量扁平、可序列化。别把模型对象、数据库连接这种东西直接塞进 state,否则一旦要序列化保存就崩。多线程并行更新同一字段时要小心丢更新,多轮任务的状态最好设计成“追加式事件列表”,而不是一个被反复覆盖的变量。
组件 9:安全护栏(Guardrails)
对应法务/风控。每一步行动都要经过审查:能不能做、权限够不够、内容合不合规。
护栏通常分三层:
- 输入层:检测用户提示词里的注入攻击(prompt injection);
- 执行层:校验工具调用权限、域名白名单、危险操作拦截;
- 输出层:检查生成内容是否包含敏感信息,是否泄漏隐私。
关键操作还要加 human-in-the-loop,也就是人工审批环节。比如 Agent 要发送邮件、删除用户数据,先挂起,人工确认后才执行。
护栏的设计难度在于平衡。太松等于没有,太严又会把 Agent 卡死。我见过一个项目,护栏拦掉了所有“删除”类工具,结果 Agent 在处理退货流程时反复尝试删除订单又被拦,最后原地打转。解法是:护栏拒绝时不要只抛一个“not allowed”就完事,要返回结构化的拒绝原因和建议的替代路径,让模型能自己调整方案。这比简单粗暴的错误信息有用得多。
3.4 保障层三件套:错误处理、观测、沙箱
组件 10:错误处理与恢复(Error Handler)
对应人事/培训部门——员工干砸了,不能直接开除,先看是能力问题还是环境问题,决定重试、换人、还是上报。
Agent 运行中会碰到各种错误:模型 API 限流、工具超时、工具返回脏数据、模型输出不符合预期格式。错误处理器要做的决策依次是:
- 能不能重试(指数退避,避免被限流);
- 要不要换方案(比如在线搜索失败,退回本地知识库);
- 要不要换模型(当前模型理解不了,fallback 到更强的模型);
- 最后才考虑终止并上报。
这里有个严重的副作用问题:重试工具调用前,必须确认工具是否幂等。发邮件、扣款这类操作重试两次就等于执行两次,后果不可控。我的习惯是在状态管理器里记录“哪些 action 已经执行、不可重复执行”,重试前先查一下。
组件 11:日志与可观测性(Observability)
对应监控中心/会议记录员。每一轮模型调用(prompt、completion、token 数、耗时)、每一次工具调用(参数、结果、状态)、每一次状态变更,都要被记录在案。
工具方面有 LangSmith、Langfuse、AgentOps,也可以直接用 OpenTelemetry 做分布式追踪。日志的价值不只是事后 debug,更是质量评测——你以后优化 prompt、优化工具描述,都得靠这些日志对照实际表现。
我最大的教训是:日志一定要有 trace_id 串联。并行任务里如果每个步骤各自打日志,没有统一请求 ID,出了问题根本没法复盘。还有就是要记录压缩前的完整上下文,否则一旦上下文管理器压缩出错,你连复现都做不到。成本统计也需要放在这一层,按任务聚合 token 消耗,监控预算。
组件 12:沙箱环境(Sandbox)
对应严格隔离的车间/演习场。所有有副作用的操作——执行代码、操作文件系统、访问网络、运行下载的程序——都应该在隔离环境里跑。
技术实现包括 Docker 容器、云沙箱(比如 E2B)、子进程加资源限制。要知道模型生成的代码可能是错的,也可能是被 prompt injection 诱导的恶意代码,如果你让它在宿主机上直接跑,后果不堪设想。
沙箱配置时我建议默认策略是拒绝网络请求,按需开放白名单域名。同时要给执行时间、内存上限、CPU 上限加限制,否则一个死循环代码就能耗光服务器资源。很多团队上线前觉得“我的 Agent 只查数据库,不需要沙箱”,结果模型被用户一句话诱导去读服务器上的敏感文件,那时候再补就晚了。
4. 一次完整任务的组件协同:从调研竞品到发送报告
4.1 任务设定
只看清单理解不了组件怎么配合,跑一遍最直观。假设我们有一个生产级 Harness,配置了 web_search 搜索工具和 email 发送工具,要执行的任务是:
调研 A、B、C 三家竞品的定价策略,整理成 Markdown 表格,发送到指定邮箱。
4.2 一次完整执行过程
下面按步骤走,括号里标注对应组件:
- 用户提交任务。核心调度器(1)接收任务,初始化状态管理器(8),
state = {task: "...", stage: "plan"}。 - 规划器(2)把任务拆成子任务:搜索竞品 A、搜索竞品 B、搜索竞品 C、提取价格对比、生成 Markdown、发送邮件。
- 模型适配层(3)把当前计划和之前的对话历史封装成请求,发给大模型。模型决定“先搜索竞品 A”。
- 工具注册表(6)确认存在 web_search 工具,返回调用 schema。安全护栏(9)通过校验:web_search 在白名单内,且本次搜索域名无敏感标记。
- 工具执行器(7)真正调用搜索 API。这一步如果涉及代码执行,沙箱(12)会介入,但普通 HTTP 搜索暂时不需要。
- 搜索结果返回,共 20 条。上下文管理器(5)判断内容过长,做摘要压缩,只保留 3 条关键定价信息,写回上下文。
- 记忆系统(4)把“竞品 A 的定价区间是 100-200 元”这类事实写入短期记忆,后续竞品对比时直接查。
- 状态管理器(8)更新进度:
stage: "search_done", done: ["A"],进入下一轮循环。 - 调度器(1)继续循环,依次处理竞品 B、竞品 C。每次搜索、压缩、写记忆、更新状态的过程都一样。
- 所有搜索完成,模型开始生成 Markdown 表格。生成前护栏(9)检查输出内容不包含内部数据;生成后工具执行器(7)调用 email 工具发送邮件。
- email 接口第一次 503 超时。错误处理器(10)按指数退避重试,第二次成功。记忆系统(4)记录
email_sent: true,防止后续误重发。 - 日志与可观测性(11)早已把每一步模型调用、工具调用、状态变更打上同一个 trace_id 保存下来。调度器(1)收到 finish 信号,结束循环,返回最终报告。
这个流程看下来,你会发现组件之间其实是在“接力”:调度器发起、规划器拆解、模型适配器沟通、上下文管理器裁剪、工具执行器落地、状态管理器记账、护栏把关、错误处理器兜底、观测器留痕。
4.3 协同中最容易被忽略的三个衔接点
第一,工具结果回写。模型做出调用工具的决定后,工具执行器拿到结果,必须把结果结构化成“工具名 + 状态 + 关键内容”写回上下文。这一步相当于前端组件通信里的“子传父”——工具是子组件,上下文是父组件。回写得不好,等于子组件改了数据但父组件没刷新,模型下一轮就失忆了。
第二,状态与分支的处理。Agent 任务不是一条直线,中间会出现分支:竞品 A 搜索成功、竞品 B 搜索失败待重试、用户插话要求改格式。状态管理器需要把这种“部分完成、部分待办、部分失败”的复杂情况记录下来,而不是简单存一个布尔值。
第三,重试的幂等校验。email 工具第一次失败后重试,要确认状态里没有email_sent: true标记。否则模型会因为没收到成功响应而重复发送同一封邮件,这就是线上事故了。
4.4 最小主循环长什么样
下面是我在项目里常用的一个最小 Harness 骨架,用 Python 伪代码表示:
class Harness: def __init__(self, model, tools, memory): self.llm = model # 组件3:模型适配层 self.registry = ToolRegistry(tools) # 组件6:工具注册表 self.memory = memory # 组件4:记忆系统 self.state = {"steps": []} # 组件8:状态管理器 self.observer = Logger() # 组件11:日志观测 def run(self, task): self.state["task"] = task for step in range(MAX_STEPS): # 组件1:核心调度器 prompt = self.build_prompt() # 组件5:上下文管理 action = self.llm.decide(prompt) if action.type == "finish": return action.output tool = self.registry.get(action.name) result = tool.execute(action.arguments) # 组件7:工具执行器 self.state["steps"].append({ "action": action, "result": result, "status": "done" }) self.observer.log(step, action, result) raise TimeoutError("max steps exceeded")这只是骨架,但你能看到 12 个组件里已经有了 6 个。剩下的规划器、护栏、沙箱、错误处理、上下文高级压缩等,都是在这个骨架上逐步加厚的。
5. 从 12 个组件到落地:选型、最小集和排坑经验
5.1 自研 Harness 还是用现成框架
这是每个做 Agent 的人都会纠结的问题。我的建议是:除非你的场景特殊到现有框架都套不上,否则先用现成框架跑通业务,再考虑自研。因为 Agent 应用最大的成本从来不是控制流,而是业务逻辑、工具质量和评测体系。
我按自己的经验给主流框架做个对比:
| 框架 | 核心抽象 | 适合场景 | 注意点 |
|---|---|---|---|
| LangGraph | 图状态编排 | 复杂控制流、需要人工审批、状态持久化要求高 | 概念多,上手曲线陡 |
| LlamaIndex Workflow | 事件驱动 | 数据检索 + RAG 结合紧密的任务 | 组件通信靠事件,调试要习惯 |
| CrewAI | 角色协作 | 多个 Agent 扮演不同角色协同 | 角色边界要清楚,不然互相抢活 |
| AutoGen | 对话式多 Agent | 需要多个 Agent 互相讨论/辩论 | 会话轮次会膨胀,成本高 |
| Mastra | 原生 TS 工作流 + Agent | 前端和 Node 团队做全栈应用 | 生态相对较新 |
| OpenAI Agents SDK | 轻量 Runner + 工具 | 快速验证想法、简单工具调用 | 复杂流程控制力弱一些 |
选型的时候不要看 GitHub Star 数,看你的核心需求:要人工审批就选支持状态持久化的;要跟现有 TS 技术栈融合就选 Mastra;要做研究型多角色协作就选 CrewAI/AutoGen。框架只是脚手架,12 个组件该补的还得补。
5.2 最精简的 Harness 至少要有五件事
如果你只是想快速做一个轻量 Agent,不需要一上来就搭齐全套 12 个组件。但下面五件事是底线:
- 一个能跑循环的调度器,负责决定下一步做什么并判断何时结束;
- 一个模型适配层,负责和 LLM 通信、解析结果;
- 一份工具清单,哪怕只有一个工具,也要有清晰 schema;
- 一个工具执行入口,能实际调用工具并把结果传回来;
- 一个状态对象,存住当前进度和中间结果。
第六个我会立刻补上:日志。没有日志,后续所有优化和排错都是盲人摸象。规划器、记忆系统、护栏、沙箱可以等项目跑通后再逐步加。不过上线前一定要补护栏和沙箱,这两个属于“出事才值钱”的组件,宁可提前。
5.3 实操中踩过的坑
这些坑都是我真实踩过或帮别人排查过的,按频率排序提一下:
上下文爆掉。工具返回大段文本不裁剪,直接塞进上下文。一次搜索就能吃掉 2 万 token,三轮之后模型开始忽略系统指令,回答完全跑偏。现在我对每个工具结果都强制截断,超过 2000 字符就摘要,单轮上下文预算在调度器里写死。
模型反复调用同一个工具。最常见的原因是工具结果没有正确回写,模型看不到“已经查过了”。另一个原因是工具失败后错误被代码吞掉,模型不知道失败了,于是反复尝试。处理方式是:工具结果和错误信息都显式放回上下文,并且带执行状态。
重试产生副作用。发邮件超时重试,结果邮件发了两遍。后来我在状态里加了一个action_status字段,外部副作用类工具执行前先检查是否已执行过,保证幂等。
工具描述不清晰。接了一个开源 Agent,里面同时有search和google_search两个工具,描述都写着“搜索”,模型随机调。我把描述改成了“search 查内部知识库”“google_search 查公网内容”,并把工具按类别分组,准确率立刻上来了。
记忆污染。早期把模型总结的“初步猜测”也写进长期记忆,结果后面的任务不断引用这个错误猜测。现在记忆写入规则是:只有用户明确表达的信息、已完成的工具调用结果、确定的事实性结论可以写入长期记忆,推测性的东西一律只放短期上下文。
日志没有 trace 串联。多 Agent 并行跑的时候,A 智能体的日志和 B 智能体的日志混在一起,没有统一 trace_id,定位一次诡异 bug 花了一整天。后来所有日志都强制带 trace_id,并继承到子任务。
护栏拦截导致任务卡死。Agent 要删除一张临时表,护栏拦截后抛了个异常,模型不知道发生了什么,只能不停重试。现在护栏拦截时返回结构化信息:“操作被拦截,原因是权限不足,建议改用 XX 方式”,模型就能自己调整。
沙箱没限制网络。测试代码执行工具时,模型生成了一段curl命令,沙箱又没断外网,差点把内部服务的接口探测了一遍。从那以后,沙箱的默认策略就是禁网,需要联网的工具单独申请白名单。
最后再说一个我的小习惯
每次接触一个新框架,我会先按“想、备、做、护”四个阶段把它的组件列一遍,给每个组件标出在框架源码里的对应模块和接口。这个动作花不了十分钟,但能让你快速摸清整个框架的设计思路,而不是被各种抽象名词绕晕。
Agent Harness 这个东西,说复杂也复杂,说简单也简单。复杂在组件多、协同链路长;简单在只要抓住“决策、准备、执行、保障”这条主线,所有组件的位置和职责都很清晰。希望这篇拆解能让你在搭建自己的 Agent 系统时,心里先有一张完整地图,而不是照着别人的 demo 一顿复制,复制完还不知道哪里会崩。