מדריך קלוד קוד בעברית

תיעוד 176

הזרמת תשובות בזמן אמת

קבלו תשובות בזמן אמת מ-Agent SDK כשטקסט וקריאות כלים זורמים פנימה

כברירת מחדל, ה-Agent SDK מחזיר AssistantMessage מלא עבור כל בלוק תוכן לא ריק, כמו בלוק טקסט או קריאת כלי, אחרי ש-Claude מסיים לייצר את הבלוק. כדי לקבל עדכונים הדרגתיים בזמן שטקסט וקריאות כלים נוצרים, הפעילו הזרמת הודעות חלקיות.

[!TIP]

הדף הזה עוסק בהזרמת פלט (קבלת טוקנים בזמן אמת). למצבי קלט (איך שולחים הודעות), ראו שליחת הודעות לסוכנים. אפשר גם להזרים תשובות באמצעות ה-Agent SDK דרך ה-CLI.

#הפעלת הזרמת פלט

כדי להפעיל הזרמה, הגדירו את include_partial_messages (Python) או את includePartialMessages (TypeScript) ל-true באפשרויות. כך ה-SDK מחזיר הודעות StreamEvent שמכילות אירועי API גולמיים כשהם מגיעים, בנוסף ל-AssistantMessage ול-ResultMessage הרגילים.

הקוד שלכם צריך אז:

  1. לבדוק את הסוג של כל הודעה כדי להבדיל בין StreamEvent לסוגי הודעות אחרים
  2. עבור StreamEvent, לחלץ את השדה event ולבדוק את type שלו
  3. לחפש אירועי 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:

שניהם מכילים אירועים גולמיים של 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 מסביר איך הקריאה הזו מופיעה בזרם שלכם.

#הצעדים הבאים

עכשיו כשאפשר להזרים טקסט וקריאות כלים בזמן אמת, עיינו בנושאים הקשורים האלה: