DeepSeekは2026年8月12日にV4 Proをプレビュー版から正式リリースしました。ローンチに関する報道では、コーディング、ツール利用、数十ステップにわたる長期タスクを扱うエージェント的なワークフローが主要な用途として挙げられています。この用途では、チャット補完だけでなくファンクションコーリングの実装が重要です。
この記事では、DeepSeek V4 Proでツールスキーマを定義し、Pythonの標準openai SDKからツールを呼び出し、複数ターンのエージェントループを実装します。さらに、公開前にApidogでリクエスト、レスポンス、ツール呼び出しをテストする手順も解説します。
DeepSeek APIキーの準備がまだの場合は、先にDeepSeek V4 APIの使用方法を確認してください。
TL;DR
-
deepseek-v4-pro(GAビルド: DeepSeek-V4-Pro-0813)は、OpenAI互換のファンクションコーリングをサポートします。tools配列を送信し、tool_callsを受け取り、結果をrole: "tool"メッセージとして返します。 - Pythonでは、標準の
openaiSDKにbase_url="https://api.deepseek.com"を設定するだけで利用できます。 - エージェントループは「モデル呼び出し → ツール実行 → 結果追加」を、モデルがツール呼び出しを停止するまで繰り返す実装です。
- ツール呼び出しの品質は、モデル単体ではなくツール説明、JSONスキーマ、プロンプト、実行ハーネスに左右されます。実際のツール定義で回帰テストを実行してください。
- 自動プレフィックスキャッシュでは、キャッシュヒットした入力トークンは100万トークンあたり$0.003625で課金され、キャッシュミス時の$0.435より大幅に低コストです。
なぜツール呼び出しがV4 Proの主要ユースケースなのか
DeepSeek V4 Proの仕様は、長いコンテキストを扱いながら複数ツールを連鎖させるエージェント向けの構成です。
| 仕様 | DeepSeek V4 Pro |
|---|---|
| アーキテクチャ | スパースMoE: 総パラメータ数1.6T、トークンあたり49Bがアクティブ |
| コンテキストウィンドウ | 1Mトークン |
| 最大出力 | 384Kトークン |
| 入力価格 | 100万トークンあたり$0.435(キャッシュミス時)、$0.003625(キャッシュヒット時) |
| 出力価格 | 100万トークンあたり$0.87 |
| ファンクションコーリング | OpenAI互換のtools配列とtool_calls応答 |
| その他のインターフェース | Anthropicメッセージ形式、DeepSeek Responses API |
1Mトークンのコンテキストは、過去のツール結果を含む長い会話履歴を保持するのに役立ちます。加えて、同じ会話プレフィックスを繰り返し送るエージェントループでは、プレフィックスキャッシュがコストに大きく影響します。
モデルはプロバイダー比較用にOpenRouterでdeepseek-v4-pro-0813としてリストされています。
ただし、Hacker Newsのローンチ議論でも指摘されているように、ツール呼び出しの結果はハーネスに敏感です。同じモデルでも、フレームワーク、システムプロンプト、ツール説明、スキーマ設計で挙動が変わります。ベンチマークだけで判断せず、実際に本番で使うツール定義で検証してください。
DeepSeekのファンクションコーリングの仕組み
ファンクションコーリングで、モデル自身がAPIやローカル関数を実行するわけではありません。
たとえばモデルは、文章で回答する代わりに、次のような構造化された要求を返します。
{"order_id": "ORD-10442"}
アプリケーション側がこの引数でget_orderを実行し、その結果をモデルへ返します。基本フローは次の5ステップです。
-
messagesと、各関数をJSONスキーマで定義したtools配列を送信する。 - モデルがツール利用を判断し、
tool_callsとfinish_reason: "tool_calls"を返す。 - アプリケーションが引数を検証・解析し、実際の関数またはAPIを実行する。
- 結果を、呼び出しIDに紐付けた
role: "tool"メッセージとして履歴に追加する。 - モデルが追加のツールを要求するか、最終回答を返す。
OpenAIのファンクションコーリングを使ったことがあれば、ほぼ同じワイヤフォーマットです。既存コードの多くは、ベースURLとモデル名の変更だけで移植できます。
DeepSeekの公式ドキュメントにはAnthropic互換メッセージエンドポイントとResponses APIもありますが、この記事ではOpenAI互換インターフェースを使います。
ステップ1: クライアントをセットアップする
まずopenai SDKをインストールし、APIキーを環境変数に設定します。
pip install openai
export DEEPSEEK_API_KEY="sk-..."
次に、DeepSeek APIを向くクライアントを作成します。
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com",
)
以降の例ではmodel="deepseek-v4-pro"を使用します。これはGAビルドのDeepSeek-V4-Pro-0813に解決されます。
ステップ2: ツールスキーマを定義する
例として、オンラインストアのサポートエージェントを作成します。最初のツールは、注文IDから注文情報を検索するget_orderです。
ツール定義には次の3要素が必要です。
- 関数名
- モデルがツールを選択するための説明
- 引数のJSONスキーマ
tools = [
{
"type": "function",
"function": {
"name": "get_order",
"description": (
"注文IDで顧客の注文を検索します。注文のステータス、"
"運送業者、追跡番号、および推定配達日を返します。"
"ユーザーが注文の場所や状態を尋ねる際には常に使用してください。"
),
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "注文ID。形式は「ORD-10442」のようになります。",
}
},
"required": ["order_id"],
},
},
}
]
descriptionは単なるドキュメントではありません。モデルは説明文を使って「いつ」「どのツールを」呼び出すかを判断します。
次に、実際の注文サービスの代わりとなるスタブ関数を用意します。
def get_order(order_id: str) -> dict:
"""実際の注文サービスを置き換えるスタブ。"""
fake_db = {
"ORD-10442": {
"status": "shipped",
"carrier": "DHL",
"tracking_number": "4281337005",
"estimated_delivery": "2026-08-15",
},
"ORD-10587": {
"status": "processing",
"estimated_ship_date": "2026-08-14",
},
}
return fake_db.get(
order_id,
{"error": f"Unknown order ID: {order_id}"},
)
本番では、この関数内からGET /orders/{order_id}などのバックエンドAPIを呼び出します。
ステップ3: 最初のツール呼び出しを行う
モデルが注文情報を知らない状態で、注文ステータスを尋ねます。
messages = [
{
"role": "system",
"content": "あなたはオンラインストアのサポートエージェントです。",
},
{
"role": "user",
"content": "私の注文ORD-10442はどこにありますか?",
},
]
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=messages,
tools=tools,
)
message = response.choices[0].message
print(message.tool_calls[0].function.name)
# get_order
print(message.tool_calls[0].function.arguments)
# {"order_id": "ORD-10442"}
この時点でモデルは通常の文章を返す代わりに、get_orderの実行要求を返します。レスポンスは概ね次のような形式です。
{
"id": "chatcmpl-8f3a1c",
"object": "chat.completion",
"model": "deepseek-v4-pro",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0_f1c29a44",
"type": "function",
"function": {
"name": "get_order",
"arguments": "{\"order_id\": \"ORD-10442\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 312,
"completion_tokens": 24,
"total_tokens": 336,
"prompt_cache_hit_tokens": 0,
"prompt_cache_miss_tokens": 312
}
}
実装で確認すべきポイントは3つです。
-
finish_reasonが"tool_calls"なら、ツール実行が必要です。 - 各呼び出しの
idは、結果を返す際のtool_call_idとして必ず使います。 -
argumentsはJSON文字列です。json.loads()前に、形式不正の可能性を考慮してください。
ステップ4: 関数を実行して結果を返す
ツール呼び出しを受け取ったら、引数を解析して関数を実行します。その後、会話履歴には次の2つを追加します。
-
tool_callsを含むアシスタントメッセージ - 実行結果を含む
role: "tool"メッセージ
import json
tool_call = message.tool_calls[0]
args = json.loads(tool_call.function.arguments)
result = get_order(**args)
messages.append(message)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result),
})
final = client.chat.completions.create(
model="deepseek-v4-pro",
messages=messages,
tools=tools,
)
print(final.choices[0].message.content)
# あなたの注文ORD-10442はDHLで発送され、
# 2026年8月15日までに到着予定です。追跡番号:4281337005。
tool_call_idは厳密に一致させる必要があります。モデルが1ターンで複数のtool_callsを返した場合、次のモデル呼び出しの前に、すべての呼び出しに対応するtoolメッセージを追加してください。
ステップ5: 完全なエージェントループを実装する
実際のエージェントでは、1回のツール呼び出しで終わらないことがあります。
たとえば、注文検索、返品ポリシーの取得、配送状況の確認、メール文面の生成といった複数ステップを連鎖させるケースです。そのため、モデルが通常の最終回答を返すまでループを継続します。
import json
TOOLS_BY_NAME = {
"get_order": get_order,
}
def run_agent(client, messages, tools, max_rounds=10):
"""最終回答を返すか、ラウンド上限に到達するまでエージェントを実行する。"""
for _ in range(max_rounds):
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=messages,
tools=tools,
)
message = response.choices[0].message
messages.append(message)
# ツール要求がなければ最終回答
if not message.tool_calls:
return message.content
# 1ターン内のすべてのツール呼び出しを処理
for tool_call in message.tool_calls:
try:
fn = TOOLS_BY_NAME.get(tool_call.function.name)
if fn is None:
raise ValueError(
f"Unknown tool: {tool_call.function.name}"
)
args = json.loads(tool_call.function.arguments)
result = fn(**args)
except Exception as exc:
# エラーをモデルへ返し、修正または代替手段を選ばせる
result = {
"error": str(exc),
}
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result),
})
raise RuntimeError(
f"Agent did not finish within {max_rounds} rounds"
)
max_roundsは必須のガードです。ツールエラーや不適切なプロンプトが原因で再試行を繰り返した場合でも、無制限のAPI呼び出しや課金を防げます。
並行ツール呼び出し
ユーザーが「ORD-10442とORD-10587のステータスを比較して」と依頼した場合、V4 Proは同一ターンで複数のツール呼び出しを返すことがあります。
"tool_calls": [
{
"id": "call_0_a7d1",
"type": "function",
"function": {
"name": "get_order",
"arguments": "{\"order_id\": \"ORD-10442\"}"
}
},
{
"id": "call_1_b3e9",
"type": "function",
"function": {
"name": "get_order",
"arguments": "{\"order_id\": \"ORD-10587\"}"
}
}
]
前述のrun_agent()は、内側のforループで複数呼び出しを処理できます。各ツール結果には、それぞれ対応するtool_call_idを指定してください。
ツールがネットワークI/O中心なら、asyncio.gather()やワーカープールを使って各呼び出しを並行実行できます。ただし、モデルに結果を返す前に、すべてのtool_callsに対応する結果を揃える必要があります。
これは、モデルがサンドボックス内でオーケストレーションコードを生成するGPT-5.6のプログラム的なツール呼び出しとは異なります。DeepSeekのファンクションコーリングでは、実行権限と信頼境界はアプリケーション側ランタイムに残ります。
思考モードとツール
V4 Proには複数の思考モードがあり、複雑な計画が必要なターンと単純な検索ターンで推論量を調整できます。モード名やデフォルト設定は公式ドキュメントを確認してください。
思考を有効にすると、ツール呼び出しとともにreasoning_contentが返されます。
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=messages,
tools=tools,
extra_body={
"thinking": {
"type": "enabled",
},
},
)
message = response.choices[0].message
print(message.reasoning_content)
print(message.tool_calls)
reasoning_contentは、モデルがどのツールを選んだかを調査する際に役立ちます。特に、ツール説明が曖昧な場合や、似たツールを誤選択する場合のデバッグに有効です。
ただし、アシスタントメッセージを会話履歴へ追加する前にreasoning_contentを削除し、思考モードは計画が必要なターンに限定してください。推論は出力として、100万トークンあたり$0.87で課金されます。
エラー処理: モデルが呼び出しを誤った場合
エージェントループでは、まれなエラーでも繰り返しによって影響が増幅されます。少なくとも次のケースを処理してください。
-
argumentsが不正なJSON - JSONスキーマに適合しない引数
- 存在しないツール名
- バックエンドAPIのタイムアウトや失敗
- ビジネスルールに違反する値
jsonschemaで引数を検証する例です。
pip install jsonschema
import json
from jsonschema import ValidationError, validate
schema = tools[0]["function"]["parameters"]
try:
args = json.loads(tool_call.function.arguments)
validate(instance=args, schema=schema)
result = get_order(**args)
except (json.JSONDecodeError, ValidationError) as exc:
result = {
"error": f"Invalid arguments: {exc}",
"hint": (
"「ORD-10442」のような注文ID文字列を指定して、"
"get_orderを再度呼び出してください。"
),
}
重要なのは、例外でエージェントを即座に停止するのではなく、ツール結果としてエラーを返すことです。hintを含めると、モデルが次のラウンドで修正済みの引数を使って再試行しやすくなります。
また、ツール実行はセキュリティ境界です。外部入力に誘導されたモデルがdelete_orderのような破壊的操作を呼び出せる場合、そのリスクはツールに与えた認証情報の権限と同じだけ大きくなります。AIエージェント向けの最小特権APIキーの考え方に従い、ツールごとに必要最小限の権限だけを付与してください。
リリース前にApidogでツール呼び出しをテスト・デバッグする
エージェントのツールは、多くの場合バックエンドAPIの薄いラッパーです。APIが曖昧、不安定、または仕様と実装で不一致なら、その問題はモデルのツール利用にも直接影響します。
Apidogを使うと、ツールの基盤となるAPIと、モデルへのリクエストを同じワークフローで検証できます。
バックエンドAPIを先に設計する
ApidogのビジュアルデザイナーでGET /orders/{order_id}を定義します。ツールのJSONスキーマとAPI仕様を対応付けて管理すると、引数名や型の乖離を防げます。バックエンド完成前にモックする
Apidogのスマートモックを使い、スキーマに基づくレスポンスを返します。実サービスの実装中でも、get_orderを含むエージェントループを早期に検証できます。DeepSeek APIの生ペイロードを確認する
https://api.deepseek.comへ、同じmessagesとtoolsを含むリクエストを送信します。生のtool_callsJSONを確認すると、ネスト構造の誤り、引数の二重エンコード、想定外の型をすばやく特定できます。会話を回帰テストに変換する
finish_reason、ツール名、引数形式、ツール結果の処理をアサートします。ツール説明やスキーマを変更するたびにテストを実行してください。実際のツール定義に対する回帰スイートは、本番での挙動を確認するための実用的なベンチマークになります。
より詳しい構成は、ApidogテストハーネスにAIエージェントを接続するを参照してください。
Apidogを無料でダウンロードして、モックサーバーとテストシナリオを使って検証してみてください。
エージェントループのコストとプレフィックスキャッシュ
エージェントループでは、各ラウンドで会話履歴、システムプロンプト、ツールスキーマ、過去のツール結果を再送します。
10ラウンド目では、初期プロンプトやツール定義が10回目の入力にも含まれます。V4 Proの自動プレフィックスキャッシュは、このような繰り返し入力のコストを抑えるために重要です。
- キャッシュミス時: 100万トークンあたり$0.435
- キャッシュヒット時: 100万トークンあたり$0.003625
たとえば10万トークンの会話履歴を再送する場合、キャッシュなしでは約$0.0435ですが、キャッシュヒットなら約$0.0004です。
実際のヒット率はレスポンスのusage.prompt_cache_hit_tokensで確認できます。
キャッシュヒット率を高く保つには、次を守ってください。
- 以前のメッセージを途中で書き換えない
-
tools配列の順序・内容・文字列をラウンド間で安定させる - システムプロンプトを毎ターン動的に変更しない
- 可変情報は会話末尾に追加する
プレフィックスキャッシュの仕組みは、プロンプトキャッシングとは何かでも解説しています。
また、deepseek-v4-flashは100万トークンあたり$0.14/$0.28と低価格に見える場合があります。単発のツールルーティングには適する一方、10回以上のツール呼び出しを連鎖させるワークフローでは、再試行や失敗が増えるとコスト削減が相殺される可能性があります。複雑なエージェントでは、Proを安全なデフォルトとして評価してください。
FAQ
ツール定義にもトークンコストはかかりますか?
はい。tools配列は各リクエストの入力トークンとして扱われます。
ただし、ツール定義をラウンド間で変更しなければ、最初のラウンド以降はキャッシュされたプレフィックスに含まれ、キャッシュヒット料金で処理されます。
ファンクションコーリングと構造化出力は組み合わせられますか?
はい。
一般的には、ツールで中間データを取得し、最終回答だけを構造化出力スキーマで整形します。これにより、後続処理で自然言語の文章を解析する必要がなくなります。
まとめ
DeepSeek V4 Proのファンクションコーリングは、OpenAI互換のtools、tool_calls、tool_call_id付きのtoolメッセージで実装できます。
実装の中心は次のループです。
モデル呼び出し → ツール実行 → 結果追加 → モデル呼び出し
本番運用では、次を優先してください。
- ツール説明とJSONスキーマを具体的に書く
- 引数を検証してからツールを実行する
- ツールエラーをモデルへ構造化して返す
-
max_roundsで無限ループを防ぐ - 過去メッセージと
tools配列を安定させ、プレフィックスキャッシュを活用する - 実際のツールスキーマを使った回帰テストを維持する
モデルのベンチマークだけでは、あなたのツール設計に対する実際の挙動は判断できません。バックエンドAPIを明確に設計し、モックで早期検証し、Apidogでツール呼び出しシナリオを継続的にテストしてください。
Top comments (0)