1. 从能聊到能干活Harness 到底卡在哪个环节大多数人第一次搭 AI Agent都会经历同一个阶段模型接上了提示词调通了对话效果看着还行但只要让它真正去执行一个多步骤任务立刻就露馅——要么中途忘了自己干到哪一步要么工具调用参数拼错要么循环几轮之后开始胡言乱语。问题往往不在模型本身而在于模型外面那层壳没搭好。这层壳业内习惯叫Harness。直译是马具挽具意思很形象模型是那匹力气很大的马但光有马跑不起来你得有一套缰绳、鞍具、轭具把它约束住、引导好才能让它拉着车往一个方向走。Harness 就是 Agent 里负责约束和引导模型真正干活的那套工程结构。很多人把 Harness 和 Agent 框架混为一谈其实两者关注点不一样。框架比如各种编排库解决的是怎么把组件拼起来而 Harness 解决的是模型每一步该看到什么、该输出什么、输出之后怎么落地、出错怎么兜。前者是骨架后者是神经和肌肉。你去看那些真正能跑通复杂任务的 Agent模型可能换来换去但 Harness 的设计思路高度相似——基本都能拆成 7 个子系统。这篇就把这 7 个子系统一个个拆开讲清楚每个是干什么的、为什么必须有、实际写的时候容易在哪翻车。适合已经能跑通单轮对话、想让 Agent 真正下地干活的人。如果你还停留在调 API 拿回复的阶段看完这篇会对整个工程结构有个清晰的地图。提示Harness 不是某个具体产品的名字而是一类工程结构的统称。不同团队叫法不同但核心职责高度重合。下面这 7 个子系统是我在多个项目里反复验证后收敛出来的划分方式。2. 子系统一Agent Loop——整个 Harness 的心跳2.1 为什么循环是第一个要解决的问题单轮问答不需要循环输入进去输出出来结束。但 Agent 干活天然是多轮的——它要先理解任务再决定调哪个工具拿到工具结果后再判断下一步直到任务完成。这个判断—执行—观察—再判断的过程就是一个循环。Agent Loop 就是驱动这个循环的引擎。它决定了什么时候继续循环、什么时候停下来、每一轮把什么信息喂给模型。听起来简单但这是整个 Harness 里最容易写出死循环、最容易烧钱、最容易让 Agent 鬼打墙的地方。我见过太多新手写的 Loop 长这样把用户问题丢给模型模型说要调工具调完把结果拼回对话历史再丢给模型如此往复直到模型不再要求调工具为止。这个逻辑在简单场景能跑但一旦任务复杂就会出现三种典型故障。2.2 三种典型故障与对应的 Loop 设计第一种是无限循环。模型可能因为工具返回的结果不符合预期反复调用同一个工具。比如让它查一个不存在的文件工具每次都返回文件不存在模型每次都再查一遍。没有轮次上限这个循环能跑到天荒地老。第二种是提前退出。模型在任务只完成一半时就认为搞定了直接给出最终答案。这在多步骤任务里特别常见因为模型看不到全局进度只看到当前上下文。第三种是上下文爆炸。每一轮的工具返回结果都往对话历史里塞几轮之后上下文就撑满了要么超长被截断要么成本飙升。针对这三种故障Loop 的设计要带上三个机制最大轮次限制、显式的任务状态跟踪、上下文压缩策略。最大轮次好理解设个硬上限比如 15 轮到了就强制收尾。任务状态跟踪则需要你在 Loop 外面维护一个结构化的进度对象每一轮把已完成什么、还剩什么注入到提示里让模型有全局感。上下文压缩则是当历史超过阈值时把早期的工具调用结果摘要化只保留关键结论。# Agent Loop 的核心骨架伪代码突出控制逻辑 MAX_TURNS 15 state {goal: task, done: [], pending: []} for turn in range(MAX_TURNS): context build_context(state, recent_history) action llm.decide(context) # 模型决定下一步 if action.type final: return action.answer # 模型认为完成 result execute_tool(action) # 落地执行 state update_state(state, action, result) recent_history compress_if_needed(recent_history, result) return force_wrap_up(state) # 超轮次强制收尾2.3 一个容易被忽略的细节循环的退出条件要显式化很多人依赖模型自己说完成作为退出条件这是最不可靠的做法。更稳的方式是让 Loop 自己判断任务状态里的 pending 列表空了才算完成。模型说完成但 pending 还有内容就继续循环并提示它还有未完成项。这个改动看着小但实测下来能显著降低半途而废的概率。因为模型在每一轮都能看到明确的待办清单而不是靠自己的记忆去判断进度。3. 子系统二LLM Integration——别把模型当成一个黑盒函数3.1 模型接入不只是发请求收回复LLM Integration 这个子系统新手往往觉得最简单——不就是调 API 吗但真正做过生产级 Agent 的人都知道这里是坑最多的地方之一。原因在于Agent 对模型的要求和普通对话完全不同。普通对话里模型输出一段文字就完事了。但 Agent 里模型的输出要被解析成结构化动作——它得告诉 Harness 我要调哪个工具、参数是什么。这就要求模型输出必须是可解析的格式而且格式要稳定。3.2 结构化输出的三种实现路径与取舍目前主流有三种做法各有取舍。第一种是提示词约束 正则解析。在提示里明确要求模型按 JSON 格式输出然后用正则或 JSON 解析器提取。优点是通用任何模型都能用缺点是模型偶尔会跑格式输出带 markdown 代码块包裹的 JSON或者干脆漏个括号解析就崩了。第二种是模型原生的函数调用能力。现在主流模型基本都支持 function calling 或 tool use模型直接返回结构化的工具调用对象不用你自己解析。这是最稳的方式优先推荐。缺点是不同模型的调用格式有差异切换模型时要适配。第三种是约束解码。在推理层面限制模型只能输出符合某个语法规则的 token从根上保证格式正确。效果最好但需要模型侧支持不是所有部署方式都能用。我的建议是能用原生函数调用就用原生实在不行再退回提示词约束并且一定要在解析层加容错和重试。解析失败时不要把错误直接抛给用户而是把你上次输出格式不对请重新按格式输出作为一条消息塞回去让模型自己修。3.3 模型选型的现实考量Agent 场景下选模型不能只看谁最聪明。要考虑三个维度指令遵循能力能不能稳定按格式输出、工具调用准确率参数拼得对不对、延迟和成本Agent 是多轮的单轮成本会被放大十几倍。一个反直觉的经验在 Agent 场景里一个指令遵循强、工具调用稳的中等模型往往比一个最聪明但爱自由发挥的顶级模型更好用。因为 Harness 需要的是可预测性而不是单轮的惊艳表现。模型每次都能老老实实按格式输出、按规则调工具整个系统才稳。注意切换模型时一定要重新跑一遍工具调用的回归测试。不同模型对同一个工具描述的理解可能完全不同A 模型能正确填的参数B 模型可能填错。4. 子系统三Tool Registry——工具不是越多越好4.1 工具注册表的核心职责Tool Registry 管的是Agent 能用哪些工具、每个工具怎么描述、怎么调用。听起来像个简单的字典但它直接决定了模型能不能选对工具。核心问题在于模型选工具靠的是工具的描述文本。描述写得含糊模型就会选错工具太多模型就会挑花眼。所以 Tool Registry 不只是存工具还要负责工具的描述优化和筛选。4.2 工具描述的写法直接决定调用准确率我踩过的一个坑早期给工具写描述写的是查询数据库。结果模型经常在不需要查库的时候也去调它。后来改成根据用户提供的订单号从订单库查询该订单的详细状态包括发货、物流、签收信息。仅当用户明确询问某个具体订单的状态时使用调用准确率立刻上去了。关键是把三件事写清楚这个工具做什么、什么情况下用、什么情况下不要用。尤其是最后一条很多人不写但它是减少误调用的关键。4.3 工具数量膨胀后的筛选策略当工具超过 15 到 20 个模型的选错率会明显上升。这时候有两个策略。一是分组。把工具按领域分组先让模型选组再在组内选工具。相当于把一次大选择拆成两次小选择准确率会高很多。二是动态筛选。根据当前任务状态只把相关的工具暴露给模型。比如任务还在信息收集阶段就只暴露查询类工具不暴露写入类工具。这需要 Harness 维护一个当前阶段允许的工具集。工具数量推荐策略预期选错率1-10全量暴露低10-20全量暴露 描述优化中20-50分组选择中低50动态筛选 分组可控4.4 工具执行的安全边界Tool Registry 还要管一件事哪些工具是危险的。写文件、删数据、发请求这类有副作用的工具不能让它随便执行。常见做法是加一层确认机制——危险工具在执行前要么要求人工确认要么在沙箱环境里先跑一遍。我在实际项目里的做法是给每个工具打一个risk_level标签低风险的直接执行高风险的走确认流程。这个标签在注册工具时就定好不要等到运行时再判断。5. 子系统四Memory Context——Agent 的记性怎么管5.1 短期记忆和长期记忆要分开设计Agent 的记忆分两种混在一起设计必然出问题。短期记忆是当前任务执行过程中的上下文——这一轮调了什么工具、返回了什么、当前进度到哪。它跟着 Agent Loop 走任务结束就清空。长期记忆是跨任务、跨会话的信息——用户的偏好、历史交互的关键结论、领域知识。它需要持久化存储并且在合适的时候被检索出来注入上下文。新手最常见的错误是把所有东西都往对话历史里塞既当短期又当长期结果就是上下文越来越长模型越来越糊涂。5.2 上下文窗口的分配策略上下文窗口是有限资源怎么分配直接决定 Agent 的表现。我的经验分配大致是这样系统提示和工具描述占 20% 到 30%当前任务状态占 10%近期对话历史占 30% 到 40%检索到的长期记忆占 10% 到 20%剩下留作输出缓冲。这个比例不是死的但核心原则是当前任务状态必须始终可见因为它决定模型下一步做什么长期记忆按需检索不要一股脑全塞进去。5.3 记忆检索的触发时机长期记忆什么时候检索两个时机最有效任务开始时拉取相关背景以及模型显式请求时模型判断需要历史信息。前者是主动注入后者是被动查询。主动注入要控制量只拉最相关的几条被动查询则可以把检索做成一个工具让模型自己决定要不要查。提示记忆检索的相关性排序不要只靠向量相似度。加上时间衰减近期记忆权重更高和重要性标签标记为关键的记忆优先效果会好很多。6. 子系统五Planning Decomposition——把大任务拆成能执行的步骤6.1 为什么 Agent 需要显式的规划层一个复杂任务比如帮我整理这份销售数据并生成周报直接丢给模型它很可能东一榔头西一棒子。Planning 子系统的职责就是把这个大任务拆成有序的小步骤让 Agent 一步步执行。规划有两种模式先规划后执行和边执行边规划。前者在任务开始时一次性生成完整计划执行时按计划走后者每一步都重新判断下一步做什么。先规划的好处是全局感强不容易跑偏坏处是计划可能一开始就错了后面全错。边执行的好处是灵活能根据中间结果调整坏处是容易失去全局方向。实际项目里我通常用混合模式先出一个粗粒度的计划3 到 5 个大步骤每个大步骤内部再边执行边细化。6.2 任务拆解的粒度控制拆得太粗模型执行时还是不知道怎么做拆得太细规划本身就成了负担而且容易过度约束模型。我的经验粒度是每个子任务应该是一次工具调用或一次明确的推理能完成的。比如查询上季度销售数据是一个合适的粒度处理数据就太粗打开文件、读取第 3 行、提取第 2 列就太细。6.3 计划执行中的动态调整计划不是圣旨。执行过程中发现某个步骤不可行要允许调整。但调整要有约束不能一遇到困难就推翻整个计划。我的做法是计划里的每个步骤有状态待执行、进行中、完成、失败。失败时先尝试重试或换方法连续失败两次才触发重新规划。这样既保留了灵活性又避免了频繁推翻计划导致的混乱。7. 子系统六Execution Sandbox——让动作真正落地且不出事7.1 执行层要解决的核心矛盾Execution 子系统负责把模型决定的动作真正执行掉——调 API、读写文件、跑代码。这里的核心矛盾是既要让 Agent 有能力做事又要防止它做坏事。模型可能会生成有问题的代码、访问不该访问的资源、执行破坏性操作。Sandbox 就是用来隔离这些风险的。7.2 沙箱的几种隔离级别隔离级别从弱到强大致分四档进程内隔离同进程跑只做权限检查、子进程隔离独立进程限制资源、容器隔离独立文件系统和网络、虚拟机隔离最强但最重。选哪一档取决于 Agent 要执行什么。如果只是调几个受控的 API进程内隔离加权限检查就够了。如果要让 Agent 跑任意代码那必须上容器甚至虚拟机。隔离级别适用场景开销安全性进程内受控 API 调用极低低子进程脚本执行低中容器任意代码执行中高虚拟机高风险操作高极高7.3 执行结果的标准化工具执行完返回给模型的结果要标准化。不要直接把原始输出丢回去而是包装成统一格式成功还是失败、关键信息是什么、有没有需要模型注意的异常。这一步做得好模型对执行结果的理解会准确很多。我见过太多 Agent 因为工具返回了一大坨原始日志模型抓不住重点导致后续判断出错。8. 子系统七Observability Guardrails——看不见的 Agent 最危险8.1 可观测性不是可选项Agent 是多轮、多工具、有状态的系统出问题时如果看不到中间过程排查就是盲人摸象。Observability 子系统要记录每一轮的输入输出、工具调用详情、状态变化、耗时和成本。这些数据不只是用来排错的也是优化 Harness 的依据。比如你发现某个工具被频繁误调用就能针对性优化它的描述发现某类任务总是超轮次就能调整规划策略。8.2 护栏要拦在哪些位置Guardrails 是安全网要在几个关键位置设卡输入侧拦截明显有害的请求工具调用侧拦截高风险操作输出侧过滤不当内容循环侧检测异常模式比如反复调用同一工具。护栏的设计原则是宁可误拦不可漏放但误拦率也不能太高否则 Agent 会变得畏手畏脚。我的做法是分级明确有害的直接拦疑似有害的走确认正常的放行。8.3 成本监控是隐形的护栏Agent 多轮调用成本很容易失控。一个跑飞的循环可能几分钟烧掉几十块。所以成本监控要实时设一个单任务成本上限超了就强制终止并告警。这个上限怎么定先跑一批典型任务统计平均成本然后设成平均值的 3 到 5 倍。既给了复杂任务空间又能拦住异常。9. 七个子系统怎么串起来一次完整任务的流转把七个子系统串起来看一次任务的大致流转是这样的用户输入进入Guardrails 做输入检查。Planning 子系统把任务拆成粗粒度计划。Agent Loop 启动每一轮从 Memory 拉取上下文从 Tool Registry 拉取可用工具。LLM Integration 让模型基于当前状态决定下一步动作。如果是工具调用Execution 子系统在 Sandbox 里执行结果标准化后回填。每一轮的状态变化、调用详情写入 Observability。循环直到任务完成或触发退出条件输出经 Guardrails 检查后返回。这七个部分不是孤立的它们之间有大量交互。比如 Memory 的上下文分配会影响 LLM 的判断Tool Registry 的描述质量会影响 Execution 的成功率。所以搭 Harness 不能一个子系统一个子系统孤立地做要有整体视角。10. 搭 Harness 时我踩过的几个真实坑第一个坑是过早优化。一开始就想把七个子系统都做得很完善结果哪个都没做好。后来改成先跑通最小闭环——Loop 加 LLM 加一个工具能干活了再逐个补强。这个顺序很重要。第二个坑是工具描述偷懒。前面提过工具描述直接决定调用准确率。我早期为了快描述写得很简略结果调试工具调用花的时间比写描述多十倍。后来养成习惯每个工具的描述都当小作文写。第三个坑是忽略上下文压缩。有个任务跑到第八轮突然开始胡言乱语排查半天发现是上下文超了被截断把关键的任务状态截掉了。加了压缩策略后问题消失。第四个坑是没有成本上限。有次测试一个循环逻辑写错了Agent 反复调用一个付费 API半小时烧掉一笔不小的费用。从那以后所有 Agent 都强制加成本监控。第五个坑是可观测性做得太晚。前期没记录中间过程出问题只能靠猜。后来补上日志排查效率提升非常明显。这个投入绝对值得越早做越好。11. 关于 Harness 的几个常见误解第一个误解是Harness 就是提示词工程。提示词只是其中一小部分Harness 更多是工程结构问题——循环怎么控、状态怎么管、工具怎么注册、执行怎么隔离。提示词写得好但 Harness 结构烂Agent 照样跑不起来。第二个误解是模型够强就不需要 Harness。恰恰相反模型越强能做的事越多越需要 Harness 来约束和引导。没有 Harness强模型只会更自由地跑偏。第三个误解是Harness 一次搭好就不用管了。Harness 是要持续迭代的。任务类型变了、工具变了、模型换了Harness 都要跟着调。它更像一个需要长期维护的系统而不是一次性工程。第四个误解是子系统越多越好。七个是我收敛出来的必要集合不是让你无限加。每个子系统都有维护成本加之前先问不加它Agent 会出什么问题如果答不上来就不加。12. 从最小可用 Harness 开始的落地路径如果你现在就想动手我的建议是按这个顺序来第一步搭一个带最大轮次限制的 Agent Loop接一个模型注册一个工具跑通模型决定调工具、工具执行、结果回填、模型给最终答案这个最小闭环。第二步把工具描述写规范加上结构化输出解析和容错重试。这一步做完Agent 的稳定性会有质的提升。第三步加上任务状态跟踪和上下文压缩让 Agent 能处理多步骤任务而不跑偏。第四步补上执行隔离和可观测性让 Agent 能安全地做更多事同时出问题能排查。第五步加规划层和长期记忆让 Agent 能处理更复杂的任务。这个顺序的核心逻辑是先让它能干活再让它干得稳最后让它干得复杂。反过来做很容易在还没跑通闭环的时候就陷入细节最后什么都没搭起来。Harness 这个东西说穿了就是把让模型真正干活这件事拆成一个个可控的工程问题。七个子系统不是标准答案而是一张地图。你照着这张地图走至少不会在搭 Agent 的时候迷路。至于每个子系统做到什么程度取决于你的任务复杂度和风险容忍度——这个平衡只能在实际项目里慢慢找。