DeepSeek V4 Pro APIでの関数呼び出しの使い方

DeepSeek V4 Pro 関数呼び出し実践ガイド:ツールスキーマ、完全なPythonエージェントループ、並列ツール呼び出し、思考モード、エラーハンドリング、キャッシュコスト、Apidogでのツール呼び出しのテスト

INEZA Felin-Michel

INEZA Felin-Michel

13 8月 2026

DeepSeek V4 Pro APIでの関数呼び出しの使い方

Apidog エンタープライズ

オンプレミスデプロイ

SSO & RBAC

SOC 2 準拠

Apidog Enterpriseを見る

DeepSeekは2026年8月12日にV4 Proをプレビュー版から正式リリースし、そのローンチに関する報道では、エージェント的なワークフロー、つまりコーディング、ツール利用、そして数十のステップを連鎖させながらも途切れることのない長期的タスクが主要な点として挙げられています。この位置づけにより、他のどのAPI機能よりもファンクションコーリングが重要となり、これこそがリリース週のガイドでは触れられていない唯一の機能です。これまでのすべてのチュートリアルはチャット補完で止まっています。

本記事ではさらに踏み込みます。ツールスキーマを定義し、標準のPython openai SDKを使って最初のツール呼び出しを行い、完全なエージェントループを構築し、エージェントを公開する前にApidogで全体をテストします。まだDeepSeek APIキーをお持ちでない場合は、DeepSeek V4 APIの使用方法に関するガイドで設定してから、ここに戻ってきてください。

ボタン

TL;DR

なぜツール呼び出しがV4 Proの主要なユースケースなのか

DeepSeekはエージェント向けにV4 Proを構築しました。その仕様書はエージェントのランタイムチェックリストのように読めます。

仕様 DeepSeek V4 Pro
アーキテクチャ スパースMoE: 総パラメータ数1.6T、トークンあたり49Bがアクティブ
コンテキストウィンドウ 1Mトークン
最大出力 384Kトークン
入力価格 100万トークンあたり$0.435 (キャッシュミス時)、100万トークンあたり$0.003625 (キャッシュヒット時)
出力価格 100万トークンあたり$0.87
ファンクションコーリング OpenAI互換のtools配列とtool_calls応答
その他のインターフェース Anthropicメッセージ形式、DeepSeek応答API

各項目はエージェントの問題に対応しています。1Mトークンのウィンドウは、長いエージェントの全ツール結果履歴を保持し、384Kの出力上限は大きな構造化ペイロードのための余地を残し、プレフィックスキャッシュはループの経済性を機能させます。このモデルはプロバイダー比較のためにOpenRouterにdeepseek-v4-pro-0813としてリストされています。

コードの前に一つ注意点があります。Hacker Newsのローンチに関する議論では、開発者たちがツール呼び出しの性能がハーネスに非常に敏感であると報告しました。同じモデルでも、フレームワーク、プロンプトスキャフォールディング、スキーマのスタイルによってスコアが良くなったり悪くなったりするのです。ベンチマークでは、あなたのツールスキーマをどのように扱うかはわかりません。実際の定義を使ってテストしてください。

DeepSeekのファンクションコーリングの仕組み

ファンクションコーリングは、モデルが何かを実行することを意味するものではありません。モデルは散文ではなく、「{"order_id": "ORD-10442"}get_orderを呼び出せ」という構造化された要求で応答します。あなたのコードがその関数を実行し、結果を返し、モデルは実際のデータで続行します。このサイクルは以下の通りです。

  1. messagesと、各関数をJSONスキーマで記述したtools配列を送信します。
  2. モデルはツールが必要であると判断し、tool_callsfinish_reason: "tool_calls"で応答します。
  3. あなたのコードが引数を解析し、実際の関数を実行します。
  4. 結果を、呼び出しのIDに紐付けられたrole: "tool"メッセージとして追加します。
  5. モデルは別のツールを要求するか、最終的な回答を生成します。

OpenAIのファンクションコーリングを扱ったことがあるなら、これは同じワイヤフォーマットです。ほとんどのエージェントコードは、ベースURLとモデル名を変更するだけで移植できます。DeepSeekの公式ドキュメントには、Anthropic互換のメッセージエンドポイントとレスポンスAPIも記載されていますが、このガイドではOpenAI互換のインターフェースに限定します。

ステップ1: クライアントのセットアップ

SDKをインストールし、DeepSeekを指すように設定します。

pip install openai
export DEEPSEEK_API_KEY="sk-..."
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: ツールスキーマの定義

オンラインストアのサポートエージェントを構築します。最初のツールは注文を検索します。ツールの定義には、名前、説明、およびパラメータのJSONスキーマという3つの部分があります。

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_order",
            "description": (
                "注文IDで顧客の注文を検索します。注文のステータス、"
                "運送業者、追跡番号、および推定配達日を返します。ユーザーが"
                "注文の場所や状態を尋ねる際には常にこれを使用してください。"
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {
                        "type": "string",
                        "description": "注文ID。形式は「ORD-10442」のようになります。",
                    }
                },
                "required": ["order_id"],
            },
        },
    }
]

説明は単なる飾りではありません。モデルはそれを読んでいつツールを呼び出すかを決定します。曖昧な説明は、モデルがツールを無視したり、間違ったツールを選択したりする最大の理由です。

スキーマが記述するローカル関数(実際の注文サービス用にスタブとして用意されています):

def get_order(order_id: str) -> dict:
    """Stub for your real order service."""
    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}"})

ステップ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が付与されます。そしてargumentsは自分で解析するJSON文字列なので、たまに形式が不正な場合があることを想定してください。

ステップ4: 関数を実行し、結果を返す

関数を実行し、その後2つのメッセージを追加します。tool_callsを含むアシスタントのターンと、結果を保持するtoolメッセージです。

import json

tool_call = message.tool_calls[0]
args = json.loads(tool_call.function.arguments)
result = get_order(args)

messages.append(message) # the assistant turn containing tool_calls
messages.append({
    "role": "tool",
    "tool_call_id": tool_call.id, # must match the id from the response
    "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のリンクは厳格です。次のモデルのターンまでに、すべてのtool_callsエントリに対応するtoolメッセージが必要です。そうでない場合、リクエストは失敗します。

ステップ5: 完全なエージェントループ

実際のエージェントは呼び出しを連鎖させます。注文の検索、払い戻しポリシーの確認、メールの作成など、各ステップは前のステップに依存します。パターンは、モデルが通常の回答を返すまで、モデルを呼び出し続け、モデルが要求するものを実行し続けることです。

TOOLS_BY_NAME = {"get_order": get_order}

def run_agent(client, messages, tools, max_rounds=10):
    """Run the model until it produces a final answer or hits the cap."""
    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: # no tool requests: we're done
            return message.content

        for tool_call in message.tool_calls:
            fn = TOOLS_BY_NAME.get(tool_call.function.name)
            try:
                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)} # feed failures back to the model
            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")

フレームワークやエージェントSDKは、このループを精巧にしたものです。max_roundsの上限は、失敗するツールを再呼び出しし続けるモデルが、青天井の請求ではなく、クリーンな失敗になるように変換します。

並行ツール呼び出し

「ORD-10442とORD-10587のステータスを比較して」と2つの検索を求めると、V4 Proはしばしば両方を1つのターンでバッチ処理します。

"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で各呼び出しに応答し(次のターンまでにすべての呼び出しに一致する結果が必要です)、バッチを並行して実行できます。これは、モデルがサンドボックス内でオーケストレーションコードを記述するGPT-5.6のプログラム的なツール呼び出しとは異なる哲学です。DeepSeekは実行と信頼境界をあなたのランタイムに保持します。

思考モードとツール

V4 Proには3つの思考モードが付属しているため、困難な計画ターンでは推論の労力を増やし、ルーチンの検索ではそれをスキップできます(モード名とデフォルトについては公式ドキュメントを参照してください)。思考を有効にすると、APIはモデルのトレースを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) # the planning trace
print(message.tool_calls) # the calls it settled on

トレースはモデルがなぜツールを選択したかを示しており、通常はそこで不適切なスキーマが明らかになります。アシスタントのターンを履歴に追加する前にreasoning_contentを削除し、思考は計画重視のターンに限定してください。推論は出力として1Mトークンあたり$0.87で課金されます。

エラー処理: モデルが呼び出しを誤った場合

不正な形式のツール呼び出しはまれですが、エージェントループはあらゆる障害モードを増幅させます。基本的なパターンは、不正な呼び出しでクラッシュせず、問題をツール結果として返し、モデルに再試行させることです。これは、json.loadsで失敗する引数や、ビジネスルールを破る値にも対応します。

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フィールドは重要です。1行の修正で、通常は次のラウンドで修正された再試行が生成されます。エージェントのエラーもセキュリティイベントとして扱ってください。攻撃者から提供された引数でdelete_orderを呼び出すように仕向けられたモデルは、その背後にあるキーと同じくらい危険であり、これはAIエージェント向けの最小特権APIキーのケースに当たります。誤った呼び出しがインシデントにならないように、クレデンシャルの範囲を限定してください。

リリース前にApidogでツール呼び出しをテストし、デバッグする

すべてのツールはAPIの薄いラッパーであり、モデルは今やそのAPIのコンシューマです。バックエンドのエンドポイントが曖昧であったり不安定であったりする場合、モデルはそのすべてを受け継ぎます。ここにApidogがループ内でその地位を確立する理由があります。

  1. まずバックエンドAPIを設計します。ApidogのビジュアルデザイナーでGET /orders/{order_id}を仕様として定義します。ツールのJSONスキーマは仕様から直接導き出されるため、両者が気づかずに乖離することはありません。
  2. バックエンドが存在する前にモックします。Apidogのスマートモックはスキーマから現実的な応答を提供するため、実際のサービスが構築中の間でもエージェントループはget_orderに対して実行されます。
  3. 生のペイロードを検査します。Apidogから同じmessages + toolsボディをhttps://api.deepseek.comに送信し、生のtool_calls JSONを直接読み取ります。ネストが間違ったプロパティや二重エンコードされた引数は、一度の検査で明らかになります。
  4. 会話をテストシナリオに変換します。finish_reasonと引数の形式をアサートし、スキーマ変更ごとにスイートを実行します。Hacker Newsで報告されたハーネスの感度を考慮すると、実際のスキーマに対するリグレッションスイートは本番を予測するベンチマークとなります。ApidogテストハーネスにAIエージェントを接続するで、より深いパターンを参照してください。

Apidogを無料でダウンロードして、一緒に試してみてください。モックサーバーとテストシナリオは無料ティアに含まれています。

エージェントループのコスト(そしてキャッシュがそれを決定する理由)

エージェントループは、各ラウンドで会話全体を再読み込みします。10ラウンド目には、システムプロンプト、ツールスキーマ、および9ラウンドの結果が10回目に課金されます。V4 Proの自動プレフィックスキャッシュは、この問題を解消します。各ラウンドの入力は前のラウンドに少し追加されたものとなるため、ほぼすべてのプレフィックスは1Mトークンあたり$0.435ではなく、$0.003625で課金されます。10万トークンの会話を再読み込みするコストは、キャッシュされていない場合は約$0.0435ですが、キャッシュされている場合は約$0.0004です。使用状況ブロックのprompt_cache_hit_tokensは、実際のヒット率を示します。

そのヒット率を高く保つには、以前のメッセージを変更せず、tools配列をラウンド間でバイト単位で安定させてください。プロンプトキャッシングとは何かに関する私たちの入門記事で、その仕組みを解説しています。また、1Mトークンあたり$0.14/$0.28のdeepseek-v4-flashが魅力的に見えるかもしれませんが、これは単一ショットのツールルーティングには問題ありませんが、10回以上の呼び出しを連鎖させるループでは性能が低下するため、再試行によって節約分が食いつぶされてしまいます。エージェントにはProの方が安全なデフォルトです。

FAQ

ツール定義にはトークンコストがかかりますか?

はい、tools配列はすべてのリクエストで入力として扱われます。これを安定させておけば、ラウンド1の後にキャッシュされたプレフィックスに結合され、それ以降はキャッシュヒットレートで課金されます。

ファンクションコーリングと構造化出力を組み合わせることはできますか?

はい。一般的なパターンとして、ツールが中間データを取得し、構造化された出力スキーマが最終的な回答をフォーマットするため、ダウンストリームのコードが散文を解析する必要はありません。

まとめ

DeepSeek V4 Proでのファンクションコーリングの実装は、意図的に面白みに欠けるものです。OpenAI互換のスキーマ、tool_calls配列、ID付きのtoolメッセージ。ステップ5のループが全体のアーキテクチャであり、キャッシュヒット価格により、ほとんどのチームが予想するよりも安価になります。ベンチマークではわからないのは、モデルがあなたのスキーマに対してどのように振る舞うかです。バックエンドAPIを意図的に設計し、早期にモック化し、Apidogにツール呼び出しシナリオのリグレッションスイートを保持して、スキーマの変更によってエージェントが静かに壊れないようにしてください。

ボタン

ApidogでAPIデザイン中心のアプローチを取る

APIの開発と利用をよりシンプルなことにする方法を発見できる