「Claude API、なんだか今日は遅いな」と感じたとき、最初に頭をよぎるのは「モデルを軽いものに変えようか」という選択肢ではないでしょうか。ですが、私の経験では、レイテンシ問題の半分以上はモデル側ではなく、リクエストが Anthropic のサーバーにたどり着くまでの「届くまでの経路」と、レスポンスをユーザーに返すまでの「返し方」に原因がありました。
ここではClaude API のレスポンスを体感で速くするためにインフラ層で打てる4つの施策を整理します。Opus を Sonnet にダウングレードする前に、ぜひ読み進めてみてください。
「モデルを速くする」前に「届くまで」を疑う
Claude API のリクエストが完了するまでの時間は、おおまかに4つの区間に分かれます。
- ① クライアント → Anthropic API ゲートウェイまでのネットワーク往復
- ② API ゲートウェイでの認証・キュー待機
- ③ モデル本体での推論時間
- ④ レスポンスがクライアントに戻るネットワーク往復
世の中のチューニング記事の多くは ③ に集中していますが、東京から us-east-1 経由で叩いた場合、① と ④ だけで 200〜400ms 程度を消費します。Sonnet の典型的な推論時間が 500ms 前後だとすると、ネットワーク区間が全体の 30〜40% を占めている計算です。ここを削らずにモデルだけ速くしても、ユーザーが感じる「待たされている時間」はあまり縮まりません。
私が運用しているサービスでも、最初は「とにかくモデルを軽くする」方向で改善を試みていましたが、結果的にもっとも効いたのは下記4つのインフラ施策でした。
1. リージョン選定で TTFB が変わる — 物理距離は嘘をつかない
Claude API は Anthropic 直接エンドポイントだけでなく、AWS Bedrock や Google Vertex AI 経由でも利用できます。それぞれが配置されているリージョンは異なるため、サーバーから物理的に近いリージョンを選ぶだけで、TTFB(Time To First Byte)が体感できるレベルで短縮します。
私が日本リージョンの ECS から計測した実測値では、おおよそ次のような差が出ました。
- Anthropic 直接(us-east-1 ルーティング): 平均 RTT 約 180ms
- AWS Bedrock(us-west-2): 平均 RTT 約 130ms
- AWS Bedrock(ap-northeast-1): 平均 RTT 約 25ms
東京リージョンの Bedrock を選ぶだけで、ネットワーク区間が一桁縮みます。Bedrock の Claude は Anthropic 直接 API と機能差がほぼなくなってきていますので、レイテンシが要件に効くアプリケーションでは積極的に検討する価値があります。
# bedrock_client.py
# Tokyo リージョン (ap-northeast-1) の Bedrock 経由で Claude を呼ぶ。
# Anthropic 直接 API より TTFB が短くなる典型的なパターン。
import boto3
import json
bedrock = boto3.client(
"bedrock-runtime",
region_name="ap-northeast-1", # 物理的に最も近いリージョンを指定
)
def ask_claude(prompt: str) -> str:
body = json.dumps({
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 1024,
"messages": [{"role": "user", "content": prompt}],
})
resp = bedrock.invoke_model(
modelId="anthropic.claude-sonnet-4-6-v1:0",
body=body,
)
payload = json.loads(resp["body"].read())
return payload["content"][0]["text"]
# 期待動作: 東京リージョンの Lambda/ECS から呼んだとき、TTFB が概ね 150ms 以下に収まるなお、リージョンを変えるだけで全部が速くなるわけではありません。モデルがそのリージョンで提供されているか、価格帯がどうなっているかは事前に確認しておく必要があります。Bedrock の Claude 提供状況は AWS のドキュメントで確認できますので、移行前に必ず照合してください。
2. HTTP 接続プーリングで毎回のハンドシェイクを省く
リクエストごとに新しい TCP コネクションと TLS ハンドシェイクを行っていると、それだけで毎回 100〜200ms を浪費します。Node.js の fetch や Python の requests をデフォルト設定のまま使うと、接続が再利用されないケースが少なくありません。
Anthropic SDK は内部で接続を再利用しますが、自前で fetch ベースの薄いクライアントを書いている場合は、HTTP/2 の Agent や Keep-Alive Agent を明示的に設定するのが効きます。
// claude-client.ts
// Node.js で keep-alive エージェントを使い、接続を再利用する例。
// 短時間に多数のリクエストを発行するバッチ処理で特に効果が大きい。
import Anthropic from "@anthropic-ai/sdk";
import { Agent } from "undici";
const dispatcher = new Agent({
keepAliveTimeout: 60_000, // 60 秒は接続を維持
keepAliveMaxTimeout: 300_000, // 最長 5 分まで延命
connections: 32, // 同時 32 本までプール
pipelining: 1,
});
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY!,
fetch: (url, init) =>
// @ts-expect-error: undici dispatcher を fetch の第2引数に注入
fetch(url, { ...init, dispatcher }),
});
export async function chat(prompt: string) {
return client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
});
}
// 期待動作: 連続 100 リクエスト時の平均レイテンシが、デフォルト設定比で 80ms ほど短縮するハンドシェイクの削減は地味に見えますが、バッチジョブやエージェント実行のように1リクエスト=1セッションで終わらない処理では、累積するとかなりの差になります。
3. プロンプトキャッシュの戦略的配置でトークン処理を 90% スキップする
長いシステムプロンプトや大きなコンテキストを毎回フルで送っている場合、Claude のプロンプトキャッシュ機能を使うとレイテンシとコストの両方が劇的に下がります。キャッシュヒット時はトークンの再パース・再エンベッディングがスキップされるため、特に1回目の応答が出るまでの時間が短くなります。
設計のコツは「変わらない部分」と「変わる部分」を意識的に分けることです。私はだいたい次のような順序で messages を組み立てています。
- 最上部: システム指示(数千トークン規模・キャッシュ対象)
- 中段: 参照ドキュメントや RAG コンテキスト(キャッシュ対象)
- 最下段: ユーザーごとの可変クエリ(キャッシュ対象外)
// cached_messages.ts
// system プロンプトと参照コンテキストにキャッシュブレイクポイントを置く構成。
// 2回目以降の呼び出しで初回 200〜400ms かかっていた部分が 30ms 程度に縮む。
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
export async function answer(question: string, longContext: string) {
return client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
system: [
{
type: "text",
text: "あなたは社内ドキュメントに精通したアシスタントです。常に出典を明示してください。",
cache_control: { type: "ephemeral" }, // ← ここまでをキャッシュ
},
{
type: "text",
text: longContext, // 数万トークンの参照資料
cache_control: { type: "ephemeral" }, // ← ここまでもキャッシュ
},
],
messages: [{ role: "user", content: question }],
});
}
// 期待動作: 同じ system + longContext で連続呼び出しすると、
// レスポンスの "cache_read_input_tokens" が大半を占め、TTFB が顕著に短縮するキャッシュは 5 分の TTL があり、その間のヒット率を最大化することがポイントです。ユーザーごとの会話セッションが短時間で終わってしまうサービスでは効きづらいですが、ナレッジ検索や社内チャットボットのように同じコンテキストを繰り返し参照するケースでは投資対効果が非常に高い施策です。
4. ストリーミング採用で「最初の文字」を 1 秒以内に出す
ユーザーが感じる「速さ」は、レスポンス全文が返ってくるまでの時間ではなく、画面に最初の文字が現れるまでの時間で決まります。チャット UI やライティング支援のように出力が長くなる用途では、必ず stream: true を使うことをおすすめします。
// streaming.ts
// Server-Sent Events 風にトークンを順次クライアントに流す例。
// 全文を待たずに表示できるので、体感レイテンシが大きく改善する。
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
export async function* streamAnswer(prompt: string) {
const stream = await client.messages.stream({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
});
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
yield event.delta.text;
}
}
}
// 期待動作: ユーザー画面に最初のトークンが現れるまでの時間(TTFB)が、
// 一括応答の半分以下に短縮するケースが多いストリーミングを採用するときは、途中切断・再接続・タイムアウトの扱いが少し複雑になりますので、運用前にテストしておくとよいでしょう。詳しくは Claude API のストリーミング切断を診断する手順 で整理していますので、必要に応じて参照してみてください。
レイテンシ計測なしの最適化は信頼できない
ここまでの4施策はどれも有効ですが、計測のないままに導入してしまうと、効果が出ているのか分からないまま「なんとなく速くなった気がする」で終わってしまいがちです。私が必ず計測しているのは次の3つの値です。
- TTFB(最初のトークンが返るまでの時間)
- 全完了時間(最後のトークンが返るまでの時間)
- 入力トークン数のうちキャッシュヒットした割合
これらは Claude API の OpenTelemetry 観測ガイド で扱った OpenTelemetry の Span 属性として簡単に出力できます。施策ごとにビフォー・アフターを記録しておくと、効果の薄い施策を切り分けられて再現性のある改善になります。
今日からできる、最初の打ち手
4つの施策を一度に入れるのは現実的ではありませんから、私は普段こんな順序で着手しています。
まずは Bedrock の東京リージョンに切り替えて TTFB を計測します。次にキャッシュブレイクポイントを system プロンプトに置く。その2つで効果が見えてから、接続プーリングとストリーミングへ進む。順番を守ると、施策ごとの効果が明確に切り分けられて、後戻りが少なく済みます。
「Sonnet を Haiku に変える」という判断は、本当はこの4施策をひと通り試してから検討するもの、というのが私の感覚です。モデルを変えると応答品質も変わってしまいますので、インフラ側で稼げる速度はインフラ側で稼ぐ、というスタンスをまずは取ってみてください。