AI Agent Harness 拆解:7 大子系统让大模型真正干活
1. 拆开“AI Agent 真正干活的 Harness”这层壳
很多人第一次听到 Harness 这个词,脑子里浮现的是测试框架里的“脚手架”,或者 CI/CD 流水线里跑用例的那层壳。但放到 AI Agent 的语境里,Harness 的含义完全不一样——它指的是让大模型真正能“动手干活”的那套运行时骨架。你可以把它理解成 Agent 的“操作系统”:模型是 CPU,工具是外设,记忆是内存,而 Harness 就是把这些东西调度起来、让它们协同工作的那层内核。
我最早接触这个概念是在做一个自动化工单处理的 Agent 项目。当时天真地以为,把提示词写好、把工具函数注册进去,模型就能自己跑起来。结果一上真实环境就崩了:模型调工具调一半忘了上下文、工具报错后模型不知道该怎么重试、多轮对话里状态全乱套。后来才明白,问题不在模型,而在于我根本没有一套像样的 Harness。模型再强,没有 Harness 兜着,它就是个只会聊天的“嘴炮”,干不了活。
所以这篇文章想聊的,就是标题里那个问题:AI Agent 真正干活的 Harness 到底是啥,为什么拆开来就是 7 个子系统。我会按我自己踩坑和搭系统的顺序,把这 7 个子系统一个个拆开讲清楚,每个子系统解决什么问题、怎么实现、有哪些坑。适合正在从 0 到 1 搭 Agent 的开发者,也适合已经有一个能跑的 Demo、但一上生产就各种翻车的朋友。看完你至少能判断:你现在的 Agent 缺的是哪一块。
2. 为什么 Agent 需要一个 Harness,而不是一堆函数
2.1 从“能聊天”到“能干活”的鸿沟
大模型本身的能力边界很清楚:给它输入,它给你输出。它不会主动去查数据库、不会自己发邮件、不会记得你昨天说过什么。所谓 Agent,就是在这层输入输出外面套一圈机制,让模型能感知环境、做出决策、执行动作、观察结果、再决策。这个循环,业内叫 Agent Loop。
但 Agent Loop 只是最核心的那一环。真正让 Agent 在生产环境里稳定干活的,是围绕这个循环建立起来的一整套支撑系统。我见过太多项目,Agent Loop 写得挺漂亮,结果一遇到工具超时、模型输出格式错乱、多轮任务状态丢失,整个系统就瘫了。这些问题的根源,都是 Harness 层没做好。
打个比方:Agent Loop 是发动机,Harness 是整辆车。发动机再好,没有变速箱、没有刹车、没有油箱,车也上不了路。Harness 要解决的就是“让发动机的动力能稳定输出到轮子上”这件事。
2.2 Harness 和普通编排框架的区别
有人会问:这不就是 LangChain、LangGraph 那些框架干的事吗?我的理解是,编排框架提供的是“能力”,Harness 提供的是“约束和保障”。框架告诉你“你可以这样串工具”,Harness 告诉你“工具超时了怎么办、模型输出不合法怎么办、状态怎么持久化、并发怎么控制”。
举个具体例子。LangGraph 可以让你定义一个带条件分支的图,节点之间传递状态。但如果你要做一个每天处理上千个工单的 Agent,你需要考虑:同一个工单被两个请求同时处理怎么办?模型调用失败重试三次还是五次?工具返回的结果太大塞不进上下文怎么截断?这些都不是编排框架默认帮你解决的,而是 Harness 要设计的。
所以我的结论是:编排框架是 Harness 的一部分,但 Harness 远不止编排。它是一套完整的运行时治理体系。
2.3 7 个子系统的整体视图
我把 Harness 拆成 7 个子系统,不是拍脑袋分的,而是按“一个 Agent 从收到任务到完成任务”的完整链路来切的。这 7 个分别是:
| 序号 | 子系统 | 核心职责 |
|---|---|---|
| 1 | Agent Loop 调度器 | 驱动“思考-行动-观察”循环 |
| 2 | LLM 集成层 | 统一模型调用、重试、降级 |
| 3 | 工具与技能注册中心 | 管理工具定义、权限、版本 |
| 4 | 记忆与状态管理 | 短期上下文、长期记忆、任务状态 |
| 5 | 上下文工程模块 | 动态组装提示词、裁剪、压缩 |
| 6 | 执行沙箱与安全层 | 隔离执行、权限校验、审计 |
| 7 | 可观测性与评估 | 日志、追踪、指标、回放 |
这 7 个不是孤立的,它们之间有明确的依赖关系。比如 Agent Loop 依赖 LLM 集成层来调模型,依赖工具注册中心来执行动作,依赖记忆管理来读写状态。下面我一个一个拆。
3. 子系统一:Agent Loop 调度器,整个 Harness 的心脏
3.1 Agent Loop 到底在循环什么
Agent Loop 的本质是一个状态机:思考 → 行动 → 观察 → 再思考。模型先根据当前上下文决定下一步做什么,然后 Harness 执行这个动作,把结果塞回上下文,再让模型继续决策。直到模型输出“任务完成”或者达到最大轮次。
听起来简单,但实现时有几个关键决策点。第一,循环的终止条件怎么定?只靠模型自己说“我完成了”非常不可靠,我实测下来,模型经常在任务没完成时就说完成了。所以我的做法是:模型声明完成 + 外部校验通过才算真完成。比如工单处理 Agent,模型说处理完了,我还要检查工单状态字段是否真的更新了。
第二,单轮里允许多少次工具调用?有些模型会在一轮里并行调多个工具,有些只调一个。我的经验是,允许并行调用但限制数量,一般不超过 5 个,否则上下文膨胀太快,而且并行工具之间如果有依赖关系会出问题。
3.2 循环控制里的三个关键参数
在 Agent Loop 里,有三个参数你必须显式设置,不能靠默认值:
- 最大轮次(max_turns):防止死循环。我一般设 15 到 25 轮,具体看任务复杂度。设太小任务做不完,设太大浪费 token 还可能跑偏。
- 单轮超时(turn_timeout):每一轮从模型调用到工具执行完成的总时间。我设 60 秒,超过就中断并让模型重新决策。
- 无进展检测阈值:如果连续 N 轮模型的动作和观察结果高度相似,说明它卡住了,强制中断。我设 3 轮。
这三个参数我踩过的坑是:一开始没设无进展检测,结果模型在一个工具报错后反复重试同一个调用,烧了几十万 token 才发现。后来加上这个检测,配合“卡住时注入提示让模型换策略”,效果好很多。
3.3 一个最小可用的 Loop 实现思路
用伪代码表达,核心逻辑大概是这样:
def agent_loop(task, max_turns=20): context = build_initial_context(task) for turn in range(max_turns): response = llm_integration.call(context) if response.is_final: if verify_completion(response, task): return response.result else: context.append(feedback("任务未真正完成,请继续")) continue for action in response.actions: result = tool_registry.execute(action) context.append(observe(result)) if no_progress_detected(context): context.append(hint("你似乎卡住了,请换一种策略")) return timeout_result()这段代码里,verify_completion和no_progress_detected是 Harness 的价值所在。没有它们,这就是个脆弱的 Demo。
4. 子系统二:LLM 集成层,别把模型调用写死在业务里
4.1 为什么需要单独一层集成
很多项目里,模型调用是散落在各处的:这里一个openai.chat(),那里一个requests.post()。这种写法在 Demo 阶段没问题,但一旦要换模型、要加重试、要做降级,就痛苦了。LLM 集成层的意义就是把模型调用收敛到一个地方,业务代码只依赖一个统一接口。
我现在的做法是定义一个LLMClient抽象,所有模型调用都走它。这样换模型只改配置,不改业务。而且重试、超时、限流、成本统计这些横切关注点,都可以在这一层统一处理。
4.2 重试与降级的实战策略
模型调用失败是常态,不是异常。网络抖动、限流、服务端 5xx,都会发生。我的重试策略是:
- 指数退避:第一次失败等 1 秒,第二次 2 秒,第三次 4 秒,最多重试 3 次。
- 区分错误类型:限流错误(429)值得重试,参数错误(400)重试没意义,直接抛。
- 降级模型:主模型连续失败后,切到备用模型。比如主用大模型,降级到小模型先保证任务能跑完。
这里有个坑:重试时要注意幂等性。如果模型调用本身有副作用(比如已经触发了工具执行),重试可能导致重复执行。所以我的原则是:模型调用本身无副作用,重试安全;工具执行有副作用,重试要谨慎。
4.3 多模型路由的考量
生产环境里,我一般会配多个模型:一个强模型做复杂决策,一个快模型做简单分类或格式化。路由逻辑可以基于任务类型、上下文长度、成本预算来定。
比如上下文超过 50K token 时,用长上下文模型;简单意图识别用便宜的小模型。这个路由逻辑放在 LLM 集成层,业务代码无感知。实测下来,这种分层路由能省 40% 以上的成本,效果几乎不降。
5. 子系统三:工具与技能注册中心,Agent 的手脚怎么管
5.1 工具定义的标准结构
工具不是随便写个函数就行的。一个规范的工具定义至少包含:名称、描述、参数 schema、执行函数、超时设置、权限要求、幂等性标记。其中描述和参数 schema 直接决定模型能不能正确调用。
我见过太多工具调用失败,根源是描述写得太模糊。比如一个查订单的工具,描述写“查询订单信息”,模型根本不知道要传什么参数。改成“根据订单号查询订单的详细状态,参数 order_id 为字符串格式的订单编号”,调用成功率立刻上去了。
5.2 工具注册与版本管理
工具会迭代,参数会变。如果没有版本管理,模型可能调用旧版本工具导致失败。我的做法是:工具注册时带版本号,Harness 在组装提示词时只暴露当前活跃版本。旧版本保留一段时间用于回放和排查。
另外,工具要分组。不是所有任务都需要所有工具。按场景分组,只把相关工具暴露给当前任务,能显著减少模型的决策负担。比如客服 Agent 不需要暴露数据库管理工具。
5.3 工具执行的超时与熔断
工具执行必须设超时,这是铁律。我一般设 30 秒,超过就中断并返回超时错误给模型。同时要有熔断机制:某个工具连续失败 N 次,暂时从可用列表里摘掉,避免模型反复调用一个坏工具。
这里分享一个实操心得:工具超时后返回给模型的信息要具体。不要只说“超时了”,要说“查询订单工具在 30 秒内未返回结果,可能是订单系统繁忙,建议稍后重试或换用其他方式”。模型拿到这种信息,才知道下一步该怎么调整。
6. 子系统四:记忆与状态管理,Agent 不能得了失忆症
6.1 短期上下文与长期记忆的分层
Agent 的记忆分两层:短期是当前任务的对话上下文,长期是跨任务的知识积累。短期上下文直接进提示词,长期记忆需要检索后注入。
短期上下文的管理核心是裁剪和压缩。上下文窗口有限,不能无限塞。我的策略是:保留最近 N 轮完整对话,更早的做摘要压缩。摘要由模型生成,保留关键决策和结果,丢弃冗余的中间过程。
长期记忆我一般用向量库存,按语义检索。但要注意:不是所有历史都值得存。我通常只存任务结论、用户偏好、失败教训这几类,避免记忆库被噪音污染。
6.2 任务状态的持久化
Agent 跑长任务时,状态必须持久化。否则进程一重启,任务就丢了。我用的是“状态快照 + 事件日志”的组合:每轮循环结束存一个快照,同时记录事件日志用于回放。
状态里要存什么?当前轮次、已完成动作、待完成动作、关键变量、错误记录。这些信息在恢复时能重建上下文,让 Agent 从中断处继续。
6.3 记忆检索的时机与策略
记忆什么时候检索?我的经验是:任务开始时检索一次,每 N 轮再检索一次。不要每轮都检索,太浪费。检索时用当前任务描述做 query,取 top-k 相关记忆注入上下文。
有个坑是:检索回来的记忆可能过时或矛盾。所以我在注入时会加时间戳和置信度,让模型自己判断是否采信。实测下来,这比直接注入效果好很多。
7. 子系统五:上下文工程模块,提示词不是写死的
7.1 动态上下文组装
上下文不是把系统提示词、历史对话、工具结果简单拼起来就完事。它需要动态组装:根据当前任务阶段,决定注入哪些信息、以什么顺序、占多少比例。
我的做法是维护一个“上下文槽位”结构:系统指令槽、任务描述槽、工具定义槽、历史对话槽、记忆槽、当前观察槽。每个槽有优先级和预算,组装时按优先级填充,超预算就裁剪低优先级内容。
7.2 上下文压缩的几种手段
上下文太长时,压缩手段有几种:摘要、截断、选择性保留、结构化压缩。摘要适合对话历史,截断适合工具返回的长文本,选择性保留适合只留关键字段,结构化压缩适合把冗长 JSON 转成紧凑格式。
我实测下来,工具返回结果的结构化压缩收益最大。很多工具返回一大坨 JSON,其实模型只需要其中几个字段。在 Harness 层做字段提取,能把上下文体积压掉 70% 以上。
7.3 提示词模板的管理
提示词不要硬编码在代码里。我用模板文件管理,支持变量替换和条件片段。不同任务用不同模板,模板可以热更新,不用重启服务。
这里有个细节:模板变更要版本化。因为提示词一变,Agent 行为就变,出问题时要能回滚到旧版本。我一般把模板版本和 Agent 版本绑定,方便追溯。
8. 子系统六:执行沙箱与安全层,别让 Agent 闯祸
8.1 为什么需要沙箱
Agent 能执行代码、能调 API、能操作文件。这些能力用好了是效率,用不好是灾难。沙箱的作用就是限制 Agent 的行动范围,让它只能做被允许的事。
我的沙箱策略分三层:网络层限制可访问的域名和端口,文件层限制可读写的目录,进程层限制可执行的命令。三层叠加,基本能挡住大部分误操作。
8.2 权限校验与审计
每个工具调用前都要过权限校验:这个 Agent 有没有权限调这个工具?这个用户有没有权限触发这个 Agent?校验不通过直接拒绝,并记录审计日志。
审计日志要包含:谁、什么时候、调了什么工具、参数是什么、结果如何。这些日志不仅是安全需要,也是排查问题的关键依据。我遇到过 Agent 半夜跑飞了,就是靠审计日志定位到是某个工具的权限配置错了。
8.3 危险操作的二次确认
对于删除、修改、发送这类有副作用的操作,我加了一道二次确认:Agent 提出操作意图后,Harness 先不执行,而是把意图展示出来,等人工确认或等模型再次确认后才执行。
这个机制在自动化流程里会降低效率,但在涉及资金、数据、对外通信的场景里,这道防线不能省。我的做法是分级:低风险操作直接执行,中风险记录并执行,高风险必须确认。
9. 子系统七:可观测性与评估,看不见的 Agent 最可怕
9.1 日志、追踪与指标
Agent 的可观测性比普通服务复杂,因为它的执行路径是动态的、不确定的。我一般采集三类数据:日志(每轮决策和动作)、追踪(一次任务的完整调用链)、指标(成功率、平均轮次、token 消耗、工具调用分布)。
追踪特别重要。一个任务可能涉及几十次模型调用和工具执行,没有追踪根本理不清。我用 OpenTelemetry 做埋点,每个任务一个 trace,每轮一个 span,工具调用一个子 span。这样出问题时能快速定位是哪一环。
9.2 回放与调试
Agent 出问题时,光看日志不够,要能回放。回放的意思是:用当时的状态和输入,重新跑一遍,看能不能复现。这要求 Harness 记录足够详细的状态快照和输入输出。
我搭的回放系统支持两种模式:精确回放(用记录的模型输出,不重新调模型)和重跑回放(重新调模型,看行为是否一致)。前者用于定位逻辑问题,后者用于评估模型稳定性。
9.3 评估体系的搭建
Agent 好不好,不能靠感觉,要靠评估。我一般建三个层次的评估:单元评估(单个工具调用是否正确)、任务评估(完整任务是否完成)、回归评估(新版本是否比旧版本好)。
评估集要持续积累。每次线上出问题,把 case 加进评估集,下次发版前跑一遍,确保不重复犯错。这个习惯坚持下来,Agent 的稳定性会肉眼可见地提升。
10. 把这 7 个子系统串起来:一个工单 Agent 的落地实录
10.1 场景与需求
我之前做过一个工单自动处理 Agent,需求是:接收用户工单,自动分类、查询相关信息、给出处理建议、必要时执行操作(如退款、改状态)。每天处理量在几千单,要求 95% 以上自动完成,失败的要能人工接管。
这个场景对 Harness 的要求很高:并发要扛得住、状态不能丢、危险操作要可控、出问题要能追溯。正好把这 7 个子系统都用上了。
10.2 各子系统的配置与协作
Agent Loop 设最大 20 轮,单轮超时 60 秒,无进展检测 3 轮。LLM 集成层配了主备两个模型,主模型做决策,备用模型做降级。工具注册中心注册了 12 个工具,按工单类型分组暴露。记忆管理用 Redis 存短期状态,向量库存历史处理记录。上下文工程做了工具结果压缩,平均上下文体积降了 60%。沙箱限制了只能访问内部 API,危险操作走二次确认。可观测性用 OpenTelemetry 全链路追踪。
这套配置跑下来,自动完成率稳定在 96% 左右,平均处理轮次 6 轮,单均 token 消耗比初版降了 45%。
10.3 上线后暴露的问题与迭代
上线第一周就遇到问题:高峰期模型调用限流,导致大量任务超时。解决办法是在 LLM 集成层加了请求队列和优先级调度,重要任务优先。
第二周发现有些工单被重复处理,原因是状态持久化有延迟。改成每轮结束同步写状态,问题解决。
第三周遇到工具返回结果过大导致上下文溢出。在上下文工程模块加了自动截断和摘要,解决。
这些问题都不是模型的问题,全是 Harness 层的问题。这也印证了我一开始的判断:Agent 能不能干活,模型只占一半,另一半全在 Harness。
11. 常见问题与排查速查
11.1 模型不调工具或乱调工具
这是最常见的问题。排查顺序:先看工具描述是否清晰,再看参数 schema 是否完整,最后看上下文里工具定义是否被截断。我遇到的大部分情况是描述太模糊。解决办法是把描述写成“什么场景下用、参数是什么格式、返回什么”,越具体越好。
11.2 任务跑一半卡住
卡住通常是模型陷入循环或等待不存在的输入。排查:看最近几轮的动作是否重复,看是否有工具一直没返回。解决办法:加无进展检测,卡住时注入提示让模型换策略,或者直接中断转人工。
11.3 上下文溢出
上下文溢出说明裁剪策略不够。排查:看哪部分占最多,通常是工具返回或历史对话。解决办法:工具返回做结构化压缩,历史对话做摘要,设置硬预算超了就裁。
11.4 并发下状态错乱
并发问题最隐蔽。排查:看是否有多个请求操作同一任务状态。解决办法:任务级加锁,状态更新用乐观锁或版本号,确保同一任务串行处理。
| 问题现象 | 可能原因 | 排查动作 | 解决手段 |
|---|---|---|---|
| 不调工具 | 描述模糊 | 检查工具描述 | 重写描述,加示例 |
| 反复调同一工具 | 无进展检测缺失 | 看动作序列 | 加检测和提示注入 |
| 上下文溢出 | 裁剪不足 | 看各部分占比 | 压缩工具结果,摘要历史 |
| 状态错乱 | 并发冲突 | 看状态更新日志 | 任务级锁,版本号 |
| 任务超时 | 模型限流 | 看调用延迟 | 队列调度,降级模型 |
12. 搭 Harness 的几个实操心得
第一个心得:先跑通最小闭环,再逐个补子系统。不要一上来就把 7 个子系统全搭齐,那样容易过度设计。我的做法是先搭 Agent Loop + LLM 集成 + 工具注册这三个核心,跑通一个简单任务,然后再按需补记忆、上下文、沙箱、可观测性。
第二个心得:每个子系统都要有降级方案。模型挂了有备用模型,工具挂了有兜底返回,记忆库挂了有本地缓存。生产环境里,任何单点故障都不能让整个 Agent 停摆。
第三个心得:评估集比代码更重要。代码可以重构,评估集是判断重构好不好的唯一标准。我每个项目都会花大量时间积累评估 case,这些 case 来自线上真实问题,比任何人工构造的测试都有价值。
第四个心得:日志要记够,但不要记太多。记太少排查不了问题,记太多存储和检索成本高。我的原则是:决策相关的全记,中间过程记摘要,敏感信息脱敏后记。
这套 Harness 搭下来,最大的感受是:Agent 的难点从来不在模型有多聪明,而在于怎么让它的聪明能稳定、可控、可追溯地转化成实际动作。这 7 个子系统,每一个都是在解决“稳定、可控、可追溯”里的某一块。缺了哪块,Agent 就会在某个场景下翻车。所以如果你现在做的 Agent 还停留在 Demo 阶段,不妨对照这 7 个子系统看看,缺的到底是哪一块。