发生了什么

一位 LocalLLaMA 用户在 M4 Mac Mini 上运行 Qwen 32B、Gemma 9B 和 Command R 32B 时,发现多步代理工作负载中存在一致的故障模式:这三个模型在链式调用进行到第 8 或 9 步左右时,工具调用准确率开始下降。故障模式具有特异性——模型会返回来自早期工具的正确参数,却将其附加到后期工具的名称上;到了第 10 次调用,模型甚至开始完全幻觉出工具名称。

一个反直觉的细节是:当这种情况发生时,16K 的上下文窗口远未填满。一个包含中等规模模式和结果的 15 步工具链通常仅消耗 3,000 至 6,000 个 token,完全在三个模型的限制范围内。故障并非源于空间耗尽,而是注意力如何在累积的工具调用历史中分配的问题。

标准基准测试未能揭示这一问题,因为它们仅测试 1-3 次工具调用。而真实的代理工作流通常需要 10-20 次顺序工具调用来完成任务,例如多步代码分析、文档检索流水线或自动分诊系统。基准测试条件与生产环境条件之间存在巨大差距。

技术深度解析

提出的解释是工具模式间的注意力稀释。每个工具定义(名称、描述、参数模式)都会占用上下文中的 token。随着工具调用历史的累积,早期的工具模式会与当前模式竞争注意力权重。在 70B 以下的模型中,每个工具的有效注意力预算降至阈值以下,导致模型无法可靠地将名称与参数签名进行匹配。

这在结构上不同于上下文溢出。vLLM 中的 PagedAttention 和 llama.cpp 中的 KV cache 都能很好地处理长上下文——但两者均未解决语义拥挤问题,即许多外观相似的 JSON 模式在注意力机制下变得模糊不清。模型拥有所有信息,只是无法正确加权。

有效的缓解措施

  • 上下文修剪:仅保留系统提示和最近 4 次工具调用。丢弃中间结果。这牺牲了对早期步骤的情景记忆,但稳定了活跃模式上的注意力分布。
  • 基于阶段的模式轮换:从模式中移除不再与当前阶段相关的工具。如果搜索阶段已完成且进入操作阶段,请在继续之前从上下文中剥离所有搜索工具定义。菜单中的工具越少,剩余工具获得的注意力权重就越高。
  • 语音上独特的工具名称:search_docssearch_code共享前缀且 token 模式相似。将它们重命名为find_in_docsgrep_repo,使用不同的动词词根,可减少参数名称交叉污染的可能性。分词器对这些名称的分割方式不同,这对注意力机制可能至关重要。
  • 模型规模:70B 以上模型表现出显著更强的鲁棒性。注意力容量随模型规模扩展,且稀释阈值在 70B 时似乎要高得多。对于 32B 及以下模型,若要实现可靠的 10+ 次调用链,必须进行手动修剪。

目前没有任何特定的 vLLM 或 llama.cpp 标志能直接解决持续工具调用的可靠性问题,但将--ctx-size减小以强制更早修剪,或将修剪逻辑构建到代理编排层(如 LangGraph 或自定义循环)是当下可行的解决方案。

与托管模型的对比

GPT-4o 和 Claude 3.5 Sonnet 在实践中能可靠地处理 15+ 次工具调用链,这可能是因为它们规模更大,且在大规模工具使用数据上进行了 extensive 微调。本地 32B 模型尚未经历针对长视野工具链的同等微调曝光。

谁应该关注

这直接影响构建本地代理系统的开发者——自主编码助手、文档处理流水线、自动研究工具等任何涉及 LLM 在多步中顺序调用工具的场景。出于成本或隐私原因使用 Ollama、llama.cpp 或 vLLM 在本地运行 Qwen、Gemma 或 Command R 模型的团队,在生产规模下将触及这一上限。

正在根据基准测试评估本地模型的 ML 工程师,应在其评估套件中添加长视野工具调用链(10-20 步)。仅包含 1-3 次调用的标准函数调用基准测试会给出关于真实世界代理可靠性的误导性画面。

在 Apple Silicon 上运行推理的 DevOps 和平台团队受到的影响尤为严重,因为 M 系列 Mac Mini 是本地推理的常见硬件,而在此硬件上运行 70B 模型并不切实际,这使得编排层的缓解策略至关重要。

本周行动指南

如果您正在运行包含超过 6 次顺序工具调用的本地代理,请立即进行测试:

  • 添加日志记录以捕获每次工具调用步骤的完整消息历史,并检查从第 8 次调用左右开始的参数/名称不匹配情况。
  • 在您的代理循环中实现滑动窗口——保留系统提示 + 最近 4 次工具调用对,驱逐旧数据:
messages = [system_msg] + messages[-8:] # 最后 4 对(调用 + 结果)
  • 审查您的工具名称是否存在共享前缀或动词模式。重命名任何仅在后缀上不同的名称。
  • 如果使用 LangGraph 或类似框架,添加阶段转换钩子,以交换活跃工具模式,而不是在整个运行过程中累积所有工具。
  • 关闭 llama.cpp 的--log-disable以检查注意力模式,或使用像llama.cpp 内置上下文日志这样的小工具来验证每次调用的实际 token 使用情况。