Doubao API は OpenAI 互換インターフェースを提供していますが、「互換」は「そのまま写せばよい」という意味ではありません。最もつまずきやすいのは 3 点です。Base URL を /v1 と書いてしまう、model に表示名を入れてしまいモデル ID やエンドポイント ID を使わない、最初の呼び出しで深い思考を有効にして出力が途中で切れる。本記事では順番に curl と Python を通し、その後にストリーミング出力、ツール呼び出し、よくあるエラー、移行チェックリストを説明します。

注記:Doubao、Volcengine Ark、Volcengine、ByteDance は ByteDance およびその関連会社の商標または製品名です。OpenAI はその権利者の商標です。本記事は技術的な接続方法の説明のみを目的としており、上記の会社とは提携、代理、推薦の関係にありません。インターフェース、モデル、割当は変更される場合があるため、公式ドキュメントとサイト内のモデル価格を基準にしてください。

一、接続前に確認する 3 点

項目 Volcengine Ark への直結 統一ゲートウェイ
Base URL https://ark.cn-beijing.volces.com/api/v3 https://ai.zhongshangqihang.com/v1
認証 Authorization: Bearer <ARK_API_KEY> Authorization: Bearer <ゲートウェイキー>
model モデル ID または ep- エンドポイント ID サイト内のモデル一覧を基準
向くケース Doubao のみを使い、自分で本人確認・チャージ・請求を行う 複数モデル、統一請求と割当

キーと Base URL は必ず対で使います。公式キーにゲートウェイのアドレスを設定したり、ゲートウェイキーに公式アドレスを設定したりすると、通常はそのまま 401 が返ります。入口を切り替えるときは、2 つを一緒に変更してください。

二、Base URL とリクエストパス:/api/v3 であり /v1 ではない

公式のデータプレーンアドレスは https://ark.cn-beijing.volces.com/api/v3 で、チャット補完のパスは /chat/completions、完全なアドレスは https://ark.cn-beijing.volces.com/api/v3/chat/completions です。OpenAI で一般的な /v1 の接頭辞はなく、さらに /v1 を結合すると 404 になります。

地域はドメインに含まれます(cn-beijing)。リソースが別の地域にある場合は、コンソールとドキュメントが示すアドレスを基準にしてください。公式ドキュメントはさらに、Coding Plan などでは別の Base URL を使うと説明しており、誤って設定すると追加料金が発生する場合があります。プランを切り替える前に確認してください。

三、model に入れるもの:モデル ID かエンドポイント ID か

公式ドキュメントは 2 つの呼び出し方式を示しています。

  • プリセット推論:モデル ID を直接指定します。たとえば doubao-seed-2-1-pro-260915 で、事前にエンドポイントを作成する必要はありません。
  • カスタム推論エンドポイント:エンドポイント ID を指定します。通常は ep- で始まり、固定デプロイ、精细な管理、LoRA 微調整モデルに向きます。

モデル ID にはバージョン日付が含まれます。「Doubao 2.1 Pro」のような表示名を書いたり、記憶で接尾辞を補ったりしないでください。Access Key(AK/SK)で署名する場合、model には必ずエンドポイント ID を指定します。API キーを使う場合は、ドキュメントに従ってモデル ID またはエンドポイント ID を指定できます。InvalidEndpointOrModel.ModelIDAccessDisabled が返る場合は、現在のアカウントがモデル ID の直接呼び出しに対応していないことを示すため、エンドポイントを作成する必要があります。

四、curl で最小リクエストを通す

まず深い思考を無効にして、変数を減らします。公式入口の例です。

export ARK_API_KEY="あなたの Volcengine Ark API キー"
export ARK_MODEL_ID="doubao-seed-2-1-pro-260915"

curl "https://ark.cn-beijing.volces.com/api/v3/chat/completions" \
  -H "Authorization: Bearer $ARK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "'"$ARK_MODEL_ID"'",
    "messages": [{"role": "user", "content": "OK とだけ返してください"}],
    "thinking": {"type": "disabled"},
    "max_tokens": 32
  }'

200 が返り、choices[0].message.content に内容があれば、キー、アドレス、モデルの 3 つが正しいと分かります。404 の場合は、model にバージョンの接尾辞が付いているか、ep- エンドポイントに切り替える必要がないかを先に確認してください。401 の場合はキーとリクエストヘッダーを先に確認し、残高を疑うのはその後です。

五、Python で OpenAI SDK を使う

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["ARK_API_KEY"],
    base_url="https://ark.cn-beijing.volces.com/api/v3",
)

resp = client.chat.completions.create(
    model=os.environ.get("ARK_MODEL_ID", "doubao-seed-2-1-pro-260915"),
    messages=[
        {"role": "system", "content": "あなたは日本語の技術アシスタントです。簡潔に回答してください。"},
        {"role": "user", "content": "推論エンドポイントとは何かを 3 文で説明してください。"},
    ],
    max_tokens=512,
    extra_body={"thinking": {"type": "disabled"}},
)

print(resp.choices[0].message.content)
print(resp.usage)

OpenAI SDK は thinking フィールドを認識しないため、extra_body で渡します。ゲートウェイやモデルが対応していない場合は、このフィールドを外せば済みます。通ったらまず usage の入力・出力 Token を確認し、そのうえでタイムアウト、同時実行、コスト予算を決めてください。

六、ストリーミング出力:delta、reasoning_content、[DONE]

stream=True を設定すると、サーバーは SSE でブロックごとに返します。レスポンスオブジェクトは chat.completion.chunk で、本文は choices[0].delta.content、思考内容は delta.reasoning_content にあります。finish_reason は最後の chunk にだけ現れ、終了時には data: [DONE] が送られます。

stream = client.chat.completions.create(
    model=os.environ["ARK_MODEL_ID"],
    messages=[{"role": "user", "content": "CSV を読み込む Python のサンプルを書いてください"}],
    stream=True,
    stream_options={"include_usage": True},
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")

思考ブロックは content が空の場合があります。すべての chunk に本文があると決めつけないでください。max_tokens が小さすぎると出力が切れます。ネットワークが中断しても受信済みの内容は残るため、クライアントは部分的な結果を保存し、再試行するかどうかを判断できる必要があります。

七、ツール呼び出し:tools、tool_calls、返送の順序

tools の書き方は OpenAI に近いものです。各ツールは type: "function" と function を含み、name、description、parameters が必須で、parameters は JSON Schema を使います。モデルが呼び出しを決めると、レスポンスの finish_reason は tool_calls になり、message.tool_calls[i].function.arguments は JSON 文字列として返るため、自分で解析する必要があります。

返送の順序は間違えられません。まず tool_calls を含む assistant メッセージを会話に追加し、次に各呼び出しに対応する role: "tool" のメッセージを、tool_call_id と結果を付けて追加します。そのうえで再度モデルにリクエストし、finish_reason が tool_calls でなくなるまで繰り返します。parallel_tool_calls は既定で true で、false にすると 1 回に 1 つのツールだけを呼ぶよう制限できます。ストリーミングでは、ツール呼び出しの差分に index が付くため、index ごとに arguments を蓄積してください。

八、よくあるエラー:401、404、429 の特定

HTTP 典型的なエラーコード 意味 対処
401 AuthenticationError キーの欠落、無効、またはヘッダーに Bearer がない キーをコピーし直し、Authorization ヘッダーを確認
404 InvalidEndpointOrModel.NotFound モデルまたはエンドポイントが存在しない、現在のスペースに権限がない コンソールから完全な ID をコピーし、地域とスペースを確認
404 ModelNotOpen モデルがアカウント内でまだ開通していない Ark コンソールで対象モデルを開通
404 InvalidEndpointOrModel.ModelIDAccessDisabled アカウントがモデル ID の直接呼び出しに対応していない カスタムエンドポイントを作成し、ep- ID に変更
400 InvalidParameter、MissingParameter パラメータの欠落、型の誤り、モデルが非対応 ドキュメントと照合してフィールドを修正し、まず最小リクエストを実行
403 AccessDenied、AccountOverdueError 権限がない、または残高がマイナス キーとスペースの権限を確認し、未払いを処理
429 RateLimitExceeded.* RPM/TPM の超過またはトラフィックの急増 同時実行を下げ、指数バックオフ、必要なら増枠を申請
500/503 InternalServiceError、ServerOverloaded 上流の異常または過負荷 少し待って再試行し、予備経路を用意

九、移行チェックリスト

公式直結からゲートウェイへ、またはゲートウェイ間で切り替える場合は、次の順序で確認します。

  1. 実プロンプトの組を固定し、現在の出力、所要時間、usage を記録して比較の基準にする。
  2. base_url と api_key を同時に変更する。片方だけ変えない。
  3. サイト内のモデル価格に従ってモデル名のマッピングを再確認し、古い名前を流用しない。
  4. ストリーミング出力を検証する。最初の Token までの時間、delta.content、finish_reason、[DONE]。
  5. ツール呼び出しを検証する。tools、tool_calls、tool_call_id の返送、マルチターンの順序。
  6. thinking、reasoning_effort などの拡張パラメータに対応しているか、既定値が異ならないかを検証する。
  7. 接続と読み取りのタイムアウトを設定し、429、5xx、タイムアウトにだけ再試行する。書き込み操作は冪等性を確保する。
  8. まず流量の 5%〜10% で 3〜7 日観察し、公式直結をロールバック経路として残す。

統一入口が必要な場合は、登録ページでテストキーを作成できます。モデルの範囲と課金の基準はモデル価格に記載されています。ZSQH AI Gateway は独立した第三者サービスであり、ByteDance とは提携関係にありません。

関連記事:

よくある質問

Base URL に /v1 を付ける必要がありますか?

いいえ。Volcengine Ark の OpenAI 互換入口は https://ark.cn-beijing.volces.com/api/v3 で、チャットのパスは /chat/completions です。さらに /v1 を結合すると 404 になります。ゲートウェイのアドレスは通常 /v1 で終わるため、両者を混ぜないでください。

model にはモデル ID と ep- で始まるエンドポイント ID のどちらを入れますか?

プリセット推論ではモデル ID、カスタムエンドポイントでは ep- ID を指定します。Access Key で署名する場合は必ずエンドポイント ID を指定します。ModelIDAccessDisabled が返る場合は、キーを変えるのではなくエンドポイントを作成する必要があります。

最初の呼び出しで深い思考を有効にすべきですか?

まず無効にすることをおすすめします。最短のリクエストで経路が通ることを確認してください。一部の Doubao モデルは深い思考が既定で有効で、有効にすると出力の前に思考内容が生成されるため、max_tokens が小さいと本文が切れやすくなります。経路を確認してからタスクに応じて有効にしてください。

ストリーミング出力に内容がないのはなぜですか?

まず stream=True を確認し、次に delta.content だけを読んで空のブロックを無視していないかを確認します。思考内容は delta.reasoning_content にあります。さらに max_tokens、ネットワークのバッファリング、プロキシが SSE を一括レスポンスにまとめていないかも確認してください。

ツール呼び出しで assistant の tool_calls を返送する必要があるのはなぜですか?

プロトコル上、assistant メッセージが tool_calls を持ち、その後の各 role: "tool" メッセージが tool_call_id で結果を対応付ける必要があります。この手順を飛ばすと、モデルがツールを重複して呼び出したり、400 を返したりする場合があります。ストリーミングでは index ごとにパラメータを蓄積してください。