门户首页
Case Study · 工程决策复盘

OLLAMA 本地模型不支持 tool_call 的根因与等效替代方案

本仓 experiment_modules/solving/adapters/ollama/ 下三个 runner —— ollama-one-shot / ollama-pipeline / ollama-shell —— 在调用 Ollama OpenAI 兼容端点时均不传 tools 字段。OpenAI 协议明明定义了 tools / tool_calls / role:"tool","正常 Agent"按理应走这条路——为什么本仓偏不?这不是一个"模型弱就放弃"的偷懒决定,而是一个经过三层叠加分析 + 任务形态分析后做出的工程选择。 这一页是一个完整的工程决策复盘,论证链 + 失败模式 + 替代实现 + 选型决策树都摊开。

生成时间:2026-09-02 18:35 · 版本 v0.2 · 生成 Agent:MiniMax Code (LLM: MiniMax-M3) · 载体:agentsoft-research-platform teaching-web-platform

概览

3
回答的问题
3
叠加层级
4
失败模式
3
ollama runner
项目说明
本卡定位工程决策复盘 · 6 卡片从"为什么"到"怎么绕"到"何时改回"
前置知识读过 llm-api-schema-reference.html 卡片 4(工具调用基础)/ llm-chat-protocol-guide.html §8(兼容方言陷阱)
读完会什么能用 L1/L2/L3 心智模型分析任何"协议层 vs 兼容层 vs 模型层"的工程问题;能解释为什么 ollama 三个 runner 不传 tools 字段;能用决策树选型
本仓对应experiment_modules/solving/adapters/ollama/{one_shot,pipeline,shell}_runner.py
源报告docs/REPORT-ollama-tool-call-substitute-20260902.md(v1.1)

第一讲 · 破题:决策全貌与三层叠加

第一讲 · 报告回答什么 + L1/L2/L3 心智模型(1 全图 + 3 个问题)
把整个决策的全貌摊开:3 个回答 + 3 层叠加——后面所有论证都站在这层之上。
第 1 讲 · 3 个问题 + L1/L2/L3 完整定义 + 1 张三层叠加图

本报告回答的 3 个问题 + L1/L2/L3 心智模型

这个决策不是"模型弱所以放弃 tool_call"那么简单——它经过三层叠加 + 任务形态两层分析才得出的工程结论。记住这个三层叠加:协议层(L1)支持 ≠ 兼容层(L2)能用 ≠ 模型层(L3)会发。任何"为什么 XX 不能用标准协议"的问题,都可以套这个分层去拆。

报告回答的 3 个问题
  1. 为什么 ollama 三个 runner 不走标准 tool_call 协议?(§1-3 回答:三层叠加 + 任务形态)
  2. 三个 runner 如何等效实现 tool_call 该承担的能力?(§4 回答:范式转移 + 能力对照表)
  3. 在哪些场景下应改回标准 tool_call?(§5 回答:决策树 + 4 场景行动项)
L1/L2/L3 三层到底是什么(前置定义)

"三层叠加"是这个报告的核心方法论。先对齐一个基础词:本页反复出现的 API(Application Programming Interface,应用程序接口)指"调用大模型服务的 HTTP 接口"。在看图之前,先把每一层是什么 / 提供什么 / 在 ollama 场景的具体表现 / 可能怎么崩讲清楚——后面所有论证都站在这层之上。

L1 协议层(Protocol Layer)
  • 是什么:由 OpenAI 等规范方定义的协议标准——tools / tool_choice / message.tool_calls / role:"tool" + tool_call_id 四个字段在文档里写得清清楚楚
  • 提供什么:一份"客户端与推理服务之间应该传什么 JSON(JavaScript Object Notation,人类可读的文本数据格式)"的合同——所有兼容端点都声明支持
  • 在 ollama 场景:Ollama 0.5+ 文档 https://github.com/ollama/ollama/blob/main/docs/openai.md 明确写了支持 /v1/chat/completions 兼容端点,包括 tools
  • 怎么崩:基本不会崩——L1 是"标准在不在"的问题。OpenAI 2023-06 起就把这套 API 钉死了
L2 兼容层(Compatibility Layer)
  • 是什么:各个推理服务(Ollama / vLLM / DeepSeek / DashScope / Together / Groq 等)自己实现的 OpenAI 兼容端点——它们读规范、写代码、暴露 HTTP 端口
  • 提供什么:一个"长得像 OpenAI API"的端点——但只是"长得像",行为未必一致
  • 在 ollama 场景:Ollama 0.5.x 之前对未知 / 不完整字段的策略是静默忽略而非报错(tool_choice:"required" 部分版本失效、strict:true 静默忽略等)
  • 怎么崩:"兼容端点不等于行为一致"——L1 通过不代表 L2 通过。这是 OpenAI cookbook 反复警告过的"方言陷阱"(详见姊妹讲义 llm-chat-protocol-guide.html §8)
L3 模型能力层(Model Capability Layer)
  • 是什么:模型本身在训练数据里学到的能力——能不能稳定输出 tool_call JSON、能不能写出符合 JSON Schema 的参数、能不能在正确位置填 id
  • 提供什么:模型推理的"格式技能"——这是模型权重决定的,不是 L1/L2 能改的
  • 在 ollama 场景:本机实测的 qwen3.8:27b-mlx / qwen3.6:35b-coding-mxfp8 是 coding 任务微调,没做 tool_call SFT——发不出 tool_call JSON
  • 怎么崩:训练数据决定能力边界——换 prompt 没用、换 API 没用、换兼容层没用,必须换模型(或换带 function-calling 微调的版本,如 qwen2.5:7b-instruct、nous-hermes:function-calling)
三层对照表(一眼看清差异)
层谁来定提供什么ollama 场景的具体表现失败模式
L1 协议层OpenAI 等规范方API 字段的合同定义工具调用 4 字段在文档里写得清清楚楚基本不崩;2023-06 起稳定
L2 兼容层Ollama / vLLM / DeepSeek / DashScope 等"长得像 OpenAI"的端点对未知字段静默忽略而非报错方言陷阱:"兼容 ≠ 行为一致"
L3 模型层模型训练数据(预训练 + SFT + RLHF)输出 tool_call JSON 的格式技能coding 模型没做 tool_call SFT,发不出 JSON换啥都救不了,只能换模型
为什么必须三层分开看:如果不分层,工程师容易跳到结论——"ollama 不行就上云端 API"——但实际上 L1 通过 + L2 通过 + L3 失败 = 换哪个端点都救不了,必须换模型。反过来 L1 + L2 都有问题、但 L3 很强,换云端 API 就好,不用换模型。三层是相互独立的故障域——任一层薄弱都崩,但修法完全不同。
图 1 · L1/L2/L3 三层叠加心智模型
flowchart TB L1["L1 协议层
标准 OpenAI Chat Completions
定义 tools / tool_choice / tool_calls 字段"] L2["L2 兼容层
Ollama / vLLM / DeepSeek / DashScope
「OpenAI 兼容端点」的方言实现"] L3["L3 模型能力层
模型本身的训练数据 / SFT 决定
能不能稳定输出 tool_call JSON"] L1 -->|协议层支持| L2 L2 -->|兼容层能用| L3 L3 -->|模型会发| Result["实际能否 tool_call"] L1 -.->|L1 通过 不等于 端到端可用| Trap1["协议陷阱"] L2 -.->|L2 通过 不等于 模型能调| Trap2["方言陷阱"] L3 -.->|L3 通过 不等于 弱模型不崩| Trap3["训练数据陷阱"]
图 1 · L1/L2/L3 三层叠加:每一层都"通过"了,端到端仍可能崩。ollama 三个 runner 的"不传 tools"是把这三层叠加 + 任务形态一起权衡后的工程选择。
核心方法论:分析"为什么 XX 不能用标准协议"时,不要只看协议层。要看 L1(标准在不在)+ L2(兼容端点行为一不一致)+ L3(模型训练数据支不支持)三层。三层都"通过"才真能用,任一层薄弱都可能导致端到端不可用——这是 ollama 三个 runner 的核心教训。
读者画像(本报告主要给谁看)
  • 代码审计者:评估"为什么 ollama runner 源码里没传 tools"——看完能讲清"这不是偷懒,是工程权衡"
  • 架构决策者:决定"该上 ollama 还是云端 API"——看完能用决策树选型
  • 新 agent 开发者:准备接入新 agent 类型——看完知道"何时该传 tools,何时不该传"
spec §0 报告目的 ref: REPORT-ollama-tool-call-substitute-20260902.md

第二讲 · 协议基线:标准 tool_call 长什么样

第二讲 · 4 字段 + 1 往返 + 1 关键概念(前置:协议讲义卡片 4)
先把"标准 tool_call"摆出来——后面才说"为什么不走这条路"。
第 2 讲 · 4 字段 + 1 往返 + 1 关键概念

OpenAI Chat Completions 的 tool_call:4 字段 + 完整往返

tool_call 是 OpenAI 协议中"让模型发起工具调用"的标准机制。涉及 4 个标准字段:请求体 tools / tool_choice,响应体 message.tool_calls,消息回填 role:"tool" + tool_call_id。"正常 Agent"全部走这条路——但这条路对 LLM(Large Language Model,大语言模型)输出的"格式技能"要求很高。

4 个标准字段速查
字段位置类型作用
tools请求体顶层ToolDefinition[]工具定义列表(含 JSON Schema)
tool_choice请求体顶层"auto" | "none" | "required" | {type:"function", function:{name}}控制模型是否必须调 / 调哪个
message.tool_calls响应 choices[0].messageToolCall[]模型发起的工具调用
role:"tool" + tool_call_id消息数组单条消息工具结果回填,与 tool_calls[].id 严格配对
完整工具调用往返(伪代码)
① Agent 组装 messages + tools  ──→  POST /v1/chat/completions
② 解析响应,读 finish_reason
       │
       ├── "stop"            → 收尾退出
       │
       └── "tool_calls"      → 逐个执行:
                                  - json.loads(arguments)  ← 防御 try/except
                                  - 本地执行 shell/函数/HTTP
                                  - 取 stdout
                                  - append {"role":"tool", tool_call_id, content}
                              → 回到 ①(携带工具结果再请求)
tool_call 响应里 ToolCall 的结构
{
  id: string,                  // "call_..." 配对键
  type: "function",
  function: {
    name: string,
    arguments: string          // ⚠️ JSON 字符串,不是对象!需 json.loads + 防御
  }
}
关键概念:tool_call JSON 是"格式技能"——模型在生成阶段必须做三件事:
1. 语义层:决定调哪个工具
2. 结构层:生成符合 JSON Schema 的参数
3. 语法层:在正确位置填入 id / type:"function" 字段
三层都"必须"模型在 SFT 阶段见过大量"问题 → tool_call JSON"样本对才能稳定输出。这是卡片 3 L3 模型能力层的伏笔——没有这种 SFT 的模型,发不出 tool_call JSON。

第三讲 · 为什么不传 tools:L1/L2/L3 三层叠加拆解

第三讲 · 三层叠加 + 4 类失败模式 + 文献支持(核心论证章)
核心论证:协议层 L1 + 兼容层 L2 + 模型能力 L3 三层叠加,任意一层薄弱都崩。
第 3 讲 · L1 + L2 + L3 + 4 失败模式 + 3 文献

三层叠加:协议层标准 ≠ 兼容层能用 ≠ 模型层会发

本节是报告的核心论证章。"不传 tools"不是单一原因——是协议层(L1)支持 + 兼容层(L2)方言陷阱 + 模型层(L3)训练数据限制三层叠加的工程结论。三层都"通过"才真能用;任一层薄弱,端到端仍崩。

L1 协议层:tools 字段标准存在 ✅

所有宣称"OpenAI 兼容"的 chat completions 端点(Ollama / vLLM / DeepSeek / DashScope / Together / Groq 等)都声明支持 tools 字段。OpenAI Chat Completions v2023-06 起即稳定,tools / tool_choice / message.tool_calls / role:"tool" + tool_call_id 全部标准字段定义清晰。

L1 结论:这是协议标准,不是问题。L1 通过。
L2 Ollama 兼容层:方言陷阱 ⚠

Ollama 0.5.x 之前对未知 / 不完整字段的策略是静默忽略而非报错:

  • tool_choice: "required" 或指定函数形式:部分版本失效
  • 旧版本对 tools 数组里的 strict: true 字段:静默忽略
  • 工具定义里 parameters.strict: true 强约束模式:取决于版本

加上本仓 one_shot_runner.py D4 决策记录的血泪事实:

血泪经验:Ollama 默认上下文窗口 4096,SWE-bench 的长 prompt(issue + 指令常超 4K)会被静默截断;旧版本 Ollama 对未知字段静默忽略不报错。
—— "兼容端点不等于行为一致"是 L2 的核心陷阱。
L3 模型能力层:训练数据决定能力边界 ❌(根本原因)

根本原因:模型输出 tool_call JSON 的能力完全由训练数据决定。

训练阶段训练内容学到什么
预训练(Pre-training)TB 级网页 / 代码 / 书籍语言模式、续写、bash 命令
监督微调(SFT)万-百万级指令-响应对按指令回答、输出特定格式(含 tool_call JSON)
RLHF / DPO人类偏好标注答得更像人、更稳定

tool_call JSON 是一种"格式技能",必须在 SFT 阶段见过大量"问题 → tool_call JSON"样本对才能稳定输出。

本机 2026-09-02 实测用的两个模型
模型训练定位tool_call SFT?表现
qwen3.8:27b-mlxSWE-bench 风格 coding 任务❌ 无不会 tool_call
qwen3.6:35b-coding-mxfp8SWE-bench 风格 coding 任务❌ 无不会 tool_call

这两个模型重在"读代码 + 写 patch",没有专门做 tool_call SFT,所以发不出 tool_call JSON。

对比:哪些模型能做 tool_call
模型tool_call 能力备注
GPT-4 / GPT-5(OpenAI)✅ 极稳定内部有 API 调用日志 + 专门 SFT
Claude Sonnet 4.5(Anthropic)✅ 极稳定同上
Qwen2.5-Coder-32B-Instruct✅ 可用Qwen 官方有 function-calling 微调
NousResearch Hermes Function Calling✅ 可用专门做 function-calling 训练
Qwen3.8:27b-mlx❌ 不可用coding 任务微调,无 tool_call SFT
Qwen3.6:35b-coding-mxfp8❌ 不可用同上
4 类失败模式(本机 2026-08-30/31 实测)

用 opencode 1.14.29 + qwen3.6:35b-coding / qwen3.8:27b-mlx 跑了 5 轮,4 类失败模式:

失败模式表现根因
"说改不改"诊断正确但 edit tool_call 从未发出,"Let me fix" × N 死循环弱模型发不出结构化 JSON tool_call;但 bash/read 文本工具实测 100% 可靠(轮次 ① 60 次调用)
"跑偏"glob/read 游荡到 ~/.claude/skills无工作目录约束
"上下文压缩崩溃"Tool call not allowed while generating summary上下文累积 + harness 自动压缩时模型失控

—— 即便 L1 + L2 都"通过",L3 模型能力不足仍是稳定失败。

文献支持(3 篇)
来源结论
CodeAct (arXiv:2402.01030)可执行动作 vs JSON tool_call:成功率 +20%、轮次 -30%;对未 tool-tuning 的开源模型提升更大
Let Me Speak Freely (arXiv:2408.02442)结构化格式限制伤弱模型推理
AgentDiet(本仓 wiki/60)上下文不能靠模型自治,须外部强制压缩
核心洞察:给模型的输出格式按"抄写+填空"设计,绝不按"计算+对齐"设计。
—— 弱模型走纯文本协议 + harness 侧正则解析,比强行走 JSON tool_call 更可靠。
姊妹:协议讲义 §8 方言陷阱 spec §2 三层叠加 paper: CodeAct + Let Me Speak Freely

第四讲 · 任务形态分析:SWE-bench 本质上不需要 tool_call(v1.1 新增)

第四讲 · 通用 vs 窄义任务 + 5 步全文本化分析(v1.1 补充章节)
把论证从"模型能力"上升到"任务形态"——更上游、更根本。
第 4 讲 · 2 任务形态对比 + 5 步全文本化 + 1 一句话总结

v1.1 新增:不是模型弱,是任务窄

第三讲解释了"为什么弱模型发不出 tool_call JSON"——但还有更上游的问题:SWE-bench(SWE = Software Engineering 软件工程 + bench = benchmark 基准测试,用真实 GitHub issue 评测代码 agent 的软件工程基准)这个任务本身就不需要 tool_call。"读代码 + 写 patch"这个任务完全可以被完全文本化,harness 把"该读什么"准备好,模型只管吐 diff。三个 ollama runner 不传 tools 字段,不是"因为模型弱",是"因为这个任务根本不需要 tool_call"。弱模型用纯文本 bash 协议反而是顺水推舟。

通用 Agent vs 窄义任务:tool_call 是必需还是冗余?
任务形态工具集流程需要什么
通用 Agent(能调任何工具)运行时才确定(搜天气 / 查日历 / 发邮件 ...)动态tool_call 是必需(统一接口)
窄义任务(SWE-bench 风格)固定(读文件 / 跑测试 / 写文件)固定(issue → 找文件 → 改 → 验证)工具可预先写死在 harness

SWE-bench 任务是后者——工具集是确定的,流程也是确定的。tool_call 提供的"通用对话能力"对它冗余。

SWE-bench 任务"纯文本化"分析(5 步全展开)
步骤需要什么是否需要"调工具"?
1. 读 issue文本输入即可❌
2. 找相关文件已知文件名 + 路径(jsonl 里有)❌ 不需要 ls/grep
3. 读文件内容文本输入即可(harness 提前塞 prompt)❌
4. 改代码输出 unified diff❌ 不需要 edit 工具
5. 跑测试验证由 harness 执行❌ 不需要 exec 工具

—— 5 步里 0 步真的需要模型调工具。

"读代码 + 写 patch"任务的本质
输入:issue 文本(用户报的问题描述)
输出:unified diff(对哪些文件加什么行 / 删什么行)

—— 两端都是纯文本。

模型需要的"信息"(哪些文件、文件里有什么)在任务开始前就已经确定了:

  • SWE-bench jsonl 里有 instance_id / repo / base_commit / problem_statement
  • 仓库文件已经在 worktree 里(harness 备仓的)
  • 关键文件可以用 BM25 / git log 反向定位(ollama-pipeline 路线)

所以"读代码"这件事harness 提前做了,把代码文本塞进 prompt;"写 patch"模型直接吐 unified diff。

prompt: "这是 issue...这是相关文件 a.py 内容...这是相关文件 b.py 内容..."
output: "diff --git a/a.py ... @@ -10,3 +10,4 @@ ... +fixed"

—— 这个纯文本 in / 纯文本 out的范式下,tool_call 真的没有位置。

三个 runner 各自的"绕开 tool_call"路线
runner怎么绕开
ollama-one-shotharness 把 issue 塞 prompt,模型一次性吐 patch——连"读文件"都被 harness 跳过(碰运气)
ollama-pipelineharness 先用 BM25 + ast 裁剪代码,把"该读什么"自动准备,模型只看 harness 给的代码包吐 patch
ollama-shell模型自己写 bash 命令(cat / sed / pytest),harness 执行并回填——用纯文本 bash 协议代替 tool_call 的"调工具"能力

—— 三种路线都没用 tool_call 字段,但都完成了"读代码 + 写 patch + 验证"的全部工作。

对照:通用 Agent 为什么必须 tool_call
步骤需要什么tool_call
1. 用户问"今天北京天气怎样"输入❌
2. 模型需要查天气 API动态外部数据✅ get_weather
3. 模型需要查日历动态外部数据✅ get_calendar
4. 模型需要发邮件触发外部动作✅ send_email
5. 总结回复用户输出❌

—— 通用 Agent 必须 tool_call,因为"做什么工具"在运行时才确定。

一句话总结:
"读代码 + 写 patch"这个任务本身就完全可以用纯文本表达——harness 把"该读什么"准备好,模型只管吐 diff。这是任务形态决定的,不是模型能力决定的。tool_call 的真正价值在"通用 Agent 调任意工具"——SWE-bench 任务太窄,工具集 + 流程都固定,harness 可以替模型干"调工具"这件事,所以 tool_call 是冗余的。
—— 三个 ollama runner 不传 tools 字段,不是因为模型弱,是因为这个任务根本不需要 tool_call。弱模型用纯文本 bash 协议反而是顺水推舟。
spec §3 任务形态 ref: REPORT v1.1 新增章节

第五讲 · 等效实现:三个 runner 怎么绕开

第五讲 · 范式转移 + 概念对照 + 能力矩阵 + prompt 全文
把"绕过 tool_call"的工程实现摊开——从范式概念到代码 prompt。
第 5 讲 · 1 范式图 + 1 概念对照 + 1 能力矩阵 + 1 prompt

范式转移:从 API 层工具调用 → Harness 层工具调用

三个 runner 都把"工具调用"这件事从API 层降级到Harness 层:tool_call 从"模型对 API 的结构化输出"变成"模型对 harness 的纯文本输出",harness 自己解析并执行。协议兼容性让位给模型可执行性——这个权衡是 ollama runner 设计的核心。

图 2 · 范式转移:正常 Agent vs ollama-shell
flowchart TB subgraph Normal["🟦 正常 Agent 强模型 + 标准 OpenAI"] direction TB N1["Agent loop"] N2["POST messages + tools"] N3["LLM API"] N4["tool_call JSON
强模型稳定输出"] N5["Agent loop 解析 → 调工具 → 回填 tool result"] N1 --> N2 --> N3 --> N4 --> N5 --> N1 end subgraph Shell["🟧 ollama-shell 弱模型 + 纯文本协议"] direction TB S1["Agent loop"] S2["POST messages
不传 tools"] S3["LLM API"] S4["bash 代码块
cat f.py
弱模型 100% 可靠"] S5["Agent loop 正则解析 → 执行 bash → 截断回填"] S1 --> S2 --> S3 --> S4 --> S5 --> S1 end
图 2 · 关键区别:tool_call 从"模型对 API 的结构化输出"降级为"模型对 harness 的纯文本输出"。协议兼容性让位给模型可执行性。
tool_call 概念 vs ollama-shell 等效实现
OpenAI tool_call 概念ollama-shell 等效实现
tools: [{name:"read_file", ...}]不传 tools;prompt 明确"输出 bash 块"
tool_choice: "auto"不传;prompt 限定"每轮一个 bash 块或 SUBMIT"
message.tool_calls[].function.name: "read_file"cat sympy/core/_print_helpers.py
message.tool_calls[].function.arguments: {"path": "..."}bash 命令参数(harness 按 shell token 拆)
message.tool_calls[].id(配对键)shell-transcript.jsonl 里的 step 编号
role:"tool" + tool_call_id 回填bash stdout/stderr 作为下一轮 user 消息
finish_reason: "tool_calls"下一轮 client.chat_messages() 调用
finish_reason: "stop"用户输出 SUBMIT

结论:能力等价,协议不同。tool_call 的所有语义都在 ollama-shell 中得到了纯文本版复刻。

三个 runner 能力范围对照
runner等效路线能力范围代价
ollama-one-shot直接出 patch(不读代码)1 个候选 patch模型看不到代码,碰运气
ollama-pipelineBM25 + ast 裁剪代码包 + 采样 N=6 + 验证投票0 tool_call;6 个候选 patch 选最优5-8 min/题;多 LLM 调用
ollama-shell纯文本 bash 协议 + 5 道护栏 + 三级超时循环到 SUBMIT;多 tool_call 等效25-40 min/题(50 步 × 30s)
三个 runner 的"工具调用语义"覆盖率
tool_call 语义one-shotpipelineshell
读文件❌✅(crop_spans)✅(cat/read)
写文件❌✅(候选 patch 落盘)✅(cat > f <<EOF)
跑测试❌✅(judge_candidates 跑 venv pytest)✅(pytest)
多轮规划❌❌(6 个独立采样)✅(循环到 SUBMIT)
采样放大❌✅(默认 6 个)❌
cwd 约束❌❌✅(CwdJail 护栏)
重复检测❌❌✅(RepeatDetector)
max_steps 硬顶❌❌✅(OLLAMA_SHELL_MAX_STEPS=50)
上下文预算❌❌✅(BudgetGuard 外部截断)
LeakGuard(git 历史泄漏)❌❌✅
ollama-shell 的 prompt 全文(看它如何把模型逼向"bash 块"路线)
你在一个代码仓库里修复 issue。每轮你必须且只能输出以下两种之一:

1. 一个 bash 命令块(我会执行并把输出回给你):
```bash
<一条命令>
```
2. 单词 SUBMIT(确认修复完成,我会提取 git diff 作为答案)

红线:
- 所有操作限定在当前仓库目录内,禁止访问其它路径
- 禁止修改 tests/ 下任何文件
- 禁止运行 git log / git show / git blame(仓库历史与未来提交对你不可见)
- 用预装 venv 的 python 跑测试:<VENV_PYTHON>
- 最小改动修复;改动后必须重新运行相关测试验证

—— 明确让模型走"bash 块"路线(这模式模型在预训练时见过海量 bash 教程),避开"tool_call JSON"路线(需要专门 SFT)。

spec §4 等效实现 ref: shell_prompt.py + shell_guards.py

第六讲 · 选型决策:什么时候改回标准 tool_call 协议

第六讲 · 决策树 + 4 场景行动项 + 切云端注意点(1 决策树 + 1 行动表)
收口:什么时候继续走 ollama 路线、什么时候必须切到标准 tool_call。
第 6 讲 · 1 决策树 + 1 行动表 + 1 切云端提醒

选型决策树:4 路径收敛到 4 种 runner

最后一张卡给可执行的决策:什么场景用什么 runner、什么场景必须切到云端。决策只看一个轴:模型本身能不能稳定输出 tool_call JSON——能就上标准协议,不能就走 ollama-shell 等效路线。

图 3 · 选型决策树(按模型能力分支)
flowchart TB Start["你要用什么模型"] Start --> Q1{云端强模型
gpt-5 / claude-sonnet-4-5
deepseek-reasoner} Start --> Q2{本地大参数 70B+
Qwen2.5-Coder
DeepSeek-Coder-V2} Start --> Q3{本地 27B/35B
coding 模型
qwen3.8:27b-mlx 等} Q1 -->|是| Path1["用 tools 字段
标准 OpenAI tool_call 循环
协议层 + 模型层都支持"] Q2 -->|可能| Path2["可能能用 tools 字段
先小规模验证
注意 Ollama 兼容层方言陷阱"] Q3 -->|是| Path3["不传 tools 字段
用 ollama-shell 纯文本 bash 协议
等效实现 tool_call 全部语义"] Path1 --> Rec1["推荐 kimi-fast 或 qwen-one-shot
或新增 openai-agent runner"] Path2 --> Rec2["先跑 10 题验证
再决定走哪条"] Path3 --> Rec3["推荐 ollama-shell 多轮规划
ollama-pipeline 采样放大
ollama-one-shot 粗筛"]
图 3 · 按"模型能不能稳定输出 tool_call JSON"分支——能走标准,不能走 ollama 等效路线
4 场景行动项(按规模 × 质量)
场景行动
1000+ 题粗筛ollama-one-shot(快)
10-100 题稳健ollama-pipeline(采样放大)
5-20 题高质量ollama-shell(多轮规划)
必须用 tool_call 协议换云端 API(gpt-5 / claude-sonnet-4-5)
必须在本地用 tool_call换模型:qwen2.5:7b-instruct 或 nous-hermes:function-calling
切换到云端 API 的注意点

如果从 ollama 切到云端 API,只需替换 _client.py 的 base_url 和认证头,三个 ollama runner 的循环逻辑可以直接复用(或改用 kimi-fast / qwen-one-shot runner 走 one-shot 路径)。

但云端 API 的 tool_call 行为更稳定,有可能反而是 ollama-shell 的循环模式显得冗余(强模型不需要 5 道护栏)。届时可考虑直接用 qwen-one-shot(云端 one-shot + 强模型)或新增 openai-agent runner 走标准 tool_call 协议。

收口金句:这个决策给工程界的最大教训不是"ollama 模型弱",而是 "分析工程问题时分三层:协议层、兼容层、模型层——再加一层任务形态"。
任何"为什么 XX 不能用标准协议"的问题,都可以套这个分层去看:L1(标准在不在)+ L2(兼容端点行为一不一致)+ L3(模型训练数据支不支持)+ 任务形态(这个任务本身需要这个协议吗)。
—— 这四层都"通过"才真能用;任一层薄弱都崩。
spec §5 选型决策 ref: REPORT-ollama-tool-call-substitute-20260902.md v1.1

收口:把 6 张卡片装回一个心智

一页总结:ollama 三个 runner 不传 tools 字段,是三层叠加 + 任务形态两层分析后的工程结论,不是偷懒。
  • 卡片 1:报告回答 3 个问题;L1/L2/L3 心智模型——分析工程问题的标准分层
  • 卡片 2:标准 tool_call 的 4 字段 + 完整往返;tool_call JSON 是"格式技能",需要专门 SFT
  • 卡片 3:L1 协议标准 + L2 兼容层方言陷阱 + L3 模型训练数据限制 = 三层叠加;4 类失败模式实测 + 3 篇文献支持
  • 卡片 4(v1.1 新增):任务形态分析——SWE-bench 这个任务本身就不需要 tool_call,"读代码 + 写 patch" 5 步全文本化
  • 卡片 5:范式转移(API 层 → Harness 层)+ 概念对照表 + 能力矩阵 + ollama-shell prompt 全文
  • 卡片 6:选型决策树(按模型能力分支)+ 4 场景行动项 + 切云端注意点

这个 case study 的最大价值不是结论本身,而是"遇到工程问题时分四层去看"的方法论:L1(标准在不在)+ L2(兼容层行为一不一致)+ L3(模型训练数据支不支持)+ 任务形态(这个任务本身需要这个协议吗)。任何"为什么 XX 不能用标准协议"的问题,套这个框架去分析,都能得到稳健的工程结论。