Ví dụ về gọi hàm với SDK OpenAI cho Ollama API
Gọi công cụ cho phép mô hình yêu cầu mã của bạn chạy một hàm và sau đó sử dụng kết quả trong câu trả lời của nó. Dưới đây là một ví dụ hoàn chỉnh với SDK OpenAI cho một endpoint được lưu trữ, bao gồm truyền phát và các lỗi gây ra phần lớn các thất bại.
Cập nhật

Điểm chính
- Gửi hàm của bạn dưới dạng lược đồ JSON trong tools; mô hình sẽ trả về tool_calls thay vì văn bản.
- Bạn chạy hàm, thêm kết quả dưới dạng message role tool và gọi API lại.
- Luôn phân tích cú pháp đối số một cách phòng thủ, vì mô hình có thể trả về JSON không đúng định dạng hoặc không mong đợi.
- Các cuộc gọi công cụ được truyền phát đến dưới dạng các mảnh mà bạn phải ghép nối theo chỉ mục.
Vòng lặp trông như thế nào
Nếu bạn đang tìm kiếm một ví dụ gọi công cụ API Ollama hoạt động với endpoint được lưu trữ, cấu trúc là chuẩn của OpenAI. Bạn mô tả các hàm của mình trong mảng tools. Mô hình sẽ trả lời bằng văn bản hoặc trả về danh sách tool_calls bao gồm tên hàm và các đối số của nó. Mô hình không bao giờ tự chạy bất kỳ thứ gì. Mã của bạn sẽ chạy hàm, gửi kết quả lại và mô hình sẽ viết câu trả lời cuối cùng.
Điều đó tạo ra một lượt đi lại của việc sử dụng công cụ cho hai yêu cầu API:
- Gửi thông báo người dùng và các định nghĩa công cụ.
- Nhận một thông báo assistant với
tool_calls. Chạy từng lệnh gọi cục bộ. - Gửi các thông báo gốc, thông báo assistant và một
role: "tool"thông báo cho mỗi lệnh gọi kèm kết quả của nó. - Nhận câu trả lời văn bản cuối cùng.
Các ví dụ sử dụng URL cơ sở https://api.ollamaapi.com/v1 và model ID uncensored. Endpoint là một dịch vụ được lưu trữ, vì vậy khóa đến từ trang khóa API. Mã tương tự cũng chạy trên máy chủ Ollama cục bộ nếu bạn thay đổi URL cơ sở, khóa và tên mô hình, miễn là mô hình cục bộ hỗ trợ công cụ.
Ví dụ Python hoàn chỉnh
Ví dụ cung cấp cho mô hình một hàm, get_order_status, được hỗ trợ bởi một từ điển nhỏ. Một ứng dụng thực tế sẽ truy vấn cơ sở dữ liệu hoặc dịch vụ nội bộ tại thời điểm đó.
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)Ba chi tiết quan trọng ở đây. Thông báo assistant chứa tool_calls phải được đưa lại vào lịch sử trước các kết quả công cụ, nếu không yêu cầu tiếp theo sẽ bị từ chối hoặc gây nhầm lẫn. Mỗi thông báo công cụ phải mang tool_call_id của lệnh gọi mà nó trả lời. Và yêu cầu thứ hai vẫn phải bao gồm tools, vì mô hình có thể muốn gọi một hàm khác sau khi thấy kết quả đầu tiên.
Yêu cầu tương tự với curl
Kiểm tra với curl loại bỏ SDK khỏi bối cảnh, điều này giúp ích khi bạn không chắc liệu vấn đề nằm ở mã của bạn hay ở yêu cầu.
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"
}'Lời phản hồi thành công đầu tiên có finish_reason được đặt thành tool_calls, content rỗng hoặc null và mảng tool_calls. Trường arguments là một chuỗi chứa JSON, không phải đối tượng lồng nhau, đó là lý do tại sao ví dụ Python gọi json.loads.
Trường tool_choice kiểm soát mức độ mạnh mẽ của yêu cầu. auto cho phép mô hình tự quyết định. Truyền một đối tượng đặt tên một hàm yêu cầu chính xác hàm đó. Sử dụng auto trừ khi bạn có lý do, vì ép buộc một lệnh gọi trên một thông báo không cần nó sẽ tạo ra các đối số được bịa đặt.
Vòng lặp tương tự trong Node
Phiên bản Node sử dụng gói openai chính thức và tuân theo các bước tương tự. Phiên bản này xử lý nhiều hơn một lần gọi mỗi lượt với 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);
}Truyền phát các lệnh gọi công cụ
Với stream: true, phản hồi đến qua SSE và các lệnh gọi công cụ đến từng mảnh. Mỗi chunk mang một mục delta.tool_calls với index. Mảnh đầu tiên cho một index chứa ID lệnh gọi và tên hàm. Các mảnh sau thêm các phần của chuỗi arguments. Bạn nối các phần theo index và chỉ phân tích JSON sau khi stream kết thúc.
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"]))Hãy nhớ rằng một yêu cầu stream giữ vị trí concurrency của nó cho đến khi stream kết thúc. Khóa đầy đủ cho phép 8 yêu cầu cùng lúc và 300 mỗi phút, khóa dùng thử là 2 và 60. Nếu bạn chạy các vòng lặp công cụ cho nhiều người dùng song song, hãy tính mỗi stream đang mở vào con số đầu tiên.
Các lỗi thường gặp và cách khắc phục
Đối số không đúng định dạng hoặc bị thiếu
Mô hình là mô hình trọng số mở, và các mô hình mở đôi khi trả về các đối số không phải JSON hợp lệ, thiếu trường bắt buộc hoặc tạo thêm trường. Hãy bọc json.loads trong khối try, xác thực theo lược đồ của bạn và khi có lỗi, hãy gửi lại lỗi đó dưới dạng thông báo công cụ. Mô hình thường tự sửa lỗi ở lượt tiếp theo. Hãy giới hạn vòng lặp ở vài lần lặp để lỗi cứng đầu không thể chạy mãi mãi.
Mô tả quá mơ hồ
Mô hình chọn một hàm bằng cách đọc tên và mô tả của nó. Một mô tả như "lấy dữ liệu" không cung cấp gì cho nó để làm việc. Hãy nói rõ hàm làm gì, các đầu vào có nghĩa là gì và bao gồm một giá trị ví dụ trong mô tả tham số.
Quá nhiều công cụ cùng lúc
Mỗi định nghĩa công cụ đều tính là các token đầu vào trên mỗi yêu cầu. Ở mức $0.15 cho 1M token đầu vào, mười công cụ mỗi công cụ 150 token sẽ thêm 1.500 token, tương đương $0.000225 mỗi yêu cầu, nhỏ nhưng không bằng không khi ở quy mô lớn. Quan trọng hơn, một menu dài làm cho việc lựa chọn kém tin cậy hơn. Chỉ cung cấp các công cụ liên quan đến bước hiện tại.
Kết quả công cụ quá lớn
Bất cứ thứ gì bạn trả về đều được đưa lại làm đầu vào. Cắt bớt các hàng cơ sở dữ liệu lớn hoặc trang web thành các trường mà mô hình cần. Cửa sổ ngữ cảnh là 100.000 token tổng cộng, vì vậy một kết quả rất lớn có thể lấn át cuộc trò chuyện.
Trộn công cụ với chế độ JSON
Chế độ JSON (response_format với json_object) có sẵn, nhưng hãy sử dụng nó cho câu trả lời cuối cùng, không phải cho các lượt mà bạn muốn mô hình gọi một hàm. Hãy kiểm tra sự kết hợp này trên các prompt của riêng bạn trước khi dựa vào nó.
Chuyển từ Ollama cục bộ và những điều cần kiểm tra
Nếu bạn đã xây dựng vòng lặp công cụ đầu tiên của mình trên máy chủ Ollama cục bộ, hầu như không có gì thay đổi. Thay thế URL cơ sở bằng https://api.ollamaapi.com/v1, sử dụng khóa thực và đặt mô hình thành uncensored. Các tuyến Ollama gốc như /api/chat không tồn tại ở đây. Chỉ POST /v1/chat/completions và GET /v1/models mới có sẵn, vì vậy mã được viết cho API gốc cần lệnh gọi kiểu OpenAI thay thế.
Trước khi phát hành, hãy chạy danh sách kiểm tra ngắn này:
- Xác nhận rằng khóa hoạt động với
GET /v1/models. - Ghi lại tiêu đề
X-Request-Idcho mọi yêu cầu, để một lần chạy không thành công có thể được truy vết. - Theo dõi
X-Balance-USDtrong các tiêu đề phản hồi nếu bạn chạy các vòng lặp công cụ không giám sát. - Xử lý HTTP 429: chờ khi
rate_limitedhoặcconcurrency, và tuân thủRetry-After: 5khi quá tải. - Đặt giới hạn cứng trên số lần lặp vòng lặp.
Vòng lặp công cụ tạo ra ít nhất hai yêu cầu cho mỗi câu hỏi của người dùng, vì vậy chi phí tăng theo đó. Ví dụ, 2.000 vòng lặp với 1.200 token đầu vào và 150 token đầu ra cho mỗi yêu cầu, hai yêu cầu mỗi lần, sẽ thành 4.000 yêu cầu, 4,8 triệu token đầu vào và 0,6 triệu token đầu ra. Điều đó là $0,72 cộng $0,30, hoặc tổng cộng $1,02. Trang giá cả có các tùy chọn nạp tiền, và tài liệu liệt kê các trường yêu cầu được hỗ trợ. Người dùng mới có thể thử tất cả những điều này với tín dụng dùng thử $0,50, không cần thẻ.
Hướng dẫn khác
Hỏi đáp
Endpoint có hỗ trợ gọi hàm không?
Có. Nó chấp nhận công cụ và tool_choice, trả về tool_calls, truyền phát chúng dưới dạng delta và nhận kết quả lại dưới dạng các thông báo với role tool.
Tại sao yêu cầu thứ hai lại thất bại sau khi gọi hàm?
Nguyên nhân thường gặp là thiếu một thông báo assistant hoặc một thông báo công cụ không có tool_call_id phù hợp. Hãy thêm lượt assistant chứa tool_calls trước, sau đó là một thông báo công cụ cho mỗi lệnh gọi.
Mô hình có thể gọi nhiều công cụ trong một lượt không?
Mô hình có thể trả về danh sách với nhiều hơn một mục, vì vậy bạn hãy lặp qua tất cả và gửi một kết quả cho mỗi mục. Đừng giả định danh sách chỉ có một mục.
Tôi có cần thư viện Ollama cho việc này không?
Không. Các SDK OpenAI chính thức là đủ. Hãy trỏ URL cơ sở đến endpoint và sử dụng mô hình uncensored.
Chỉ cần điền biểu mẫu là xong
Tạo tài khoản, sao chép khóa, thay đổi URL cơ sở. Đó là toàn bộ quá trình thiết lập.