ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

智能体基础设施层:Agent Harness的工程实践与容错设计

智能体基础设施层:Agent Harness的工程实践与容错设计 几个月前我在一个内部项目里憋得实在难受团队里三个同学各自调大模型接口写出来三个风格完全不一样的“智能体AI”有的把对话历史全部塞进prompt跑几轮就把上下文窗口撑爆有的工具调用没有超时和重试一次网络抖动直接让整个任务失败还有的干脆就是while循环套两层模型输出什么就执行什么跑半天你不知道它会走到哪一步。那段时间我反复在想一个问题我们缺的真的不是模型能力而是给智能体一个可靠的“运行环境”。后来我花了几周时间把手上的几个智能体项目重新整理沉淀出一套轻量的 Agent Harness——你可以把它理解为智能体的基础设施层专门负责上下文管理和多步编排顺便把自主容错、工具注册、状态跟踪这些脏活累活收拢起来。这篇文章就是把这套实践的思路、代码骨架、踩过的坑和实测数据整理成一份漫游指南笔记希望对正在做 AI 智能体应用、被上下文策略和多步调用折磨的朋友有点用。1. 为什么我放弃「裸调大模型」转而搭一套 Agent Harness1.1 智能体的「裸奔现场」没有 Harness 时我在做什么我见过太多“智能体Demo”的代码长这样一段while循环里面把一个巨大的 prompt 反复发送给模型模型返回的 JSON 里有个action字段代码再用一个巨大的if-else去分发。跑第一轮的时候一切正常到第三轮、第五轮前面几轮的工具返回结果全堆在消息列表里token 数直线上升很快触及窗口上限。更麻烦的是一旦某个工具抛出异常整个while循环就炸了。模型已经“思考”了五步前四步的上下文全在第五步因为一个网络超时没拿到结果循环直接退出任务从头再来。这种体验做过的人都懂看起来能跑实际上脆弱得不行。我当时的项目里有一个智能体任务读取用户上传的表格、做数据清洗、调用统计脚本、生成分析报告、最后把报告改成 PPT 大纲。整个流程五六个工具调用中间任何一步出问题用户就要重新上传文件从头再来。这种场景下缺一个专门负责“托底”的中间层每多一个工具风险就翻一倍。1.2 Harness 与 Agent 的分工谁是大脑谁在托底先把概念理清楚。Agent 是那个做决策的大脑——它负责理解任务、拆解步骤、决定调哪个工具、解读工具返回的结果。Harness 是承载大脑的整个身体——它管记忆、管流程推进、管错误恢复、管资源限制。用开车来类比最简单Agent 是司机负责看路、打方向盘、决定什么时候超车Harness 是车架、刹车、仪表盘和路况系统司机踩油门的时候它保证燃油供应司机打滑的时候它启动防滑司机困了的时候它提醒你休息。没 Harness 的 Agent 就像把方向盘焊在发动机上能走但每一个坑都可能散架。所以 Harness 的核心职责就三条管住上下文、编排好流程、兜住所有错误。至于模型用 GPT 还是 Claude是 ReAct 模式还是 Plan-and-Execute 模式Harness 都不关心——这也是它和业务 Agent 解耦的关键。1.3 什么时候值得上 Harness什么时候没必要这不是一个非黑即白的问题。我自己的判断标准很朴素单轮问答、简单 RAG不需要 Harness。一个 prompt 模板加一个检索函数够了硬上框架只会增加延迟和复杂度。两步以内的固定流程用简单 if-else 也能撑住Harness 可以从简。三步以上、包含多个工具、对可靠性有要求必须上 Harness。尤其是任务可能半路失败、需要恢复或重试的场景Harness 的容错价值直接决定智能体能不能从“Demo级”变成“可用级”。我在这篇笔记里要讲的就是后一种场景下的实践。它不一定适合所有团队但如果你的智能体也已经出现了“上下文撑爆、工具调用失控、失败无法恢复”这三个症状那这套思路基本都能对症。2. 上下文管理给智能体建立一套「分级记忆」体系2.1 上下文窗口不够的根本矛盾不是容量是注意力很多人有个误区觉得上下文管理就是“别超过 128K tokens”。其实窗口够不够大只是表面问题真正影响智能体质量的是注意力漂移。我做过一个实验一个 8K tokens 的任务描述插在一条 60K tokens 的历史对话中间模型执行到后半段时对任务细节的理解明显变差。这不是模型“忘了”而是长上下文里早期的关键信息被大量中间内容稀释了模型对关键约束的关注度下降。另一个现实约束是成本。每次请求都携带全部历史哪怕新内容只有几百 token账单也是按全部上下文算的。随着对话轮次增加token 消耗是线性甚至超线性增长的。如果你的智能体一天跑几千次任务光上下文这一项就能吃掉大半成本预算。所以上下文管理本质上是在做两件事控制模型每轮真正需要看到的 token 量把关键信息放到更容易被注意到的位置。前者靠裁剪和压缩后者靠信息分层。2.2 四级上下文分层让该活的活下来该扔的扔得掉我在 Harness 里把上下文分成四个层级每一层有不同的存活策略层级内容存活策略典型token占比L0 系统层角色设定、全局规则、安全红线每次请求必带不裁剪10%-15%L1 任务层当前任务目标、输入参数、硬性约束整个任务生命周期内保留5%-10%L2 交互层模型-工具之间的往返记录滑动窗口只保留最近几轮50%-60%L3 记忆层历史对话摘要、关键事实、已完成的步骤压缩后进外部存储按需召回20%-30%这套分层的核心逻辑是系统层和任务层是智能体的“宪法”绝不能丢交互层是“工作台”只留最近用得到的东西记忆层是“档案室”平时锁在柜子里需要时再调取。L0 和 L1 好理解关键是 L2 和 L3 怎么配合。我的做法是每次与模型交互时L2 里只保留最近的 N 轮工具调用记录比如 5 轮更早的交互记录会被压缩成摘要放进 L3。压缩的动作本身由模型完成给它一段历史让它输出一段包含“目标、已完成、进行中、下一步、失败项”五个字段的摘要。这样 L2 保持在一个稳定的大小模型每轮看到的都是最近的工作状态注意力不容易被旧历史带偏。2.3 裁剪与压缩的工程实现滑动窗口加摘要仓库实现上不复杂但有一个关键细节压缩摘要不能只存一个“总摘要”而是每个子任务阶段维护自己的摘要否则跨多个子任务的长期上下文会混在一起。我给每个阶段建了一个摘要记录格式大致是dataclass class StageSummary: stage_id: str # 阶段唯一标识 goal: str # 该阶段的目标 completed: list[str] # 已完成的动作 in_progress: str # 当前进行中的动作 next_steps: list[str] # 下一步计划 blockers: list[str] # 阻塞项 / 风险项 created_at: float然后上下文构建的流程就变成从任务开始时的输入中提取 L1 任务层内容固定不动。把系统层的 L0 规则放在消息序列最前面确保模型第一眼看到的是规则不是历史对话。L2 用滑动窗口保留最近 5 轮交互模型消息 工具返回更早的从消息列表里移除。被移除的交互记录触发摘要生成写入对应 StageSummary并在 L3 位置插入一行“摘要索引”告诉模型“这段历史我已经压缩了如果还需要细节可以告诉我再展开”。第 4 步尤其重要。直接删掉旧记录会导致模型丢失完成过的工作但如果不告诉它“这部分有存档”需要细节时它不会主动要求补充。插入摘要索引相当于在模型心里埋了个锚关键时刻它会说“请查一下之前 X 步骤的原始日志”Harness 再从这里把详情拼进下一轮请求。滑动窗口的 N 值我调过几轮5 轮偏保守信息足够但偶尔会丢关键工具返回8 轮上下文会涨得快成本压力大。最终在 6 轮左右平衡你也可以按任务复杂度自己试但别超过 10 轮。3. 编排实践ReAct 循环怎么搭工具调用怎么管3.1 ReAct 循环的最小可运行骨架说完上下文再讲编排。我选的基础模式是 ReAct——Reason and Act。流程就是模型看到当前状态 - 思考下一步 - 要么直接给最终答案要么调一个工具 - Harness 执行工具并返回结果 - 模型继续思考循环往复。这个循环的骨架代码其实很短核心就十几行def run_agent(task: str, harness: Harness, max_steps: int 15): harness.start(task) for step in range(max_steps): response harness.llm.chat(harness.messages()) action harness.parse_action(response) # 解析出结构化动作 if action.type final_answer: harness.finish(action.content) return action.content result harness.execute_tool(action) # Harness 负责执行Agent 不碰实现细节 harness.record_observation(result) # 写入上下文并触发压缩策略 harness.check_interrupt() # 检查是否触发护栏 raise MaxStepExceeded(task)你可能会问这也太简单了吧实际项目中真正麻烦的都在harness.execute_tool和harness.record_observation这两个方法内部——工具怎么被调用、返回怎么被记录、失败怎么处理。这些恰恰是裸调模型时大家最容易潦草应对的部分。3.2 工具注册表告别 if-else 地狱很多初版智能体的工具调用是这样的模型输出一段 JSON代码里if action.name search: do_search()加一个工具就加一个分支。这种写法在工具数超过五六个之后就会失控而且模型经常拼错工具名if-else 根本兜不住。我用的方案是工具注册表模式。每个工具用一个装饰器注册进 Harness工具名、参数 schema、执行函数三者绑定harness.tool( namecalc, description执行数学计算支持加减乘除和括号, parameters{ type: object, properties: { expression: {type: string, description: 数学表达式如 (12)*3} }, required: [expression] } ) def calc(expression: str) - str: # 这里用安全的表达式解析库不用 eval result safe_eval(expression) return f{expression} {result}模型侧看到的工具描述就是description和parametersHarness 侧通过注册表找到对应的执行函数。模型拼错工具名时注册表可以给出“最接近的候选”把错误提示回填给模型让它自己纠正。加了新工具只需增加一个函数不需要改任何循环逻辑。这里有个经验工具描述一定要写清楚“什么时候该用”和“什么时候不该用”光写“能做什么”不够。否则模型会拿着计算器去算日期拿着搜索去查常量。我在一个金融问答智能体上吃过亏模型把“查询汇率”和“查询利率”两个工具反复搞混后来我在 description 里各加了一句“仅用于XXX场景其他场景请使用另一工具”错误率立刻降了一半。3.3 从单循环到多阶段编排什么任务需要升级ReAct 单循环适合中等复杂度的任务但有些场景单靠一个循环会非常低效。比如“分析这份财报并生成投资摘要”直接扔进 ReAct 循环里模型会在“读文件、查行情、搜新闻、算指标”之间来回跳经常做完 A 忘记 B或者做完了前面忘了后面的目标。这种情况我建议升级成多阶段编排每个阶段是一个独立的 ReAct 循环阶段之间通过结构化的状态对象传递信息。class FinancialAnalysisPipeline: def __init__(self, harness_factory): self.factory harness_factory def run(self, file_path): # 阶段1: 数据提取 extraction self.factory.create_harness(extraction_agent) raw_data extraction.run(f从 {file_path} 中提取财务数据) # 阶段2: 指标计算 calculation self.factory.create_harness(calculation_agent) metrics calculation.run(f计算以下指标的同比变化: {raw_data}) # 阶段3: 报告生成 report self.factory.create_harness(report_agent) result report.run(f生成投资摘要结合这些指标: {metrics}) return result多阶段的好处有三个一是每个阶段上下文独立不会出现“前面读文件的细节干扰后面写报告”的问题二是某一步失败了只需要重跑该阶段不用整个任务重来三是每个阶段可以选用不同的模型或提示策略比如提取阶段用更强的模型、生成阶段用更快更便宜的模型。缺点是阶段之间的结构设计要花心思状态对象字段没定义好后面会越缠越乱。我的建议是先单循环跑通之后再看哪一步最容易出错、哪一步上下文最乱再决定要不要拆阶段。不要一开始就把流程设计得很重。4. 自主容错控制把「报错就死」变成「错了能绕」4.1 智能体运行实例先设计成状态机容错的第一步不是写 try-except而是把智能体的运行生命周期定义清楚。我把它建模成一个状态机每个状态有明确的进入条件和退出条件pending任务等待调度running模型正在推理等待响应waiting_tool工具调用已发出等待返回error某个环节出错进行错误分类recovering执行恢复策略比如重试、降级或绕路done完成或确认失败有了状态机错误处理就不需要散落在各处了。所有异常统一进入error状态由容错模块决定下一步是回到running还是直接结束。这相当于给智能体装了一个“总开关”错误不再直接打穿主流程。4.2 错误分级与恢复策略一览在 Harness 里我把错误分成四类每一类的处理方式完全不同错误类型典型例子处理策略可重试网络超时、模型限流、临时5xx退避重试最多3次每次间隔递增可跳过非关键工具失败、可选数据缺失记录 warning继续执行后续步骤需降级工具返回格式不对、模型表达力不足更换实现摘要替代全文、缓存替代实时查询需终止安全红线、任务语义不可满足立即停止输出结构化失败原因一个实际的例子智能体在调用一个天气 API 时超时了。如果是可重试错误直接重试如果重试两次还失败就把工具标记为“当前不可用”告诉模型换一个天气数据源如果所有天气源都失败而且这个信息对后续任务关键那就向用户发一个确认“是否跳过天气因素继续分析”而不是默默终止任务。这里的关键是恢复动作要反馈给模型。就是说Harness 不只是机械地重试它要把“工具调用失败、已切换备用源、当前使用的是源B”这些信息写进下一条消息传给模型让模型基于新的状态继续决策。否则模型以为自己还在用源A后续推理就会出现偏差。4.3 防失控护栏不能只靠模型自觉容错之外还有一类问题防不住——模型跑着跑着跑偏了或者在一个错误方向上反复尝试。对这种“软失控”我得加三道护栏最大步数限制这个必须有。我一般设 15 步模型绕不出来就强制结束并输出“已尝试过多步建议简化目标或人工介入”。Token 与成本预算给每个任务设一个 token 上限达到上限后 Harness 自动切换到更小模型或缩短上下文再试一次还不行就终止。敏感操作二次确认部分工具发邮件、删文件、转账执行前需要用户确认。Harness 在调用这类工具时先返回一个待确认状态等用户点头才真正执行。这在多步任务里特别重要因为模型很容易在漫长的循环里“顺手”触发一个不该触发的操作。我见过一个没有护栏的例子一个自动发帖智能体在循环里连续给同一个帖子发了五条评论成本本身不高但后果非常尴尬。从那以后凡是具有“对外作用力”的工具我一律默认加确认机制宁可多一步交互也不能让模型自由发挥。5. 实测数据与踩坑记录有 Harness 和没有差多少5.1 一个可复现的对比实验为了验证 Harness 的价值我做了一个比较可控的实验。任务设计成一个通用的“信息聚合”型任务给定一份商品列表智能体需要调用三个模拟工具价格查询、库存查询、运费计算筛选出“有货且运费低于总价10%”的商品并按价格升序输出前三名。我设计了两个版本Baseline裸调方式——一个长 prompt 塞给模型让它自己“记住”调用工具的结果所有工具调用逻辑写在主循环里没有重试和状态管理。Harness 版套用我的上下文分级 工具注册表 容错模块。两组用同一个模型、同一批任务各跑 10 次中途由脚本随机注入 20% 比例的工具超时故障模拟真实网络环境。5.2 三组关键指标的对比指标BaselineHarness 版变化任务完成率60%95%35%平均 token 消耗12.4K / 任务8.1K / 任务-35%平均耗时42 秒35 秒-17%故障场景完成率20%90%70%完成率的提升主要来自容错和状态管理baseline 在工具超时后直接失败Harness 版会自动重试、切换备用来源并继续。token 消耗的下降主要靠上下文压缩baseline 的 prompt 越滚越大Harness 版每轮都有滑动窗口和摘要后期 token 基本稳定。有一个指标值得单独说故障场景完成率。一旦加入故障注入baseline 几乎全军覆没Harness 版还能保持 90% 的完成率。这说明 Harness 的价值不是“顺境加速”而是“逆境存活”。如果你的智能体任务链路长、依赖外部接口这种韧性差距会在生产环境里放大成数量级的体验差异。5.3 我踩过最深的三个坑第一个坑是上下文裁剪太激进。早期我把滑动窗口设成 3 轮token 是省了但模型经常丢失“已经查过某个数据”这个事实重复查询同样的工具。后来我在压缩摘要里显式加入“已查询的键值清单”每轮模型都知道哪些信息已经有了不再重复劳动。第二个坑是工具 schema 与实际实现不一致。有个日期处理工具schema 里写的是接受YYYY-MM-DD实际函数内部却要求datetime对象于是模型每次传格式都对、执行必报错连着重试了三四轮才被护栏截停。后来我专门加了一个工具自检流程注册工具时用一组示例参数实测跑通跑不通就不允许注册进 Harness。这种问题不发生在第一次调用而往往发生在参数边界值自检流程能拦掉大半。第三个坑是重试引发成本翻倍。刚开始我对所有超时错误都做 5 次重试结果一个限流故障下单个任务烧掉了正常任务 6 倍的 token。后来我改成梯次退避第 1 次等 1 秒、第 2 次等 2 秒、第 3 次等 4 秒超过 3 次就降级而不是继续重试。同时每个任务设置总 token 上限上限归零即强制终止。成本控制不能靠“应该不会出错”的侥幸必须靠硬上限。6. 落地清单与下一步想做的事6.1 最小可用 Harness 的八件事如果你也想从零搭一套别贪多先把这八件事做扎实定义状态机pending、running、waiting_tool、error、done明确每个状态的进入和退出条件。实现上下文分层L0 系统层固定、L1 任务层保底、L2 交互层滑动窗口、L3 记忆层摘要化。做摘要生成与召回把被裁掉的历史转成结构化摘要并给模型一个“需要细节可要求展开”的提示。搭工具注册表装饰器注册、schema 自动生成、自检用例必须通过。写循环控制器ReAct 循环的步数限制、token 预算、成本上限三个护栏缺一不可。做错误分类与恢复至少区分可重试、可跳过、需降级、需终止四类并落实对应动作。加人工确认机制对外有副作用的工具默认要二次确认。记录运行日志每一步的输入、输出、错误、恢复动作都打日志否则出问题你根本不知道模型在做什么。这八件事里第 2 和第 6 是收益最大、也最容易被忽略的。上下文是智能体能力的隐性天花板容错是智能体能否从演示走向生产的生死线。6.2 多智能体共享上下文与更远的探索单智能体的 Harness 跑通之后我接下来想折腾的方向是多智能体协作。多个智能体各管一个子任务时它们的上下文应该如何共享我的初步设想是共享一个外部记忆库用向量检索做按需召回。每个智能体的 L3 摘要统一写入这个记忆库其他智能体在需要某个信息时先通过语义检索找到对应摘要再决定是否获取完整内容。这样可以避免每个智能体都携带全局上下文又能保持信息的可追溯性。目前这个方案还在实验阶段等沉淀出更多可说的结果再单独写一篇笔记。另外多模态大模型的最新进展2026 年这波多模态能力肉眼可见地在变强也会影响 Harness 的设计。图片、音频片段进入上下文之后token 分配策略、摘要格式、缓存机制都要重新考虑。比如一段 5 分钟的音频压缩成文本摘要可能只有 200 tokens但语义信息可能损失很多。这个方向的探索我还没有成熟结论就不硬写经验了。6.3 一点个人体会做智能体项目这一年多我最深的感受是模型的能力波动远大于我们预期真正决定智能体能不能用的是外面这层壳结不结实。Harness 听起来是个挺“工程”的词但它解决的问题其实很朴素——让智能体不忘记重要的事、不被小故障击穿、不跑着跑着失控。如果你正在做类似的智能体项目建议先别急着去追最新最强的模型把现有的模型装进一个可靠的 Harness 里大概率比换个更贵的模型更有用。最后分享一个实用小技巧给 Harness 加一个“回滚点”机制每个工具调用前把上下文快照存一份万一后面发现模型把信息搞错了可以直接恢复到某个快照重新推演。这个功能在调试时能救你的命。
返回列表