Agent SDKストリーミング入力とシングルメッセージ入力の使い分け
Agent SDKのストリーミング入力とシングルメッセージ入力を評価軸で比較し、画像添付・割り込み・実行環境ごとの使い分けをまとめます。
Agent SDKには2つの入力モードがある
Agent SDKでエージェントとやり取りする方法は、ストリーミング入力とシングルメッセージ入力の2つに分かれます。ストリーミング入力は常駐プロセスとして動く対話的なセッションで、公式ドキュメントも推奨モードと位置づけています。シングルメッセージ入力は1回のリクエストで完結する単発クエリで、continueによるセッション継続は使えますが会話的なやり取りには向きません。どちらを選ぶかで、実装できる機能とコードの構造がまったく変わります。
ストリーミング入力モードでできること
ストリーミング入力は、ユーザー入力を受け取りながら割り込みを処理し、権限リクエストを表面化し、セッション管理をこなす長寿命プロセスとしてエージェントを動かします。持続的なセッションの中で使える機能は次の5つです。
- 画像をメッセージへ直接添付して視覚的な分析をさせられる
- 複数のメッセージを順番に処理しつつ途中で割り込める
- セッション中フルにツールとカスタムMCPサーバーへアクセスできる
- 最終結果だけでなく応答が生成される過程をリアルタイムに見られる
- 複数ターンにわたる会話の文脈を自然に維持できる
実装は非同期ジェネレータでメッセージをyieldし、query()のpromptにそのジェネレータを渡す形になります。
import { readFile } from "fs/promises";
import { query, type SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
async function* generateMessages(): AsyncGenerator<SDKUserMessage> {
yield {
type: "user",
message: { role: "user", content: "Analyze this codebase for security issues" },
parent_tool_use_id: null,
};
await new Promise((resolve) => setTimeout(resolve, 2000));
yield {
type: "user",
message: {
role: "user",
content: [
{ type: "text", text: "Review this architecture diagram" },
{
type: "image",
source: {
type: "base64",
media_type: "image/png",
data: await readFile("diagram.png", "base64"),
},
},
],
},
parent_tool_use_id: null,
};
}
for await (const message of query({
prompt: generateMessages(),
options: { maxTurns: 10, allowedTools: ["Read", "Grep"] },
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}PythonではClaudeSDKClientを使い、query()にメッセージジェネレータを渡してからreceive_response()で応答を読みます。1回のreceive_response()ループは最初の結果メッセージで終わるため、複数の応答を読みたい場合はquery()とreceive_response()のペアをメッセージごとに繰り返す実装にします。TypeScript版は逆に、1つのループでジェネレータがyieldする全メッセージへの応答を続けて受け取れるため、同じ「2通のメッセージを送って2つの応答を見る」処理でも、言語によってループの組み方が変わる点に注意します。
上のサンプルは作業ディレクトリにあるdiagram.pngという画像ファイルを読み込む前提で書かれています。手元で動かす場合は同名の画像を用意するか、読み込み先のファイル名をコード側で書き換えてから実行してください。
シングルメッセージ入力の使い所と制限
シングルメッセージ入力は実装がシンプルですが、できることは限られます。向いているのは、1回きりの応答が欲しいとき、画像添付やセッション途中の制御メソッドが不要なとき、そしてLambda関数のようなステートレスな環境で動かす必要があるときです。
このモードは、メッセージへの直接的な画像添付、動的なメッセージキューイング、リアルタイムの割り込み、自然な複数ターン会話のいずれにも対応しません。continue: true(TypeScript)やcontinue_conversation=True(Python)でセッションを継続することはできますが、それは「前回の続きから新しいクエリを1件投げる」動作であって、対話的な会話ではありません。
// 1回きりのクエリ
try {
for await (const message of query({
prompt: "Explain the authentication flow",
options: { maxTurns: 5, allowedTools: ["Read", "Grep"] },
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}
} catch (error) {
console.error(`Query failed: ${error}`);
}
// セッション管理付きで会話を継続
for await (const message of query({
prompt: "Now explain the authorization process",
options: { continue: true, maxTurns: 5 },
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}クエリがerror_max_turnsのようなエラー結果で終わると、シングルメッセージのquery()は最終結果メッセージをyieldした後で、失敗内容を含むエラーをthrowします。処理を続ける必要があるコードでは、必ずtryブロックで囲みます。
2つの入力モードを評価軸で比較する
| 評価軸 | ストリーミング入力 | シングルメッセージ入力 |
|---|---|---|
| 画像添付 | ストリーミング入力メッセージに直接添付できる | シングルメッセージ入力直接添付はできない |
| 途中の割り込み・追加指示 | ストリーミング入力実行中に送信できる | シングルメッセージ入力対応しない |
| 複数ターンの自然な会話 | ストリーミング入力セッション内で自然に継続する | シングルメッセージ入力continueで継続はできるが会話的ではない |
| 実行モデル | ストリーミング入力常駐プロセスとして持続する | シングルメッセージ入力1回ごとに完結するリクエスト |
| 向いている環境 | ストリーミング入力対話型UI、chatインターフェース | シングルメッセージ入力ステートレス環境(Lambda等)、単発処理 |
表からわかるとおり、両者はどちらかが上位互換という関係ではありません。ストリーミング入力は機能が豊富な代わりに常駐プロセスの管理が要り、シングルメッセージ入力は機能を絞る代わりに呼び出しごとに独立して完結します。
エラーハンドリングの違いに注意する
2つのモードはエラーの見え方も違います。TypeScriptのストリーミング入力では、メッセージジェネレータの中で例外が発生すると(存在しないファイルを読もうとしたときなど)、ストリームは元のエラーではなくClaude Code process aborted by userという文言で終了します。このメッセージを見たら、まずジェネレータ内部のコードを疑う必要があります。バンドルされたSDKソースの長いミニファイ済みの行がエラーの前に出ることもあるため、出力の末尾まで読むようにします。
Pythonのストリーミング入力では、ジェネレータの例外はデバッグレベルでログに記録されるだけで、例外自体はraiseされずセッションが応答なしで止まります。出力がないままセッションがハングしたら、デバッグログを有効にしてジェネレータを確認します。
canUseToolを使うならPythonはストリーミング必須
Pythonのcan_use_toolコールバックはストリーミングモードを前提にしています。query(prompt=generator)のように有限のメッセージストリームを渡すと、SDKは最後のメッセージの後で入力ストリームを閉じてしまい、登録済みのフックやインプロセスのMCPサーバーが開いたままにしていない限り、権限コールバックが呼ばれる前に閉じてしまいます。ClaudeSDKClient.connect()をプロンプトなしで呼び、ClaudeSDKClient.query()でメッセージを送る形にすれば、ストリームは自動的に開いたままになりフックは不要です。承認や確認質問のハンドリングを実装する具体的な手順はAgent SDK canUseToolで承認と確認質問を処理するにまとめています。
それぞれの強みと弱み
ストリーミング入力の強みは、機能の網羅性とリアルタイム性です。画像添付・割り込み・ツール統合・応答の逐次表示・文脈の自然な維持まで、Agent SDKが持つほぼすべての能力にフルアクセスできます。弱みは、プロセスをどう常駐させ続けるかという運用の設計が別途必要になる点です。メッセージジェネレータが例外を投げたときのエラーメッセージが本来の原因を隠してしまう挙動(前述)もあり、デバッグの見通しはシングルメッセージ入力より悪くなりがちです。
シングルメッセージ入力の強みは、呼び出しごとに完結する単純さです。プロセスの寿命がリクエストの寿命と一致するため、Lambda関数のような環境にそのまま乗せられ、エラーハンドリングも「1回のクエリが成功したか失敗したか」だけを見ればよいシンプルな形になります。弱みは、画像添付・割り込み・自然な複数ターン会話という、対話的な体験に欠かせない機能を最初から諦める必要がある点です。continueでセッションを継続しても、それは新しいクエリを1件積み増しているだけで、会話の途中で介入する手段にはなりません。
どちらのモードを選ぶかは、機能の充実度を取るか、プロセスライフサイクルの単純さを取るかという設計判断です。両方が必要に見える場合は、多くの場合は対話部分とバッチ処理部分を別のエンドポイントに分け、それぞれに向いたモードを充てる構成に落ち着きます。
移行するときに見落としやすい落とし穴
シングルメッセージ入力のcontinue: trueは、同じセッションで次のクエリを1件投げるだけの仕組みです。実行中のクエリへ追加の指示を割り込ませる手段ではありません。運用中に「途中で方向転換させたい」という要件が出てきた時点で、シングルメッセージのままでは実現できず、ストリーミング入力への書き換えが必要になります。この境目を先に理解しておかないと、後から作り直す羽目になります。
ストリーミング入力のジェネレータは、yieldした順序どおりにメッセージが処理される設計です。複数のメッセージを条件を待たずに一気にyieldしてしまうと、意図せず「あらかじめキューに積んだ順」で機械的に処理されるだけになり、ユーザーの操作に応じて逐次送るというストリーミングの利点が消えてしまいます。awaitで条件やユーザー操作を待ってから次のyieldに進む書き方が前提です。
maxTurnsやallowedToolsのようなオプションは両モードで共通して渡せますが、canUseToolのような一部のコールバックはPythonのストリーミングモードでしか安定して動かない制約があります。モードを選ぶ前に、使いたいコールバックや機能がどちらのモードを前提にしているかを先に確認しておくと、実装の途中でモードごと書き直す事態を避けられます。
使い分け早見表
| やりたいこと | おすすめのモード | 理由 |
|---|---|---|
| chat UIや対話的なエージェント体験を作る | おすすめのモードストリーミング入力 | 理由割り込み・画像添付・リアルタイム応答がすべて必要になる |
| Slack botのような常駐アプリケーション | おすすめのモードストリーミング入力 | 理由セッションを保持したまま複数ユーザーのメッセージを処理できる |
| Lambda・Cloud Functionsのような単発処理 | おすすめのモードシングルメッセージ入力 | 理由プロセスが都度終了する環境では常駐セッションを持てない |
| バッチ処理で1つのプロンプトだけ投げる | おすすめのモードシングルメッセージ入力 | 理由会話の継続や割り込みが不要で実装がシンプルになる |
canUseToolで承認やAskUserQuestionを扱う(Python) | おすすめのモードストリーミング入力 | 理由ストリームを開いたままにしないとコールバックが呼ばれない |
よくある質問
ストリーミング入力とシングルメッセージ入力は同じアプリ内で併用できますか
ドキュメントはどちらか一方を選ぶ前提で書かれており、モードの併用そのものを明示的にはサポートしていません。用途ごとに経路を分け、対話的なチャット部分はストリーミング入力、バッチ処理部分はシングルメッセージ入力という構成にするのが安全です。
maxTurnsはどちらのモードでも同じ意味ですか
はい。maxTurnsはモードに関係なく、1回のクエリでエージェントが実行できるターン数の上限として同じように働きます。ストリーミング入力でメッセージを複数回yieldしても、maxTurnsはクエリ全体を通した上限である点は変わりません。
まとめ
ストリーミング入力とシングルメッセージ入力は、どちらか一方が優れているわけではなく、動かす環境と必要な機能で選ぶものです。対話的なUIや常駐アプリケーションを作るならストリーミング入力、Lambdaのようなステートレスな単発処理ならシングルメッセージ入力が基本線になります。迷ったときは、画像添付・実行中の割り込み・複数ターンの自然な会話のどれか1つでも必要かどうかで判断すると、ほぼ機械的にどちらか決まります。後から要件が増えてシングルメッセージからストリーミングへ書き換える手戻りを避けるためにも、実装に着手する前にこの判断を済ませておく価値があります。
実装の入り口としてはClaude Agent SDK入門で最小構成のクエリを組んでから、Agent SDKでSlack常駐botを作るでストリーミング入力を使った常駐アプリケーションの実例を確認するとつながりが見えます。セッションを外部ストレージへ永続化する話はAgent SDK SessionStoreでセッションをS3やRedisに永続化するで扱っています。