TH ▾

ตัวอย่างการเรียกใช้เครื่องมือ Ollama API ด้วย OpenAI SDK API Ollama ด้วย SDK OpenAI

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

อัปเดต

ภาพประกอบโมเดลมอบหมายงานให้ฟังก์ชันและรับผลลัพธ์ในลูป

ประเด็นสำคัญ

  1. ส่งฟังก์ชันของคุณเป็น JSON schema ใน tools; โมเดลจะตอบกลับด้วย tool_calls แทนข้อความ
  2. คุณรันฟังก์ชัน เพิ่มผลลัพธ์เป็นข้อความ role tool และเรียกใช้ API อีกครั้ง
  3. พาร์สอาร์กิวเมนต์แบบป้องกันข้อผิดพลาดเสมอ เพราะโมเดลอาจคืนค่า JSON ที่ผิดรูปแบบหรือคาดไม่ถึง
  4. การเรียกใช้ฟังก์ชันแบบสตรีมมิงมาถึงเป็นชิ้นส่วนที่คุณต้องประกอบเข้าด้วยกันตามดัชนี

ลักษณะของลูปเป็นอย่างไร

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

นั่นคือการเดินทางไปกลับหนึ่งรอบของการใช้เครื่องมือสองคำขอ API:

  1. ส่งข้อความผู้ใช้และคำจำกัดความของเครื่องมือ
  2. รับข้อความผู้ช่วยที่มี tool_calls รันแต่ละการเรียกในเครื่อง
  3. ส่งข้อความต้นฉบับ ข้อความผู้ช่วย และข้อความ role: "tool" หนึ่งข้อความต่อการเรียกพร้อมผลลัพธ์
  4. รับคำตอบข้อความสุดท้าย

ตัวอย่างใช้ 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 นั่นคือการตั้งค่าทั้งหมด

รับคีย์ APIอ่านเอกสาร