Cookieを使用しています. Cookieとブラウザストレージは、ログイン状態の維持と設定の保存に使われます。許可いただいた場合は、サイトの利用状況も計測します。はいつでも変更できます。詳しくはCookieポリシーをご覧ください。

ガイド: ツール呼び出しエージェント

はじめに

ガイド: ツール呼び出しエージェント

ツール呼び出しエージェントを作るガイドです。ツール定義、ステップ上限付きのループ、並列ツール呼び出し、構造化された最終出力、モデルの選び方、コスト管理を、openai パッケージのコードで順に示します。コードは公式の openai パッケージを使います。

エージェントとはループです。モデルが会話を読み、自分のツールを 1 つ以上呼び出すことを決め、あなたがそれを実行して結果を返し、モデルが回答できるようになるまで続けます。api14 は、ツールに対応するすべてのモデルで OpenAI の tools 形式をサポートしているため、モデル ID を変えるだけで、OpenAI、Anthropic、Google などのプロバイダーで同じコードが動作します。

このガイドでは、ステップ数の上限、並列のツール実行、構造化された最終回答、コスト管理を備えた小さなエージェントを構築します。コードは公式の openai パッケージを、ベース URL https://api14.com/api/v1 で使います。

ツールを定義する

各ツールは、名前、モデルが読む説明、引数の JSON Schema で構成されます。説明は具体的に書いてください。それがモデルにとって唯一のドキュメントになります。

import json, os
from openai import OpenAI

client = OpenAI(base_url="https://api14.com/api/v1", api_key=os.environ["API14_API_KEY"])

def search_orders(customer_email: str) -> list:
    return [{"id": "A-1001", "status": "shipped"}]  # データベースへのクエリ

def get_shipment(order_id: str) -> dict:
    return {"order_id": order_id, "carrier": "UPS", "eta": "2026-10-09"}  # API 呼び出し

TOOLS = {"search_orders": search_orders, "get_shipment": get_shipment}

tools = [
    {"type": "function", "function": {
        "name": "search_orders",
        "description": "List a customer's orders with their status.",
        "parameters": {"type": "object", "properties": {"customer_email": {"type": "string"}},
                       "required": ["customer_email"]},
    }},
    {"type": "function", "function": {
        "name": "get_shipment",
        "description": "Carrier and estimated delivery date for one order.",
        "parameters": {"type": "object", "properties": {"order_id": {"type": "string"}},
                       "required": ["order_id"]},
    }},
]

ステップ上限付きのループ

各反復は別々に課金されるリクエストなので、必ずステップ数に上限を設けてください。ツールのエラーはクラッシュさせず、コンテンツとしてモデルに返します。モデルは通常、引数を修正して回復します。

MAX_STEPS = 8

def run_tool(call):
    try:
        args = json.loads(call.function.arguments)
        result = TOOLS[call.function.name](**args)
    except Exception as e:  # 不正な JSON、未知のツール、または関数の失敗
        result = {"error": str(e)}
    return {"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)}

def agent(task, model="openai/gpt-5-mini"):
    messages = [
        {"role": "system", "content": "You are a support agent. Use tools to look things up. Be brief."},
        {"role": "user", "content": task},
    ]
    spent = 0.0
    for step in range(MAX_STEPS):
        r = client.chat.completions.create(model=model, messages=messages, tools=tools, max_tokens=1000)
        spent += r.usage.cost
        msg = r.choices[0].message
        if not msg.tool_calls:
            return msg.content, spent
        messages.append(msg)
        messages.extend(run_tool(call) for call in msg.tool_calls)
    raise RuntimeError(f"No answer after {MAX_STEPS} steps (spent ${spent:.4f})")

print(agent("Where is my order? My email is ada@example.com"))

ループ内のすべてのリクエストで tools を送ってください。多くのプロバイダーは、リクエストにツール定義がないと tool メッセージを拒否します。

並列ツール呼び出し

parallel_tool_calls(対応するモデルではデフォルトで有効)を使うと、モデルは 1 ターンで複数の呼び出しを要求できます。たとえば、3 件の注文に対する get_shipment です。それらを並行して実行し、呼び出しごとに 1 つの tool メッセージを任意の順序で追加します。tool_call_id が各結果を呼び出しに結び付けます。Node では次のようになります。

import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api14.com/api/v1", apiKey: process.env.API14_API_KEY });
const MAX_STEPS = 8;

async function agent(messages, tools, impl, model = "openai/gpt-5-mini") {
  for (let step = 0; step < MAX_STEPS; step++) {
    const res = await client.chat.completions.create({ model, messages, tools, max_tokens: 1000 });
    const msg = res.choices[0].message;
    if (!msg.tool_calls?.length) return msg.content;

    messages.push(msg);
    const results = await Promise.all(msg.tool_calls.map(async (call) => {
      let content;
      try {
        content = JSON.stringify(await impl[call.function.name](JSON.parse(call.function.arguments)));
      } catch (err) {
        content = JSON.stringify({ error: String(err) });
      }
      return { role: "tool", tool_call_id: call.id, content };
    }));
    messages.push(...results);
  }
  throw new Error(`No answer after ${MAX_STEPS} steps`);
}

ツールに順序が重要な副作用(作成してから更新するなど)がある場合は、parallel_tool_calls: false を設定して、モデルが 1 ターンに 1 つの呼び出しだけを要求するようにします。

構造化された最終出力

エージェントの回答を別のコードに渡す場合は、スキーマに一致する JSON を要求します。シンプルで移植性の高いパターンは、ループで事実を集めた後に、追加の呼び出しを 1 回行う方法です(ここでの messages はループの会話です。agent() から回答とともに返してください)。

schema = {
    "type": "object",
    "properties": {
        "order_id": {"type": "string"},
        "status": {"type": "string", "enum": ["processing", "shipped", "delivered", "unknown"]},
        "eta": {"type": ["string", "null"]},
        "reply": {"type": "string"},
    },
    "required": ["order_id", "status", "eta", "reply"],
    "additionalProperties": False,
}

final = client.chat.completions.create(
    model="openai/gpt-5-mini",
    messages=messages + [{"role": "user", "content": "Summarize the result for our ticket system."}],
    tools=tools,
    tool_choice="none",
    response_format={"type": "json_schema", "json_schema": {"name": "ticket", "strict": True, "schema": schema}},
)
ticket = json.loads(final.choices[0].message.content)

tool_choice: "none" を指定すると、履歴に tool メッセージが含まれるため必要な tools をリクエストに残したまま、それ以上の呼び出しを止められます。解析した JSON は必ず検証してください。構造化出力を参照してください。

モデルの選び方

  • 機能: モデルの supported_parameters に tools が含まれている必要があります。GET /models で確認してください。モデルを参照してください。最終ステップでは、structured_outputs または response_format を探します。
  • ルーティング: 送信するすべてのパラメーターに対応するプロバイダーにのみリクエストが送られるように、provider: { "require_parameters": true } を追加します(Python では extra_body 経由)。プロバイダールーティングを参照してください。
  • サイズ: openai/gpt-5-mini のような小さなモデルは、適切に説明された少数のツールを、確実かつ低コストで扱えます。ツールが多い、複数ステップの長い計画がある、または誤ったツールを選び続ける場合は、より大きなモデルに移行してください。
  • 自分のツールでテストする: 同じ 20〜50 件の実際のタスクを 2〜3 個の候補モデルで実行し、成功率、タスクあたりのステップ数、usage.cost の合計を比較します。
curl -s "https://api14.com/api/v1/models?category=text" \
  | jq -r '.data[] | select(.supported_parameters | index("tools")) | "\(.id)  \(.pricing.prompt)"'

コストの管理

エージェントは 1 つのタスクに多数のリクエストを行うことがあり、各ステップで会話全体が再送されます。複数のレベルで支出を制限してください。

  • 上記のとおり、ステップ上限(MAX_STEPS)とリクエストごとの max_tokens。
  • タスクごとの予算: ステップ全体で usage.cost を合計し、しきい値を超えたら停止します。
  • 短いツール結果: モデルが必要とするフィールドだけを返します。大きなツール出力は、その後のステップごとに入力トークンとして課金されます。
  • キーごとの 1 日の USD 上限: API キーでエージェント専用のキーを作成し、1 日の上限を設定します。上限に達すると、次の UTC 日までリクエストは 429 daily_limit_reached を返します。このコードは再試行ではなく、エージェントを停止して処理してください。
  • モデル許可リスト: キーを、テストしたモデルに制限します。それ以外は 403 model_not_allowed を返します。
  • プロンプトキャッシュ: キャッシュされたプレフィックスの割引が適用されるように、システムプロンプトとツール定義を、すべてのリクエストの先頭で同一に保ちます。プロンプトキャッシュを参照してください。

ツール呼び出し、レート制限、エラーを参照してください。