使用 OpenAI SDK 的 Ollama API 工具呼叫範例
工具呼叫讓模型要求你的程式碼執行函式,然後在回答中使用結果。下方提供使用 OpenAI SDK 針對託管端點的完整範例,以及串流輸出和導致大多數失敗的常見錯誤。
更新

重點摘要
- 將你的函式以 JSON 格式放在 tools 中;模型會回傳 tool_calls 而非文字。
- 你執行函式,將結果以 role tool 訊息附加,然後再次呼叫 API。
- 務必謹慎地解析參數,因為模型可能會回傳格式錯誤或非預期的 JSON。
- 串流輸出的工具呼叫以片段形式到達,你需要透過 index 將它們拼接起來。
迴圈的外觀
如果你正在尋找可針對託管端點運作的 Ollama API 工具呼叫範例,其格式為標準的 OpenAI 格式。你在 tools 陣列中描述你的函式。模型會以文字回答,或回傳一個 tool_calls 清單,列出函式名稱及其參數。模型不會執行任何程式碼。你的程式碼負責執行函式、將輸出結果回傳,然後模型會撰寫最終的回應。
這使得工具使用的一次往返需要兩個 API 請求:
- 傳送使用者訊息和工具定義。
- 接收帶有
tool_calls的 assistant 訊息。在本地執行每個呼叫。 - 傳送原始訊息、assistant 訊息,以及每個呼叫對應的一則
role: "tool"訊息及其結果。 - 接收最終的文字回答。
範例使用 Base URL https://api.ollamaapi.com/v1 和模型 ID uncensored。端點是託管服務,因此金鑰來自 API 金鑰頁面。如果你更改 Base 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 帶有 delta.tool_calls 條目,其中包含 index。一個 index 的第一個片段包含呼叫 ID 和函式名稱。後續片段會新增 arguments 字串的切片。你按 index 拼接切片,並在串流結束後才解析 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 進行驗證,並在失敗時將錯誤以工具訊息回傳。模型通常會在下一輪自動修正。將迴圈次數限制在幾次以內,以免頑固的錯誤無限執行。
描述過於模糊
模型透過閱讀函式名稱和描述來選擇函式。像 「取得資料」 這樣的描述讓它無從下手。說明函式的作用、輸入的含義,並在參數描述中包含範例值。
同時工具過多
每個工具定義在每個請求中都計為輸入 token。每 1M 輸入 token 為 $0.15,十個各 150 token 的工具會增加 1,500 個 token,即每次請求 $0.000225,雖然金額小但在大量時並非零。更重要的是,長清單會降低選擇的可靠性。僅提供與當前步驟相關的工具。
工具結果過大
你回傳的任何內容都會作為輸入傳回。將大型資料庫列或網頁修剪為模型所需的欄位。上下文視窗總共為 100,000 個 token,因此非常大的結果可能會擠佔對話空間。
將工具與 JSON 模式混合
JSON 模式(response_format 搭配 json_object)可用,但請用於最終回答,而非用於希望模型呼叫函式的輪次。在依賴它之前,請在自己的提示詞上測試這種組合。
從本地 Ollama 遷移及檢查事項
如果你針對本地 Ollama 伺服器建立了第一個工具迴圈,幾乎沒有變化。將 Base 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,以 delta 串流輸出,並將結果以 role 為 tool 的訊息傳回。
為什麼工具呼叫後的第二個請求會失敗?
常見原因是缺少助理訊息,或是工具訊息缺少對應的 tool_call_id。先附加包含 tool_calls 的助理回合,然後為每個呼叫傳送一則工具訊息。
模型可以在一個回合中呼叫多個工具嗎?
它可以回傳包含多個條目的清單,因此請遍歷所有條目,並為每個條目傳送一則結果。不要假設清單只有一個項目。
我需要 Ollama 函式庫才能這樣做嗎?
不需要。官方的 OpenAI SDK 就足夠了。將 Base URL 指向端點,並使用 uncensored 模型。