תיעוד 176
הזרמת תשובות בזמן אמת
קבלו תשובות בזמן אמת מ-Agent SDK כשטקסט וקריאות כלים זורמים פנימה
כברירת מחדל, ה-Agent SDK מחזיר AssistantMessage מלא עבור כל בלוק תוכן לא ריק, כמו בלוק טקסט או קריאת כלי, אחרי ש-Claude מסיים לייצר את הבלוק. כדי לקבל עדכונים הדרגתיים בזמן שטקסט וקריאות כלים נוצרים, הפעילו הזרמת הודעות חלקיות.
[!TIP]
הדף הזה עוסק בהזרמת פלט (קבלת טוקנים בזמן אמת). למצבי קלט (איך שולחים הודעות), ראו שליחת הודעות לסוכנים. אפשר גם להזרים תשובות באמצעות ה-Agent SDK דרך ה-CLI.
#הפעלת הזרמת פלט
כדי להפעיל הזרמה, הגדירו את include_partial_messages (Python) או את includePartialMessages (TypeScript) ל-true באפשרויות. כך ה-SDK מחזיר הודעות StreamEvent שמכילות אירועי API גולמיים כשהם מגיעים, בנוסף ל-AssistantMessage ול-ResultMessage הרגילים.
הקוד שלכם צריך אז:
- לבדוק את הסוג של כל הודעה כדי להבדיל בין
StreamEventלסוגי הודעות אחרים - עבור
StreamEvent, לחלץ את השדהeventולבדוק אתtypeשלו - לחפש אירועי
content_block_deltaשבהםdelta.typeהואtext_delta, שמכילים את מקטעי הטקסט עצמם
הדוגמה הבאה מפעילה הזרמה ומדפיסה מקטעי טקסט כשהם מגיעים. שימו לב לבדיקות הסוג המקוננות: קודם עבור StreamEvent, אחר כך עבור content_block_delta, ואז עבור text_delta:
from claude_agent_sdk import query, ClaudeAgentOptions
from claude_agent_sdk.types import StreamEvent
import asyncio
async def stream_response():
options = ClaudeAgentOptions(
include_partial_messages=True,
allowed_tools=["Bash", "Read"],
)
async for message in query(prompt="List the files in my project", options=options):
if isinstance(message, StreamEvent):
event = message.event
if event.get("type") == "content_block_delta":
delta = event.get("delta", {})
if delta.get("type") == "text_delta":
print(delta.get("text", ""), end="", flush=True)
asyncio.run(stream_response())import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "List the files in my project",
options: {
includePartialMessages: true,
allowedTools: ["Bash", "Read"]
}
})) {
if (message.type === "stream_event") {
const event = message.event;
if (event.type === "content_block_delta") {
if (event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
}
}#עיון ב-StreamEvent
כשהודעות חלקיות מופעלות, אתם מקבלים אירועי הזרמה גולמיים של Claude API עטופים באובייקט. לסוג יש שמות שונים בכל SDK:
- Python:
StreamEvent(ייבוא מ-claude_agent_sdk.types) - TypeScript:
SDKPartialAssistantMessageעםtype: 'stream_event'
שניהם מכילים אירועים גולמיים של Claude API, לא טקסט מצטבר. צריך לחלץ ולצבור את דלתאות הטקסט בעצמכם.
השדה parent_tool_use_id הוא תמיד None ב-Python ו-null ב-TypeScript. אירועי זרם נפלטים רק עבור הסשן הראשי. דלתאות ברמת הטוקן מסוכני משנה לא מועברות הלאה. כדי לשייך פלט לסוכן משנה, השתמשו בהודעות מלאות, שנושאות parent_tool_use_id. ראו זיהוי הפעלת סוכן משנה.
Claude Code מגדיר את user_message_uuid על אירוע הזרם הראשון שאינו ping בתור, ושוב כשההודעה שהתור עונה עליה משתנה, לפי התנאים ב-user_message_uuid. ה-StreamEvent של Python לא חושף את השדה הזה.
השדה event מכיל את אירוע ההזרמה הגולמי מ-Claude API. סוגי אירועים נפוצים כוללים:
| סוג אירוע | תיאור |
|---|---|
message_start | התחלה של הודעה חדשה |
content_block_start | התחלה של בלוק תוכן חדש (טקסט או שימוש בכלי) |
content_block_delta | עדכון הדרגתי לתוכן |
content_block_stop | סוף של בלוק תוכן |
message_delta | עדכונים ברמת ההודעה (סיבת עצירה, שימוש) |
message_stop | סוף ההודעה |
#זרימת הודעות
Claude Code פולט AssistantMessage כשכל בלוק תוכן לא ריק מסתיים, כך שתשובה עם בלוק טקסט וקריאת כלי מחזירה שני אובייקטי AssistantMessage. כל אחד נושא רק את בלוק התוכן שלו, ושניהם חולקים את אותו מזהה הודעה, שקוראים כ-message.message.id ב-TypeScript וכ-message.message_id ב-Python. כשהודעות חלקיות מופעלות, כל AssistantMessage מגיע לפני אירוע content_block_stop של הבלוק, ואתם מקבלים הודעות בסדר הזה:
StreamEvent (message_start)
StreamEvent (content_block_start) - text block
StreamEvent (content_block_delta) - text chunks...
AssistantMessage - complete text block
StreamEvent (content_block_stop)
StreamEvent (content_block_start) - tool_use block
StreamEvent (content_block_delta) - tool input chunks...
AssistantMessage - complete tool_use block
StreamEvent (content_block_stop)
StreamEvent (message_delta)
StreamEvent (message_stop)
... tool executes ...
... more streaming events for next turn ...
ResultMessage - final resultבלי הודעות חלקיות מופעלות, אתם מקבלים את כל סוגי ההודעות חוץ מ-StreamEvent. סוגים נפוצים כוללים SystemMessage (אתחול סשן), AssistantMessage (בלוקי תוכן מלאים), ResultMessage (תוצאה סופית), והודעת גבול דחיסה שמציינת מתי היסטוריית השיחה נדחסה (SDKCompactBoundaryMessage ב-TypeScript, SystemMessage עם subtype "compact_boundary" ב-Python).
#טיפול בזרם שנקטע
אם זרם נקטע באמצע הודעה, אתם עדיין מקבלים את message_stop של ההודעה לפני שהתור מסתיים. סיבות לזרם שנקטע כוללות תור שהופסק, חיבור שנפל, ונפילה חזרה לבקשה שאינה בהזרמה. content_block_stop ו-message_delta יכולים להיעדר לפני אותו message_stop:
content_block_stop: מגיע עבור הבלוק שהיה פתוח, אלא אם הבלוק הזה הוא קריאת כלי. אםmessage_stopמגיע בזמן שבלוק של קריאת כלי עדיין פתוח, התייחסו לקלט של הקריאה הזו כלא שלם.message_delta: מגיע כשאתם מפסיקים את התור, ויכול להיעדר אחרי חיבור שנפל או נפילה חזרה לבקשה שאינה בהזרמה, אז אל תחכו לאחד על הודעה שנקטעה.
לפני Claude Code v2.1.290, זרם שנקטע יכול היה לסיים את התור בלי message_stop, כך שתשובה שאתם מרנדרים מאירועי זרם יכולה הייתה להישאר מוצגת כמתבצעת. ה-Agent SDK של TypeScript כולל את Claude Code v2.1.290 או חדש יותר החל מ-v0.3.290, וה-Agent SDK של Python החל מ-v0.2.164. אם תשובה נשארת מוצגת כמתבצעת אחרי שהתור מסתיים, עדכנו את ה-SDK.
#הזרמת קריאות כלים
גם קריאות כלים זורמות בהדרגה. אפשר לעקוב מתי כלים מתחילים, לקבל את הקלט שלהם בזמן שהוא נוצר, ולראות מתי הם מסתיימים. הדוגמה הבאה עוקבת אחרי הכלי הנוכחי שנקרא וצוברת את קלט ה-JSON בזמן שהוא זורם פנימה. היא משתמשת בשלושה סוגי אירועים:
content_block_start: הכלי מתחילcontent_block_deltaעםinput_json_delta: מקטעי קלט מגיעיםcontent_block_stop: קריאת הכלי הושלמה
from claude_agent_sdk import query, ClaudeAgentOptions
from claude_agent_sdk.types import StreamEvent
import asyncio
async def stream_tool_calls():
options = ClaudeAgentOptions(
include_partial_messages=True,
allowed_tools=["Read", "Bash"],
)
# Track the current tool and accumulate its input JSON
current_tool = None
tool_input = ""
async for message in query(prompt="Read the README.md file", options=options):
if isinstance(message, StreamEvent):
event = message.event
event_type = event.get("type")
if event_type == "content_block_start":
# New tool call is starting
content_block = event.get("content_block", {})
if content_block.get("type") == "tool_use":
current_tool = content_block.get("name")
tool_input = ""
print(f"Starting tool: {current_tool}")
elif event_type == "content_block_delta":
delta = event.get("delta", {})
if delta.get("type") == "input_json_delta":
# Accumulate JSON input as it streams in
chunk = delta.get("partial_json", "")
tool_input += chunk
print(f" Input chunk: {chunk}")
elif event_type == "content_block_stop":
# Tool call complete - show final input
if current_tool:
print(f"Tool {current_tool} called with: {tool_input}")
current_tool = None
asyncio.run(stream_tool_calls())import { query } from "@anthropic-ai/claude-agent-sdk";
// Track the current tool and accumulate its input JSON
let currentTool: string | null = null;
let toolInput = "";
for await (const message of query({
prompt: "Read the README.md file",
options: {
includePartialMessages: true,
allowedTools: ["Read", "Bash"]
}
})) {
if (message.type === "stream_event") {
const event = message.event;
if (event.type === "content_block_start") {
// New tool call is starting
if (event.content_block.type === "tool_use") {
currentTool = event.content_block.name;
toolInput = "";
console.log(`Starting tool: ${currentTool}`);
}
} else if (event.type === "content_block_delta") {
if (event.delta.type === "input_json_delta") {
// Accumulate JSON input as it streams in
const chunk = event.delta.partial_json;
toolInput += chunk;
console.log(` Input chunk: ${chunk}`);
}
} else if (event.type === "content_block_stop") {
// Tool call complete - show final input
if (currentTool) {
console.log(`Tool ${currentTool} called with: ${toolInput}`);
currentTool = null;
}
}
}
}#בניית ממשק משתמש בהזרמה
הדוגמה הזו משלבת הזרמת טקסט והזרמת כלים לממשק אחיד. היא עוקבת אם הסוכן מבצע כרגע כלי (באמצעות דגל in_tool) כדי להציג מחווני סטטוס כמו [Using Read...] בזמן שכלים רצים. טקסט זורם כרגיל כשלא נמצאים בתוך כלי, והשלמת כלי מפעילה הודעת "done". הדפוס הזה שימושי לממשקי צ'אט שצריכים להציג התקדמות במהלך משימות סוכן מרובות שלבים.
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
from claude_agent_sdk.types import StreamEvent
import asyncio
import sys
async def streaming_ui():
options = ClaudeAgentOptions(
include_partial_messages=True,
allowed_tools=["Read", "Bash", "Grep"],
)
# Track whether we're currently in a tool call
in_tool = False
async for message in query(
prompt="Find all TODO comments in the codebase", options=options
):
if isinstance(message, StreamEvent):
event = message.event
event_type = event.get("type")
if event_type == "content_block_start":
content_block = event.get("content_block", {})
if content_block.get("type") == "tool_use":
# Tool call is starting - show status indicator
tool_name = content_block.get("name")
print(f"\n[Using {tool_name}...]", end="", flush=True)
in_tool = True
elif event_type == "content_block_delta":
delta = event.get("delta", {})
# Only stream text when not executing a tool
if delta.get("type") == "text_delta" and not in_tool:
sys.stdout.write(delta.get("text", ""))
sys.stdout.flush()
elif event_type == "content_block_stop":
if in_tool:
# Tool call finished
print(" done", flush=True)
in_tool = False
elif isinstance(message, ResultMessage):
# Agent finished all work
print(f"\n\n--- Complete ---")
asyncio.run(streaming_ui())import { query } from "@anthropic-ai/claude-agent-sdk";
// Track whether we're currently in a tool call
let inTool = false;
for await (const message of query({
prompt: "Find all TODO comments in the codebase",
options: {
includePartialMessages: true,
allowedTools: ["Read", "Bash", "Grep"]
}
})) {
if (message.type === "stream_event") {
const event = message.event;
if (event.type === "content_block_start") {
if (event.content_block.type === "tool_use") {
// Tool call is starting - show status indicator
process.stdout.write(`\n[Using ${event.content_block.name}...]`);
inTool = true;
}
} else if (event.type === "content_block_delta") {
// Only stream text when not executing a tool
if (event.delta.type === "text_delta" && !inTool) {
process.stdout.write(event.delta.text);
}
} else if (event.type === "content_block_stop") {
if (inTool) {
// Tool call finished
console.log(" done");
inTool = false;
}
}
} else if (message.type === "result") {
// Agent finished all work
console.log("\n\n--- Complete ---");
}
}#מגבלות ידועות
- פלט מובנה: כשהודעות חלקיות מופעלות, ה-JSON זורם כמקטעי
input_json_deltaלא מאומתים של קריאת כלי, ורק התוצאה המאומתת מגיעה ל-ResultMessage.structured_outputהסופי. זיהוי קריאת הכלי StructuredOutput מסביר איך הקריאה הזו מופיעה בזרם שלכם.
#הצעדים הבאים
עכשיו כשאפשר להזרים טקסט וקריאות כלים בזמן אמת, עיינו בנושאים הקשורים האלה:
- שאילתות אינטראקטיביות מול שאילתות חד פעמיות: בחרו בין מצבי קלט לפי תרחיש השימוש שלכם
- פלטים מובנים: קבלו תשובות JSON מטיפוס מהסוכן
- הרשאות: שלטו באילו כלים הסוכן יכול להשתמש