Harness Engineering这个说法这两周几乎是以刷屏的方式出现在我关注的好几个技术社群里。有人把它翻译成“控制框架”有人叫它“工程束”但不管叫什么大家讨论的核心其实非常一致大模型的能力边界已经摆在那了决定Agent智能体到底好用还是难用的越来越多地取决于模型外面那层“脚手架”搭得好不好。我自己的项目在Deep Agents深度智能体上断断续续折腾了大半年对这一点的体会尤其深。所谓Deep Agents简单说就是那些要自主完成多步复杂任务的智能体比如“帮我排查这个仓库里的内存泄漏定位后直接把补丁提交上来再跑一遍测试”或者“去调研这三家竞品的最新动态整理成一份带结论的分析报告”。这类任务没法靠一次对话搞定Agent必须自己拆解目标、规划步骤、多次调用工具还要在中间根据结果调整方向。而Harness Engineering就是负责把这些能力“武装”到一个普通模型身上的工程体系——它不改变模型本身但决定了模型能把本事发挥出几分。这篇文章我不打算讲什么宏大理论就老老实实把我这段时间做Deep Agents时关于Harness的思考、拆解、落地代码和踩坑记录整理出来给同样在折腾Agent的朋友一些可参考的素材。如果你发现自己的Agent总是“看起来聪明、用起来拉胯”那问题大概率不在模型而在harness。1. 为什么Harness Engineering突然被推到了台前1.1 先理解Deep Agents真正难在哪里很多人刚开始做Agent时会有一个错觉只要底层模型够强Agent就能自动处理好一切。但这个想法在真实任务里很快会被打击得体无完肤。同一个模型用裸的对话接口让它去调用工具它可能连参数都填不对但换上精心设计的工具定义和调用流程之后它能稳定完成非常复杂的多步操作。差别在哪就差别在那层让模型“更容易做对事”的工程设施上。拿我做过的一个数据分析Agent来说初始版本给它接了五六个工具让它自己决定怎么查数据库、怎么做统计、怎么出报告。听起来很灵活对吧实际跑起来却是另一番景象它经常在没必要的时候调用工具把简单问题复杂化有时候查一次就够了它非要查五六次更让人抓狂的是它偶尔会忘记之前查到的结果反复执行同一个查询。这些问题没有一个是模型“变笨了”而是我根本没有给它搭好一套行为的约束和引导框架。Deep Agents之所以难恰恰因为它的能力来自“模型推理”和“外部工具”的组合。组合带来的自由度很高但自由度本身就是双刃剑。没有规则模型就会在庞大的可能性空间里东冲西撞。而Harness Engineering要做的就是把这个自由度有意识地收敛成一套可预期、可控制、可复用的执行模式。它不是限制模型的发挥而是把模型的能力往正确的方向引导。1.2 Harness的定位不是模型而是模型身边的“整套装备”你可以把基础模型想象成一名刚毕业的高材生——底子很好脑子转得快但没经验不知道职场里的具体规则和打法。Harness就是这位高材生入职后得到的整套装备工位上的操作手册系统提示词、内部系统的使用指南工具说明、每天的待办清单任务拆解机制、还有遇到问题时的上报流程错误处理。这套装备的质量往往比员工本身的聪明程度更容易决定最后产出好不好。一个脑子很灵但没有流程的人做起事来依然可能一团乱一个能力中等但手里有一套清晰SOP和趁手工具的人反而能稳定交付。同样的道理Harness Engineering 的核心任务就是用一套系统的方法把“模型该看什么、能碰什么、怎么碰、碰错了怎么办”全部明确下来。这个视角一转变很多问题就豁然开朗了。以前Agent表现不好我的第一反应是“要不要换个更大的模型”现在我的第一反应是“是不是harness的哪里没有设计好”。后者的改动成本低得多而且往往效果立竿见影。Anthropic那篇流传很广的文章里把workflow和agent做了区分其实讲的也是这个东西你要明确agent的外围机制是什么而不是让模型在每次调用时临时抱佛脚去猜。2. 一个合格Harness的模块拆解2.1 工具层工具描述比工具本身更重要工具层是Harness里最基础也最容易被忽视的一部分。很多人的做法是把函数签名丢给模型就算完事但实际效果差得远。这里有个关键认知模型对工具的理解完全建立在工具描述的文本之上。它看不到你的函数内部是怎么实现的也猜不到某个参数的隐含规则——你写在描述里的内容就是它能知道的一切。我在做SQL Agent时第一版工具描述只写了“执行SQL查询返回结果集”。结果模型经常把UPDATE语句也发过来了虽然我的接口在底层做了拦截但一次好好的数据分析任务就卡住了。后来我把描述改成了“在只读副本上执行SQL查询仅允许SELECT查询超时30秒返回最多100行结果按表格形式返回”并且把参数说明补充完整。从那以后模型误用工具的几率大幅下降。工具描述里值得注意的几个点第一明确边界能做什么、不能做什么要写清楚模型不是人不会“察言观色”第二把参数的含义、格式、单位、可选项都写明白模型对参数的填写准确度会肉眼可见地提升第三如果有依赖关系或调用顺序最好直接写在描述里比如“该工具应该先于analyze_trend调用”。这些信息看着琐碎但每一条都可能在关键时刻避免一次无效调用。2.2 上下文层模型“看到的信息环境”需要精心编排Deep Agents在运行过程中上下文context是动态累积的。每调用一次工具返回结果都会塞进对话历史每走一步模型都会基于前几步继续推理。如果这个累积过程不加以管理上下文很容易变成一个臃肿、杂乱、充满噪声的信息池。模型就像在一间堆满杂物的房间里找钥匙不是找不到而是找得很吃力。上下文层要解决的核心问题有三个保留什么、压缩什么、丢掉什么。系统提示词和最初的用户任务属于“保留”范畴它们是模型理解目标的锚点工具返回的原始数据往往是“压缩”的主要对象——尤其是日志、列表、长文内容全量塞进去既浪费token又稀释注意力而那些已经被消化过的中间推理过程则可以考虑折叠成更简洁的摘要。我常用的做法是给历史消息做分层管理。最近几轮的用户消息、助手消息、工具结果保持完整。更早的轮次尤其是那些已经完成使命的中间步骤则替换为一段模型生成的摘要——这个摘要可以专门由一次“压缩调用”产生也可以在上一步结束时顺手让模型输出。这种分层策略能让模型始终聚焦在“当前最重要”的信息上而不是被历史细节淹没。2.3 控制层循环、终止条件与行动约束控制层是Harness里最像一个“程序”的部分。它负责回答几个关键问题Agent在执行过程中如何循环什么条件下认为任务已经完成如果一直得不到结果怎么办模型是否允许连续调用多个工具还是只能一步步来我见过不少新手做Agent时连一个最基础的最大调用轮次都没有设置结果模型在一个任务上反复横跳把API账单跑出天价才被发现。这其实是Harness设计里最低级、但也最常见的失误。控制层至少要做到限制最大轮次、设定任务完成的判断条件、在超出限制时给出可读的错误现场。另外行动约束也很重要。有些任务允许多工具并行调用比如同时拉取多个接口有些则必须串行比如依赖前面工具的输出。Harness应该根据任务类型施加相应的约束而不是放任模型自由发挥。自由是Agent的天性但约束才是工程化的前提。3. 实操如何从零构建一套可落地的Harness3.1 一个最小的Deep Agent主循环下面这段代码是我在项目里实际使用的一个极简Harness骨架。它把上面说的几个模块都串起来了一个最大轮次限制、一个工具注册表、一个简单的记忆容器。用来跑通流程、做原型验证绰绰有余。from typing import Callable, Dict, List, Any class ToolSpec: def __init__(self, name: str, description: str, parameters: Dict[str, Any], func: Callable): self.name name self.description description self.parameters parameters self.func func def to_openai_schema(self) - Dict[str, Any]: return { type: function, function: { name: self.name, description: self.description, parameters: self.parameters, }, } class Harness: def __init__(self, model: Callable, tools: List[ToolSpec], max_turns: int 10, system_prompt: str ): self.model model self.tool_map: Dict[str, ToolSpec] {t.name: t for t in tools} self.tool_schemas [t.to_openai_schema() for t in tools] self.max_turns max_turns self.messages: List[Dict[str, Any]] [] if system_prompt: self.messages.append({role: system, content: system_prompt}) def run(self, task: str) - Any: self.messages.append({role: user, content: task}) for turn in range(self.max_turns): response self.model( messagesself.messages, toolsself.tool_schemas, tool_choiceauto, ) self.messages.append({ role: assistant, content: response[content], tool_calls: response.get(tool_calls, []), }) tool_calls response.get(tool_calls, []) if not tool_calls: # 模型没有要求调用工具视为任务完成 return response[content] for call in tool_calls: result self._execute_tool(call[function][name], call[function][arguments]) self.messages.append({ role: tool, tool_call_id: call[id], content: result, }) # 超过最大轮次返回现场信息而不是空白 raise RuntimeError(fAgent exceeded max_turns{self.max_turns}) def _execute_tool(self, name: str, args_json: str) - str: tool self.tool_map.get(name) if not tool: return fError: unknown tool {name} try: args json.loads(args_json) result tool.func(**args) return json.dumps(result, ensure_asciiFalse, defaultstr) except Exception as e: return fError executing {name}: {type(e).__name__}: {e}这段代码虽然短但已经覆盖了控制层和工具层的基本职责。几个关键设计点超轮次之后不是直接return一个空值而是抛出异常并保留现场信息这样方便排查问题工具执行出错时把异常信息作为工具返回值返回给模型而不是让整个流程崩溃——模型有时候能根据错误信息自我修正这是个廉价但有效的容错手段。3.2 系统提示词设计给Agent立规矩系统提示词是Harness里成本最低、影响面却非常广的一个杠杆。一个好的系统提示词应该让模型在没有额外思考负担的情况下就知道自己的角色是什么、做事的基本顺序是什么、遇到什么情况应该停下来、最终交付物长什么样。我给数据分析Agent写的系统提示词大致分四段。第一段定义角色“你是一名资深数据分析师通过工具分析数据并回答用户问题”。第二段规定流程“接到任务后先判断需要哪些数据再调用工具获取数据最后基于数据给出结论如果数据不足可以多次查询”。第三段说明输出要求“最终回答必须包含核心结论、关键数据、可能的局限性”。第四段设置自我检查“在给出最终结论之前检查你是否已经掌握了足够的证据如果没有先补充查询”。这里要特别注意克制——提示词不是写得越多越好。过于冗长的提示词会稀释模型的注意力反而让重要的约束起不到作用。我踩过一次坑给一个Agent写了长达两千字的系统提示词结果它在简单任务上的表现反而变差了因为注意力被太多规则分散。后来精简到五六百字效果立刻回升。提示词工程和Harness设计一样核心不是“多给信息”而是“给到关键信息”。3.3 工具结果的后处理与压缩策略工具调用返回的结果不一定适合直接塞给模型尤其是那些本身就很长的内容。比如爬虫返回的完整网页HTML、数据库返回的几千行查询结果、日志检索工具返回的原始日志片段。如果原封不动推进上下文不仅浪费token还会让模型在后续推理时被噪声干扰。我的经验是给每个工具设计一个独立的后处理器。拿SQL查询来说返回结果如果超过一定行数就截断成前N行并在末尾附上一句“结果共1234行以上为前20行”。拿网页抓取来说先用简单的文本提取把HTML标签剥掉再截断到目标长度。这些后处理逻辑看似琐碎却是维持整个Agent上下文健康的关键措施。除了逐工具处理还可以在Harness层面做一层全局的“记忆压缩”。当一个任务执行了很久、历史消息非常多时我会把早期轮次压缩成摘要替换掉原文。这个摘要可以由模型自己生成调用一次带引导的文本总结也可以简单地用“最终结论关键步骤”这种手工模板。实测下来模型生成的摘要更自然但成本略高模板方式更省适合对成本敏感的场景。3.4 结构化输出让模型的中间决策可以被程序消费Deep Agents不只关心最终答案更关注模型在中间过程中做出的决策。工具调用是否合理下一步打算做什么有没有偏离任务目标这些信息如果只是埋在自由文本里程序没法有效地检查和干预。所以我会要求模型在某些关键节点输出结构化的中间结果最常见的就是JSON格式的“下一步行动计划”。还是那个SQL Agent我会让它在每次工具调用前先输出一个JSON块包含“当前理解”用一句话说明当前进度、“下一步动作”调用哪个工具、参数是什么、“为什么这样做的理由”。这个JSON块可以被Harness解析用于记录审计日志、做异常检测比如发现模型重复同一个动作三次、甚至在必要时中断流程。相比完全靠模型在工具调用的参数里自解释这种结构化输出让整个系统的可控性强了很多。实现方式其实不复杂只要在系统提示词里给出明确的JSON格式模板并在解析时允许一定的容错比如用正则提取JSON片段就能稳定工作。少数情况下模型输出不合法JSON我会把解析错误作为一条工具消息返回提示模型修正格式——这和工具执行出错时的处理策略是一致的。4. 常见问题与排查技巧实录4.1 模型反复调用同一个工具陷入原地打转这是Deep Agents运行中最容易遇到的现象模型好像卡住了一样一遍又一遍地查同一个数据、执行同一个动作只是参数略微变化甚至参数完全一样。归根到底是因为模型在上下文里缺少一个“这个动作已经做过了”的信号。我在Harness里加了两道防线。第一道是在工具结果返回时加入一个简短的历史提示“你在第3轮执行过相同查询当时的结果是XXX。请不要重复相同查询除非你有新的筛选条件。”这话虽然简单但对模型的影响非常直接。第二道防线是程序层面的检测——在控制循环里检查工具调用签名是否重复如果发现连续N次同样的调用就强制终止并抛出“Detected repetitive behavior”错误。两道防线结合起来能把死循环的发生率降到极低。4.2 模型编造工具结果直接给出看似合理的答案这个问题比较隐蔽也非常危险。当模型认为“自己知道答案”时它可能不调用工具直接在文本里把结果编出来而且语气非常肯定。这种情况在模型面对不熟悉的领域时尤其常见——它会把训练数据里的记忆当成事实一本正经地输出。Harness层面的应对思路是在系统提示词里强调“所有数据必须来自最新工具调用的返回结果禁止根据常识猜测数据”并且在控制循环里不轻易接受“无工具调用”的回复。对高风险场景我甚至会强制要求模型在给出最终答案前至少调用过一个工具。这种方式虽然有点“暴力”但对防止幻觉非常有效。4.3 上下文窗口溢出怎么及时止损上下文窗口溢出是另一个高频事故尤其在任务步骤多、工具返回长文本的时候。等到报错才处理往往已经晚了。我的做法是提前干预在每次工具返回后检查当前消息总token数超过阈值时先触发压缩逻辑把历史摘要化之后再继续。压缩策略要灵活。如果最占空间的是某个大型工具结果而且它已经被后续步骤用过了那可以直接删除用一个简短的文字摘要替代。如果占用的是模型的中间推理过程可以让模型自己总结成三句话。这个提前干预机制几乎杜绝了我项目里的上下文溢出问题。注意阈值不要设到模型窗口的100%留个20%的缓冲给后续步骤是更稳妥的选择不然压缩动作本身就是一次额外的token消耗。4.4 模型调用工具时反复填错参数填错参数这个问题的根源通常是工具描述不够具体。比如一个接收日期字符串的参数你没写清楚格式模型就可能一会儿传“2024-01-01”一会儿传“2024/01/01 00:00:00”导致工具端解析失败。解决问题的路径有两条。第一是在描述里把格式精确到示例级别“日期格式必须为YYYY-MM-DD示例2024-01-01”第二是在工具执行端做宽容处理比如把日期解析封装得更鲁棒能自动识别多种格式。但两条路各有优劣前者的成功率更高因为模型能直接遵循后者更省事但对格式的混乱可能无解。最合理的做法是先用描述解决问题再在工具端兜底。5. 后续还能往哪些方向延伸5.1 让Harness拥有“自我反思”能力这是目前我用下来性价比最高的进阶优化之一。具体做法是在Agent执行完一轮完整任务后增加一个额外的反思调reflection turn让模型回顾整个过程指出自己哪些步骤是多余的、哪些信息是缺失的、如果重新来过会怎么调整。然后把反思结论作为输入让模型重新执行或修正结果。这个机制对提升最终答案质量很有帮助代价是额外的token消耗和一倍的运行时间是否启用要看具体场景的容错要求。5.2 为不同任务类型定制不同的Harness模板Deep Agents的一大误区是把所有任务都塞进同一个通用Harness里。实际工程中数据分析、代码开发、网页操作、客服对话它们对工具、上下文、控制逻辑的需求差异非常大。比如代码开发Agent需要把文件读写和命令行执行作为一等公民而数据分析Agent更看重查询结果的表格化呈现。与其找一个万能模板不如为每个高频场景做一套带默认工具的Harness配置跑起来更顺手调试方向也明确很多。5.3 观察性建设没有日志就没有调试的抓手最后这一点虽然不性感但可能是我最想强调的。Deep Agents天然是非确定性的如果Harness里没有日志系统一次失败可能根本找不到原因。我在Harness里记录了每一次模型决策、工具调用的输入输出、耗时、token消耗、以及最终结果。遇到问题回放日志时往往一眼就能看出是模型跑偏了、工具报错了还是上下文被污染了。观察性不是收尾工作而是Harness工程里面向长期迭代的基础设施。我在实际项目中最大的体会是Deep Agents的优化空间远比想象中大但它不属于模型侧而属于外围的Harness侧。与其反复纠结要不要换更强的模型不如先把手里的Harness打磨到位。工具描述、上下文管理、控制循环、错误恢复——每一项打磨下去都能看到Agent稳定性的肉眼可见提升。做Agent确实有运气的成分但把Harness工程做到位等于把运气变成了稳定发挥。
