跳至主要內容

Tools 與 Function Calling

讓模型呼叫你定義的函式:工具定義、tool_choice、完整三步流程與串流處理。

Tools 與 Function Calling

Tool calling 讓模型能使用外部能力。模型本身不會執行工具,它只會告訴你「請用這些參數呼叫這個函式」;你執行後把結果送回去,模型再據此產生最終回覆。

Inference 把各家供應商的工具介面統一成 OpenAI 格式,所以同一份工具定義可以直接套用在 OpenAI、Anthropic 與 Gemini 模型上。

工具定義

{
  "type": "function",
  "function": {
    "name": "get_weather",
    "description": "取得指定城市的目前天氣。",
    "parameters": {
      "type": "object",
      "properties": {
        "city": { "type": "string", "description": "城市名稱,例如「台北」" },
        "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
      },
      "required": ["city"]
    }
  }
}

description 寫得越清楚,模型判斷得越準確。parameters 使用 JSON Schema。

tool_choice

值行為
"auto"模型自行決定要不要呼叫工具。有 tools 時的預設值。
"none"禁止呼叫工具,只產生文字。
"required"強制至少呼叫一個工具。
{"type": "function", "function": {"name": "get_weather"}}強制呼叫指定工具。

三步流程

第一步:帶著工具送出請求

curl https://api.alphacurve.io/v1/chat/completions \
  -H "Authorization: Bearer $INFERENCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4o",
    "messages": [{ "role": "user", "content": "台北現在天氣如何?" }],
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "取得指定城市的目前天氣。",
        "parameters": {
          "type": "object",
          "properties": { "city": { "type": "string" } },
          "required": ["city"]
        }
      }
    }],
    "tool_choice": "auto"
  }'

第二步:模型回傳 tool_calls

{
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "id": "call_a1b2c3",
            "type": "function",
            "function": {
              "name": "get_weather",
              "arguments": "{\"city\":\"台北\"}"
            }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ]
}

注意 arguments 是 JSON 字串,不是物件,需要自行 parse。

第三步:把執行結果送回去

tool 訊息必須帶 tool_call_id,且要對應到剛才那一筆 id。

{
  "messages": [
    { "role": "user", "content": "台北現在天氣如何?" },
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [{ "id": "call_a1b2c3", "type": "function",
        "function": { "name": "get_weather", "arguments": "{\"city\":\"台北\"}" } }]
    },
    {
      "role": "tool",
      "tool_call_id": "call_a1b2c3",
      "content": "{\"temp_c\": 31, \"condition\": \"多雲\"}"
    }
  ]
}

完整範例(Python)

import json
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.alphacurve.io/v1",
    api_key=os.environ["INFERENCE_API_KEY"],
)

TOOLS = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "取得指定城市的目前天氣。",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
}]

def get_weather(city: str) -> dict:
    return {"city": city, "temp_c": 31, "condition": "多雲"}

messages = [{"role": "user", "content": "台北現在天氣如何?"}]

while True:
    resp = client.chat.completions.create(
        model="openai/gpt-4o",
        messages=messages,
        tools=TOOLS,
        tool_choice="auto",
    )
    msg = resp.choices[0].message
    messages.append(msg.model_dump(exclude_none=True))

    if not msg.tool_calls:
        print(msg.content)
        break

    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        result = get_weather(**args)
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "content": json.dumps(result, ensure_ascii=False),
        })

完整範例(Node / TypeScript)

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.alphacurve.io/v1",
  apiKey: process.env.INFERENCE_API_KEY!,
});

const tools: OpenAI.Chat.ChatCompletionTool[] = [{
  type: "function",
  function: {
    name: "get_weather",
    description: "取得指定城市的目前天氣。",
    parameters: {
      type: "object",
      properties: { city: { type: "string" } },
      required: ["city"],
    },
  },
}];

function getWeather(city: string) {
  return { city, temp_c: 31, condition: "多雲" };
}

const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [
  { role: "user", content: "台北現在天氣如何?" },
];

for (;;) {
  const resp = await client.chat.completions.create({
    model: "openai/gpt-4o",
    messages,
    tools,
    tool_choice: "auto",
  });

  const msg = resp.choices[0].message;
  messages.push(msg);

  if (!msg.tool_calls?.length) {
    console.log(msg.content);
    break;
  }

  for (const call of msg.tool_calls) {
    const args = JSON.parse(call.function.arguments);
    messages.push({
      role: "tool",
      tool_call_id: call.id,
      content: JSON.stringify(getWeather(args.city)),
    });
  }
}

串流下的 tool calls

串流時 arguments 會被拆成多段,需依 index 累積:

acc = {}

for chunk in client.chat.completions.create(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "台北天氣?"}],
    tools=TOOLS,
    stream=True,
):
    for tc in (chunk.choices[0].delta.tool_calls or []):
        slot = acc.setdefault(tc.index, {"id": "", "name": "", "arguments": ""})
        if tc.id:
            slot["id"] = tc.id
        if tc.function and tc.function.name:
            slot["name"] = tc.function.name
        if tc.function and tc.function.arguments:
            slot["arguments"] += tc.function.arguments

print(acc)  # arguments 完整後才可 json.loads

實務建議

  • 一定要驗證參數。 模型產生的 arguments 是不可信輸入,執行前務必以 schema 驗證。
  • 控制迴圈次數。 設定最大回合數(例如 8 回合),避免無限工具迴圈把餘額燒光。
  • 工具數量保持精簡。 工具定義佔用輸入 token,而且太多選項會降低選擇準確度。
  • 錯誤也要回傳。 工具執行失敗時,把錯誤訊息當成 tool 訊息內容送回去,模型通常能自行修正。
  • 確認模型支援。 呼叫 GET /v1/models,檢查 capabilities.tools。