ตัวอย่างการเรียกใช้เครื่องมือ Ollama API ด้วย OpenAI SDK API Ollama ด้วย SDK OpenAI
การเรียกใช้ฟังก์ชันช่วยให้โมเดลสั่งให้โค้ดของคุณรันฟังก์ชันแล้วใช้ผลลัพธ์นั้นในคำตอบของโมเดล ด้านล่างคือตัวอย่างที่สมบูรณ์พร้อม OpenAI SDK ที่ทำงานกับ endpoint ที่โฮสต์ไว้ รวมถึงสตรีมมิงและข้อผิดพลาดที่ทำให้ล้มเหลวบ่อยที่สุด
อัปเดต

ประเด็นสำคัญ
- ส่งฟังก์ชันของคุณเป็น JSON schema ใน tools; โมเดลจะตอบกลับด้วย tool_calls แทนข้อความ
- คุณรันฟังก์ชัน เพิ่มผลลัพธ์เป็นข้อความ role tool และเรียกใช้ API อีกครั้ง
- พาร์สอาร์กิวเมนต์แบบป้องกันข้อผิดพลาดเสมอ เพราะโมเดลอาจคืนค่า JSON ที่ผิดรูปแบบหรือคาดไม่ถึง
- การเรียกใช้ฟังก์ชันแบบสตรีมมิงมาถึงเป็นชิ้นส่วนที่คุณต้องประกอบเข้าด้วยกันตามดัชนี
ลักษณะของลูปเป็นอย่างไร
หากคุณกำลังมองหาตัวอย่างการเรียกใช้เครื่องมือ Ollama API ด้วย OpenAI SDK Ollama API ที่ใช้งานได้กับ endpoint ที่โฮสต์ไว้ โครงสร้างจะเป็นไปตามมาตรฐานของ OpenAI คุณอธิบายฟังก์ชันของคุณในอาร์เรย์ tools โมเดลจะตอบกลับเป็นข้อความหรือส่งรายการ tool_calls ที่ระบุชื่อฟังก์ชันและอาร์กิวเมนต์ โมเดลจะไม่รันอะไรเลย โค้ดของคุณจะเป็นผู้รันฟังก์ชัน ส่งผลลัพธ์กลับ และโมเดลจะเขียนคำตอบสุดท้าย
นั่นคือการเดินทางไปกลับหนึ่งรอบของการใช้เครื่องมือสองคำขอ API:
- ส่งข้อความผู้ใช้และคำจำกัดความของเครื่องมือ
- รับข้อความผู้ช่วยที่มี
tool_callsรันแต่ละการเรียกในเครื่อง - ส่งข้อความต้นฉบับ ข้อความผู้ช่วย และข้อความ
role: "tool"หนึ่งข้อความต่อการเรียกพร้อมผลลัพธ์ - รับคำตอบข้อความสุดท้าย
ตัวอย่างใช้ base URL https://api.ollamaapi.com/v1 และ model ID uncensored เอนด์พอยต์เป็นบริการที่โฮสต์ไว้ ดังนั้นคีย์มาจากหน้า API key โค้ดเดียวกันยังทำงานกับเซิร์ฟเวอร์ Ollama ในเครื่องได้ หากเปลี่ยน base URL, คีย์ และชื่อโมเดล ตราบใดที่โมเดลในเครื่องรองรับ tools
ตัวอย่าง Python แบบสมบูรณ์
ตัวอย่างมอบฟังก์ชันเดียวให้โมเดล คือ get_order_status ซึ่งทำงานกับ dictionary เล็กๆ แอปพลิเคชันจริงจะ query ฐานข้อมูลหรือบริการภายในในจุดนั้น
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)สามรายละเอียดสำคัญที่นี่ ข้อความของ assistant ที่มี tool_calls ต้องถูกส่งกลับเข้าไปใน history ก่อนที่ tool results จะถูกส่งกลับ มิฉะนั้นคำขอถัดไปจะถูกปฏิเสธหรือทำให้สับสน ข้อความของ 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 ไม่ใช่ object แบบซ้อนกัน นี่คือเหตุผลที่ตัวอย่าง Python เรียกใช้ json.loads
ฟิลด์ tool_choice ควบคุมความเข้มแข็งของคำขอ auto ให้โมเดลตัดสินใจ การส่ง object ที่ระบุชื่อฟังก์ชันจะขอให้เรียกฟังก์ชันนั้นโดยเฉพาะ ใช้ 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 และ tool calls จะมาถึงเป็นชิ้นส่วน แต่ละชิ้นมี entry delta.tool_calls พร้อม index ชิ้นแรกสำหรับ index หนึ่งๆ จะเก็บ call ID และชื่อฟังก์ชัน ชิ้นส่วนต่อมาจะเพิ่ม slice ของสตริง arguments คุณนำ slice มาต่อกันตาม index และทำการ parse 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 หากคุณรัน tool loops สำหรับผู้ใช้หลายคนแบบขนาน ให้นับแต่ละสตรีมที่เปิดอยู่เข้ากับตัวเลขแรก
จุดบกพร่องและวิธีแก้ไข
อาร์กิวเมนต์ที่ผิดรูปแบบหรือหายไป
โมเดลเป็นโมเดลแบบเปิดน้ำหนัก และโมเดลแบบเปิดบางครั้งส่งอาร์กิวเมนต์ที่ไม่ใช่ JSON ที่ถูกต้อง, ขาดฟิลด์ที่จำเป็นหรือสร้างฟิลด์เพิ่มขึ้นมา ห่อ json.loads ด้วย try block, ตรวจสอบกับ schema ของคุณ และเมื่อล้มเหลว ให้ส่งข้อผิดพลาดกลับเป็น tool message โดยปกติโมเดลจะแก้ไขตัวเองในรอบถัดไป จำกัดลูปไว้ที่ไม่กี่รอบเพื่อให้การล้มเหลวที่ดื้อรั้นไม่สามารถรันได้ตลอดไป
คำอธิบายที่คลุมเครือเกินไป
โมเดลเลือกฟังก์ชันโดยอ่านชื่อและคำอธิบาย คำอธิบายเช่น "ดึงข้อมูล" ทำให้โมเดลไม่มีข้อมูลให้ทำงาน ให้ระบุสิ่งที่ฟังก์ชันทำ ความหมายของอินพุต และรวมค่าตัวอย่างในคำอธิบายพารามิเตอร์
เครื่องมือมากเกินไปในครั้งเดียว
คำจำกัดความของ tool แต่ละรายการนับเป็น input tokens ในทุกคำขอ ที่ $0.15 ต่อ 1M input tokens เครื่องมือสิบรายการขนาด 150 tokens แต่ละตัวเพิ่ม 1,500 tokens ซึ่งคือ $0.000225 ต่อคำขอ น้อยแต่ไม่ศูนย์เมื่อมีปริมาณมาก สำคัญกว่านั้น เมนูที่ยาวทำให้การเลือกมีความน่าเชื่อถือลดลง ควรเสนอเฉพาะ tools ที่เกี่ยวข้องกับขั้นตอนปัจจุบัน
ผลลัพธ์เครื่องมือที่มีขนาดใหญ่เกินไป
สิ่งที่คุณส่งกลับจะถูกป้อนกลับเป็น input ตัด database rows หรือ web pages ขนาดใหญ่ให้เหลือเฉพาะฟิลด์ที่โมเดลต้องการ หน้าต่างบริบทมีขนาด 100,000 tokens รวมทั้งหมด ดังนั้นผลลัพธ์ขนาดใหญ่มากอาจเบียดบังการสนทนา
การผสมเครื่องมือกับโหมด 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-style แทน
ก่อนที่คุณจะส่งมอบ ให้รันรายการตรวจสอบสั้นๆ นี้:
- ยืนยันว่าคีย์ใช้งานได้กับ
GET /v1/models - บันทึกหัวเรื่อง
X-Request-Idทุกครั้งที่เรียก เพื่อให้สามารถติดตามการเรียกที่ล้มเหลวได้ - ตรวจสอบ
X-Balance-USDในหัวเรื่องของการตอบกลับ หากคุณรันลูปเครื่องมือแบบไม่มีการดูแล - จัดการ HTTP 429: รอ
rate_limitedหรือconcurrencyและเคารพRetry-After: 5เมื่อโหลดเกิน - ตั้งค่าขีดจำกัดสูงสุดสำหรับจำนวนรอบการวนซ้ำ
tool loop ทำอย่างน้อยสองคำขอต่อคำถามของผู้ใช้ ดังนั้นค่าใช้จ่ายจะเพิ่มขึ้นตามจำนวนนั้น ตัวอย่างเช่น 2,000 loops พร้อม input tokens 1,200 และ output tokens 150 ต่อคำขอ สองคำขอต่อรอบ รวมเป็น 4,000 คำขอ, 4.8M input tokens และ 0.6M output tokens ซึ่งคิดเป็น $0.72 บวก $0.30 หรือรวม $1.02 หน้า ราคา มีตัวเลือก top-up และ docs ระบุฟิลด์คำขอที่รองรับ ผู้ใช้ใหม่สามารถลองทั้งหมดนี้ได้ด้วยเครดิตทดลองใช้ $0.50 ซึ่งไม่ต้องใช้บัตร
คู่มือเพิ่มเติม
ถาม-ตอบ
เอนด์พอยต์รองรับการเรียกใช้ฟังก์ชันหรือไม่?
ใช่ รองรับ tools และ tool_choice, คืนค่า tool_calls, สตรีมพวกมันเป็นโทเคนเดลต้า และรับผลลัพธ์กลับเป็นข้อความที่มี role tool
ทำไมคำขอที่สองจึงล้มเหลวหลังจากการเรียกใช้เครื่องมือ?
สาเหตุทั่วไปคือการขาดข้อความผู้ช่วย หรือข้อความเครื่องมือที่ไม่มี tool_call_id ที่ตรงกัน ให้เพิ่มเทิร์นผู้ช่วยที่มี tool_calls ก่อน จากนั้นข้อความเครื่องมือหนึ่งข้อความต่อการเรียก
โมเดลสามารถเรียกใช้เครื่องมือหลายตัวในเทิร์นเดียวได้หรือไม่?
สามารถส่งคืนรายการที่มีรายการมากกว่าหนึ่งรายการ ดังนั้นให้วนลูปทั้งหมดและส่งผลลัพธ์หนึ่งรายการสำหรับแต่ละรายการ อย่าสันนิษฐานว่ารายการมีรายการเดียว
ฉันต้องการไลบรารี Ollama สำหรับสิ่งนี้หรือไม่?
ไม่ SDK ทางการของ OpenAI เพียงพอ ชี้ Base URL ไปที่เอนด์พอยต์และใช้โมเดล uncensored
คีย์ของคุณอยู่ห่างแค่แบบฟอร์มเดียว
สร้างบัญชี คัดลอกคีย์ และเปลี่ยน base URL นั่นคือการตั้งค่าทั้งหมด