問題の概要: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.1やMistralなどのオープンソースモデルの特定のバージョンでサポートされています。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は、エージェントや複雑な業務アプリケーションを構築する上で強力な基盤となります。本ガイドを参考に、スムーズな開発を進めてください。