📚 全体地図を見る
APIでLLMを呼ぶとは技術的に何をやっているのか
「APIでClaudeを呼ぶ」「OpenAI APIをつないだ」。
毎日耳にする言い回しですが、中で何が起きているのかを問われると、急に答えが曖昧になる人が多い領域です。
SDKを入れて client.messages.create(...) と1行書けば答えが返ってくる、そこまでは分かる。でもその1行がネットワーク層で具体的にどのHTTPリクエストなのか、なぜある時はタイプ直後から文字が流れ始めて別の時は3秒待たされるのか、なぜ「モデルが関数を実行した」という言い方が技術的にはほぼ嘘なのか。ここを一度きちんと洗うと、この先のagent・streaming・tool useの話がまったく違って読めるようになります。
この記事は6,000〜7,500字でそれを一気に洗います。
- Anthropic Messages vs OpenAI Chat CompletionsのJSON構造
- Streamingが実はSSEという古いWeb標準で動いているという話
- Tool Useラウンドトリップは4ステップで、モデルは関数を実行していないという話
- トークン経済学 — 価格表・バッチ・キャッシュ・Opus 4.7トークナイザ +35%の罠
- Rate limitが5軸(RPM/TPM/RPD/TPD/IPM)で効いている話
- SDK vs Raw HTTPの使い分け
いきましょう。
1. POSTリクエスト一発がすべて
最初にこの一文を頭に刻んでから進みます。
LLM APIを呼ぶ = https://api.anthropic.com/v1/messages にHTTPS POSTを1発打つ、それだけ。
本当にそれだけです。
Anthropicだろうと OpenAIだろうと、SDKだろうとPythonでもNodeでもGoでも、最終的にネットワーク層で起きているのはHTTPSの上にJSONボディを載せたPOSTが1発。残り(SDK・streaming・tool use)は全部、このPOST 1発をどう包むか・応答をどう読むかの話でしかありません。
curlで直接叩くとこうなります。
POST /v1/messages HTTP/1.1
Host: api.anthropic.com
x-api-key: sk-ant-...
anthropic-version: 2023-06-01
content-type: application/json
{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"system": "You are a helpful assistant.",
"messages": [
{"role": "user", "content": "LLM APIって何?"}
]
}
これで全部。x-api-key にキーを載せて、JSONボディを投げると、レスポンスはJSONで返ってきます。
戻りもそう難しくない形です。
{
"id": "msg_01XYZ...",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-7",
"content": [
{"type": "text", "text": "LLM APIというのは..."}
],
"stop_reason": "end_turn",
"usage": {"input_tokens": 18, "output_tokens": 142}
}
contentに回答テキストusageに今回の呼び出しで使ったトークン数(これが課金基準)stop_reasonに終わった理由(end_turn/max_tokens/stop_sequence/tool_useのいずれか)
SDKだろうとcurlだろうと、この構造の上で動いています。だからデバッグがこじれたら raw HTTPに降りて見るのが一番速いと言われるわけで、SDKが隠しているものを剥がすと、下はこのJSON二塊しか残らないのです。
2. Anthropic Messages vs OpenAI Chat — 双子のように違う
どちらも「メッセージ配列を投げると答えが返る」という骨格は同じ。ただし覗き込むと、小さな差が実装中に何度も足を引っかけてきます。大きい差を2つだけ。
差1 — systemの位置。
Anthropic Messages APIは system が messages の外側 にあります。
{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"system": "You are a senior Python engineer.",
"messages": [
{"role": "user", "content": "..."}
]
}
OpenAI Chat Completions は system が messages 配列の最初の要素 として入ります。
{
"model": "gpt-5",
"messages": [
{"role": "system", "content": "You are a senior Python engineer."},
{"role": "user", "content": "..."}
]
}
同じ概念で位置が違う、それだけ。でもOpenAIコードをClaudeに移植するときに一番バグが出る場所はここです。SDKが吸収してくれるから普段は気付かないだけで。
差2 — max_tokens の格付け。
Anthropicは max_tokens が必須。抜くと400エラー。「出力長の上限は常に明示せよ」という設計思想です。
OpenAIは max_tokens (または max_completion_tokens) がオプション。指定しなければモデルの上限までフルに出します。
細かい差に見えて実務では大きい。Claudeはトークン予算を先に固定するのでコスト予測がしやすい。OpenAIは指定せず回すと予想外の請求書が飛んでくる余地があります。
共通点 はこう整理できます。
messagesは{role, content}オブジェクトの配列roleはuser/assistant/system(OpenAIはtoolも、Anthropicはtool_use/tool_resultブロックで表現)stream: trueを入れるとServer-Sent Eventsに切り替わるtools配列に関数スキーマを入れるとtool useモード
2026年4月時点で、Anthropicの現行主力は Opus 4.7 · Sonnet 4.6 · Haiku 4.5 の3枚、OpenAIは GPT-5 · GPT-5.4 がフラグシップ。モデル名を差し替えれば残りはほぼそのまま動くインターフェースです。
3. Streaming = SSEプロトコルの上で回っている話
ChatGPTで答えが一文字ずつ流れてくるあの挙動、ネットワーク層では Server-Sent Events(SSE) という古いWeb標準の上で動いています。
WebSocketではありません。HTTP/1.1のコネクションを開いたまま、サーバーが data: {...}\n\n 形式のテキストチャンクを連続で押し込んでくる片方向プッシュ。単方向なので実装もずっとシンプルです。
リクエストで stream: true を入れるだけで、応答は一塊で返ってこなくなり、イベント列に刻まれて飛んできます。
Anthropicの場合、イベント順序はかなり構造的です。
event: message_start
data: {"type":"message_start","message":{"id":"msg_01...","role":"assistant",...}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"LLM"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" API"}}
... (数十〜数百のdelta) ...
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":142}}
event: message_stop
data: {"type":"message_stop"}
まとめると message_start → content_block_start → content_block_delta × N → content_block_stop → message_delta → message_stop がワンセット。
OpenAIはもう少しフラットで、choices[0].delta.content にトークン断片が連続で入り、終わると finish_reason 付きの最終チャンクのあとに data: [DONE] 一行が打たれてコネクションが閉じます。
data: {"choices":[{"delta":{"content":"LLM"},"finish_reason":null}]}
data: {"choices":[{"delta":{"content":" API"},"finish_reason":null}]}
...
data: {"choices":[{"delta":{},"finish_reason":"stop"}]}
data: [DONE]
なぜこう設計するか。
LLMは前回見た通り、1トークンずつ順次生成します。完成してから丸ごと渡す形だと、500トークン出力は体感5〜10秒じっと待たされる。Streamingは最初のトークンが生成された瞬間からクライアントへ押し込むので、TTFT(Time To First Token) が1秒前後まで落ちます。ChatGPTの「タイピングされる」UXが成立しているのはこれ。
◀── event: message_start
◀── event: content_block_start
◀── event: content_block_delta (TTFT ≈ 0.5〜1.5s)
◀── event: content_block_delta × N
◀── event: content_block_stop
◀── event: message_delta (usage)
◀── event: message_stop
SDKを使えば for chunk in response ループで書けるようにSSEパースは自動ですが、デバッグするならcurlに --no-buffer を付けて生チャンクを直接見るのが最短です。
4. Tool Useラウンドトリップ — モデルは関数を実行しない
ここで一番誤解が積もります。
「Claudeが天気APIを呼んだ」「GPTが自分のDBを叩いた」。
違います。 モデルは関数を実行していません。モデルは「この関数をこの引数で呼べ」というJSONを吐いただけで、実際に実行したのはあなたのコードです。
この一点を押さえるとtool use系バグの9割は解けます。
流れは4ステップ。
[1] クライアント → モデル: toolsスキーマと一緒にリクエストを送る。
{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"tools": [{
"name": "get_weather",
"description": "Get current weather for a city",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}],
"messages": [
{"role": "user", "content": "東京の天気は?"}
]
}
[2] モデル → クライアント: stop_reason: "tool_use" と一緒に、関数呼び出しの意図をJSONで返してくる。
{
"stop_reason": "tool_use",
"content": [
{"type": "text", "text": "天気を確認します。"},
{"type": "tool_use", "id": "toolu_01ABC",
"name": "get_weather",
"input": {"city": "Tokyo"}}
]
}
OpenAIではこれが tool_calls 配列で返ってきます。名前が違うだけで概念は同じ。
[3] クライアントが実際に関数を実行する。 ここでモデルは関与しません。get_weather("Tokyo") を実APIでもDBでも何でもいいので呼んで結果を得るのは、まるごとあなたのコードの責任です。
[4] クライアント → モデル: 実行結果を tool_result としてmessagesに追加し、同じ会話コンテキストで再リクエストする。
{
"messages": [
{"role": "user", "content": "東京の天気は?"},
{"role": "assistant", "content": [
{"type": "text", "text": "天気を確認します。"},
{"type": "tool_use", "id": "toolu_01ABC",
"name": "get_weather", "input": {"city": "Tokyo"}}
]},
{"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "toolu_01ABC",
"content": "21°C, 晴れ"}
]}
]
}
ここで初めてモデルは「東京は今21度で晴れですね」という最終回答をテキストで出してきます。
messages + tools[ {name, input_schema} ]
stop_reason:”tool_use” + { name, input } (JSON)
※ モデルは実行しない。「呼べ」と言うだけ。
実際に get_weather(“Tokyo”) を実行 → “21°C, 晴れ”
messagesにtool_resultを追加 → 再リクエスト → 最終回答
この構造から来る帰結が大事です。
- モデルが危険なクエリ(例:
DROP TABLE users)を「実行」することはできません。実行するのはあなたのコード。あなたが止めれば止まる。 - 1ターンの中でtoolが複数回回るのもあり。tool_use → execute → tool_result → tool_use → execute → tool_result → … の形でagent loopがこの上で回ります。
- だからagent開発は「モデルが賢いか」よりも「tool_resultをどれだけ整理して戻すか」が生産性を決めます。
5. トークン経済学 — 価格表と Opus 4.7 +35% の罠
APIをプロダクションに載せる前に、必ず一度は向き合わないといけないのが課金構造。
2026年4月時点の主要モデル価格を並べると以下の通り。すべて 1Mトークンあたり USD。
| モデル | Input | Output |
| Claude Opus 4.7 | $5 | $25 |
| Claude Sonnet 4.6 | $3 | $15 |
| Claude Haiku 4.5 | $1 | $5 |
| GPT-5 | $1.25 | $10 |
| GPT-5.4 | $2.50 | $15 |
| GPT-4.1 nano | $0.10 | $0.40 |
読みどころ3つ。
ポイント1 — 出力は入力の4〜5倍高い。
ほぼすべてのモデルでoutput単価はinputの4〜5倍。だからコスト最適化の第1手は出力長を削るです。max_tokens をタイトに切る、system に「短く答えよ」と書く、必要ならJSONモードで構造化出力だけ受ける、という順番。
ポイント2 — 割引カードが2枚ある。
- Batch API: -50%。リアルタイム性がいらない大量処理(データ分類・一括翻訳など)はBatchに投げれば半額。代わりに24時間以内返却というSLA。
- プロンプトキャッシュ: キャッシュヒット部分で最大 -90%。同じsystem promptや長い文書を繰り返し投げるケースで大幅割引。Anthropicは
cache_controlブロックを明示的に張る方式、OpenAIは一定条件で自動。
RAGや固定の長いsystem promptを使うアプリでキャッシュはほぼ必須。張らずに回すと金が溶けます。
ポイント3 — Opus 4.7「同じテキストで+35%トークン」の罠。
Anthropicの公式発表によると、Opus 4.7は新しいトークナイザを採用しており、同じ入力テキストが旧バージョン比で約+35%多いトークン数としてカウントされます。
つまり、単価表だけ見ると同額でも、実質コストが1.35倍になっているに近い挙動。Sonnet 4.6 → Opus 4.7 への切り替え時に、カード単価だけで「$15 → $25、1.67倍」と算出すると外します。トークン数の増加分まで掛けて約2.25倍で見積もるべきです。
公式ドキュメントに「注意」として明記された罠なのでこれは推定ではありません。モデル切替前に実トークン数を1回ベンチしておくのが無難。
6. Rate Limit 5軸 — RPMだけ見ると刺される
開発者が一番ぶつかるのが 429 Too Many Requests。ここで多くの人がRPM(分あたり要求数)だけ上げようとしますが、レート制限はそれより多層です。
OpenAI公式ドキュメント基準で、制限は5軸で効きます。
- RPM (Requests Per Minute) — 分あたり要求数
- TPM (Tokens Per Minute) — 分あたり処理トークン合計(入力+出力)
- RPD (Requests Per Day) — 日次要求数
- TPD (Tokens Per Day) — 日次トークン合計
- IPM (Images Per Minute) — ビジョン/画像入力専用(マルチモーダル向け)
このうちどれか1つにヒットした時点で429が落ちます。「リクエスト頻度は低いのになぜ弾かれる?」→ 答えの多くは長大promptでTPMを焼き切った、というパターン。
OpenAIのTier構造を例に取ると、
- Tier 1 (加入直後): 数万TPMレベル
- Tier 2〜3 (スタートアップ初期、累計 $50〜$500 決済): 2〜4M TPM
- Tier 5 (累計 $1,000+ 決済、30日以上): 30M TPM クラスまで解放
Tierは累計決済額とアカウント年齢で自動昇格。「APIをたくさん使っていれば勝手に緩む」と考えて大きく外れません。
Anthropicも構造は近く、Usageダッシュボードで現在tierと上限が確認できます。
429が出たときの定番対応は指数バックオフ(exponential backoff) + jitter。
for attempt in range(5):
try:
return client.messages.create(...)
except RateLimitError:
wait = (2 ** attempt) + random.random()
time.sleep(wait) # 1s, 2s, 4s, 8s, 16s (+ランダムjitter)
SDKの多くはこれを内蔵していて自動リトライしてくれます。だからSDKを使えば大抵の429は音もなく復旧する。Raw HTTPに降りるとこのロジックを自力で組むことになります。
7. SDK vs Raw HTTP — どう使い分けるか
結論から。
- プロダクション = SDK
- デバッグ = Raw HTTP
SDKは隠していない顔をして、実は結構いろいろ隠しています。SSEパース、リトライ、タイムアウト、トークン数集計、型チェック(TypeScript/Pydantic)、全部SDKがやってくれる。プロダクションでこれを自前実装する理由はほぼありません。
ただし変なエラーが落ちたり、SDKバージョンとAPIバージョンがずれて新機能が効かなかったりしたら、raw HTTPに降りるのが最短。curlに -v(verbose)を付けて実際のヘッダ・ボディ・ステータスを直接見ると、大抵そこで犯人が見えます。
SDKでもcurlでも、最初に見たあの構造 — JSONボディを載せたHTTPS POST一発 — の上で動いているのは変わりません。これを一度身体化しておけば、新API(例: Gemini、Mistral)を初見で当たっても1〜2時間で勘所がつかめます。
8. 一行に畳むと
- LLM API呼び出し = JSONボディを載せたHTTPS POST一発 + 応答JSONのパース。
- Anthropic と OpenAI は
systemの位置とmax_tokensの必須/任意で分岐する。 - Streamingは SSEプロトコル。イベント列に刻まれて入ってくる。
- Tool useは 4ステップのラウンドトリップ で、モデルは関数を実行しない。実行するのはあなたのコード。
- トークン経済学は Outputが Inputの4〜5倍、Batch -50%・キャッシュ -90% の割引あり、Opus 4.7は同じテキストで +35%トークン の罠。
- Rate limitは RPM・TPM・RPD・TPD・IPM の5軸。429が出たら指数バックオフ。
- SDKはプロダクション用、Raw HTTPはデバッグ用。底は同じ。
この7点が頭にあれば、agentフレームワークのコードを読むときどれがモデルの話でどれが包装の話かがクッキリ分かれます。
FAQ
Q1. SDKを使わずcurl直打ちでも大丈夫?
技術的にはプロダクションでもいけます。ただしSSEパース、指数バックオフ、タイムアウト制御、stream途中の切断処理まで全部自力で書くので手間が大きい。学習とデバッグなら curl -v --no-buffer が最強、サービス配備は公式SDKを推奨です。どうせSDK内部も同じHTTPS POSTなので。
Q2. モデルが関数を実行しないなら「Claude Codeがファイルを編集した」は嘘なの?
嘘ではありません。正確には、Claude Code(harness)が tool_useブロックを受け取り、実際にファイルシステムAPIを叩いた。モデルは「このファイルにこの内容を書け」というJSONを吐いただけで、実行しているのはharness側です。ユーザーからは一体に見えるけれど、技術的にはきっちり分かれている。これがagentセキュリティ設計の出発点でもあって、harnessが権限を絞れば、モデルがどれだけ危険なtool_useを吐いても実際には止まる、という構造になります。
Q3. Opus 4.7に切り替えたらコストが想定よりはるかに高いです。原因は?
候補は3つ。(1) 出力がinputの5倍単価だという前提を外している、(2) Opus 4.7の新トークナイザで同じテキストが約+35%多いトークンに換算されている、(3) プロンプトキャッシュを張らずに同じsystem promptを毎回フル料金で再送している。3つとも確認すると、たいていここで当たります。
次回予告
P3ではこの「API呼び出し」という概念が、クラウド(Anthropic・OpenAI) と オープンソース(Llama・Mistralなどローカル実行) でそれぞれどう枝分かれしていくのかを扱います。同じHTTP POSTに見えても、裏側で起きていることは全然違うので。
ソースリスト
- Anthropic — Messages API Reference: https://docs.anthropic.com/en/api/messages
- Anthropic — Pricing: https://platform.claude.com/docs/en/about-claude/pricing
- Anthropic — Tool Use Overview: https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview
- Anthropic — Implementing Tool Use: https://platform.claude.com/docs/en/agents-and-tools/tool-use/implement-tool-use
- Anthropic Engineering — Advanced Tool Use: https://www.anthropic.com/engineering/advanced-tool-use
- OpenAI — API Reference Overview: https://developers.openai.com/api/reference/overview
- OpenAI — Streaming Responses Guide: https://developers.openai.com/api/docs/guides/streaming-responses
- OpenAI — Chat Completions Streaming Events: https://developers.openai.com/api/reference/resources/chat/subresources/completions/streaming-events
- OpenAI — Pricing: https://developers.openai.com/api/docs/pricing
- OpenAI — Rate Limits Guide: https://developers.openai.com/api/docs/guides/rate-limits
- ◀ 前回: P1. Claude family の技術的差分と 4.7 アップグレード
- 今回: P2. APIでLLMを呼ぶとは技術的に何か
- ▶ 次回: P3. クラウド vs オープンソース LLM (準備中)
毎週月曜朝の「AIのしくみ地図」ニュースレターを無料でお届けします → 会員登録する




