Qwen3.7-Max 通过 OpenRouter 调用工具时为什么频繁出错?

作者:袖梨 2026-09-13

Qwen3.7-Max 通过 OpenRouter 在 OpenCode 中频繁重复调用工具,可能来自模型决策、OpenRouter 实际路由的 provider、并行工具调用默认值或 Agent 执行器的去重策略,不能只看模型名称就判断根因。最安全的临时措施是关闭并行工具调用,并在执行层按工具名与规范化参数做幂等检查,尤其不要让重复的 git commit、部署或写入命令直接执行。

原帖报告的不是普通调用失败

来源帖子描述了三类重复行为:同一个文件被读取两次,本地 CI 检查同时启动三个相同 runner,以及三个并行工具调用执行同一条提交命令。发帖者也明确承认测试时间短、比较不科学,并询问问题来自 Qwen 还是 OpenCode。

因此,可以确认的是一次用户侧观察,不是已证实的模型缺陷。需要保存原始 API 响应,确认模型是否真的返回多个相同 tool_calls,还是 OpenCode 在重试、恢复会话或执行阶段重复调度了同一个调用。

工具调用链路中有四个可能的重复点

  1. 模型在一次响应中生成多个相同工具调用。
  2. OpenRouter 切换或回退 provider 后重新生成了请求。
  3. 客户端因网络或解析错误重试,却重复消费上一轮结果。
  4. Agent 执行器没有按调用 ID 去重,或把同一计划并发执行多次。

界面上看到三条相同命令,无法区分这四种情况。必须同时记录请求 ID、OpenRouter generation 信息、实际 provider、响应中的 tool call ID、工具名、参数和执行器日志。

先关闭并行工具调用

OpenRouter 官方工具调用文档说明,多数模型默认允许并行工具调用,可以在请求中将 parallel_tool_calls 设为 false,要求模型一次只请求一个工具。对于文件读取,这会降低吞吐;对于提交、部署和数据库写入,却是更稳妥的诊断起点。

{
  "model": "实际使用的OpenRouter模型ID",
  "messages": [],
  "tools": [],
  "parallel_tool_calls": false
}

如果 OpenCode 的当前 provider 配置不能透传该字段,应在对应 provider 的额外请求参数中配置,或用最小 OpenRouter 请求直接验证。不要假设配置文件已生效,应在脱敏后的实际请求体中确认字段存在。

固定 provider 排除路由差异

OpenRouter 默认会在同一模型的多个 provider 之间路由,以提高可用性,并可在主 provider 不可用时回退。不同 provider 对工具格式、并行调用和模型模板的实现可能存在差异。OpenRouter 也会基于工具调用成功率等信号调整工具请求的 provider 顺序。

复现时应指定一个 provider,并暂时关闭 fallback。对同一最小任务重复运行,随后换另一个 provider 做对照。如果异常只出现在某个 provider,问题更可能位于该部署或模板;如果所有 provider 都稳定返回相同重复调用,才更支持模型或统一客户端层的问题。

检查原始 tool_calls

保存模型返回的未经 OpenCode 转换的 JSON。对每个工具调用记录 id、函数名和完整参数,并将 JSON 参数按键排序后生成规范化指纹。判断时分三种情况:

  • 不同 ID、相同名称和相同参数:模型或上游返回了重复建议。
  • 同一 ID 被执行多次:客户端执行器缺少去重或发生重复消费。
  • API 响应只有一次,执行日志却有多次:问题明确位于 Agent 调度层。

流式响应中的工具参数可能分片返回,必须先按 choice 和工具索引拼接,再计算指纹。把每个参数片段当成独立调用,也会制造看似重复的执行。

执行层必须有幂等保护

即使模型和路由都正常,生产 Agent 也不能假设工具调用永不重复。网络重试、进程恢复和人工重新提交都可能再次执行同一动作。读取文件等无副作用工具可以缓存结果;写入类工具应要求幂等键和前置状态检查。

fingerprint = hash(tool_name + canonical_json(arguments))

if fingerprint in completed_calls:
    return completed_calls[fingerprint]

if is_destructive(tool_name):
    require_confirmation()

result = execute_once(tool_name, arguments)
completed_calls[fingerprint] = result

指纹的有效范围要限定在当前任务或明确时间窗口内,避免把用户有意重复的操作永久拦截。对 git commit 可先检查工作树和当前 HEAD;对 CI 可复用同一提交对应的在途任务;对支付或数据库写入应使用服务端幂等键。

避免错误重试放大副作用

只对尚未被服务端接受的推理请求做有限重试。若连接在工具调用返回后中断,客户端不能直接重新执行工具,而应先检查调用 ID 或目标系统状态。OpenRouter 的 provider fallback 处理的是模型请求可用性,并不替应用保证工具副作用只发生一次。

把工具状态划分为“建议、已批准、执行中、已完成、结果已回传”五个阶段。恢复会话时从持久化状态继续,而不是重新生成并执行整轮计划。这样即使模型返回重复建议,也不会重复落地。

设计公平的复现测试

  1. 固定 OpenCode、模型 ID、OpenRouter provider 和提示词。
  2. 关闭 fallback 与并行工具调用,只暴露一个只读工具。
  3. 连续运行多次,保存原始响应和执行日志。
  4. 开启并行调用,观察重复是否只在该配置出现。
  5. 更换 provider,但保持其他条件不变。
  6. 绕过 OpenCode 直接调用 OpenRouter,比较原始 tool_calls。

测试结果应报告重复调用次数、总工具调用数、具体 provider 和样本量,而不是只写“频繁”。一次短测试只能发现故障案例,不能给出模型总体工具调用错误率。

什么时候应向哪一方反馈

直接 OpenRouter 请求就返回重复工具调用时,向 OpenRouter 提供请求 ID、provider、模型 ID 和脱敏响应;固定 provider 后仍稳定复现,可同时反馈模型 provider。原始响应只有一个调用而 OpenCode 执行多次时,应向 OpenCode 提交版本、会话日志和执行器事件序列。

不要公开 API Key、私有仓库路径、源代码或提交内容。对于已经产生副作用的重复操作,先停止自动重试并检查实际状态,再继续测试。

Qwen3.7-Max 经 OpenRouter 的重复工具调用问题,关键不在于马上认定哪一层有 Bug,而在于保留调用边界。关闭并行调用、固定 provider、比较原始响应和执行日志,并在工具层实施幂等与确认机制,既能定位责任层,也能避免诊断期间重复提交或部署。

相关文章

精彩推荐