“查一下拿铁多少钱”听起来像是在让 AI 调接口。实际发生的事分成两段:模型先返回一个结构化的工具请求;本地程序检查请求后,才决定要不要真的调用函数。模型会提议调用,执行权仍在你的程序手里。

我用 Ollama 和本机 Qwen3.8-27B 做了一个只读菜单例子:查已知商品时模型发出 get_drink_price 请求;查菜单里没有的饮品时,它不调用工具;要求改价时,它也没有越过只读能力。示例用虚构菜单,不连网、不写文件、不改商品价格。

Function Calling 是什么

Ollama 把 Tool Calling(也叫 Function Calling)称为模型调用工具并把结果纳入回复的能力。请求中会附上工具名称、用途和参数结构;模型可以返回普通文字,也可以返回 tool_calls,其中写明它建议调用的函数和参数。Ollama 工具调用文档

一轮完整调用可以看成四步:

  1. 程序把用户消息和工具定义发给模型。
  2. 模型返回工具名和参数,例如 get_drink_price({"drink":"拿铁"})。
  3. 程序检查工具名、参数和权限,再执行本地函数。
  4. 程序把函数结果作为 tool 消息发回模型,由模型组织成用户能读懂的答复。

因此,工具定义不是函数实现,也不是权限控制。只把工具名告诉模型,并不会自动访问你的数据库;真正的访问发生在你写的分发代码里。反过来,如果分发器无条件执行模型返回的任意名称和参数,风险也在这段代码里。

准备本机环境

项目 本次环境
Ollama 0.35.1,http://127.0.0.1:11434
模型 qwen3.8-27b:latest,已在本机安装,约 16GB
运行脚本 Python 3 标准库,无需安装第三方包
练习包 下载 Function Calling 练习包

Qwen3.8 的 Ollama 模型页标注支持工具调用;这篇教程只验证它在本地菜单样例中的表现,不据此推断它在其他模型、复杂工具或业务系统中的准确率。Qwen3.8 模型页

下载并解压练习包后,在解压目录运行:

ollama --version
ollama list
python3 tool_call_demo.py --self-test
python3 tool_call_demo.py

ollama list 里需要有 qwen3.8-27b:latest。如果尚未安装该模型,先参考 Ollama 模型页中的本机运行命令;模型文件较大,下载前确认磁盘空间。

脚本只向 127.0.0.1:11434/api/chat 发请求。菜单固定为拿铁 26 元、美式 22 元、燕麦拿铁 30 元;工具只有查询,没有改价或下单能力。

先看模型返回的工具请求

脚本把工具参数声明成一个 JSON Schema:drink 是必填字符串,只能是三种菜单饮品之一。用户问“帮我查一下拿铁多少钱”后,本机模型实际返回了:

{
  "name": "get_drink_price",
  "arguments": {"drink": "拿铁"}
}

这一步还没有查询菜单。脚本随后把工具名交给本地分发器,分发器确认它在允许列表中,并检查参数只能包含 drink。检查通过后才读取 Python 字典里的价格,得到:

{"ok": true, "drink": "拿铁", "price_yuan": 26}

程序再把这个结果作为 tool 消息放回对话,模型第二次回复“拿铁的价格是 26 元”。Ollama 的 Chat API 用 assistant.tool_calls 和 role: tool 消息表示这段往返。API 消息格式

常见失败:逐层排查

模型没有发起调用

模型可能直接回答、拒绝调用,或没识别到该用哪个工具。先检查请求里是否真的传了 tools,工具说明是否清楚,函数用途是否和用户问题匹配。若任务明确要求必须查数据,可以在应用流程中识别“没有工具请求”并提示补充信息;不要把模型的猜测当成函数结果。

本次问菜单没有的“青柚气泡美式”,模型没有发起调用,而是列出可查的三种饮品并说明该商品不在范围内。这个结果符合样例要求,但一次回答不代表模型永远会拒绝越界请求,所以分发器仍需要独立校验。

工具名或参数不合法

工具名是模型生成的内容。分发器要用固定映射表查找函数,不能直接 eval() 或动态导入模型给出的名称。参数也要做程序校验:必填值是否存在、类型是否正确、是否有多余字段、枚举值是否在业务允许范围内。

练习脚本带有 5 条断言:合法查询成功;未知工具、缺少参数、多余参数、菜单外饮品都会被拒绝。JSON Schema 能帮助模型整理参数,但真正接入业务时仍要在程序端复核。

工具执行了,模型却没读懂结果

常见原因是没有把工具结果按正确格式接回对话,或者没有再次请求模型。单次调用流程执行一次回传即可;模型需要多个工具时,程序必须循环处理 tool_calls,每个调用结果都对应回传。Ollama 官方文档分别展示了单次、并行和多轮工具调用。多轮工具调用示例

循环要有上限,处理未知函数、超时和工具错误时也要返回明确结果。否则模型可能反复请求同一个工具,或者因缺少结果而编造结论。

把“能调用”误当成“有权限”

本次再问“把拿铁价格改成 1 元”,程序提供的工具里没有修改函数。模型回答自己无法改价,工具调用次数为 0。即使模型猜对了要修改的字段,当前程序也没有可执行的写入路径。

接真实系统时,把读取和写入拆成不同工具;写入工具再做用户身份验证、输入校验、审批或二次确认、审计记录。不要只靠模型说“我不会修改”来保护数据。

本机实测结果

运行环境为 Ollama 0.35.1、Qwen3.8-27B、本机 API。一次模型实测中,三个问题共发出 4 次 Chat 请求:

问题 工具请求 结果
查拿铁价格 get_drink_price(drink="拿铁") 程序读取虚构菜单,返回 26 元;模型复述正确
查青柚气泡美式 无 模型说明不在可查范围,没有编造价格
把拿铁改成 1 元 无 模型说明没有修改权限,菜单未变化

已缓存模型下,本次脚本端到端约 20 秒;查询拿铁的模型往返约 10.4 秒。耗时会随机器、模型是否已加载和 Ollama 版本变化。参数分发器的 5 条自检全部通过。原始请求摘要和结果保存在练习包的 model-runs.json 与 实测记录.md。

这只是一个本地模型、一个简单函数、一次实测。真实商品价格、库存、订单和客户信息需要连接经授权的数据源,并另外验证权限、错误恢复和并发行为。

开始时容易踩的坑

  • 以为模型返回函数名就已经执行了。 要在应用中找到实际分发器,确认它执行了哪个本地函数。
  • 相信模型生成的参数一定符合 schema。 接口边界仍要检查必填、类型、范围和额外字段。
  • 只测试正常输入。 再测不存在的商品、缺少字段、越权写入、工具超时和返回空值。
  • 给模型暴露过宽能力。 一个查询问题只给查询工具;不要顺便开放文件、命令、支付、发消息等无关操作。
  • 忘记把工具结果回传。 模型拿不到结果,就只能继续询问或猜测。

工具清单

工具 用途
Ollama 在本机启动模型并提供 Chat API
Qwen3.8 本次测试的本机模型

本文和练习包使用虚构菜单,没有访问线上商店、真实数据库或用户资料。

接下来可以做什么