1. 模型升级为什么总会把 Agent 的 Harness 搞崩做 Agent 应用开发的人大概都经历过这样一个夜晚模型供应商发了一封邮件说新版本模型上线了能力更强、推理更准、价格还降了。你兴冲冲地把配置里的模型名一改本地跑几个 case 看着都挺正常于是推上生产。结果第二天早上打开监控发现工具调用成功率从 98% 掉到了 70%一堆原本跑得好好的任务开始报 schema 校验失败Agent 在某个环节反复循环出不来。这不是模型变笨了而是Harness 没跟上模型的变化。先把概念理清楚。所谓 Harness直译是马具、挽具在 Agent 开发语境里它指的是包裹在 LLM 外面那一整套工程脚手架提示词模板、工具Tool/Function的 schema 定义、消息历史的管理策略、输出解析器、重试与容错逻辑、循环终止条件、上下文裁剪规则等等。LLM 是大脑Harness 是神经系统和四肢的接口。模型升级换的是大脑但神经系统如果还按旧大脑的脾气设计就会出现信号对不上的问题。很多人把 Harness 和 Agent 混为一谈其实两者是不同层次的东西。Agent 是一个能自主决策、调用工具、完成多步任务的智能体这个整体概念Harness 是让这个智能体能够稳定运转的工程外壳。同一个 Agent 逻辑换一套 Harness稳定性可能天差地别。这也是为什么热词里harness 和 agent 区别被反复搜索——大家在实际开发中逐渐意识到真正决定 Agent 能不能上生产的往往不是模型多强而是 Harness 做得多扎实。模型升级对 Harness 的冲击本质上是接口契约的隐性变更。模型厂商在发布新版本时通常只会强调能力提升但下面这些东西可能悄悄变了对 system prompt 的遵循程度有的版本更听话有的版本更有主见工具调用时参数的填充风格有的倾向填全所有字段有的只填必填项对 JSON 输出格式的严格程度有的会自作主张加 markdown 代码块包裹多轮对话中对历史消息的权重分配遇到模糊指令时的默认行为追问 vs 猜测 vs 拒绝对特殊 token、分隔符的敏感度这些变化单看都很小但叠在一起就足以让一套精心调过的 Harness 失效。下面我会按实际排查和改造的顺序把这件事拆开讲透。2. 先搞清楚你的 Harness 到底管了哪些事在动手改之前得先有一张Harness 资产清单。我见过太多团队模型一升级就手忙脚乱根本原因是他们自己都不清楚 Harness 里到底有哪些东西是跟模型强耦合的。这里给一份我常用的盘点框架你可以对着自己的项目过一遍。2.1 提示词层最容易被低估的耦合点提示词层包括 system prompt、few-shot 示例、工具描述、输出格式约束。这一层和模型的关系最紧密也最脆弱。system prompt 里常见的坑是过度指令。比如你写了你必须始终以 JSON 格式输出不要添加任何解释文字在旧模型上它老老实实照做新模型可能觉得用户这个问题需要先解释一下于是先输出一段自然语言再给 JSON。你的解析器直接崩。few-shot 示例也是重灾区。示例的数量、顺序、格式在不同模型上的效果差异很大。有的模型对示例的格式极其敏感示例里多一个换行都可能影响输出有的模型则更看重示例的语义而非格式。模型升级后原来精心挑选的 3 个示例可能不再是最优组合。工具描述tool description同样关键。工具的名字、参数说明、使用场景描述直接决定模型会不会在正确的时机调用正确的工具。新模型可能对描述里的某些措辞理解不同导致该调工具的时候不调或者调了不该调的工具。2.2 工具 schema 层结构化输出的隐形战场工具 schema 定义了每个工具接受什么参数、参数类型是什么、哪些必填。这一层的问题往往在模型升级后才暴露。举个真实场景你的工具有一个可选参数timeout类型是 integer。旧模型在不需要时就不填这个字段新模型可能倾向于把所有字段都填上于是填了个timeout: null或者timeout: 0。如果你的后端没做空值处理直接拿这个值去设置超时就会出问题。再比如枚举类型。你定义format只能是json或xml旧模型严格从这两个里选新模型可能输出JSON大写或者application/json。schema 校验直接失败。还有一种情况是模型开始发明参数。你在 schema 里没定义的字段模型自作主张加进去如果你的解析器是严格模式就会拒绝整个调用。2.3 消息历史与上下文管理层多轮对话里历史消息怎么存、怎么裁剪、怎么压缩是 Harness 的核心职责之一。模型升级后这一层的问题通常表现为上下文理解漂移。旧模型可能对最近 3 轮对话权重很高新模型可能更依赖 system prompt 的全局约束。这会导致同样的历史消息在新模型上产生完全不同的决策。如果你的裁剪策略是保留最近 N 轮而新模型恰好需要更早的上下文才能做对决策就会出现明明之前聊过它却忘了的情况。上下文压缩summarization也是同理。你用一个小模型或者规则来压缩历史压缩后的摘要喂给主模型。主模型升级后对摘要的理解方式变了可能抓不住摘要里的关键信息。2.4 输出解析与容错层这一层是模型升级后最先报警的地方。输出解析器负责把模型的自然语言输出转成结构化数据容错层负责在解析失败时重试或降级。常见的解析策略有几种正则提取、JSON 解析、基于分隔符的切分、用另一个 LLM 做结构化抽取。模型升级后输出的表面格式最容易变——多了一层 markdown 代码块、换了个分隔符、在 JSON 前后加了说明文字。如果你的解析器不够健壮就会大面积失败。容错层的问题在于重试策略。旧模型失败一次重试时给个更明确的提示就能成功新模型可能失败的原因完全不同你用同样的重试提示它还是失败甚至陷入死循环。2.5 循环控制与终止条件Agent 的核心是循环调用模型和工具直到任务完成。循环什么时候停是 Harness 的关键决策。旧模型可能比较果断任务完成就输出终止信号。新模型可能更谨慎总想再确认一下导致循环次数增加。如果你的终止条件是最多循环 10 次新模型可能在第 10 次还没结束被强制中断任务失败。反过来有的新模型更激进过早认为任务完成提前终止导致任务没做完。把上面这五层盘清楚你就有了改造 Harness 的地图。接下来讲具体怎么排查和改造。3. 模型升级后的 Harness 排查链路模型升级出问题最忌讳的就是凭感觉改提示词。我见过有人一遇到问题就疯狂加提示词加到最后 system prompt 有三千字问题没解决反而引入了新的冲突。正确的做法是按链路系统排查。3.1 第一步建立可复现的回归测试集在改任何东西之前先要有一套能稳定复现问题的测试集。这套测试集不需要很大但要覆盖你的核心场景。我的做法是维护一个regression_cases.json每个 case 包含输入、期望的工具调用序列、期望的最终输出、以及这个 case 覆盖的场景标签。模型升级后先跑一遍这套 case看哪些挂了。{ case_id: tool_call_basic_001, input: 帮我查一下北京今天的天气, expected_tools: [get_weather], expected_params: {city: 北京}, tags: [tool_calling, single_step] }跑完之后你会得到一张失败分布图是工具调用失败多还是输出解析失败多还是循环控制失败多。这张图决定了你后面排查的优先级。提示测试集要包含边界 case比如空输入、超长输入、需要多步工具调用的复杂任务、需要拒绝回答的敏感请求。这些 case 最能暴露模型升级带来的行为变化。3.2 第二步隔离变量定位是提示词问题还是 schema 问题失败 case 拿到手后不要急着改。先做变量隔离。具体做法是固定 Harness 的其他部分只改一个变量看失败是否消失。比如你怀疑是 system prompt 的问题那就把 system prompt 换成旧版本的其他不变重跑 case。如果问题消失说明确实是提示词层的问题如果问题还在说明问题在 schema 或解析层。这个过程中日志要打全。我建议在 Harness 里加一个调试模式把每次模型调用的完整输入system prompt 历史 当前输入和完整输出都记下来。模型升级后的问题十有八九能在原始日志里找到线索。我踩过的一个坑是日志只记了模型的最终输出没记中间的工具调用参数。结果排查了半天最后发现是模型把date参数从2024-01-15格式改成了Jan 15, 2024后端解析失败。如果日志里有原始参数这个问题五分钟就能定位。3.3 第三步用 LLM as Judge 做批量行为对比当失败 case 比较多人工看不过来时可以用 LLM as Judge 的方式做批量对比。思路是用同一个 Judge 模型分别评估旧模型和新模型在相同输入下的输出从几个维度打分。评估维度可以包括工具调用是否正确、参数是否完整、输出格式是否合规、是否有多余内容、是否遵循了 system prompt 的约束。Judge 的 prompt 要写得具体给出明确的评分标准。judge_prompt 你是一个 Agent 输出评估器。给定一个任务输入、期望的工具调用、以及模型的实际输出 请从以下维度打分1-5 分 1. 工具选择是否正确 2. 参数是否完整且格式正确 3. 输出是否严格符合要求的格式 4. 是否有不必要的额外内容 请以 JSON 格式返回评分和理由。 Judge 的结果不是绝对真理但能帮你快速定位哪一类问题最集中。比如你发现 80% 的失败都是输出格式不合规那改造重点就很明确了。3.4 第四步区分模型行为变化和Harness 缺陷这一步很关键。有些问题看起来是模型升级导致的实际上是 Harness 本身就有缺陷只是旧模型恰好没触发。比如你的 JSON 解析器没有处理 markdown 代码块包裹的情况。旧模型从来不包裹所以一直没暴露。新模型开始包裹了问题就出来了。这本质上是 Harness 的健壮性不足不是模型的错。区分方法很简单问自己如果模型输出稍微变一下这个逻辑还能不能扛住。如果答案是不能那就是 Harness 缺陷应该修 Harness而不是去改提示词让模型别那样输出。这个判断很重要因为它决定了你的改造方向。修 Harness 是一劳永逸的改提示词是治标不治本的。4. 提示词与工具 schema 的适配改造排查清楚问题分布后就进入改造阶段。这一章讲提示词和工具 schema 这两个最核心的适配点。4.1 提示词从硬约束转向软引导 强校验旧模型时代很多人习惯用强硬的提示词来约束输出比如你必须绝对不要只能。这种写法在遵循度高的模型上有效但新模型往往更聪明也更有主见硬约束反而可能引发对抗或者被忽略。我的经验是把提示词改成软引导 强校验的组合。软引导是指用更自然、更符合模型理解习惯的方式表达意图强校验是指在 Harness 层做严格的格式检查不依赖模型自觉。举个例子。旧写法你必须只输出 JSON不要有任何其他文字。JSON 必须包含 name 和 age 两个字段。新写法请以 JSON 格式返回结果包含 name 和 age 两个字段。如果需要可以在 JSON 前简要说明你的推理过程。然后在 Harness 层解析器自动提取 JSON 部分忽略前面的说明文字。这样模型有表达空间你的系统也有确定性。这个转变的核心逻辑是不要和模型的能力对抗而是用工程手段兜底。模型想解释就让它解释你负责把有用的部分提取出来。4.2 few-shot 示例的重新筛选模型升级后few-shot 示例需要重新评估。我的做法是准备一个示例池比如 20 个候选示例然后用贪心策略筛选出最优组合。具体步骤先跑一遍所有示例单独使用时的效果选出效果最好的几个然后逐步增加示例数量每次加入一个能最大程度提升整体效果的示例直到效果不再提升或开始下降。这个过程听起来麻烦但可以用脚本自动化。关键是评估指标要明确——用你的回归测试集的通过率作为指标就行。还有一个细节示例的格式要和你的输出解析器对齐。如果你的解析器能处理 markdown 包裹示例里就可以带包裹如果不能示例就要干净。示例和解析器的行为要一致否则模型会困惑。4.3 工具 schema 的防御性设计工具 schema 的改造原则是防御性设计——假设模型会以各种奇怪的方式填充参数你的 schema 和解析器都要能扛住。具体做法所有参数都做类型转换。模型给字符串5你转成整数 5给null你用默认值。枚举值做大小写归一化。模型给JSON你转成json。必填参数做存在性检查缺失时给出明确的错误信息让模型知道要补什么。忽略未定义的额外字段不要让它们导致整个调用失败。参数描述里写清楚格式要求比如日期格式为 YYYY-MM-DD减少模型猜测空间。def normalize_tool_args(args, schema): normalized {} for param, spec in schema.items(): value args.get(param) if value is None: if spec.get(required): raise ValueError(f缺少必填参数: {param}) continue if spec[type] integer: value int(value) elif spec[type] string and enum in spec: value str(value).lower() if value not in spec[enum]: raise ValueError(f参数 {param} 的值 {value} 不在允许范围内) normalized[param] value return normalized这段代码看着简单但能挡掉大量模型升级带来的参数格式问题。4.4 工具描述的措辞优化工具描述是模型决定调不调、怎么调的关键依据。模型升级后同样的描述可能被理解成不同的意思。优化工具描述的几个原则说清楚什么时候用而不只是是什么。比如不要只写查询天气而要写当用户询问某个城市的天气情况时使用。说清楚什么时候不用。比如如果用户只是闲聊提到天气不要调用此工具。参数描述要具体给出格式示例。比如city: 城市名称如北京、上海。避免歧义词汇。像处理获取操作这种宽泛的词容易让模型误判。我一般会在模型升级后把工具描述重新过一遍用新模型跑几个该调和不该调的 case看它的判断是否符合预期。不符合就调整描述措辞。5. 输出解析、容错与循环控制的加固提示词和 schema 改完接下来是 Harness 的下半身——输出解析、容错、循环控制。这三块是 Agent 稳定性的最后防线。5.1 输出解析器的分层设计一个健壮的输出解析器应该是分层的从宽松到严格逐层尝试。第一层直接 JSON 解析。如果模型输出就是干净的 JSON直接成功。第二层提取代码块。如果输出里有json ...包裹提取出来再解析。第三层正则提取。用正则找到第一个{到最后一个}之间的内容尝试解析。第四层修复常见错误。比如单引号转双引号、去掉尾随逗号、补全缺失的括号。第五层LLM 兜底。前面都失败调用一个小模型做结构化抽取。def parse_output(raw): # 第一层 try: return json.loads(raw) except json.JSONDecodeError: pass # 第二层 code_block re.search(r(?:json)?\s*(.*?), raw, re.DOTALL) if code_block: try: return json.loads(code_block.group(1)) except json.JSONDecodeError: pass # 第三层 brace_match re.search(r\{.*\}, raw, re.DOTALL) if brace_match: try: return json.loads(brace_match.group(0)) except json.JSONDecodeError: pass # 第四层、第五层... raise ParseError(f无法解析输出: {raw[:200]})这个分层设计的好处是模型输出格式怎么变你都有对应的处理层。模型升级后即使输出风格变了大概率也能被某一层接住。5.2 重试策略要因错而异旧的重试策略往往是失败了就重试重试时加一句请严格按格式输出。这在模型升级后可能失效因为失败原因变了。我的做法是把错误分类不同错误用不同的重试策略错误类型重试策略格式错误重试时强调格式要求附上正确示例工具选择错误重试时补充工具使用场景说明参数缺失重试时明确指出缺失的参数循环未终止重试时给出更明确的终止条件内容拒绝不重试直接降级或转人工重试次数也要控制。我一般设置最多 2 次重试超过就降级。无限重试是 Agent 系统的大忌容易烧钱还解决不了问题。5.3 循环终止条件的多信号判断循环什么时候停不能只靠模型说我完成了。我一般用多信号判断模型显式输出终止标记如[DONE]或特定的 finish_reason模型连续 N 轮没有调用任何工具达到了预设的最大循环次数检测到重复的工具调用同样的工具、同样的参数任务目标已达成用规则或小模型判断这几个信号是或的关系任何一个触发就终止。这样即使模型升级后行为变了也不会陷入死循环。重复调用检测特别有用。模型升级后有的模型会陷入调用工具-看到结果-再调用同样工具的循环。检测到重复调用就直接中断能省下大量 token。5.4 上下文裁剪的动态策略上下文裁剪不能一刀切。我的做法是根据任务类型动态调整短任务3 轮以内全量保留不裁剪中等任务3-10 轮保留 system prompt 最近 5 轮 关键工具结果长任务10 轮以上保留 system prompt 最近 3 轮 历史摘要 关键工具结果关键工具结果是指那些对后续决策有影响的工具输出比如查询到的数据、计算的结果。这些不能裁掉否则模型会重复调用工具。模型升级后要重新评估裁剪策略。新模型可能对上下文的依赖方式变了原来够用的裁剪可能不够或者原来需要的现在可以省。用回归测试集验证一下就知道。6. 把 Harness 改造变成可持续的工程实践上面讲的都是模型升级后怎么救火。但真正成熟的团队应该把 Harness 的适配变成常态化的工程实践而不是每次升级都手忙脚乱。6.1 建立模型升级的预演机制在模型正式升级前先做预演。具体做法是在测试环境接入新模型跑完整的回归测试集生成对比报告。报告里包含通过率变化、失败 case 分布、平均 token 消耗变化、平均延迟变化。这份报告能让你在升级前就知道这次升级会带来什么影响提前准备改造方案。而不是等生产出问题了才反应。预演机制的关键是测试集要持续维护。每次线上发现新的失败 case都要补进测试集。这样测试集越来越全预演越来越准。6.2 用配置化隔离模型相关参数Harness 里和模型强相关的参数应该配置化而不是硬编码。比如model_config: name: your-model-name temperature: 0.1 max_tokens: 4096 prompt_version: v2.3 tool_schema_version: v1.8 retry_policy: adaptive context_strategy: dynamic这样模型升级时你只需要改配置不用改代码。不同模型可以用不同的配置组合方便 A/B 测试。配置化还有一个好处是可回滚。新模型出问题改回旧配置就行不用重新部署代码。6.3 监控指标要覆盖 Harness 的每一层生产环境的监控不能只看任务成功率这一个指标。要分层监控提示词层模型对 system prompt 的遵循率工具层工具调用成功率、参数校验失败率解析层输出解析成功率、各层解析器的命中分布循环层平均循环次数、循环终止原因分布成本层平均 token 消耗、单任务成本这些指标能帮你快速定位问题出在哪一层。模型升级后哪个指标异常就往哪一层排查。6.4 版本管理提示词、schema、代码要一起管提示词、工具 schema、Harness 代码这三者的版本要一起管理。因为它们是一个整体单独改任何一个都可能破坏兼容性。我的做法是用一个统一的版本号比如harness-v2.3对应一套提示词、一套 schema、一套代码。模型升级时创建一个新的 harness 版本测试通过后再切换。出问题可以快速回滚到旧版本。这套机制听起来重但一旦建立起来模型升级就从惊心动魄的救火变成了按部就班的迭代。7. 几个我踩过的真实坑和应对心得最后分享几个我在实际项目中踩过的坑都是模型升级后才暴露的希望能帮你少走弯路。坑一模型开始自作聪明地补全参数。有个工具的参数是date用户没指定日期时旧模型会留空让后端用默认值。新模型升级后它会自动填上今天的日期。结果后端逻辑是date 为空则用配置的默认日期现在 date 有值了默认逻辑失效查出来的数据不对。应对方法是在参数描述里明确写如果用户未指定日期请不要填写此参数同时在 Harness 层做空值语义的统一。坑二模型对 system prompt 里的示例和指令混淆。我在 system prompt 里放了一个输出示例旧模型知道那是示例新模型却把示例内容当成了指令的一部分开始模仿示例里的具体内容。应对方法是把示例和指令用明确的分隔符隔开并在示例前加以下仅为格式示例内容无关的说明。坑三模型升级后 token 消耗暴涨。新模型更话痨每次输出都带一大段推理过程。虽然推理质量提升了但 token 成本翻倍。应对方法是在提示词里明确要求推理过程控制在 50 字以内同时在 Harness 层对输出做长度截断保留结构化部分截断冗余解释。坑四模型对工具返回结果的信任度变了。旧模型倾向于相信工具返回的结果新模型开始质疑工具结果反复调用同一个工具想验证。应对方法是在工具描述里加一句此工具返回的结果是权威的无需重复调用验证同时在 Harness 层做重复调用检测。坑五模型升级后对拒绝的边界变了。有些原本能正常处理的请求新模型开始拒绝。这通常是因为新模型的安全策略更严格。应对方法是在 system prompt 里明确说明你的应用场景和允许范围给模型足够的上下文来判断。如果还是拒绝就要考虑调整请求的表达方式或者换用对场景更匹配的模型。这些坑的共同点是它们都不是模型变差了而是模型变了。你的 Harness 是按旧模型的行为模式设计的模型行为一变Harness 就不适配了。所以应对的核心思路永远是理解模型的新行为然后调整 Harness 去适配它而不是试图用提示词把模型掰回旧行为。模型升级是常态Harness 的适配也应该是常态。把上面这套排查链路、改造方法、工程实践建立起来你就能在每次模型升级时从容应对而不是被动救火。这套东西的价值在模型迭代越来越快的今天只会越来越高。