問題の概要:vLLMのOpenAI互換APIでFunction Callingが機能しない
vLLMは、大規模言語モデル(LLM)を高速に推論するためのオープンソースライブラリです。その大きな特徴の一つが、OpenAI APIと互換性のあるサーバーを提供している点です。しかし、OpenAI APIの重要な機能である「Function Calling」をvLLMサーバーで使用しようとすると、以下のような問題に直面することがあります。
- 関数の定義をAPIリクエストに含めても、モデルが関数を認識せず、通常のチャット応答のみを返す。
"tools"または"functions"パラメータを指定しているにもかかわらず、InvalidRequestErrorやスキーマエラーが発生する。- モデルが関数呼び出し(
tool_calls)を生成するが、vLLMサーバーがそのレスポンスを正しく処理・返却しない。
具体的なエラーメッセージ例としては、以下のようなものがあります。
openai.BadRequestError: Error code: 400 - {'message': 'Invalid tool schema. Got {"type": "function", "function": {"name": "get_weather", ...}}. Please refer to the documentation for the correct request format.'}
# モデルが tool_calls を生成しない場合の典型的なレスポンス
{
"id": "chatcmpl-...",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "申し訳ありませんが、その情報は私にはわかりません。" // 関数を呼び出すべき内容なのに通常応答
}
}]
}
原因の解説
vLLMでFunction Callingが正しく動作しない主な原因は、以下の3点に集約されます。
1. モデルの対応状況
Function Calling(またはvLLM/OpenAIの用語では「ツール使用」)は、モデル自体がその機能をサポートしている必要があります。すべてのLLMがこの機能をネイティブにサポートしているわけではありません。特に、vLLMがデフォルトで使用するモデル定義ファイル(model.py)が、ツール使用のための特別なロジック(チャットテンプレートの調整など)を含んでいない場合があります。
2. APIリクエストの形式ミス
vLLMのOpenAI互換APIは、OpenAI APIと「ほぼ」同じですが、細かいパラメータ名やリクエスト/レスポンスの構造に差異がある場合があります。OpenAIの最新API形式(例: tools パラメータ)と旧形式(functions パラメータ)の混同、あるいはvLLMが期待する正確なJSONスキーマとの不一致がエラーの原因となります。
3. vLLMサーバーの起動オプションとバージョン
Function Callingを有効にするためには、vLLMサーバーを適切なオプションを付けて起動する必要があります。また、vLLMは活発に開発が進んでいるプロジェクトのため、バージョンによってFunction Callingのサポート状況や実装方法が大きく異なることがあります。古いバージョンではサポートが不完全なケースが多々あります。
解決方法:ステップバイステップでの実装
以下、vLLM v0.4.0以降を想定した、Function Calling(ツール使用)を正常に動作させるための手順です。
ステップ1: vLLMの最新版をインストール
まず、vLLMを最新バージョンにアップグレードします。Function Callingのサポートは比較的新しい機能です。
pip install -U vllm
# または、開発版を試す場合
# pip install git+https://github.com/vllm-project/vllm.git
ステップ2: 適切なモデルを選択・ダウンロード
Function Callingを明確にサポートしているモデルを選択します。多くの最新のチャットモデル(例: Qwen2.5-7B-Instruct, Llama-3.2-3B-Instruct, gpt-3.5-turbo互換モデルなど)が該当します。ここでは例として Qwen/Qwen2.5-7B-Instruct を使用します。
ステップ3: vLLMサーバーをツール使用対応で起動
サーバー起動時に、--enable-prefix-caching と --chat-template オプションを指定することが、ツール使用を安定させるための重要なポイントです。モデルによって適切なチャットテンプレートが異なります。
# Qwen2.5-Instruct モデルでサーバーを起動する例
vllm serve Qwen/Qwen2.5-7B-Instruct
--enable-prefix-caching
--chat-template qwen
デフォルトではポート8000でサーバーが起動します。--chat-template はモデルに合わせて変更してください(例: llama2, chatml, qwen)。
ステップ4: 正しい形式でAPIリクエストを送信
OpenAI Pythonクライアントライブラリを使用して、tools パラメータを用いてリクエストを送信します。旧式の functions パラメータは使用しないでください。
from openai import OpenAI
import json
# vLLMサーバーを指定してクライアントを初期化
client = OpenAI(
api_key="token-abc123", # ダミーでOK。vLLMはAPIキーを検証しない。
base_url="http://localhost:8000/v1" # vLLMサーバーのエンドポイント
)
# 使用可能な関数(ツール)を定義
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"],
},
},
}
]
# ユーザーメッセージ
messages = [
{"role": "user", "content": "東京都の現在の天気はどうですか?摂氏で教えてください。"}
]
# APIリクエストの送信
try:
response = client.chat.completions.create(
model="Qwen/Qwen2.5-7B-Instruct", # サーバー起動時のモデル名と一致させる
messages=messages,
tools=tools,
tool_choice="auto", # "auto", "none", または特定の関数を指定
max_tokens=512
)
except Exception as e:
print(f"リクエストエラー: {e}")
exit()
# レスポンスの解析
message = response.choices[0].message
print(f"Assistant Response: {message.content}")
if message.tool_calls:
print("nモデルが関数呼び出しを要求しました:")
for tool_call in message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
print(f" 関数名: {func_name}")
print(f" 引数: {func_args}")
# ここで実際の関数を実行するロジックを記述
# if func_name == "get_current_weather":
# result = get_current_weather(**func_args)
# ...(ツール実行結果を次のメッセージとして送信する)
ステップ5: ツール実行結果を送信して会話を継続(オプション)
モデルが tool_calls を生成した場合、その結果を次のメッセージとしてサーバーに送り返すことで、モデルが結果を解釈した最終応答を得ることができます。
# ステップ4の続き (message.tool_calls が存在した場合)
# 1. ツールを実行し結果を取得(ダミー結果)
tool_result = {"temperature": 22, "condition": "晴れ", "unit": "celsius"}
# 2. 会話履歴にツール呼び出しとその結果を追加
messages.append(message) # アシスタントのツール呼び出しメッセージを追加
messages.append({
"role": "tool",
"tool_call_id": tool_call.id, # 対応する tool_call の ID
"content": json.dumps(tool_result, ensure_ascii=False)
})
# 3. 追加のコンテキストを含めて再度リクエスト
second_response = client.chat.completions.create(
model="Qwen/Qwen2.5-7B-Instruct",
messages=messages,
max_tokens=512
)
final_message = second_response.choices[0].message
print(f"n最終応答: {final_message.content}")
よくあるエラーとその対処法
エラー1: Invalid tool schema
原因: tools リストの構造がvLLMサーバーが期待する形式と一致しない。特に、function オブジェクト内の parameters のスキーマ定義が不正。
解決策: 上記のコード例のように、type, properties, required を厳密にJSON Schema形式で記述する。オンラインバリデータでJSONスキーマを検証する。
エラー2: モデルが tool_calls を生成しない
原因1: モデルがツール使用に対応していない、またはチャットテンプレートが不適切。
解決策1: モデルを変更するか、--chat-template オプションを試行錯誤する。モデルのドキュメントで推奨テンプレートを確認。
原因2: ユーザーの質問が、定義された関数の能力範囲外で、モデルが関数呼び出しが必要ないと判断。
解決策2: 関数の description をより明確に記述し、質問を関数の目的に沿ったものに変えてテストする。
エラー3: サーバー起動時の警告やエラー
WARNING: The model does not have a chat template. Please specify one using `--chat-template`.
解決策: この警告が出た場合は、必ず --chat-template オプションを明示的に指定してサーバーを再起動してください。これがないとツール使用が不安定になります。
まとめ・補足情報
vLLMでOpenAI互換のFunction Callingを利用するには、「適切なモデル選択」「正しいサーバー起動オプション」「OpenAI API v1形式(tools)に準拠したリクエスト」の3点が重要です。本記事で紹介した手順に従うことで、オープンソースのLLMを用いた高速で低コストなFunction Callingアプリケーションを構築できるようになります。
補足:
- vLLMの開発は非常に速いため、最新の情報は公式GitHubリポジトリやドキュメントを常に参照してください。
- パフォーマンスを最大限引き出すには、
--tensor-parallel-sizeオプションでGPU並列化を調整したり、--gpu-memory-utilizationを設定したりすることも検討しましょう。 - より複雑なツール使用(複数ツールの同時呼び出しなど)をテストする場合は、まず小規模なモデルで動作確認を行い、その後目的の大規模モデルに移行することをお勧めします。
vLLMのFunction Calling機能をマスターすることで、RAG(検索拡張生成)やエージェントシステムなど、より高度で実用的なAIアプリケーションの開発基盤を手に入れることができるでしょう。