最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Qwen3.7-Max 在 OpenCode 中为什么会调用不存在的 unknown 工具?
时间:2026-09-13 08:36:01 编辑:袖梨 来源:一聚教程网
Qwen3.7-Max 在 OpenCode 中调用名为 unknown 的工具,通常不表示系统里存在一个隐藏工具。它表示模型或中间适配层返回的 function.name 没有匹配当前请求的 tools[]。应先保存 OpenRouter 原始响应与 OpenCode 实际发送的工具定义,确认 unknown 是上游直接生成、流式解析后产生,还是客户端名称映射失败后的占位值。
错误的准确含义
来源帖子显示,OpenCode 提示模型尝试调用不可用的 unknown 工具,并同时列出当前可用工具。这类错误发生在执行之前:调度器找不到对应工具,因此不应创建一个同名空工具来“看看会传什么”。这样做会掩盖根因,还可能把本应拒绝的参数交给意外代码执行。
OpenRouter 官方将工具调用错误分为无效 JSON、未知名称和参数不符合 schema 三类。其中 UnknownName 的定义就是返回的 function.name 不存在于请求的工具数组。这个分类能说明校验失败的位置,但不能单独判断错误来自模型、provider 模板还是 OpenCode。
工具名称可能在哪一层丢失
- OpenCode 组装请求时没有发送预期工具,或权限配置把它移除。
- OpenRouter 或实际 provider 转换协议时改变了工具定义。
- 模型生成了字面量
unknown,而不是已提供的名称。 - 流式工具名分片没有正确拼接,客户端用占位值代替空名称。
- MCP 或自定义工具注册名与执行器中的映射名不同。
只看终端中的最终提示无法区分这些情况。至少需要比较请求、原始响应和执行器错误三份证据。
保存实际发送的 tools 数组
打开脱敏调试日志,保存模型请求中的完整 tools[]。每个工具都应有唯一、稳定且清晰的函数名、用途描述和合法参数 schema。名称不能在一次会话中动态改变,也不要让多个插件注册相同名称。
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read one file from the workspace",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string"}
},
"required": ["path"],
"additionalProperties": false
}
}
}
如果错误日志列出的可用工具与请求体不一致,说明工具列表在客户端内部又被过滤或覆盖。OpenCode 的全局、项目和 agent 级权限都可能影响工具可见性;agent 级设置还可能覆盖全局设置。
检查 OpenCode 权限与工具注册
OpenCode 使用 permission 控制工具是允许、询问还是禁止。被禁止的工具不应继续暴露给模型。自定义工具和 MCP 工具常带有前缀,模型看到的正式名称可能与服务器内部函数名不同。
诊断时先关闭非必要插件和 MCP 服务,只保留一个内置只读工具。如果最小工具集正常,再逐个恢复扩展。每恢复一个扩展,都记录最终工具名列表;最先引入名称冲突或 unknown 的扩展就是主要调查对象。
核对原始 OpenRouter 响应
绕过 OpenCode 的界面格式化,保存同一请求的原始响应。关注 choices[].message.tool_calls[].function.name、工具调用 ID 和 arguments。根据结果分流:
- 原始响应就是
unknown:模型或实际 provider 返回未知名称。 - 原始响应名称正确,OpenCode 日志变成
unknown:客户端转换或注册映射有问题。 - 原始名称为空或被截断:检查流式增量拼接。
- 名称存在但请求 tools 中没有:检查请求构造和权限过滤。
不要只打印最终文本,因为工具调用通常位于结构化字段中,正文内容可能为空。
流式响应要按索引累计
流式工具调用中,ID、名称和参数可能出现在不同数据块。客户端应按 choice 索引和工具调用索引维护缓冲区,只追加非空片段,不能用后续空字符串覆盖已经收到的名称。
for event in stream:
for call in event.tool_call_deltas:
key = (event.choice_index, call.index)
if call.name_fragment:
buffers[key].name += call.name_fragment
if call.arguments_fragment:
buffers[key].arguments += call.arguments_fragment
validate_after_stream_end(buffers, advertised_tools)
若非流式请求正常、流式请求出现 unknown,证据会明显指向增量解析或协议转换。反之,两种模式都返回相同未知名称,则应继续检查模型与 provider。
固定 provider 做对照
OpenRouter 可以为同一模型路由到不同 provider,并允许失败时回退。不同 provider 的模型模板和工具兼容性可能不同。复现时固定一个 provider、关闭 fallback,并在模型页面查看该 endpoint 的 Tool Call Error Rate。
随后在同一提示、同一工具 schema 下更换 provider。若只有一个 provider 生成未知名称,应把请求 ID 和 provider 信息提交给 OpenRouter;若所有 provider 都一致失败,则更可能是模型行为或共享请求结构问题。
不要自动执行未匹配工具
执行器必须采用白名单:只有名称完全匹配、参数 JSON 可解析且通过 schema 校验的调用才能进入权限判断。未知名称应被拒绝,并将简洁错误作为工具结果反馈给模型,允许它选择现有工具重新尝试。
对 shell、写文件、提交、部署等有副作用的工具,应默认询问或拒绝,不能把 unknown 映射到通用 shell。OpenCode 官方权限规则支持对工具设置 allow、ask 和 deny;排查期间可将编辑和 shell 保持为询问。
一个可复现的测试顺序
- 固定 OpenCode、模型 ID、OpenRouter provider 和版本。
- 仅暴露一个名字清楚的只读工具,执行非流式请求。
- 保存 tools 数组、原始 tool_calls 和 OpenCode 执行日志。
- 开启流式输出,比较工具名是否发生变化。
- 逐个恢复 MCP、自定义工具和 agent 权限。
- 更换 provider 重复相同请求,统计 UnknownName 比例。
成功标准不是“偶尔不报错”,而是所有返回工具名都属于当前请求的白名单,参数均能通过 schema,且未知调用不会进入执行阶段。
所以,unknown 是工具名称校验失败的表现,不是需要补装的工具。先比较请求中的工具清单、OpenRouter 原始响应和 OpenCode 映射结果,再通过非流式对照与固定 provider 缩小范围;同时保持严格白名单和权限确认,才能在定位兼容问题时避免意外执行。