使用 OpenAI SDK 的 Ollama API 函数调用示例
函数调用允许模型请求你的代码运行函数,然后在回答中使用结果。下面是一个针对托管接口的完整 OpenAI SDK 示例,包含流式输出以及导致大多数失败的常见错误。
已更新

要点
- 将函数以 JSON 架构的形式传入 tools;模型会返回 tool_calls 而非文本。
- 你运行该函数,将结果作为 role 为 tool 的消息追加,然后再次调用 API。
- 始终防御性地解析参数,因为模型可能返回格式错误或意外的 JSON。
- 流式输出的工具调用以片段形式到达,你必须按索引将它们拼接起来。
循环的样子
如果你在寻找针对托管接口的 ollama api 工具调用示例,其格式遵循标准的 OpenAI 规范。你在 tools 数组中描述你的函数。模型要么以文本形式回答,要么返回一个 tool_calls 列表,其中包含函数名称及其参数。模型不会执行任何操作。你的代码执行函数,将输出发送回去,然后模型生成最终回复。
这使得一次工具使用涉及两个 API 调用:
- 发送用户消息和工具定义。
- 接收带有
tool_calls的 assistant 消息。在本地运行每次调用。 - 发送原始消息、assistant 消息,以及每条调用对应的一条
role: "tool"消息及其结果。 - 接收最终文本答案。
示例使用基础 URL https://api.ollamaapi.com/v1 和模型 ID uncensored。该端点是一项托管服务,因此密钥来自 API 密钥页面。只要你更改基础 URL、密钥和模型名称,且本地模型支持工具,相同的代码也可以针对本地 Ollama 服务器运行。
完整的 Python 示例
该示例向模型提供一个函数 get_order_status,由一个小字典支持。实际应用中,此时会查询数据库或内部服务。
import json
from openai import OpenAI
client = OpenAI(base_url="https://api.ollamaapi.com/v1", api_key="YOUR_KEY")
ORDERS = {"A1001": "shipped", "A1002": "processing", "A1003": "delivered"}
def get_order_status(order_id: str) -> str:
return ORDERS.get(order_id, "not found")
tools = [{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Look up the status of an order by its ID.",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "Order ID such as A1001"}
},
"required": ["order_id"],
},
},
}]
messages = [{"role": "user", "content": "Where is order A1002?"}]
first = client.chat.completions.create(
model="uncensored",
messages=messages,
tools=tools,
tool_choice="auto",
)
msg = first.choices[0].message
if msg.tool_calls:
messages.append(msg) # keep the assistant turn with its tool_calls
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = get_order_status(**args)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": result,
})
final = client.chat.completions.create(
model="uncensored",
messages=messages,
tools=tools,
)
print(final.choices[0].message.content)
else:
print(msg.content)这里有三个细节很重要。包含 tool_calls 的 assistant 消息必须在工具结果之前放回历史记录中,否则下一次请求会被拒绝或混淆。每条 tool 消息必须携带它所回答调用的 tool_call_id。并且第二次请求仍应包含 tools,因为模型可能在看到第一个结果后希望调用另一个函数。
使用 curl 的相同请求
使用 curl 测试可以排除 SDK 的影响,这有助于当你不确定问题是出在你的代码中还是请求中时。
curl https://api.ollamaapi.com/v1/chat/completions \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "uncensored",
"messages": [{"role": "user", "content": "Where is order A1002?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Look up the status of an order by its ID.",
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"]
}
}
}],
"tool_choice": "auto"
}'成功的初次回复将 finish_reason 设置为 tool_calls,content 为空或 null,并包含一个 tool_calls 数组。arguments 字段是包含 JSON 的字符串,而不是嵌套对象,这就是 Python 示例调用 json.loads 的原因。
tool_choice 字段控制请求的强制程度。auto 让模型自行决定。传递一个包含函数名的对象会请求特定的函数。除非有理由,否则使用 auto,因为在不需要调用的消息上强制调用会产生虚构的参数。
Node 中的相同循环
Node 版本使用官方 openai 包并遵循相同的步骤。此版本使用 Promise.all 处理一次回复中的多次调用。
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.ollamaapi.com/v1",
apiKey: process.env.API_KEY,
});
const handlers = {
get_order_status: async ({ order_id }) =>
({ A1001: "shipped", A1002: "processing" })[order_id] ?? "not found",
};
const tools = [{
type: "function",
function: {
name: "get_order_status",
description: "Look up the status of an order by its ID.",
parameters: {
type: "object",
properties: { order_id: { type: "string" } },
required: ["order_id"],
},
},
}];
const messages = [{ role: "user", content: "Where is order A1001?" }];
const first = await client.chat.completions.create({
model: "uncensored", messages, tools,
});
const msg = first.choices[0].message;
if (msg.tool_calls?.length) {
messages.push(msg);
const results = await Promise.all(msg.tool_calls.map(async (call) => {
const args = JSON.parse(call.function.arguments);
const out = await handlers[call.function.name](args);
return { role: "tool", tool_call_id: call.id, content: String(out) };
}));
messages.push(...results);
const final = await client.chat.completions.create({
model: "uncensored", messages, tools,
});
console.log(final.choices[0].message.content);
} else {
console.log(msg.content);
}流式工具调用
使用 stream: true 时,回复通过 SSE 到达,工具调用以片段形式到达。每个 chunk 包含一个带有 index 的 delta.tool_calls 条目。一个索引的第一个片段包含调用 ID 和函数名。后续片段添加 arguments 字符串的切片。按索引拼接切片,并在流结束后解析 JSON。
stream = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Where is order A1003?"}],
tools=tools,
stream=True,
)
calls = {}
for chunk in stream:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
for part in (delta.tool_calls or []):
slot = calls.setdefault(part.index, {"id": "", "name": "", "args": ""})
if part.id:
slot["id"] = part.id
if part.function and part.function.name:
slot["name"] = part.function.name
if part.function and part.function.arguments:
slot["args"] += part.function.arguments
for slot in calls.values():
print(slot["name"], json.loads(slot["args"]))请记住,流式请求会一直占用其并发槽位,直到流结束。完整密钥允许同时 8 个请求和每分钟 300 个请求,试用密钥允许 2 个和 60 个。如果你为许多用户并行运行工具循环,请将每个打开的流计入第一个数字。
常见陷阱及修复方法
格式错误或缺失的参数
该模型是一个开放权重模型,开放模型有时会返回不是有效 JSON 的参数,遗漏必填字段,或虚构一个额外字段。将 json.loads 包裹在 try 块中,根据你的 schema 进行验证,失败时将错误作为 tool 消息发送。模型通常在下一轮自行纠正。将循环限制在几次迭代内,以防止顽固的失败无限运行。
描述过于模糊
模型通过阅读函数的名称和描述来选择函数。诸如“获取数据”这样的描述让它无从下手。说明函数做什么、输入的含义,并在参数描述中包含示例值。
同时工具过多
每个工具定义在每次请求中都算作输入 token。每 1M 输入 token 价格为 $0.15,十个每个 150 token 的工具会增加 1,500 个 token,即每次请求 $0.000225,虽然金额小但在大量使用时不为零。更重要的是,长长的菜单会让选择变得不可靠。只提供与当前步骤相关的工具。
工具结果过大
你返回的任何内容都会作为输入反馈。将大型数据库行或网页修剪为模型所需的字段。上下文窗口总共为 100,000 个 token,因此非常大的结果可能会挤占对话空间。
将工具与 JSON 模式混合使用
JSON 模式(response_format 配合 json_object)可用,但请将其用于最终答案,而不是用于希望模型调用函数的轮次。在依赖它之前,请在自己的提示词上测试该组合。
从本地 Ollama 迁移及检查事项
如果你针对本地 Ollama 服务器构建了第一个工具循环,几乎没有什么变化。将基础 URL 替换为 https://api.ollamaapi.com/v1,使用真实密钥,并将模型设置为 uncensored。原生 Ollama 路由如 /api/chat 在此处不存在。只有 POST /v1/chat/completions 和 GET /v1/models 可用,因此为原生 API 编写的代码需要使用 OpenAI 风格的调用。
在发布之前,运行以下简短检查清单:
- 确认密钥在
GET /v1/models下有效。 - 为每次调用记录
X-Request-Id标头,以便追踪失败的运行。 - 如果你无人值守地运行工具循环,请监控响应标头中的
X-Balance-USD。 - 处理 HTTP 429:在
rate_limited或concurrency时等待,并在过载时遵守Retry-After: 5。 - 设置循环迭代的硬限制。
工具循环对每个用户问题至少发起两次请求,因此成本会随之增加。例如,2,000 次循环,每次请求包含 1,200 个输入 token 和 150 个输出 token,每次循环两次请求,总计 4,000 次请求,4.8M 输入 token 和 0.6M 输出 token。这相当于 $0.72 加上 $0.30,总计 $1.02。定价页面 提供了充值选项,文档 列出了支持的请求字段。新用户可以使用 $0.50 的试用额度来体验所有这些功能,无需绑定银行卡。
更多指南
问答
该接口是否支持函数调用?
是的。它接受 tools 和 tool_choice 参数,返回 tool_calls,以增量方式流式传输,并以 role 为 tool 的消息形式接收结果。
为什么工具调用后的第二次请求会失败?
常见原因是缺少 assistant 消息,或者 tool 消息缺少匹配的 tool_call_id。请先追加包含 tool_calls 的 assistant 轮次,然后为每个调用发送一条 tool 消息。
模型能否在一次轮次中调用多个工具?
模型可以返回包含多个条目的列表,因此请遍历所有条目,并为每个条目发送一条结果。不要假设列表只包含一个条目。
为此需要 Ollama 库吗?
不需要。官方 OpenAI SDK 即可。将基础 URL 指向该端点,并使用 uncensored 模型。