news 2026/10/8 4:00:29

Agent Harness 实战:上下文管理与 ReAct 编排落地指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent Harness 实战:上下文管理与 ReAct 编排落地指南

1. 从“能聊”到“能干”:Agent Harness 到底解决了什么

很多人第一次接触智能体,脑子里浮现的画面是“一个会自己思考、自己调工具、自己完成任务的 AI”。但真上手写起来,你会发现一个尴尬的现实:模型本身只会“输出文本”,它不会自己记住上一步干了什么,不会自己判断该不该调用工具,更不会在工具报错之后优雅地重试。所谓“智能体”,其实是我们用一圈代码把模型“架”起来之后才出现的东西。这一圈代码,就是Agent Harness。

我更喜欢把 Harness 翻译成“驾驭层”或者“马具”——它不负责提供马力,它负责把马力导向正确的方向。模型是发动机,Harness 是方向盘、变速箱和仪表盘。你给它一个目标,它负责把目标拆成一步步动作,把每一步的上下文喂给模型,把模型吐出来的东西解析成“思考”还是“行动”,执行完再把结果塞回上下文,循环往复直到任务结束。ReAct(Reasoning + Acting)就是这套循环最经典的范式:让模型先想一步(Thought),再决定做什么(Action),拿到观察结果(Observation)后继续想,直到给出最终答案。

这篇笔记围绕上下文管理与编排实践展开,核心想讲清楚三件事:第一,Harness 和 Agent 到底差在哪,为什么这个区分对工程实现至关重要;第二,上下文管理为什么是智能体最容易翻车的地方,怎么管才不炸;第三,编排(Orchestration)在真实项目里长什么样,用OpenAI SDK这类工具怎么落地。适合已经能调通一次 API、但一写多轮工具调用就乱套的开发者,也适合想搞明白“智能体框架图”背后到底发生了什么的产品和测试同学。

我踩过的坑基本都集中在两个地方:上下文越滚越长导致模型开始“失忆”或者胡言乱语,以及工具调用的编排逻辑写成一坨面条,加一个新工具就要改五处代码。下面把这两块拆开讲透。

2. 先分清 Harness 和 Agent:别把马具当成马

2.1 一个生活化类比:马、马具与骑手

想象你要骑马去一个地方。马有力量、能跑,但它不知道目的地,也不会看红绿灯。马具(鞍、缰绳、嚼子)把马的力量传导到骑手想要的方向。骑手才是决策者,他看地图、判断路况、决定加速还是转弯。

对应到智能体系统里:模型是马,它提供推理和生成能力;Harness 是马具,它负责上下文组装、工具调用解析、循环控制、错误处理;你的业务逻辑(或者上层 Agent 定义)是骑手,它定义目标、约束和成功标准。很多人把这三者混为一谈,写出来的代码就会既想当马又想当骑手,最后逻辑纠缠不清。

这个区分为什么重要?因为它决定了你的代码分层。Harness 应该是通用、可复用的,换一个模型、换一套工具,Harness 基本不动;Agent 定义是业务相关的,换个任务就要改。如果你把工具解析逻辑硬编码在业务循环里,那每加一个工具就是一场灾难。

2.2 Harness 的四大核心职责

一个合格的 Harness,我认为至少要扛起这四件事:

  • 上下文组装:把系统提示、历史消息、工具定义、当前观察结果拼成模型能吃的格式,并且控制总长度。
  • 输出解析:从模型返回的文本或结构化字段里,判断这是“思考”“工具调用”还是“最终答案”。
  • 循环控制:决定什么时候继续、什么时候停、最多循环几次、超时怎么办。
  • 工具执行与回填:真正去调用函数/API,把结果(包括报错)格式化后塞回上下文。

这四件事里,上下文组装和循环控制是最容易出问题的。前者关乎模型能不能“记住”,后者关乎任务会不会“跑飞”。

2.3 ReAct 循环的骨架长什么样

用伪代码描述 ReAct 在 Harness 里的样子,大概是这样:

context = [system_prompt, user_goal] for step in range(max_steps): response = model.chat(context, tools=tool_schemas) thought, action = parse(response) if action is None: return thought # 最终答案 observation = execute_tool(action) context.append(response) context.append(observation)

看着简单,但每一行背后都有坑。parse怎么解析才稳?execute_tool报错了怎么办?context涨到几万 token 了怎么截断?这些才是 Harness 的真正工作量所在。下面几节逐个拆。

3. 上下文管理:智能体的记忆与遗忘艺术

3.1 为什么上下文会“越滚越炸”

ReAct 循环每走一步,就往上下文里追加一条模型输出和一条工具观察结果。假设一次工具调用返回 500 token,走 20 步就是 1 万 token 的纯观察数据,再加上模型自己的思考文本,轻松突破模型的上下文窗口。这时候会出现两种典型症状:一是模型开始忽略早期的关键指令(比如“不要删除文件”),二是模型对最近的观察结果过度敏感,反复纠结同一个错误。

我实测过一个搜索类智能体,任务刚开始时它能记住“用户要的是 2023 年之后的数据”,走到第 15 步之后它开始拿 2019 年的结果回来。原因就是早期的约束被挤到了上下文边缘,注意力权重被稀释了。上下文管理不是“能塞就塞”,而是“该记的记牢,该忘的果断忘”。

3.2 三种主流的上下文压缩策略

业界常见的做法有三类,我按侵入性从低到高排:

策略做法优点代价
滑动窗口只保留最近 N 轮实现简单早期约束丢失
摘要压缩把旧消息交给模型总结成一段保留语义多一次模型调用,有信息损耗
结构化记忆把关键事实抽成键值对单独存精准、可检索需要设计抽取逻辑

我的经验是混合使用:系统提示和用户原始目标永远钉在最前面,不参与压缩;中间的观察结果用滑动窗口;每走 5 步做一次摘要,把“已经确认的事实”抽出来放进一个独立的memory字段,每轮都带上。这样既控制了长度,又保住了关键约束。

3.3 工具定义也是上下文的一部分

很多人只盯着对话历史,忘了工具 schema 本身也占 token。如果你挂了 30 个工具,每个工具的 JSON schema 平均 100 token,光工具定义就 3000 token。更麻烦的是,工具太多会让模型选择困难,调用准确率下降。

我的做法是按需挂载:根据当前任务阶段动态决定暴露哪些工具。比如任务初期只给“搜索”和“读取”,确认了目标之后再挂“写入”和“发送”。这需要在 Harness 里维护一个“工具可见性”状态,属于编排逻辑的一部分。实测下来,把工具从 20 个收敛到 5 个,工具调用的正确率能明显提升。

3.4 一个可直接抄的上下文结构模板

context = { "system": SYSTEM_PROMPT, # 永不压缩 "goal": user_goal, # 永不压缩 "memory": {}, # 结构化事实,每轮带上 "history": [], # 滑动窗口,保留最近 N 轮 "tools": visible_tool_schemas, # 动态挂载 }

组装成模型消息时,system和goal合并成 system message,memory序列化成一段文本放在 system 末尾,history按顺序展开。这个结构我用了大半年,换过三个模型都没出过大问题。

注意:memory里的键值对要定期清理,过期的、被后续观察推翻的事实必须删掉,否则模型会拿着旧结论当真理。

4. 编排实践:让循环可控、可观测、可恢复

4.1 编排的本质是状态机

把 ReAct 循环想象成一个状态机,状态包括:THINKING(等模型输出)、ACTING(执行工具)、OBSERVING(回填结果)、DONE(结束)、FAILED(失败)。编排就是定义这些状态之间的转移条件和守卫。

为什么用状态机思维?因为真实任务里,工具调用可能超时、可能返回空、可能返回格式错误的数据。如果你只写一个while True循环,这些异常会直接把整个流程带崩。用状态机,你可以为每个状态定义超时和重试策略,出问题时能精确定位卡在哪一步。

4.2 循环终止条件:别让智能体“想太多”

终止条件至少要有四个:

  • 模型主动给出最终答案:解析到没有工具调用,直接返回。
  • 达到最大步数:防止死循环,一般设 15 到 25 步。
  • 连续重复动作:如果连续两步调用同一个工具、参数几乎一样,强制中断。
  • 总耗时超限:给整个任务设一个 wall-clock 上限。

我见过最离谱的案例是一个智能体在“搜索-没找到-再搜索”之间循环了 40 多次,烧掉大量 token 还没结果。加了“连续重复动作检测”之后,这类问题基本绝迹。检测逻辑很简单:把最近两次的(tool_name, args)做哈希比对,相同就中断并返回当前最佳答案。

4.3 用 OpenAI SDK 落地工具调用

OpenAI SDK 的工具调用(function calling)返回的是结构化的tool_calls字段,比早期靠正则解析文本靠谱得多。核心流程:

response = client.chat.completions.create( model="gpt-4o", messages=messages, tools=tool_schemas, tool_choice="auto", ) msg = response.choices[0].message if msg.tool_calls: for call in msg.tool_calls: result = dispatch(call.function.name, call.function.arguments) messages.append(msg) messages.append({ "role": "tool", "tool_call_id": call.id, "content": result, }) else: final_answer = msg.content

这里有个容易忽略的点:tool_call_id必须原样回填,否则下一轮模型会报错或者对不上号。我早期手写消息时漏过这个字段,模型直接返回 400,排查了半天。

4.4 工具执行层要做的三件事

工具执行不是简单调个函数就完事,Harness 在这一层要兜住三件事:

  1. 参数校验:模型生成的 JSON 参数经常有类型错误,比如该传 int 传了 str。执行前用 schema 校验一遍,不合法就返回一个“参数错误”的观察结果让模型自己修。
  2. 超时与重试:每个工具设独立超时,网络类工具重试 2 次,写操作类工具不重试(避免重复写入)。
  3. 结果截断:工具返回的内容可能很长,超过阈值就截断并加一句“结果已截断,如需完整内容请分页获取”。

这三件事做完,工具层的稳定性会有质的提升。尤其是参数校验,它把“模型犯错”变成了“模型自我纠正”的机会,而不是直接崩溃。

5. 常见问题与排查技巧实录

5.1 模型不调用工具,直接瞎编答案

这是最高频的问题。原因通常有三个:工具描述写得太模糊、系统提示没强调“必须用工具获取事实”、或者工具太多导致模型懒得选。排查顺序:先看工具 description 是否说清楚了“什么时候用”,再在 system prompt 里加一句“涉及事实性信息必须通过工具获取,不得凭记忆回答”,最后精简工具数量。

5.2 工具调用参数总是格式错误

模型生成的 JSON 偶尔会多一个逗号、少一个引号。解决办法是在 Harness 里加一层容错解析:先尝试标准 JSON 解析,失败后用宽松解析库兜底,再失败就把原始字符串和错误信息一起回填给模型,让它重新生成。实测这样能把参数错误的自愈率提到 90% 以上。

5.3 上下文超长导致模型“失忆”

前面讲过,核心是分层管理。补充一个技巧:在 system prompt 末尾固定加一句“以下是本次任务的关键约束,请始终遵守”,然后把memory里的关键事实列在它下面。位置固定在末尾,模型的注意力更容易命中。

5.4 排查速查表

症状可能原因排查动作
不调工具描述模糊/工具过多精简工具,强化 system 提示
参数报错JSON 格式问题加容错解析与回填
反复循环无重复检测加动作哈希比对
忘记约束上下文过长分层管理,关键约束钉末尾
工具超时无超时控制每工具独立超时+重试策略

5.5 两个我踩过的坑

第一个坑是把工具执行结果直接str()塞回上下文,结果 Python 字典的引号转义把模型搞晕了。正确做法是统一用json.dumps(result, ensure_ascii=False),保证格式干净。

第二个坑是在循环里同步调用慢工具,一个 HTTP 请求卡 30 秒,整个任务就僵住。后来改成异步执行加超时,Harness 的响应性好了很多。如果你的工具里有网络请求,强烈建议从一开始就上异步。

6. 写在最后的一点个人体会

这套 Harness 我前后重构了三次,从最开始一个 200 行的while循环,到现在分层清晰的上下文管理加状态机编排,最大的感受是:智能体的复杂度不在模型,而在模型外面那圈代码。模型能力再强,上下文喂错了、循环控不住、工具兜不住,任务照样失败。

如果你正准备动手写自己的 Agent Harness,我的建议是先别追求功能全,先把“上下文分层”和“循环终止条件”这两件事做扎实。这两个点稳了,后面加工具、换模型、接业务都是顺水推舟。至于 ReAct 的具体提示词模板,网上版本很多,选一个结构清晰的,然后根据自己的工具集微调就行,没必要迷信某一种写法。真正决定成败的,永远是那些不起眼的工程细节。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 4:00:10

C语言条件判断全解析:if、switch、三目运算符与常见坑

我记得刚学C语言的时候,最让我困惑的其实不是指针,而是那个看似人畜无害的if。照着书上的例子敲,编译没问题,可运行结果就是跟预期拧着来。后来慢慢排查才发现,问题往往不出在if本身,而是出在它的“条件”上…

作者头像 李华
网站建设 2026/10/8 3:59:55

基恩士KV06N+昆仑通态:全自动LED划线点装机控制系统解析

做LED封装设备这几年,我经手过不少小机器,但要说最典型的,还得是前阵子完整交付的这台全自动LED划线点装机。核心控制用的是基恩士KV06N这台小PLC,触摸屏是昆仑通态,三根轴全上伺服,视觉定位、自动划线、点…

作者头像 李华
网站建设 2026/10/8 3:59:53

劳动法知识库+AI技能包:打工人维权第一响应层搭建指南

1. 为什么打工人需要把劳动法知识“装进电脑”第一次听到“把劳动法 Skill 装进电脑”这个说法,很多人会以为是某种法律咨询软件,或者是一个能自动帮你打官司的AI律师。其实不是。它本质上是一套结构化的知识库 可调用的AI技能包,把散落在《…

作者头像 李华
网站建设 2026/10/8 3:59:44

JavaWeb数码推荐平台:轻量级可调试推荐系统实现

简介:本资源是一套基于JavaWeb技术栈开发的数码产品推荐平台系统,适用于计算机专业本科生毕业设计、Java全栈学习者及前后端分离项目实践者,解决数码商品分类展示、动态筛选与会员制下载管理等典型电商场景需求。压缩包共812个文件&#xff0…

作者头像 李华
网站建设 2026/10/8 3:59:23

C#火车订票系统:WinForms+SQL Server事务与并发控制

简介:C#火车订票系统是一份面向初中级.NET开发者的课程设计与毕业设计参考资源,完整演示了订票、退票、车次管理和用户登录等核心业务。压缩包共128个文件,体积仅1.85MB,涵盖50个cs源码文件、24个resx界面资源、SQL Server数据库相…

作者头像 李华
网站建设 2026/10/8 3:59:10

TFLN Chiplet平台推400G每通道PIC,破解AI光互连带宽瓶颈

光学互联这两年是被AI算力硬生生抬到台前的,但真正卡脖子的往往不是光口速率本身,而是调制器能不能在功耗和体积的天花板下顶住带宽需求。HyperLight这次在TFLN Chiplet平台推出每通道400G的PIC,把薄膜铌酸锂(TFLN)、芯…

作者头像 李华