1. 从一个类比开始Agent Harness 到底是什么如果你最近在折腾 Agent 开发大概率会反复撞见一个词——Agent Harness。有人把它翻译成“智能体挽具”有人叫它“运行时骨架”还有人干脆不翻译直接说“harness”。我第一次看到这个词的时候也懵因为 harness 在英文里本意是马具、安全带跟 AI 八竿子打不着。但恰恰是这个“马具”的类比能让你在五分钟内把整个 Agent 系统的架构想明白。想象一匹马。马本身有力量、有速度、有脾气但它不知道怎么拉车、不知道往哪走、不知道什么时候停。你需要一套马具——缰绳、鞍、轭——把马的力量约束到正确的方向上再配一个车夫来发指令。在这个类比里LLM 就是那匹马它有能力但不可控Agent Harness 就是那套马具加车夫负责把模型的原始能力转化成可执行、可观测、可复现的任务流程。这个类比不是我自己编的是我在翻 Agent 框架源码时慢慢悟出来的。你会发现几乎所有主流 Agent 框架——不管是偏编排的还是偏运行时的——拆开来看都在做同一件事给 LLM 套上一层结构化的外壳让它从“会聊天”变成“会干活”。这层外壳就是 Harness。那为什么现在这个词突然火了因为大家发现单纯调 API 已经不够了。你让模型写个函数它没问题但你让它去查数据库、调接口、改文件、跑测试、失败了重试、成功了汇报——这一整套流程里模型只占其中一环剩下全是 Harness 的活。Harness 决定了 Agent 能不能稳定跑起来而不是偶尔跑通一次。这篇文章我打算从一个最朴素的类比出发把 Agent Harness 拆成 12 个组件一个一个讲清楚它们各自解决什么问题、为什么缺一不可、实际写代码时怎么落地。适合正在做 Agent 开发、被各种框架绕晕、或者想自己从零搭一套运行时的人。读完你至少能拿到一张清晰的架构地图知道每个模块该放在哪、该管什么。2. 为什么需要 HarnessLLM 裸奔的四个致命伤在拆组件之前得先搞清楚一个问题为什么不能直接拿 LLM 当 Agent 用我早期做项目时就干过这种蠢事直接把用户输入丢给模型让它返回一个 JSON 表示要执行的动作然后我解析执行。结果踩了一堆坑总结下来有四个致命伤这四个伤正好对应 Harness 要解决的核心问题。2.1 致命伤一模型输出不可靠格式随时崩LLM 本质是概率模型你让它输出 JSON它 99% 的时候给你标准 JSON剩下 1% 给你加个“好的以下是结果”的前缀或者把双引号换成单引号或者在 JSON 后面多写一段解释。这 1% 在生产环境里就是灾难。我见过最离谱的一次模型在 JSON 里塞了个注释解析器直接炸了。Harness 要做的第一件事就是输出约束与解析。它得有一层专门的解析器能容忍常见格式偏差能在解析失败时触发重试能把自然语言和结构化数据分开处理。这不是模型的问题是工程问题必须由 Harness 兜住。2.2 致命伤二模型没有记忆上下文一长就失忆LLM 的上下文窗口是有限的而且它不会主动记住任何东西。你这一轮告诉它“用户叫张三”下一轮它可能就忘了。更麻烦的是当对话轮次多了历史消息会把上下文撑爆模型开始丢失早期关键信息。Harness 必须承担记忆管理的职责。短期记忆怎么存、长期记忆怎么检索、什么时候压缩历史、什么时候注入关键事实——这些都是 Harness 的活。你不能指望模型自己管理记忆它连自己上一句说了什么都记不住。2.3 致命伤三模型不会用工具得有人递工具模型本身不能执行任何操作它只能生成文本。你要让它查天气得先告诉它有个查天气的工具再教它怎么调用再解析它的调用请求再真正执行再把结果喂回去。这一整套流程模型只负责“决定调什么”剩下的全是 Harness 的活。工具调用与执行是 Harness 最核心的能力之一。工具怎么注册、参数怎么校验、执行超时怎么处理、结果怎么格式化回传——每一个环节都有坑。我见过因为工具返回结果太长把上下文撑爆的也见过工具执行失败但模型以为成功继续往下走的。2.4 致命伤四模型会跑偏没人拉缰绳你让模型帮你订机票它可能聊着聊着开始给你推荐旅游景点。你让它改代码它可能改着改着把整个文件重写了。模型没有目标感它只会根据当前上下文预测下一个 token至于这个 token 是不是朝着目标走的它不关心。Harness 需要一套目标约束与流程控制机制。任务分解、步骤校验、循环检测、终止条件——这些都得 Harness 来管。就像车夫不能指望马自己认路得靠缰绳不断修正方向。把这四个致命伤想清楚你就能理解为什么 Harness 不是“可选优化”而是“必需基础设施”。接下来我把 Harness 拆成 12 个组件每一个都对应解决上面某个或某几个问题。3. 十二个组件逐个拆解从输入到输出的完整链路这 12 个组件不是拍脑袋凑的是我在实际搭过两套 Agent 运行时之后把重复出现的模块抽象出来的。它们有先后依赖关系但也不是严格线性有些是横切关注点。我按数据流动的顺序来讲每个组件说清楚三件事它是什么、为什么需要它、实际怎么实现。3.1 组件一输入适配层Input Adapter这是整个 Harness 的入口。用户输入可能是纯文本、可能是带附件的消息、可能是结构化指令、可能是语音转文字的结果。输入适配层负责把这些五花八门的输入统一成内部标准格式。为什么需要它因为下游组件不应该关心输入从哪来。你从网页来的输入和从命令行来的输入到了核心逻辑层应该长得一样。这层还负责做基础清洗——去掉多余空白、检测编码、提取元数据时间戳、用户 ID、会话 ID。实现上通常是一个函数或类接收原始输入返回一个标准化的Message对象。我习惯在这个对象里塞几个字段content文本内容、attachments附件列表、metadata元信息、source来源标识。别小看这层我踩过的坑是早期没做输入清洗用户从 Excel 复制的内容带了一堆不可见字符把后面的解析器搞崩了。3.2 组件二上下文组装器Context Assembler这是 Harness 里最容易被低估、但实际最影响效果的组件。它的工作是把系统提示、历史对话、检索到的知识、当前输入、工具描述这些东西拼成一个完整的 prompt塞给模型。为什么需要单独一个组件因为拼 prompt 不是简单字符串拼接。你得考虑 token 预算——系统提示占多少、历史占多少、知识占多少、留给模型输出的空间有多少。你还得考虑顺序——什么信息放前面模型更关注什么信息放后面容易被忽略。你还得考虑动态性——有些信息这轮需要下轮就不需要了。我通常把上下文组装器设计成可配置的管道每个信息源是一个Provider按优先级和 token 预算动态裁剪。比如历史对话超过预算就做摘要压缩知识检索结果按相关度排序后截断。这个组件的质量直接决定模型表现值得花时间打磨。3.3 组件三模型调用网关Model Gateway这层负责跟实际的 LLM 打交道。为什么不让业务代码直接调模型 API因为你需要统一处理一堆横切问题重试、超时、限流、降级、多模型路由、成本统计、日志记录。我见过太多项目把模型调用散落在各处结果想换个模型要改十几个文件想加重试逻辑要每个调用点都改一遍。模型调用网关把这些收敛到一个地方。它对外暴露一个统一的invoke接口内部处理所有脏活。实际实现时我会在这个网关里做几件事维护一个模型配置表哪个模型适合什么任务、实现指数退避重试、记录每次调用的 token 消耗和延迟、在模型不可用时自动切换到备用模型。这层做扎实了上层业务代码会干净很多。3.4 组件四输出解析器Output Parser模型返回的是文本但 Harness 需要的是结构化数据。输出解析器负责把文本转成程序能理解的对象。这层要处理的情况包括标准 JSON、带 markdown 代码块的 JSON、带前后缀说明的 JSON、函数调用格式、纯自然语言。我的经验是永远不要相信模型会输出完美格式。解析器要有多级容错先尝试直接解析失败后尝试提取代码块再失败尝试正则匹配关键字段最后还失败就触发重试或降级到自然语言处理。每次解析失败都要记录因为这是优化 prompt 的重要信号。这里有个细节解析器要能区分“模型真的想输出这个结构”和“模型在胡说八道”。我一般会加一个 schema 校验层字段类型不对、必填字段缺失、枚举值越界都算解析失败。3.5 组件五工具注册表Tool RegistryAgent 能干什么取决于它有哪些工具。工具注册表就是管理这些工具的地方。每个工具需要描述名字、功能说明、参数 schema、执行函数、超时设置、是否需要确认。为什么需要注册表而不是硬编码因为工具是动态的。你可能根据用户权限动态加载不同工具可能根据任务类型切换工具集可能热插拔新工具。注册表提供统一的注册、查询、描述生成接口。我设计注册表时有个习惯每个工具必须提供一段给模型看的自然语言描述而不只是函数签名。因为模型是根据描述来决定调不调这个工具的描述写得好不好直接影响调用准确率。我见过因为工具描述写得太模糊模型该调的时候不调、不该调的时候乱调。3.6 组件六工具执行器Tool Executor注册表管“有什么工具”执行器管“怎么执行工具”。这层负责参数校验、权限检查、实际调用、超时控制、异常捕获、结果格式化。参数校验特别重要。模型生成的参数经常有类型错误、缺字段、多字段。执行器要在真正调用前拦住这些错误返回清晰的错误信息让模型修正。我一般用 JSON Schema 做校验校验失败时把具体哪个字段错了告诉模型它下一轮通常能改对。超时控制也是必须的。有些工具调用外部 API可能卡住不返回。执行器要设置合理超时超时后返回一个明确的超时错误而不是让整个流程挂死。我吃过这个亏一个 HTTP 请求没设超时整个 Agent 卡了五分钟。3.7 组件七记忆管理器Memory Manager记忆分短期和长期。短期记忆就是当前会话的历史消息长期记忆是跨会话的知识沉淀。记忆管理器负责存储、检索、压缩、遗忘。短期记忆的管理核心是token 预算下的取舍。不能把所有历史都塞进去得决定保留哪些、压缩哪些、丢弃哪些。我常用的策略是最近 N 轮完整保留更早的做摘要再早的只保留关键事实。摘要本身也是一次模型调用所以要有触发条件不能每轮都摘要。长期记忆通常用向量数据库实现。把重要信息 embedding 后存起来需要时按语义相似度检索。这里的关键是什么时候写入长期记忆——不能什么都存否则检索出来的全是噪音。我一般只在任务完成、用户明确要求记住、或者检测到重要事实时才写入。3.8 组件八任务规划器Task Planner复杂任务需要分解。任务规划器负责把一个大目标拆成可执行的步骤序列。这层可以是基于规则的也可以是基于模型的实际项目中往往是混合的。基于规则的部分处理常见模式比如“先查再改”“先验证再提交”。基于模型的部分处理开放式任务让模型生成步骤列表。我倾向于让模型生成步骤但用规则做校验——比如检测循环依赖、检测步骤过多、检测缺少终止条件。规划器还要支持动态重规划。执行过程中发现某步失败了或者环境变了得能调整后续步骤。这层做得好Agent 才能处理真正复杂的任务而不是只能跑固定流程。3.9 组件九执行循环控制器Execution Loop Controller这是 Harness 的心脏。它驱动整个“思考-行动-观察”的循环调模型、解析输出、执行工具、把结果喂回模型、再调模型直到任务完成或触发终止条件。为什么需要单独的控制器因为循环控制有很多细节最大轮次限制、循环检测、终止条件判断、错误累积处理、中间状态保存。这些逻辑散在各处会很难维护。我实现控制器时必设的几个保护最大迭代次数防止无限循环、重复动作检测连续两次调同一个工具同样参数就中断、错误阈值连续失败 N 次就放弃、超时总控整个任务最长执行时间。这些保护看着简单但能避免 90% 的线上事故。3.10 组件十状态管理器State ManagerAgent 执行过程中会产生大量状态当前步骤、已完成步骤、中间结果、工具调用记录、错误信息。状态管理器负责这些状态的存储、查询、更新、持久化。为什么不能直接用变量存因为 Agent 执行可能是长时间的、可能中断恢复、可能并发多任务。状态需要能序列化、能持久化、能按会话隔离。我一般用类似状态机的设计每个状态转换都有明确触发条件和副作用。持久化特别重要。用户等了五分钟Agent 跑到一半服务重启了如果没有状态持久化一切从头再来用户体验极差。我习惯在每个关键步骤后做一次 checkpoint重启后能从最近 checkpoint 恢复。3.11 组件十一可观测性层Observability LayerAgent 是个黑盒出了问题很难查。可观测性层负责记录所有关键事件模型调用、工具执行、状态转换、错误发生。这些记录要能追溯、能聚合、能告警。我通常记录三类数据日志每个事件的详细上下文、指标调用次数、延迟、成功率、token 消耗、链路一次任务从开始到结束的完整调用链。链路追踪特别有用能一眼看出时间花在哪、哪步失败了。这层还负责把内部状态暴露给用户。用户看到的不应该只是“正在处理”而应该是“正在查询数据库”“正在生成报告”。透明度上去了用户信任度也上去了。3.12 组件十二安全与权限层Security and Permission Layer最后一个但绝不是最不重要的。Agent 能执行操作就意味着有风险。安全层负责工具权限校验、敏感操作确认、输入输出过滤、审计日志。权限校验要细到工具级别。不是所有用户都能调所有工具不是所有场景都允许执行危险操作。我一般给每个工具打上风险等级标签高风险工具需要额外确认或特定权限。敏感操作确认是个好习惯。删除文件、发送邮件、提交订单这类不可逆操作让用户确认一下。这不仅是安全考虑也是体验考虑——用户会感觉自己在掌控局面。4. 组件之间的协作关系一张运行时序图光讲单个组件还不够得说清楚它们怎么串起来。我按一次典型任务的生命周期来梳理。用户输入进来先过输入适配层标准化。然后上下文组装器把系统提示、历史、知识、当前输入拼成 prompt。模型调用网关把 prompt 发给 LLM。模型返回后输出解析器解析成结构化数据。如果解析出的是工具调用工具注册表查工具定义工具执行器校验参数并执行。执行结果通过记忆管理器记录然后回到上下文组装器把结果拼进下一轮 prompt。整个循环由执行循环控制器驱动任务规划器在需要时提供步骤指导状态管理器在每轮后保存状态。可观测性层全程记录安全层在每个关键节点做校验。这个流程看着线性实际是循环的。模型可能调多次工具可能中途重规划可能触发记忆检索。Harness 的价值就在于把这些循环和分支管理得井井有条让模型只需要关注“下一步做什么”而不用操心“怎么保证不出错”。5. 实操中的坑与经验我踩过的五个典型问题理论讲完了说点实在的。下面这五个问题是我在实际项目中真实遇到过的每个都花了时间才解决。5.1 坑一工具描述写得太技术化模型不会用早期我写工具描述习惯写函数签名那种“query_database(sql: string): ResultSet”。结果模型经常不知道该什么时候调这个工具。后来改成自然语言描述“当用户需要查询结构化数据时使用此工具输入标准 SQL 语句返回查询结果”调用准确率明显提升。经验工具描述是写给模型看的不是写给程序员看的。用自然语言说清楚“什么时候用”“输入什么”“返回什么”。5.2 坑二上下文无限增长token 烧得飞快一开始我没做上下文压缩每轮都把完整历史塞进去。结果对话到十几轮token 消耗爆炸成本飙升而且模型开始丢失早期信息。后来加了摘要压缩和关键事实提取token 消耗降了 60%。经验上下文预算要当钱花。每轮都问自己这条信息这轮真的需要吗不需要就压缩或丢弃。5.3 坑三工具执行失败后模型不知道怎么办有次工具调用超时我直接返回一个空结果。模型以为工具成功了继续往下走最后输出完全错误的结果。后来改成返回明确的错误结构包含错误类型和建议模型就能根据错误信息调整策略。经验错误也是信息。工具失败时要返回结构化的错误让模型知道发生了什么、可以怎么补救。5.4 坑四循环检测没做好Agent 卡死有次模型陷入死循环反复调同一个工具同样参数。因为没做循环检测跑了上百轮才因为 token 耗尽停下。后来加了重复动作检测连续两次相同调用就中断并提示模型。经验永远不要相信模型不会犯傻。循环检测、最大轮次、超时总控一个都不能少。5.5 坑五状态没持久化重启后从头再来有次服务更新重启一个跑了十分钟的任务丢了用户很生气。后来加了 checkpoint 机制每个关键步骤后保存状态重启后能从最近点恢复。经验Agent 执行是长流程必须假设随时可能中断。状态持久化不是可选项是必选项。6. 常见问题速查表问题现象可能原因排查方向解决建议模型输出格式错误prompt 约束不够检查系统提示是否明确格式要求加 few-shot 示例加解析重试工具调用不准确工具描述模糊检查工具描述是否说清使用场景改写成自然语言加调用示例上下文丢失关键信息压缩策略太激进检查摘要是否保留了关键事实调整压缩阈值关键事实单独存储Agent 陷入循环缺少循环检测检查是否有重复动作检测加最大轮次限制加重复检测执行超时工具无超时控制检查每个工具的超时设置统一加超时超时返回明确错误重启后任务丢失状态未持久化检查是否有 checkpoint 机制关键步骤后保存状态支持恢复token 消耗过高上下文未优化检查每轮 prompt 的 token 分布压缩历史按需检索知识权限校验缺失安全层不完善检查工具是否有权限标签加权限校验高风险操作需确认7. 从零搭一套 Harness 的最小可行路径如果你看完想自己动手我给一条最小可行路径。不用一上来就 12 个组件全做按优先级来。第一阶段先做输入适配、上下文组装、模型调用、输出解析这四个。这四个能让你跑通最基本的“输入-模型-输出”流程。工具调用可以先不做纯对话也能验证链路。第二阶段加工具注册表、工具执行器、执行循环控制器。这三个让你从“会聊天”升级到“会干活”。先支持一两个简单工具跑通循环再说。第三阶段加记忆管理器、状态管理器、可观测性层。这三个让系统从“能跑”变成“能稳定跑”。记忆解决上下文问题状态解决恢复问题可观测解决排查问题。第四阶段加任务规划器、安全权限层。这两个是高级能力处理复杂任务和多人场景时才需要。前期可以先用简单规则代替。我自己的经验是第一阶段一两天能搞定第二阶段一周左右第三阶段看需求复杂度第四阶段可以边用边加。别想着一次做完美Harness 是迭代出来的不是设计出来的。最后分享一个我自己的体会Harness 的复杂度应该跟任务复杂度匹配。如果你只是做个简单问答机器人不需要 12 个组件全上。但如果你要做能执行多步操作、能处理异常、能长期运行的 Agent那这些组件迟早都得补上。早点想清楚架构比后期重构省事得多。
阅读完成 · 觉得有帮助?