【vLLM】OpenAI互換APIでFunction Callingを実装する方法と「Invalid tools format」エラー解決

問題の概要:vLLMのOpenAI互換APIでFunction Callingが機能しない

vLLMは、その高速な推論エンジンとして人気を集めています。OpenAI互換のAPIサーバーを提供しており、Chat Completions APIを通じてFunction Calling(関数呼び出し)機能の利用も公式にサポートされています。しかし、実際に実装を試みると、以下のようなエラーに遭遇することが少なくありません。

{
  "error": {
    "message": "Invalid tools format. The 'tools' parameter must be a list of tool objects.",
    "type": "invalid_request_error",
    "param": "tools",
    "code": 400
  }
}

あるいは、ツール(関数)の定義は受け付けられたものの、モデルが全くツールを呼び出さない、または期待した構造の引数を返さないという課題も発生します。この記事では、vLLMのOpenAI互換APIでFunction Callingを正しくセットアップし、上記のエラーを解決するための実践的な手順を解説します。

原因の解説:フォーマットの不一致とモデルの制限

エラーが発生する主な原因は、以下の2点に集約されます。

1. リクエストボディのフォーマットが不正

vLLMのOpenAI互換APIは、OpenAIのAPI仕様に極力準拠していますが、細部において検証が厳格である場合や、ドキュメントに明記されていない挙動があります。特に「tools」パラメータは、単なる辞書(object)のリストではなく、OpenAIが定義する厳密なToolオブジェクトの形式に従う必要があります。形式が少しでもずれると、冒頭の「Invalid tools format」エラーが返されます。

2. 使用するモデルがFunction Callingに対応していない

Function Callingは、モデル自体がこの機能を理解し、適切なJSON形式で出力を生成できる能力が必要です。一般的には、gpt-3.5-turbo-1106以降gpt-4シリーズ、あるいはLlama 3.1Mistralなどのオープンソースモデルの特定のバージョンでサポートされています。vLLMでサポートされていないモデルや、ファインチューニングされていないベースモデルを使用すると、ツール呼び出しが行われません。

解決方法:ステップバイステップでの実装

ここからは、実際にvLLMサーバーを起動し、正しい形式でFunction Callingを行うまでの手順を説明します。

ステップ1:vLLMサーバーの起動(Function Calling対応モデル指定)

まず、Function Callingをサポートしているモデルを指定してAPIサーバーを起動します。ここでは、Llama 3.1 8B Instructモデル(`meta-llama/Meta-Llama-3.1-8B-Instruct`)を例に取ります。このモデルはFunction Calling能力を持っています。

# vLLMをインストールしていない場合
# pip install vllm

# OpenAI互換APIサーバーの起動
vllm serve meta-llama/Meta-Llama-3.1-8B-Instruct 
  --api-key token-abc123  # 任意のAPIキーを設定
  --port 8000

サーバーが`http://localhost:8000`で起動します。`–api-key`は必須ではありませんが、OpenAIクライアントを使う際に設定すると便利です。

ステップ2:正しいツール定義の作成

OpenAIクライアント(Python)を使用してリクエストを送信します。ツール定義は、`type: “function”`と、その中に関数の詳細を記述した`function`オブジェクトを含める必要があります。

import openai
from openai.types.chat import ChatCompletionMessageParam

# クライアントの設定。base_urlにvLLMサーバーのアドレスを指定。
client = openai.OpenAI(
    api_key="token-abc123", # サーバー起動時に指定したキー
    base_url="http://localhost:8000/v1" # /v1 を忘れずに!
)

# 正しいツール(関数)定義の例
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "指定された場所の現在の天気を取得します",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "都市名と国名(例:'東京,日本')",
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "温度の単位",
                    }
                },
                "required": ["location"],
            },
        }
    }
]

ポイント: `tools`はリストであり、その要素は`type`と`function`をキーに持つ辞書です。`parameters`のスキーマ定義はJSON Schema形式に厳密に従います。

ステップ3:チャット補完リクエストの送信とレスポンスの処理

ユーザーメッセージとツール定義を含めてリクエストを送信します。

# チャット補完リクエスト
try:
    response = client.chat.completions.create(
        model="meta-llama/Meta-Llama-3.1-8B-Instruct", # サーバーで起動したモデル名
        messages=[
            {"role": "user", "content": "東京都の天気はどうですか?摂氏で教えて。"}
        ],
        tools=tools, # ステップ2で定義したツールリスト
        tool_choice="auto", # "auto", "none", または特定の関数を指定
        max_tokens=500
    )
except openai.APIError as e:
    print(f"APIエラーが発生しました: {e}")
    exit(1)

# レスポンスの確認
print(f"レスポンス全体: {response}")
print(f"モデルの返答: {response.choices[0].message.content}")
print(f"ツール呼び出し有無: {response.choices[0].message.tool_calls}")

ステップ4:ツール呼び出しの実行と結果のフィードバック(オプション)

モデルがツールを呼び出した場合(`tool_calls`が存在する場合)、その関数を実行し、結果を再度APIに送信して最終的な回答を得ます。

from openai.types.chat import ChatCompletionToolParam

message = response.choices[0].message

if message.tool_calls:
    print("モデルがツールを呼び出しました。")
    # ここで実際の関数を実行(例ではモック)
    available_functions = {
        "get_current_weather": lambda location, unit="celsius": f"{location}の天気は晴れ、気温は22{unit}です。",
    }

    # ツール呼び出し結果をメッセージに追加
    messages_for_followup = [
        {"role": "user", "content": "東京都の天気はどうですか?摂氏で教えて。"},
        message, # モデルが返したツール呼び出しを含むメッセージ
    ]

    for tool_call in message.tool_calls:
        function_name = tool_call.function.name
        function_args = json.loads(tool_call.function.arguments)
        # 関数を実行
        function_response = available_functions[function_name](**function_args)
        # 実行結果をメッセージリストに追加
        messages_for_followup.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": function_response,
            "name": function_name # 'name'フィールドはvLLMの一部バージョンで必要
        })

    # ツールの実行結果をモデルに送信
    second_response = client.chat.completions.create(
        model="meta-llama/Meta-Llama-3.1-8B-Instruct",
        messages=messages_for_followup,
        max_tokens=500
    )
    print(f"最終回答: {second_response.choices[0].message.content}")

コード例・コマンド例:エラー別対応

ケース1: 「Invalid tools format」エラー

誤った例(`function`キーが直接リストに入っている):

tools = [
    {
        "name": "get_weather", # 誤り: トップレベルに'name'がある
        "description": "...",
        "parameters": {...}
    }
]

正しい例:

tools = [
    {
        "type": "function", # 必須
        "function": { # 必須
            "name": "get_weather",
            "description": "...",
            "parameters": {...}
        }
    }
]

ケース2: モデルがツールを呼び出さない

まず、モデルがFunction Callingに対応しているか確認します。vLLMサーバー起動時に以下のような警告が出ていないかログを確認します。また、`tool_choice`パラメータを`”auto”`から`”required”`や具体的な関数名(`{“type”: “function”, “function”: {“name”: “get_weather”}}`)に変更して試してみます。

# tool_choiceを「必須」に設定
response = client.chat.completions.create(
    model="...",
    messages=[...],
    tools=tools,
    tool_choice={"type": "function", "function": {"name": "get_current_weather"}}, # この関数を必ず呼び出す
)

まとめ・補足情報

vLLMのOpenAI互換APIでFunction Callingを成功させるには、「対応モデルの選択」「OpenAI公式仕様に準拠した厳密なツール定義」の2点が最も重要です。本記事で紹介した正しいフォーマットと手順に従うことで、「Invalid tools format」エラーは解消され、モデルに応じた関数呼び出しが可能になります。

補足情報:

  • モデル選定: Function Callingには、指令に従って構造化出力を生成できるように調整されたモデル(Chat/Instructモデル)が適しています。ベースモデルでは動作しないことがほとんどです。
  • vLLMのバージョン: vLLMは活発に開発が進んでいます。古いバージョンではFunction Callingのサポートが不完全な場合があるため、pip install --upgrade vllmで最新版を利用することを推奨します。
  • デバッグのコツ: 複雑なツール定義でエラーが起きる場合は、まずシンプルな1つの関数定義から始め、パラメータスキーマを段階的に複雑にしていく方法が有効です。また、vLLMサーバーのログ(起動時のターミナル)には詳細なエラー情報が出力されることが多いので、必ず確認してください。

vLLMを活用した高速なFunction Callingは、エージェントや複雑な業務アプリケーションを構築する上で強力な基盤となります。本ガイドを参考に、スムーズな開発を進めてください。

この記事は役に立ちましたか?