用 Ollama 0.4 通过 OpenAI 兼容接口向 Mistral-7B-Instruct 发送带有 tools 字段的请求,期望模型返回 tool_calls JSON,但实际返回的是纯文本:"我需要查询天气,请调用 get_weather 函数,参数为 location: '北京'"。有时返回 JSON 格式,但 function 字段缺失或键名拼写错误,导致客户端解析失败。本地 LLM 的 tool calling 支持质量参差不齐,从模型能力、prompt 构造到解析逻辑都可能是瓶颈。
常见原因
1. 模型本身不支持 function calling(非 instruct-fc 版本)
Llama-3.1、Mistral-v0.3-function-calling、Qwen2.5-Instruct 等模型在微调时加入了 function calling 数据,而基础 instruct 版本(Mistral-7B-Instruct-v0.2)没有。向不支持 tool calling 的模型发送 tools 参数,模型会把 tools 定义当作普通文本处理,然后用自然语言描述如何调用。
怎么判断:查看 HuggingFace 模型页面,找 “Function Calling” 或 “Tool Use” 标签;或测试时直接要求模型输出原始 JSON:"请以 JSON 格式输出:{"function": "get_weather", "parameters": {"location": "北京"}}" — 若模型能做到,说明它有能力,只是需要正确引导。
2. Ollama 的 tool calling 实现与模型期望的格式不一致
Ollama 0.4 内置了 tool calling 支持,但仅对特定模型(llama3.1、qwen2.5、mistral-nemo 等)做了专门适配。对未适配的模型,Ollama 会构造一个通用格式的 system prompt 来请求 JSON 输出,若模型没见过这种格式,输出会不稳定。
怎么判断:OLLAMA_DEBUG=1 ollama run <model> 然后发送 tool calling 请求,查看日志中实际传给模型的 prompt;检查 system prompt 是否包含了 tool 定义。
3. Chat template 没有包含 tool calling 专用 token
Llama-3.1 使用 <|python_tag|> 和 <|eot_id|> 作为 tool call 的边界标记;Mistral 使用 [TOOL_CALLS] 标签。若 Ollama Modelfile 中的 TEMPLATE 没有包含这些特殊标记,模型即使有 function calling 能力,也不会触发正确的输出格式。
怎么判断:查看 Ollama 官方 Modelfile(ollama show <model> --modelfile),对比官方 HuggingFace 仓库的 tokenizer_config.json 中的 chat_template,检查 tool_call 相关的标记是否存在。
4. tool 定义的 JSON schema 格式不标准
某些本地模型对 tool 的 JSON schema 比较严格,若 schema 缺少 required 字段或 type 字段格式不对,模型可能不识别 tool 定义,回退到文本描述。例如,parameters.properties.location.type 写成了 "String" 而不是 "string"(大小写敏感)。
怎么判断:用最简单的 tool 定义测试:只有一个 string 类型的必填参数,检查这种情况下 tool calling 是否正常工作。
5. llama-cpp-python 的 grammar-based sampling 与 tool calling 冲突
llama-cpp-python 支持通过 JSON grammar 强制模型输出 JSON 格式(response_format={"type":"json_object"}),但若同时启用了 tool calling 和 JSON grammar,两个约束可能冲突,导致输出被截断或格式错误。
怎么判断:检查 API 调用代码中是否同时设置了 tools 和 response_format;移除 response_format 后重试。
6. 量化过低导致 JSON 格式生成不稳定
Tool calling 要求模型精确输出特定格式的 JSON。在 Q4_K_M 以下的量化(如 IQ3_XXS)中,JSON 关键字的 token 预测概率分布更宽,容易在 "function" 字段名、JSON 括号等位置出现错误,导致输出的 JSON 不合法。
怎么判断:换成 Q5_K_M 或 Q8_0 量化版本测试,若 tool calling 成功率明显提升,就是量化精度问题。
最短修复路径
Step 1:确认模型支持 function calling
# 测试模型是否能输出格式化 JSON
ollama run mistral:7b-instruct-v0.3 'Output only valid JSON: {"function": "get_weather", "parameters": {"location": "Beijing", "unit": "celsius"}}'
# 期望输出:直接输出那段 JSON,没有多余文字
# 若输出了自然语言解释,模型可能不支持严格 JSON 输出
Step 2:使用官方支持 tool calling 的模型
# Ollama 官方支持 tool calling 的模型(截至 2026-05)
ollama pull llama3.1:8b # Llama 3.1 原生支持 tool calling
ollama pull qwen2.5:7b-instruct # Qwen 2.5 原生支持 tool calling
ollama pull mistral-nemo:latest # Mistral Nemo 支持 tool calling
# 使用 OpenAI 兼容接口测试
python3 << 'PYEOF'
import openai
client = openai.OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City name"}
},
"required": ["location"]
}
}
}]
response = client.chat.completions.create(
model="llama3.1:8b",
messages=[{"role": "user", "content": "北京今天天气怎么样?"}],
tools=tools,
tool_choice="auto"
)
msg = response.choices[0].message
if msg.tool_calls:
print("Tool call:", msg.tool_calls[0].function.name)
print("Arguments:", msg.tool_calls[0].function.arguments)
else:
print("No tool call, text:", msg.content)
PYEOF
Step 3:对不支持原生 tool calling 的模型使用 prompt engineering
import json
def build_tool_prompt(user_message: str, tools: list) -> str:
"""为不支持原生 tool calling 的模型构造 tool calling prompt"""
tool_descriptions = json.dumps(tools, ensure_ascii=False, indent=2)
return f"""你是一个助手,可以调用以下工具:
{tool_descriptions}
如果需要调用工具,请只输出以下 JSON 格式,不要有其他文字:
{{"tool_call": {{"name": "函数名", "arguments": {{参数键值对}}}}}}
如果不需要调用工具,直接回答。
用户:{user_message}"""
# 使用示例
prompt = build_tool_prompt(
"北京今天天气怎么样?",
tools=[{"name": "get_weather", "description": "查询天气", "parameters": {"location": "string"}}]
)
Step 4:实现容错的 JSON 解析
import re
import json
def parse_tool_call(response_text: str) -> dict | None:
"""容错解析,处理模型输出 JSON 不标准的情况"""
# 方法 1:直接解析
try:
return json.loads(response_text.strip())
except json.JSONDecodeError:
pass
# 方法 2:从文本中提取 JSON 块
json_pattern = r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}'
matches = re.findall(json_pattern, response_text, re.DOTALL)
for match in matches:
try:
parsed = json.loads(match)
if "tool_call" in parsed or "function" in parsed:
return parsed
except json.JSONDecodeError:
continue
return None # 无法解析,回退到文本处理
Step 5:提升量化级别
# 对于 tool calling 场景,建议 Q5_K_M 以上
ollama pull llama3.1:8b-instruct-q5_K_M
# 或通过 Modelfile 指定 Q8_0 GGUF
cat > Modelfile_q8 << 'MODELEOF'
FROM llama3.1:8b-instruct-q8_0
PARAMETER temperature 0.1
PARAMETER top_p 0.9
MODELEOF
ollama create llama3.1-fc -f Modelfile_q8
预防建议
- 为每个模型维护一份 tool calling 兼容性测试用例,CI 阶段自动验证;切换模型时重跑测试。
- 生产代码中对 tool calling 响应加超时和重试逻辑:若模型没有返回 tool_calls,加入提示后重试一次。
- 对于 tool calling 成功率不稳定的场景,设置 temperature 为 0 或 0.1,减少随机性导致的格式错误。
- 使用 grammar-based sampling(llama-cpp-python 支持)可以强制模型输出合法 JSON,但需要提前定义 grammar 规则。
- 确保 tool 的 JSON schema 严格符合 OpenAI 规范(所有 type 小写,properties 结构完整),避免模型对不规范 schema 的混淆。
- 对于复杂的 multi-tool 场景,先用单工具测试,确认基础工作后再增加复杂度。
常见问答 (FAQ)
Q: Ollama 的 tool calling 和直接用 llama-cpp-python 有什么区别? A: Ollama 在服务层处理 tool calling 逻辑,客户端通过标准 OpenAI 格式交互;llama-cpp-python 需要在应用层自己处理 chat template 和 JSON 解析。Ollama 更简单但灵活性低,llama-cpp-python 更底层但可以精确控制 prompt 格式。
Q: 为什么 tool calling 在 GPT-4o 上 100% 成功,但本地 7B 模型只有 60-70%? A: GPT-4o 的 function calling 经过大规模微调和 RLHF 强化,7B 开源模型的 function calling 训练数据少得多。解决方法:使用专门优化过 function calling 的模型(如 Functionary、xTuner-Function-Calling);或在应用层实现重试和降级逻辑。
Q: parallel tool calls(一次调用多个工具)本地模型支持吗? A: 主流本地模型(llama3.1、qwen2.5)在原生 instruct 版本中支持,但稳定性低于单工具场景。建议先测试单工具稳定后再启用 parallel tool calls,且要对每个 tool call 单独做解析容错。
Q: tool calling 返回的 arguments 有时少了一个字段,有时多了一个,如何处理?
A: 在 tool 执行层加入字段验证:必填字段缺失时用默认值补充,多余字段用 **kwargs 忽略。同时在 prompt 中强调参数的完整性:"必须包含所有 required 字段,不要添加 schema 中没有定义的字段"。
相关阅读
- Chat template 不匹配导致输出全是乱码
- Ollama Modelfile 里的 SYSTEM prompt 被忽略
- 本地模型输出在 token 中间被截断
- Cursor MCP server 连接不上
- Claude MCP server 断连
标签: #local-llm #ollama #排查