goose Ollama Tool Shim 实战:让不支持函数调用的本地模型也能执行工具
【免费下载链接】goosean open source, extensible AI agent that goes beyond code suggestions - install, execute, edit, and test with any LLM项目地址: https://gitcode.com/GitHub_Trending/goose3/goose
本文聚焦 goose 的实验特性 Ollama Tool Shim:它解决"本地模型不支持原生工具调用、或偶发以纯文本形式输出工具请求"的问题。读完本篇,你将掌握完整的启用步骤与全部环境变量配置,并理解 goose 源码中"直接解析 → 内联 JSON 解析 → 解释器模型兜底"的三段式工具调用恢复管线,以及 Ollama 与内置 llama.cpp 两种解释器后端的实现差异。
需要预先说明:Tool Shim 是一个实验性功能,文档明确提示其行为与配置项可能在未来版本中变化(见 experimental/ollama.md)。
什么是 Ollama Tool Shim,何时需要启用
goose 的工具(shell、文件读写等 MCP 工具)默认依赖模型原生的 function calling 能力:模型通过 API 的结构化字段返回工具调用。但很多本地模型(尤其是经由 Ollama、llama.cpp 运行的模型)并不支持这种机制,或者行为不稳定,典型症状包括:
- 会话中途工具突然失效——模型"调用了"工具但 goose 没有执行;
- 模型输出的是纯文本工具格式,例如
functions.shell:0 <|tool_call_argument_begin|> {...}这样的标记串,而不是 API 结构化的 tool call; - 推理模型把思考标签(
think标记)和工具调用混在一起输出,导致解析失败。
启用 Tool Shim 后,shim 会拦截模型响应,把上述文本形式的工具请求转换成 goose 可执行的结构化工具调用。核心机制是引入一个解释器模型(interpreter model):它独立于你主对话使用的任何 provider(可以是 Ollama、llama.cpp 内置推理),专门负责从文本中"读懂"工具调用意图。完整的使用与排查指南见 guides/tool-shim.md。
快速开始
官方文档给出的最小路径只有三步:
- 安装并启动 Ollama;
- 拉取默认解释器模型
mistral-nemo:
ollama pull mistral-nemo- 启用 shim 启动 goose:
GOOSE_TOOLSHIM=true goose session其中mistral-nemo是源码中写死的默认解释器模型,定义于 toolshim.rs:
/// Default model to use for tool interpretation pub const DEFAULT_INTERPRETER_MODEL_OLLAMA: &str = "mistral-nemo";工作原理:工具调用的三段式恢复管线
从源码 augment_message_with_tool_calls 的实现看,每条助手消息进入 shim 后,按以下优先级逐级处理,只有前两级都没命中时才调用解释器模型:
第一段:tokenized 标记直接解析。很多模型的文本工具输出带有固定的特殊标记,shim 用一组常量识别它们(toolshim.rs):
const TOOL_CALLS_SECTION_BEGIN: &str = "<|tool_calls_section_begin|>"; const TOOL_CALLS_SECTION_END: &str = "<|tool_calls_section_end|>"; const TOOL_CALL_BEGIN: &str = "<|tool_call_begin|>"; const TOOL_CALL_ARGUMENT_BEGIN: &str = "<|tool_call_argument_begin|>"; // ...解析器支持两种写法:标准的name <|tool_call_argument_begin|> {json}以及省略参数标记的name {json}。工具名解析还做了大量容错——去掉:0序号、functions.前缀,把.映射成 goose 工具命名约定的__,并在多个候选名中匹配真实工具列表(见 resolve_tool_name)。
第二段:内联 JSON 解析。有些模型直接输出形如{"name": "shell", "arguments": {...}}的 JSON 指令(常伴随Using tool:前缀)。shim 用括号配对扫描提取第一个 JSON 对象并做同样容错解析(parse_inline_json_tool_calls),解析成功后会清理掉文本中残留的 JSON 工具指令,避免泄漏到最终回复。
第三段:解释器模型兜底。前两段都未命中时,消息文本连同当前可用工具列表一起发给解释器模型,由后者按固定 JSON Schema 输出{"tool_calls": [{"name", "arguments"}]};若模型判断"没有工具调用",则返回名为noop的空调用,shim 会过滤掉noop。解析完成后,无论是否成功,消息都会经过sanitize_residual_markers清洗,保证原始标记(<|tool_call_begin|>等)不会出现在用户可见的最终输出里。
单元测试 crates/goose/src/providers/toolshim.rs 中的 tests 模块 覆盖了这些路径:tokenized 标记解析、内联 JSON 解析、Windows 反斜杠路径参数的 JSON 容错、以及"直接解析优先于解释器"的优先级验证。
安全细节:execute 别名只允许"确定"的 shell 转换
部分模型会用execute/execute_code这类泛化工具名包裹Developer.shell({ command: "..." })形式的 TypeScript 代码。shim 会用 tree-sitter 对这段代码做真实语法解析,仅在唯一且明确匹配Developer.shell单参命令时才转换为 goose 的 shell 工具调用(maybe_convert_execute_to_shell_tool_call)。一旦出现多个 shell 调用、动态参数、额外参数、字符串/注释/正则里的"诱饵"文本,解析直接拒绝并把整条消息内容清空(rejected_execute),宁可放弃执行也不猜测。相关拒绝用例集中在augment_does_not_interpret_rejected_execute_marker测试中,值得作为该设计意图的证据参考。
环境变量与配置参考
| 变量 | 说明 | 默认值 |
|---|---|---|
GOOSE_TOOLSHIM | 启用 tool shim(true或1) | false |
GOOSE_TOOLSHIM_BACKEND | 解释器后端:ollama、local或llama.cpp | ollama |
GOOSE_TOOLSHIM_OLLAMA_MODEL | Ollama 解释器模型 | mistral-nemo |
GOOSE_TOOLSHIM_MODEL | 本地解释器后端的模型名(使用local后端且未设置LOCAL_LLM_MODEL配置时必填) | — |
几个源码中可确认的细节,可以帮你排配置问题:
- 布尔值解析很宽容:global_toolshim() 读取
GOOSE_TOOLSHIM,parse_bool_config 接受1 / true / yes / on(小写不敏感)为真值,0 / false / no / off为假值,其余取值直接报错。 - backend 取值同样容错:parse_toolshim_backend 除文档列出的
ollama/local/llama.cpp外,还接受llama_cpp和空串(视为 ollama)。 - 本地后端模型名有明确的优先级:resolve_local_interpreter_model 先取环境变量
GOOSE_TOOLSHIM_MODEL,再回退到全局配置键LOCAL_LLM_MODEL,两者都为空时启动即报Local toolshim backend requires GOOSE_TOOLSHIM_MODEL or LOCAL_LLM_MODEL to be set。 - Ollama 地址来自统一配置:解释器请求的 base URL 由
OLLAMA_HOST配置决定,默认localhost:11434(ollama.rs),解析逻辑见 get_ollama_base_url——未带协议会补http://,未带端口会补 11434。 - 诊断入口:
goose doctor也会检查该开关(doctor.rs)。
三种典型使用组合
Ollama 作为主 provider
GOOSE_TOOLSHIM=true goose session主对话与解释器都走本地 Ollama,解释器默认mistral-nemo,需要时可用GOOSE_TOOLSHIM_OLLAMA_MODEL覆盖。
自定义 OpenAI 兼容 provider
GOOSE_TOOLSHIM=true \ GOOSE_TOOLSHIM_OLLAMA_MODEL=llama3.2 \ goose session主 provider 可以是任意 OpenAI 兼容服务(Bedrock、自建路由等)。shim 的解释器始终在本地 Ollama 上运行,与主对话使用什么 provider 无关——这正是"解释器模型独立于主 provider"设计要带来的灵活性。
内置本地推理后端(llama.cpp)
如果你本来就在用 goose 的内置本地推理,可以直接把它当解释器,无需再起一个 Ollama 实例。注意此时必须指定模型名,否则启动报错:
GOOSE_TOOLSHIM=true \ GOOSE_TOOLSHIM_BACKEND=local \ GOOSE_TOOLSHIM_MODEL=my-model-name \ goose session对应实现 LocalInterpreter 会创建一个localprovider 并显式.with_toolshim(false),避免解释器自身再进入 shim 管线造成递归。
解释器后端实现细节
Ollama 后端使用 Ollama 的结构化输出能力:post_structured 以非流式方式调用/api/chat,并在请求体中注入固定 JSON Schema(payload["format"]),约束输出必须形如:
{ "tool_calls": [ { "name": "tool_name", "arguments": { "param1": "value1" } } ] }系统提示词明确要求:检测到 JSON 格式的工具请求就转写为上述格式,否则返回{"tool_calls": [{"name": "noop", "arguments": {}}]}(interpret_to_tool_calls)。
为什么主模型收不到 tools 定义?在 shim 模式下 goose 向模型传空的工具列表,把工具说明改以文本形式写进系统提示词——modify_system_prompt_for_tool_json 会追加每个工具的Tool Name / Schema / Description与"一次只调一个工具、按 JSON 格式告知要调用的工具"的指令;历史消息中的工具请求/响应也会由 convert_tool_messages_to_text 降级为纯文本,因为部分 provider(如 Bedrock)会校验 tool_use/tool_result 块只能与工具定义共存。
故障排查
继承官方指南的四个高频问题:
1. 会话中途工具突然不工作。模型可能已从原生工具调用切换到文本格式。设置GOOSE_TOOLSHIM=true并重启会话。
2. shim 已启用但工具仍不执行。检查解释器后端可达性:
- Ollama:运行
ollama list确认服务在跑、解释器模型已拉取; - Local:确认本地推理已配置且模型名已设置(环境变量或
LOCAL_LLM_MODEL)。
3. 解释器调用太慢。换一个更小更快的解释器模型:
export GOOSE_TOOLSHIM_OLLAMA_MODEL=qwen2.5:3b4. 模型在工具调用前输出推理标签(think标记)导致解析失败。shim 会自动处理:启用后推理内容会从最终消息中剥离。
延伸阅读
- 实验特性入口文档:documentation/docs/experimental/ollama.md
- 完整 Tool Shim 指南:documentation/docs/guides/tool-shim.md
- 核心实现:crates/goose/src/providers/toolshim.rs
- 配置解析:crates/goose/src/model_config.rs
- Ollama 默认地址常量:crates/goose-providers/src/ollama.rs
【免费下载链接】goosean open source, extensible AI agent that goes beyond code suggestions - install, execute, edit, and test with any LLM项目地址: https://gitcode.com/GitHub_Trending/goose3/goose
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考