摘要:模型工具调用的 JSON 参数经常出现包壳、截断、旧字段名、字符串化等问题。本文基于真实 GEO Agent 项目,详细拆解 Vercel AI SDK 中 repairToolCall 修复回调的分层处理逻辑、DeepSeek 的标准化思路,以及概率输出与确定性执行之间的根本矛盾。


做 agent 开发有一个不对外说的秘密:你写的代码里,有相当一部分不是在实现功能,是在给模型擦屁股。

什么意思?模型调用工具的时候,需要按照预定格式传参数。你定义好了“这个参数是字符串”“那个参数是数字”“整体是一个 JSON 对象”。理想情况下模型会乖乖照做。

理想情况。

概率与确定之间

现实是,模型会变着法子给你整活。做 GEO Agent 这段时间,我把遇到的坏格式粗分了一下,至少有四类。这篇文章完整复现我的处理思路和代码结构,方便你直接抄作业。

模型坏 JSON 的四类常见情况

先看一张汇总表,感受一下模型整活的多样性:

坏法 例子 频率
JSON 外面包壳 {"arguments": { 真正的参数 }} DeepSeek 模型常见
JSON 写到一半断了 {"query": "GEO 分析", "limit": 1 流式截断
字段名用旧版 script_path 而不是 script_name 模型记住了旧文档
参数整个写成字符串 "{\"url\": \"...\"}" 而不是对象 各家模型都有

每一种情况,你的程序都得能接住。接不住,用户看到的就是一个莫名其妙的报错。说白了,模型不靠谱是常态,不是意外。

这里有个巨坑,先说结论:四类问题里,最危险的不是语法错误,而是第三类旧字段名。 因为语法错误至少会暴露出来——报错、解析失败,你能感知到。旧字段名是语法完全正确、程序却静默地执行了错误逻辑。这种错误最难查,也最伤用户体验。

repairToolCall 详解:Vercel AI SDK 的修复回调机制

我先说清楚处理这些问题的位置在哪。我用的是 Vercel AI SDK,核心处理逻辑放在 experimental_repairToolCall 这个回调里。

这个回调的触发时机是:SDK 在验证工具参数失败时,先调你注册的修复函数。你能修就返回修好的版本,修不了就返回 null 让 SDK 按默认流程报错。

换句话说,这就是 SDK 给你留的一个“抢在报错之前抢救一下”的口子。

三层修复架构:从语义到语法的完整容错

修复逻辑我分成了三层,从具体到通用依次递进。先看整体结构:

repairToolCall(toolCall, error) {
  // 第一层:语义修复(已知工具的已知问题)
  if (toolName === 'read_api_module') → 修 key → module 字段名
  if (toolName === 'run_skill_script') → 修 script_path → script_name

  // 第二层:剥壳(DeepSeek 的 arguments 包装)
  if (parsed.arguments && typeof parsed.arguments === 'object')
    → 返回 parsed.arguments

  // 第三层:JSON 语法修复
  → repairJsonString(toolCall.input)

    → 先直接 JSON.parse 试
    → 不行 → 从后往前找最后一个 } 截断再试
    → 还不行 → 逐字符扫描找未转义引号,补上
    → 最多试 200 次
}

下面逐层展开说,每一层都配具体的真实案例和代码逻辑。

第一层:语义修复——解决旧字段名问题

这是指模型返回的 JSON 语法完全正确,但字段名不对。比如我们的技能系统里有个参数叫 script_name,模型有时写成 script_path——三个月前的旧版文档是这么写的,它记住了。语法检查全通过,执行时找不到字段直接崩。

if (toolName === 'run_skill_script' && parsed.script_path && !parsed.script_name) {
  parsed.script_name = parsed.script_path;
}

听我一句劝,语义修复必须用白名单制。你只能映射你明确知道的几种情况,绝对不能做任何形式的“智能猜测”。猜错了比报错更危险,因为错误是静默的——程序不会崩,它会拿着一个错误的参数去执行,用户完全无感知。

我的代码注释里是这么写的,这句话值得你抄过去:

Only normalise this known public-audit shape; do not invent generic command line argument mappings for arbitrary scripts.

只修你见过的、确认过的坏形状。别把自己当成什么都能修的万能胶,那会出大事故。

第二层:剥壳——DeepSeek 模型的 arguments 包装

DeepSeek 的模型返回参数时有个经典毛病:在最外面包一层 {"arguments": { 真正的参数 }}。就像你点了一份外卖,结果送餐员把餐盒装在一个大箱子里,箱子上写着“餐盒”,你打开箱子,里面才是真正的餐盒。

处理逻辑很简单,检测到 arguments 字段且它是对象,就剥掉外壳返回内层:

if (parsed.arguments && typeof parsed.arguments === 'object') {
  return JSON.stringify(parsed.arguments);
}

第三层:JSON 语法修复——截断与未转义引号

模型返回的 JSON 写到一半就没了——括号没闭合、引号没配对,在流式输出里尤其常见。正常程序遇到这种直接 throw 一个 SyntaxError 就完事了。但 agent 不能这么干,用户等的是结果,不是“模型这次手滑了”。

修复函数的渐进式策略如下:

  1. 先直接 JSON.parse 尝试,能过就直接返回。
  2. 不能过,从后往前找最后一个完整的 } 位置,截断到那里再试。
  3. 还不行,逐字符扫描找到未转义的引号,给它补上。
  4. 最多试 200 次,超过就放弃,返回 null 让 SDK 走默认报错。

用伪代码表示大概是这样的:

// 尝试直接解析
try {
  return JSON.parse(input);
} catch (e) {
  // 从后往前找最后一个 }
  const lastBrace = input.lastIndexOf('}');
  if (lastBrace !== -1) {
    try {
      return JSON.parse(input.slice(0, lastBrace + 1));
    } catch (e2) {
      // 进入引号扫描修复
    }
  }
}

// 逐字符扫描,修复未转义引号
for (let i = 0; i < input.length && attempts < 200; i++) {
  if (input[i] === '"' && !isEscaped(input, i)) {
    // 补转义或补闭合引号
    attempts++;
  }
}

这个策略的思路很直白:从最乐观的情况开始试,逐步降级到最暴力的修复手段。 每一步都保证上一步确实失败才继续,不会误伤正常的 JSON。

DeepSeek 的标准化思路:把修复规则做成开放接口

DeepSeek 在 Harness 里遇到了一模一样的问题。他们的大方向跟我差不多:在工具调用执行之前加一层修复逻辑,检测已知的坏格式,能修就修,修不了就明确拒绝。

不一样的是架构选择。我是把修复逻辑硬编码在主流程里,每发现一种新的坏格式就加一条规则,属于“快速迭代、先能跑”的野路子。他们把这个机制做成一个标准化钩子,任何插件都能注册自己的修复规则,让社区来补充覆盖面。

这个思路比我高级。但代价是复杂度也上去了——你得管理这些修复规则的优先级、冲突,以及规则本身的正确性。规则一旦出错,它自己就会变成一个 bug 来源。

两种路线的选择说白了就是:你是要短期跑得快,还是要长期让更多人帮你一起修。 我的场景里工具数量有限、坏格式的类型相对收敛,硬编码的维护成本还在可接受范围内。如果你的 agent 要面对几十上百个工具、还要开放给第三方扩展,DeepSeek 的插件化路线更可持续。

本质矛盾:概率模型与确定性程序

前面讲的这些都是工程手段,但真正刺骨的是这件事背后的根本矛盾:

模型是概率的,程序是确定的。 你让概率性的输出直接驱动确定性的执行,中间必然要有一层翻译和容错。这层翻译做得好,用户感知不到它的存在;做得不好,用户就会觉得“这个 AI 怎么这么蠢”。

换个角度看,这也是为什么 agent 开发的真实工作量远超很多人预期。写 prompt、调模型这些事当然重要,但真正把产品从 demo 拉到“能用”级别的,恰恰是这些看不见的脏活。

我之前跟一个做 agent 产品的朋友聊,他说他团队里最花时间的不是调模型,是写这些“擦屁股”的代码。我深有同感。一个真正能用的 agent 和一个演示 demo 之间的差距,很大程度就藏在这些地方。

那些代码不酷。没有它们,agent 连一句话都说不完整。


本文作者来自 Adgine 团队,提供 GEO 服务(让品牌被 AI 推荐),官网 https://adgine.cn

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐