5 שלב בניית מיומנויות

Claude Agent SDK — בנייה עם Claude

הפרק הזה הוא המעבר מתיאוריה לפרקטיקה אמיתית: תבנו סוכן AI מלא עם ה-SDK הרשמי של Anthropic. תלמדו להגדיר כלים, לנהל שיחות רב-תורניות, לבנות sub-agents עם handoffs, להוסיף guardrails לבטיחות, ולהפעיל streaming בזמן אמת. בסוף הפרק — יהיה לכם סוכן production-ready עם כלים, sub-agents, guardrails ו-tracing.

מה יהיה לך בסוף הפרק הזה
מה תוכלו לעשות אחרי הפרק הזה
לפני שמתחילים
הפרויקט שלך — קו אדום לאורך הקורס

בפרקים 1-4 למדת את הבסיס: מה זה סוכן, איך הוא בנוי, איך הוא מתחבר לכלים, ובנית סביבת פיתוח מלאה. עכשיו אנחנו בונים את הסוכן הראשון שלך. Claude Agent SDK הוא ה-SDK הרשמי של Anthropic — אותה טכנולוגיה שמפעילה את Claude Code. הסוכן שתבנה כאן ישמש בסיס להשוואה עם Vercel AI SDK (פרק 6), OpenAI Agents SDK (פרק 7), ו-Google ADK (פרק 8). מומלץ לבנות את אותה פונקציונליות בכל SDK כדי להשוות.

מילון מונחים — פרק 5
מונח (English) עברית הסבר
Claude Agent SDK ערכת פיתוח סוכני Claude ה-SDK הרשמי של Anthropic לבניית סוכני AI. שכבה דקה מעל ה-API שמוסיפה agent loop, כלים, handoffs ו-guardrails
ClaudeSDKClient לקוח SDK המחלקה הראשית ב-SDK — מחברת לקוד שלכם את כל יכולות הסוכן של Claude
Tool (כלי) כלי פונקציית Python/TypeScript שהסוכן יכול להפעיל — מחשבון, קריאת קובץ, חיפוש ברשת, שליחת API
Handoff העברה מנגנון שמאפשר לסוכן אחד להעביר את השיחה לסוכן אחר — למשל מנתב שמעביר לסוכן טכני
Guardrail מעקה בטיחות בדיקת אימות שרצה על קלט או פלט — חוסמת תוכן לא רצוי, PII, או תשובות לא בטוחות
Streaming הזרמה קבלת תשובות טוקן-אחרי-טוקן בזמן אמת, במקום לחכות שכל התשובה תהיה מוכנה
RunContext הקשר הרצה אובייקט שנושא state משותף — חיבורי DB, העדפות משתמש, נתוני session — וזמין לכל הכלים והסוכנים
Trace עקבה רישום מפורט של כל הרצת סוכן: קריאות LLM, הפעלות כלים, handoffs, זמנים ועלויות
MCP Server שרת MCP תהליך שחושף כלים דרך פרוטוקול MCP — כל כלי ב-Claude Agent SDK רץ כ-MCP server פנימי
Agent Loop לולאת סוכן הלולאה הבסיסית: קבל הנחיה → חשוב → בחר כלי → הפעל → חשוב שוב → החזר תשובה (או כלי נוסף)
Extended Thinking חשיבה מורחבת יכולת ייחודית של Claude לחשוב "בקול" לפני שעונה — משפרת דיוק במשימות מורכבות
Tool Annotations הערות כלי מטא-דאטה על כלי: readOnlyHint, destructiveHint, idempotentHint — עוזרות ל-SDK להחליט על הרשאות
מתחיל 20 דקות תיאוריה

סקירה ופילוסופיה — למה Claude Agent SDK

Claude Agent SDK (שנקרא בעבר Claude Code SDK) הוא ה-SDK הרשמי של Anthropic לבניית סוכני AI. זה לא עוד framework — זה אותו קוד שמפעיל את Claude Code עצמו, הכלי שמאות אלפי מפתחים משתמשים בו יום-יום. כשאתם בונים עם Claude Agent SDK, אתם מקבלים את אותה רמה של agent capabilities.

ה-SDK זמין ב-Python (גרסה 0.1.48 ומעלה) וב-TypeScript (גרסה 0.2.71 ומעלה) — שניהם production-ready.

הפילוסופיה של ה-SDK

ארבעה עקרונות מנחים את העיצוב:

  1. Safety-First (בטיחות קודם): כל הרצה בתוך sandbox, כלים עם הרשאות מוגדרות, guardrails מובנים
  2. Minimal Abstraction (מינימום הפשטה): שכבה דקה מעל ה-API — לא מסתיר את מה שקורה מאחורי הקלעים
  3. Composable Primitives (אבני בניין מורכבות): agents, tools, handoffs, guardrails — כל אחד עומד בפני עצמו ומשתלב עם האחרים
  4. Sandboxed Execution (הרצה מבודדת): קוד רץ בסביבה מבודדת — לא יכול לגרום נזק למערכת

מתי להשתמש ב-Claude Agent SDK

3
תרחישים עיקריים ל-Claude Agent SDK: סוכנים שעורכים קוד, workflows שדורשים בטיחות קריטית, ואינטגרציה עמוקה עם יכולות Claude (extended thinking, computer use, Claude Code toolset)

כן — השתמשו ב-Claude Agent SDK כשאתם:

לא — השתמשו בכלי אחר כשאתם:

מסגרת: "החלטת Claude SDK" — מתי SDK ומתי API
תרחיש כלי מתאים למה
שאלה-תשובה פשוטה Anthropic API אין צורך ב-agent loop — API call אחד מספיק
סוכן עם 2-5 כלים Claude Agent SDK ה-SDK מנהל את הלולאה, בחירת כלים, error handling
סוכן שעורך קבצים Claude Agent SDK גישה ל-Claude Code toolset (Read, Write, Edit, Bash)
Multi-provider (Claude + GPT) Vercel AI SDK תמיכה ב-providers מרובים מובנית
סוכן עם extended thinking Claude Agent SDK תמיכה native ב-extended thinking של Claude
Orchestration מורכב של 10+ סוכנים LangGraph הפשטות graph-based למערכות מורכבות
Batch processing של 1000 בקשות Anthropic Batch API 50% הנחה, אין צורך ב-agent loop

ארכיטקטורת ה-SDK

ה-SDK בנוי משלושה שכבות:

  1. ClaudeSDKClient — המחלקה הראשית. מתחברת ל-Anthropic API, מנהלת sessions, מריצה את ה-agent loop
  2. Tools כ-MCP Servers — כל כלי מותאם שאתם מגדירים רץ כ-in-process MCP server. אותו פרוטוקול מפרק 3, אבל מובנה ב-SDK
  3. Hooks System — מנגנון להתערבות בהתנהגות הסוכן: לפני/אחרי tool calls, לפני/אחרי תשובות LLM, ועוד

מה שמבדיל את ה-SDK מ-frameworks אחרים:

עשה עכשיו 3 דקות

בדוק שה-SDK מותקן ומוכן:

# Python
pip install claude-agent-sdk
python -c "from claude_agent_sdk import ClaudeSDKClient; print('Claude Agent SDK installed!')"

# TypeScript (אופציונלי)
npm install @anthropic-ai/claude-agent-sdk
npx ts-node -e "import { ClaudeSDKClient } from '@anthropic-ai/claude-agent-sdk'; console.log('SDK ready!')"

אם קיבלת שגיאה — חזור לפרק 4, סעיף "התקנת SDKs". ודא ש-ANTHROPIC_API_KEY מוגדר ב-.env.

מתחיל 15 דקות פרקטי

הסוכן הראשון שלך — Hello World

בואו נבנה את הסוכן הראשון. 5 שורות קוד — וכבר יש לנו סוכן Claude עובד.

Python — הסוכן הבסיסי

# first_agent.py
from claude_agent_sdk import ClaudeSDKClient

# יוצרים לקוח — קורא ANTHROPIC_API_KEY מ-env אוטומטית
client = ClaudeSDKClient()

# שולחים שאילתה — הסוכן חושב ועונה
result = client.query(
    prompt="What is the capital of Israel?",
    model="claude-sonnet-4-20250514"  # אפשר גם claude-opus-4-20250514
)

# מדפיסים את התשובה
print(result.text)
# Output: The capital of Israel is Jerusalem.

# מידע על השימוש
print(f"Tokens used: {result.usage.input_tokens} in, {result.usage.output_tokens} out")
print(f"Cost: ~${(result.usage.input_tokens * 3 + result.usage.output_tokens * 15) / 1_000_000:.4f}")

מה קורה כאן:

  1. ClaudeSDKClient() — יוצר חיבור ל-API. קורא את ANTHROPIC_API_KEY מ-environment variables באופן אוטומטי
  2. client.query() — שולח prompt לסוכן, מריץ את ה-agent loop, מחזיר תוצאה
  3. result.text — הטקסט הסופי שהסוכן מחזיר
  4. result.usage — מידע על צריכת טוקנים — חשוב לניטור עלויות

TypeScript — אותו סוכן

// first_agent.ts
import { ClaudeSDKClient } from '@anthropic-ai/claude-agent-sdk';

const client = new ClaudeSDKClient();

async function main() {
  const result = await client.query({
    prompt: "What is the capital of Israel?",
    model: "claude-sonnet-4-20250514",
  });

  console.log(result.text);
  console.log(`Tokens: ${result.usage.inputTokens} in, ${result.usage.outputTokens} out`);
}

main();
טעות #1: שכחת ANTHROPIC_API_KEY

השגיאה הכי נפוצה: AuthenticationError: Missing API key. ה-SDK מחפש את המפתח ב-environment variable ANTHROPIC_API_KEY. ודאו שהוא מוגדר בקובץ .env ושאתם טוענים אותו (Python: python-dotenv, TypeScript: dotenv). אל תשימו את המפתח ישירות בקוד — לעולם!

הגדרת System Prompt

בניגוד ל-Claude Code, ב-SDK אין system prompt דיפולטי. אתם מגדירים בדיוק מה הסוכן יודע:

# agent_with_system_prompt.py
from claude_agent_sdk import ClaudeSDKClient

client = ClaudeSDKClient()

result = client.query(
    prompt="מה המרחק בין תל אביב לירושלים?",
    system_prompt="""אתה עוזר ישראלי ידידותי.
    ענה תמיד בעברית.
    תן תשובות קצרות ומדויקות.
    אם אינך בטוח — אמור שאתה לא בטוח.""",
    model="claude-sonnet-4-20250514"
)

print(result.text)
# Output: המרחק בין תל אביב לירושלים הוא כ-60 ק"מ בכביש (כ-70 דקות נסיעה ברכב).
עשה עכשיו 5 דקות

בנה את הסוכן הראשון שלך:

  1. צור קובץ first_agent.py (או .ts)
  2. הדבק את הקוד למעלה
  3. הרץ: python first_agent.py
  4. אם קיבלת תשובה — מזל טוב! יש לך סוכן Claude עובד
  5. שנה את ה-prompt ל-3 שאלות שונות וראה את התשובות

עלות: ~$0.02 לכל 3 שאילתות.

Task Progress Events — מעקב בזמן אמת

כשסוכן רץ ברקע (למשל כ-sub-agent), אפשר לקבל עדכוני התקדמות:

# progress_events.py
from claude_agent_sdk import ClaudeSDKClient

client = ClaudeSDKClient()

# הרצה עם callback להתקדמות
def on_progress(event):
    print(f"[{event.type}] {event.message}")
    if event.usage:
        print(f"  Tokens so far: {event.usage.input_tokens} in, {event.usage.output_tokens} out")

result = client.query(
    prompt="Analyze the pros and cons of microservices vs monoliths",
    model="claude-sonnet-4-20250514",
    on_progress=on_progress
)
בינוני 30 דקות פרקטי

הגדרת כלים — Tool Use

סוכן בלי כלים הוא בסך הכל chatbot. הכוח האמיתי של סוכנים הוא היכולת לעשות דברים — לחשב, לחפש, לקרוא קבצים, לקרוא ל-APIs. ב-Claude Agent SDK, כל כלי הוא פונקציית Python (או TypeScript) עם decorator פשוט.

כלי בסיסי — מחשבון

# tools_basic.py
from claude_agent_sdk import ClaudeSDKClient, tool

@tool
def calculator(expression: str) -> str:
    """
    Evaluates a mathematical expression and returns the result.
    Use this for any calculation the user needs.

    Args:
        expression: A mathematical expression like "2 + 2" or "sqrt(16) * 3"
    """
    import math
    # Whitelist of allowed operations for safety
    allowed_names = {
        k: v for k, v in math.__dict__.items()
        if not k.startswith("__")
    }
    allowed_names.update({"abs": abs, "round": round})

    try:
        result = eval(expression, {"__builtins__": {}}, allowed_names)
        return f"Result: {result}"
    except Exception as e:
        return f"Error calculating '{expression}': {str(e)}"

client = ClaudeSDKClient()

result = client.query(
    prompt="What is 47 * 89 + sqrt(144)?",
    tools=[calculator],
    model="claude-sonnet-4-20250514"
)

print(result.text)
# The agent will call calculator("47 * 89 + sqrt(144)") -> "Result: 4195.0"
# Then respond: "47 * 89 + sqrt(144) = 4,195"

# בדיקה: אילו כלים הסוכן השתמש בהם
for call in result.tool_calls:
    print(f"Tool: {call.name}, Input: {call.input}, Output: {call.output}")

מה שקורה מאחורי הקלעים:

  1. ה-@tool decorator קורא את ה-type hints ואת ה-docstring ויוצר JSON schema אוטומטי
  2. ה-schema נשלח ל-Claude כחלק מה-API call
  3. Claude מחליט אם ומתי להפעיל את הכלי
  4. ה-SDK מפעיל את הפונקציה עם הפרמטרים ש-Claude בחר
  5. התוצאה חוזרת ל-Claude שמנסח תשובה סופית
טעות #2: Docstring חסר או לא ברור

ה-docstring הוא ההוראות ש-Claude מקבל על הכלי. בלי docstring טוב, Claude לא ידע מתי להשתמש בכלי. כתבו docstring שמסביר: (1) מה הכלי עושה, (2) מתי להשתמש בו, (3) מה כל פרמטר מצפה לקבל. docstring גרוע = סוכן שמשתמש בכלי הלא נכון.

כלי עם מספר פרמטרים

# tools_advanced.py
from claude_agent_sdk import ClaudeSDKClient, tool
from typing import Optional
from enum import Enum

class TemperatureUnit(str, Enum):
    CELSIUS = "celsius"
    FAHRENHEIT = "fahrenheit"

@tool
def convert_temperature(
    value: float,
    from_unit: TemperatureUnit,
    to_unit: TemperatureUnit,
    round_to: Optional[int] = 2
) -> str:
    """
    Converts temperature between Celsius and Fahrenheit.

    Args:
        value: The temperature value to convert
        from_unit: The unit to convert from (celsius or fahrenheit)
        to_unit: The unit to convert to (celsius or fahrenheit)
        round_to: Number of decimal places (default: 2)
    """
    if from_unit == to_unit:
        return f"{value} {from_unit.value} = {value} {to_unit.value} (same unit)"

    if from_unit == TemperatureUnit.CELSIUS:
        result = (value * 9/5) + 32
    else:
        result = (value - 32) * 5/9

    result = round(result, round_to)
    return f"{value}° {from_unit.value} = {result}° {to_unit.value}"

שימו לב: Optional[int] = 2 מייצר פרמטר אופציונלי עם default ב-schema. ו-Enum מייצר רשימת ערכים קבועה ש-Claude חייב לבחור מתוכה.

מספר כלים על סוכן אחד

# multi_tools_agent.py
from claude_agent_sdk import ClaudeSDKClient, tool
import json
import os

@tool
def calculator(expression: str) -> str:
    """Evaluates a mathematical expression. Use for calculations."""
    import math
    allowed = {k: v for k, v in math.__dict__.items() if not k.startswith("__")}
    try:
        return f"Result: {eval(expression, {'__builtins__': {}}, allowed)}"
    except Exception as e:
        return f"Error: {str(e)}"

@tool
def read_file(file_path: str) -> str:
    """
    Reads and returns the contents of a file.
    Use when the user asks to read, view, or analyze a file.

    Args:
        file_path: Path to the file to read
    """
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()
        if len(content) > 10000:
            return content[:10000] + f"\n... (truncated, total {len(content)} chars)"
        return content
    except FileNotFoundError:
        return f"Error: File '{file_path}' not found"
    except Exception as e:
        return f"Error reading file: {str(e)}"

@tool
def list_directory(path: str = ".") -> str:
    """
    Lists files and directories in the given path.
    Use when the user wants to see what files exist.

    Args:
        path: Directory path to list (default: current directory)
    """
    try:
        entries = os.listdir(path)
        dirs = [f"📁 {e}" for e in entries if os.path.isdir(os.path.join(path, e))]
        files = [f"📄 {e}" for e in entries if os.path.isfile(os.path.join(path, e))]
        return "\n".join(dirs + files) or "Empty directory"
    except Exception as e:
        return f"Error: {str(e)}"

# סוכן עם 3 כלים — Claude בוחר איזה להפעיל
client = ClaudeSDKClient()

result = client.query(
    prompt="List the files in the current directory, then read package.json if it exists",
    tools=[calculator, read_file, list_directory],
    model="claude-sonnet-4-20250514"
)

print(result.text)
# Claude will:
# 1. Call list_directory(".") to see what files exist
# 2. If package.json exists, call read_file("package.json")
# 3. Summarize the contents

Error Handling בכלים

שתי אסטרטגיות להתמודדות עם שגיאות:

# error_handling.py
from claude_agent_sdk import tool

# אסטרטגיה 1: החזרת string עם הודעת שגיאה
# Claude מקבל את ההודעה ויכול להסביר למשתמש
@tool
def safe_divide(a: float, b: float) -> str:
    """Divides a by b. Returns the result."""
    if b == 0:
        return "Error: Cannot divide by zero. Please provide a non-zero divisor."
    return f"Result: {a / b}"

# אסטרטגיה 2: זריקת exception
# ה-SDK תופס את ה-exception ומעביר ל-Claude כשגיאה
@tool
def strict_divide(a: float, b: float) -> str:
    """Divides a by b. Raises error if b is zero."""
    if b == 0:
        raise ValueError("Division by zero is not allowed")
    return f"Result: {a / b}"

מתי להשתמש במה? אסטרטגיה 1 (החזרת string) עדיפה ברוב המקרים — Claude מקבל מידע מלא ויכול להסביר למשתמש מה קרה. אסטרטגיה 2 (exception) מתאימה כשהשגיאה קריטית ואתם רוצים שהיא תופיע גם ב-traces.

עשה עכשיו 10 דקות

הוסף כלי חדש לסוכן שלך:

  1. כתוב פונקציה @tool שמחפשת מילה במילון (תחזיר הגדרה כלשהי)
  2. הוסף אותה לרשימת ה-tools ב-client.query()
  3. שאל את הסוכן "What does 'ephemeral' mean and how many letters does it have?"
  4. בדוק ש-Claude משתמש גם במילון וגם במחשבון
תרגיל 1: סוכן עם 3 כלים 20 דקות
  1. צור קובץ multi_tool_agent.py
  2. הגדר 3 כלים: (1) calculator — חישובים מתמטיים, (2) read_file — קריאת קבצים, (3) web_search — מחזיר תוצאת חיפוש מדומה (mock)
  3. חבר את שלושת הכלים לסוכן אחד
  4. שלח 3 שאילתות שונות: אחת שדורשת מחשבון, אחת שדורשת קריאת קובץ, ואחת שדורשת חיפוש
  5. הדפס אילו כלים הסוכן בחר בכל שאילתה
  6. הוסף error handling — מה קורה כשהקובץ לא קיים?

הצלחה: 3 שאילתות מצליחות, כל אחת משתמשת בכלי הנכון, ושגיאות מטופלות בצורה חלקה.

עלות: ~$0.20

בינוני 20 דקות פרקטי

Streaming ופלט בזמן אמת

בלי streaming, המשתמש מחכה 10-30 שניות לתשובה מלאה. עם streaming, הוא רואה את התשובה נכתבת בזמן אמת — חוויה הרבה יותר טובה.

Streaming בסיסי

# streaming_basic.py
from claude_agent_sdk import ClaudeSDKClient

client = ClaudeSDKClient()

# במקום query() שמחזיר תוצאה מלאה — stream() שמחזיר events
for event in client.stream(
    prompt="Explain microservices architecture in 5 bullet points",
    model="claude-sonnet-4-20250514"
):
    if event.type == "text_delta":
        print(event.text, end="", flush=True)
    elif event.type == "tool_use":
        print(f"\n[Using tool: {event.tool_name}]")
    elif event.type == "done":
        print(f"\n\n--- Done. Tokens: {event.usage.input_tokens} in, {event.usage.output_tokens} out ---")

Event Types — מה מגיע ב-stream

Event Type מתי מה מכיל
text_delta כל כמה מילים event.text — טקסט חדש
tool_use כשהסוכן מפעיל כלי event.tool_name, event.tool_input
tool_result כשכלי מחזיר תוצאה event.tool_name, event.output
thinking Extended thinking (אם מופעל) event.text — מחשבות פנימיות
done סוף ההרצה event.usage — סיכום טוקנים

TypeScript — Streaming

// streaming.ts
import { ClaudeSDKClient } from '@anthropic-ai/claude-agent-sdk';

const client = new ClaudeSDKClient();

async function streamAgent() {
  const stream = client.stream({
    prompt: "Explain the Israeli tech ecosystem",
    model: "claude-sonnet-4-20250514",
  });

  for await (const event of stream) {
    switch (event.type) {
      case "text_delta":
        process.stdout.write(event.text);
        break;
      case "tool_use":
        console.log(`\n[Tool: ${event.toolName}]`);
        break;
      case "done":
        console.log(`\n\nTokens: ${event.usage.inputTokens}/${event.usage.outputTokens}`);
        break;
    }
  }
}

streamAgent();

Streaming עם כלים — Web UI

דוגמה לשרת FastAPI שמזרים תשובות סוכן ללקוח ב-Server-Sent Events:

# streaming_api.py
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from claude_agent_sdk import ClaudeSDKClient, tool
import json

app = FastAPI()
client = ClaudeSDKClient()

@tool
def get_weather(city: str) -> str:
    """Returns current weather for a city."""
    # בפרודקשן — תחליפו ב-API אמיתי
    return f"Weather in {city}: 25°C, sunny, humidity 45%"

@app.get("/chat")
async def chat(prompt: str):
    async def event_stream():
        for event in client.stream(
            prompt=prompt,
            tools=[get_weather],
            model="claude-sonnet-4-20250514"
        ):
            if event.type == "text_delta":
                yield f"data: {json.dumps({'type': 'text', 'content': event.text})}\n\n"
            elif event.type == "tool_use":
                yield f"data: {json.dumps({'type': 'tool', 'name': event.tool_name})}\n\n"
            elif event.type == "done":
                yield f"data: {json.dumps({'type': 'done', 'usage': {'input': event.usage.input_tokens, 'output': event.usage.output_tokens}})}\n\n"

    return StreamingResponse(event_stream(), media_type="text/event-stream")
עשה עכשיו 5 דקות

הפוך את הסוכן שלך ל-streaming:

  1. שנה את client.query() ל-client.stream()
  2. הוסף loop שמדפיס כל text_delta event
  3. הרץ ושאל שאלה ארוכה — ראה את התשובה נכתבת בזמן אמת
בינוני 30 דקות פרקטי

Sub-Agents ו-Handoffs — הפיצ'ר הרוצח

Sub-agents הם אחד הפיצ'רים החזקים ביותר ב-Claude Agent SDK. במקום סוכן אחד ענק שיודע הכל — בונים סוכנים מומחים ומנתב שמפנה כל בקשה לסוכן הנכון. זה בדיוק הדפוס שלמדנו בפרק 2 (Orchestrator Pattern).

Handoff בסיסי

# handoff_basic.py
from claude_agent_sdk import ClaudeSDKClient, tool, Agent, Handoff

# סוכן מומחה לחיוב
billing_agent = Agent(
    name="billing_agent",
    system_prompt="""You are a billing specialist. You help with:
    - Invoice questions
    - Payment methods
    - Refund requests
    - Pricing information
    Answer in a professional, helpful tone.""",
    model="claude-sonnet-4-20250514"
)

# סוכן מומחה לתמיכה טכנית
tech_agent = Agent(
    name="tech_agent",
    system_prompt="""You are a technical support specialist. You help with:
    - Bug reports
    - Feature questions
    - API integration issues
    - Performance problems
    Include code examples when relevant.""",
    model="claude-sonnet-4-20250514"
)

# סוכן כללי — ברירת מחדל
general_agent = Agent(
    name="general_agent",
    system_prompt="""You are a friendly general support agent. You help with:
    - General questions about the product
    - Account settings
    - Getting started guides
    If the question is about billing or technical issues, let the router handle it.""",
    model="claude-sonnet-4-20250514"
)

# סוכן מנתב — הוא מחליט לאן להפנות
router_agent = Agent(
    name="router",
    system_prompt="""You are a customer service router. Analyze the user's message and
    hand off to the appropriate specialist:
    - billing_agent: for payment, invoice, refund, pricing questions
    - tech_agent: for bugs, API, code, technical issues
    - general_agent: for everything else

    Always hand off — never answer directly.""",
    model="claude-sonnet-4-20250514",
    handoffs=[
        Handoff(target=billing_agent, description="Billing and payment questions"),
        Handoff(target=tech_agent, description="Technical support and bug reports"),
        Handoff(target=general_agent, description="General questions"),
    ]
)

client = ClaudeSDKClient()

# שאילתה שתועבר לסוכן חיוב
result = client.run(
    agent=router_agent,
    prompt="I was charged twice for my subscription last month. Can I get a refund?"
)

print(f"Handled by: {result.agent_name}")
print(f"Response: {result.text}")
# Handled by: billing_agent
# Response: I'd be happy to help with your double charge...

Handoff עם Filters — שליטה במה שעובר

לפעמים אתם לא רוצים שה-sub-agent יקבל את כל ההיסטוריה. Handoff filters מאפשרים לסנן:

# handoff_filters.py
from claude_agent_sdk import Agent, Handoff, HandoffFilter

# סוכן שמטפל ב-PII — מקבל רק את ההודעה האחרונה
pii_handler = Agent(
    name="pii_processor",
    system_prompt="Process PII requests securely. Never log or repeat PII data.",
    model="claude-sonnet-4-20250514"
)

router = Agent(
    name="router",
    system_prompt="Route messages to specialists.",
    model="claude-sonnet-4-20250514",
    handoffs=[
        Handoff(
            target=pii_handler,
            description="Handles PII-sensitive requests",
            # filter: רק ההודעה האחרונה עוברת — לא כל ההיסטוריה
            input_filter=HandoffFilter.LAST_MESSAGE_ONLY
        ),
    ]
)
טעות #3: יותר מדי handoffs ללא כיוון ברור

אם אתם נותנים לסוכן המנתב 10 אפשרויות handoff — הוא יתבלבל. כלל אצבע: עד 5 handoffs לסוכן מנתב. אם צריך יותר — בנו עץ: מנתב ראשי שמפנה ל-3 מנתבים משניים, כל אחד עם 3-4 סוכנים מומחים.

עשה עכשיו 10 דקות

בנה מערכת mini customer service:

  1. צור 2 סוכנים מומחים (billing + tech)
  2. צור סוכן מנתב עם handoffs
  3. שלח 3 שאלות: אחת על תשלום, אחת טכנית, ואחת מעורבת
  4. בדוק שהמנתב בוחר נכון בכל פעם
תרגיל 2: מערכת שירות לקוחות עם 3 סוכנים 25 דקות
  1. צור קובץ customer_service.py
  2. הגדר 3 סוכנים מומחים: billing (כולל כלי lookup_invoice), tech (כולל כלי check_status), general
  3. הגדר סוכן מנתב עם handoffs לשלושת הסוכנים
  4. כתוב 5 שאילתות בדיקה: 2 billing, 2 tech, 1 general
  5. הרץ את כל 5 ובדוק שהניתוב נכון לפחות ב-4 מתוך 5
  6. הוסף handoff filter ל-billing agent שמעביר רק את ההודעה האחרונה

הצלחה: 4/5 ניתובים נכונים, כלים פועלים ב-sub-agents, filter עובד.

עלות: ~$0.50

בינוני 25 דקות פרקטי

Guardrails — בטיחות ו-Validation

Guardrails הם "מעקות בטיחות" שבודקים את הקלט והפלט של הסוכן. בלעדיהם, סוכן יכול: לענות על שאלות שאסור, לחשוף PII, לייצר תוכן פוגעני, או לצרוך יותר מדי טוקנים. Guardrails מונעים את כל אלה.

Input Guardrail — בדיקת קלט

# guardrails_input.py
from claude_agent_sdk import ClaudeSDKClient, Agent, InputGuardrail, GuardrailResult
import re

# Guardrail: זיהוי PII (מספרי טלפון, אימיילים, תעודת זהות)
def pii_detector(input_text: str) -> GuardrailResult:
    """Detects PII in user input and blocks the request if found."""
    patterns = {
        "email": r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
        "phone_il": r'\b0[2-9]\d{7,8}\b',
        "israeli_id": r'\b\d{9}\b',
        "credit_card": r'\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b',
    }

    found = []
    for pii_type, pattern in patterns.items():
        if re.search(pattern, input_text):
            found.append(pii_type)

    if found:
        return GuardrailResult(
            blocked=True,
            message=f"PII detected ({', '.join(found)}). Please remove personal information before sending."
        )

    return GuardrailResult(blocked=False)

agent = Agent(
    name="safe_agent",
    system_prompt="You are a helpful assistant.",
    model="claude-sonnet-4-20250514",
    input_guardrails=[
        InputGuardrail(fn=pii_detector, description="Blocks PII in input")
    ]
)

client = ClaudeSDKClient()

# ניסיון עם PII — ייחסם
result = client.run(
    agent=agent,
    prompt="My phone number is 0541234567, please save it"
)
print(result.blocked)  # True
print(result.guardrail_message)  # "PII detected (phone_il)..."

# ניסיון בלי PII — יעבור
result = client.run(
    agent=agent,
    prompt="What's the weather like in Tel Aviv?"
)
print(result.blocked)  # False
print(result.text)  # Normal response

Output Guardrail — בדיקת פלט

# guardrails_output.py
from claude_agent_sdk import Agent, OutputGuardrail, GuardrailResult

# Guardrail: וידוא שהתשובה בעברית (אם נדרש)
def hebrew_only_check(output_text: str) -> GuardrailResult:
    """Ensures the response contains Hebrew text."""
    hebrew_chars = sum(1 for c in output_text if '\u0590' <= c <= '\u05FF')
    total_alpha = sum(1 for c in output_text if c.isalpha())

    if total_alpha > 0 and hebrew_chars / total_alpha < 0.3:
        return GuardrailResult(
            blocked=True,
            message="Response must be primarily in Hebrew. Regenerating..."
        )
    return GuardrailResult(blocked=False)

# Guardrail: הגבלת אורך תשובה
def max_length_check(output_text: str) -> GuardrailResult:
    """Blocks responses that are too long."""
    if len(output_text) > 5000:
        return GuardrailResult(
            blocked=True,
            message=f"Response too long ({len(output_text)} chars). Max: 5000."
        )
    return GuardrailResult(blocked=False)

agent = Agent(
    name="hebrew_agent",
    system_prompt="אתה עוזר ישראלי. ענה תמיד בעברית.",
    model="claude-sonnet-4-20250514",
    output_guardrails=[
        OutputGuardrail(fn=hebrew_only_check, description="Ensures Hebrew response"),
        OutputGuardrail(fn=max_length_check, description="Limits response length"),
    ]
)

Tripwire Guardrails — עצירה מיידית

Tripwire guardrail עוצר את ההרצה מיד — בלי לתת לסוכן להמשיך:

# guardrails_tripwire.py
from claude_agent_sdk import Agent, InputGuardrail, GuardrailResult

# Tripwire: חוסם הזרקות prompt (prompt injection)
def prompt_injection_detector(input_text: str) -> GuardrailResult:
    """Detects common prompt injection patterns."""
    injection_patterns = [
        "ignore previous instructions",
        "ignore all instructions",
        "you are now",
        "pretend you are",
        "act as if you have no restrictions",
        "disregard your system prompt",
        "override your instructions",
    ]

    lower_input = input_text.lower()
    for pattern in injection_patterns:
        if pattern in lower_input:
            return GuardrailResult(
                blocked=True,
                tripwire=True,  # עצירה מיידית — לא ממשיכים
                message="Potential prompt injection detected. Request blocked."
            )

    return GuardrailResult(blocked=False)

agent = Agent(
    name="secure_agent",
    system_prompt="You are a helpful assistant.",
    model="claude-sonnet-4-20250514",
    input_guardrails=[
        InputGuardrail(
            fn=prompt_injection_detector,
            description="Blocks prompt injection attempts",
            tripwire=True  # סימון כ-tripwire — עוצר הכל מיד
        )
    ]
)
טעות #4: הסתמכות על guardrails בלבד לבטיחות

Guardrails הם שכבה אחת בלבד. הם לא מחליפים system prompt טוב, ולא מחליפים validation ברמת ה-application. השתמשו ב-defense in depth: (1) system prompt ברור, (2) input guardrails, (3) output guardrails, (4) application-level validation. שום שכבה אחת לא מספיקה לבד.

עשה עכשיו 7 דקות

הוסף guardrail PII לסוכן שלך:

  1. העתק את pii_detector מהדוגמה
  2. הוסף אותו כ-InputGuardrail לסוכן
  3. בדוק עם הודעה שמכילה מספר טלפון — צריך להיחסם
  4. בדוק עם הודעה רגילה — צריך לעבור
בינוני 20 דקות פרקטי

ניהול Context ו-State

סוכנים אמיתיים צריכים state — חיבור ל-database, העדפות משתמש, עגלת קניות, session ID. ה-RunContext נושא את כל המידע הזה ומעביר אותו לכל כלי וסוכן.

RunContext — State משותף

# context_basic.py
from claude_agent_sdk import ClaudeSDKClient, Agent, tool, RunContext
from dataclasses import dataclass

@dataclass
class AppContext:
    """State that's shared across all tools and agents."""
    user_id: str
    user_name: str
    cart: list  # עגלת קניות
    language: str = "he"
    total_spent: float = 0.0

@tool
def add_to_cart(ctx: RunContext[AppContext], item: str, price: float) -> str:
    """
    Adds an item to the shopping cart.

    Args:
        item: Name of the item to add
        price: Price of the item in ILS
    """
    ctx.state.cart.append({"item": item, "price": price})
    ctx.state.total_spent += price
    return f"Added '{item}' (₪{price}) to cart. Total: ₪{ctx.state.total_spent}"

@tool
def view_cart(ctx: RunContext[AppContext]) -> str:
    """Shows the current shopping cart contents."""
    if not ctx.state.cart:
        return "Cart is empty"

    lines = [f"Cart for {ctx.state.user_name}:"]
    for item in ctx.state.cart:
        lines.append(f"  - {item['item']}: ₪{item['price']}")
    lines.append(f"Total: ₪{ctx.state.total_spent}")
    return "\n".join(lines)

@tool
def checkout(ctx: RunContext[AppContext]) -> str:
    """Processes the checkout and clears the cart."""
    if not ctx.state.cart:
        return "Cart is empty — nothing to checkout"

    total = ctx.state.total_spent
    items_count = len(ctx.state.cart)
    ctx.state.cart.clear()
    ctx.state.total_spent = 0.0
    return f"Order placed! {items_count} items, total ₪{total}. Thank you, {ctx.state.user_name}!"

# יוצרים context עם state ראשוני
app_context = AppContext(
    user_id="user_123",
    user_name="Yossi",
    cart=[]
)

client = ClaudeSDKClient()

agent = Agent(
    name="shopping_agent",
    system_prompt="""You are a shopping assistant for an Israeli online store.
    Help users add items, view cart, and checkout.
    Speak in Hebrew when the user writes in Hebrew.""",
    model="claude-sonnet-4-20250514",
    tools=[add_to_cart, view_cart, checkout]
)

# שיחה רב-תורנית — ה-state נשמר בין הודעות
result1 = client.run(agent=agent, prompt="Add hummus for 15 shekels", context=app_context)
print(result1.text)

result2 = client.run(agent=agent, prompt="Also add pita for 8 shekels", context=app_context)
print(result2.text)

result3 = client.run(agent=agent, prompt="Show my cart", context=app_context)
print(result3.text)

result4 = client.run(agent=agent, prompt="Checkout please", context=app_context)
print(result4.text)

Lifecycle Hooks — התערבות בכל שלב

# lifecycle_hooks.py
from claude_agent_sdk import Agent, Hooks
import time

class MonitoringHooks(Hooks):
    def on_start(self, context, agent):
        self.start_time = time.time()
        print(f"[START] Agent '{agent.name}' started")

    def on_tool_start(self, context, agent, tool_name, tool_input):
        print(f"[TOOL] Calling '{tool_name}' with {tool_input}")

    def on_tool_end(self, context, agent, tool_name, tool_output):
        print(f"[TOOL] '{tool_name}' returned: {tool_output[:100]}")

    def on_end(self, context, agent, result):
        elapsed = time.time() - self.start_time
        print(f"[END] Agent '{agent.name}' finished in {elapsed:.2f}s")
        print(f"[END] Tokens: {result.usage.input_tokens} in, {result.usage.output_tokens} out")

agent = Agent(
    name="monitored_agent",
    system_prompt="You are a helpful assistant with tools.",
    model="claude-sonnet-4-20250514",
    hooks=MonitoringHooks()
)
עשה עכשיו 5 דקות

הוסף RunContext לסוכן שלך:

  1. הגדר dataclass עם שדה user_name ו-request_count
  2. בכל tool call, הגדל את request_count
  3. אחרי 3 הרצות, הדפס "User X made Y requests"
בינוני 20 דקות פרקטי

אינטגרציה עם MCP

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

חיבור MCP Server

# mcp_integration.py
from claude_agent_sdk import ClaudeSDKClient, Agent, MCPServer

# חיבור filesystem MCP server
filesystem_server = MCPServer(
    name="filesystem",
    command="npx",
    args=["-y", "@modelcontextprotocol/server-filesystem", "/home/user/documents"]
)

# חיבור GitHub MCP server
github_server = MCPServer(
    name="github",
    command="npx",
    args=["-y", "@modelcontextprotocol/server-github"],
    env={"GITHUB_TOKEN": "your-github-token"}
)

agent = Agent(
    name="dev_assistant",
    system_prompt="""You are a development assistant. You can:
    - Read and write files using the filesystem tools
    - Interact with GitHub repos using the GitHub tools
    Help developers with code review, file management, and GitHub operations.""",
    model="claude-sonnet-4-20250514",
    mcp_servers=[filesystem_server, github_server]
)

client = ClaudeSDKClient()

# הסוכן יכול עכשיו לקרוא קבצים ולעבוד עם GitHub
result = client.run(
    agent=agent,
    prompt="List the files in my documents folder and then check my latest GitHub notifications"
)
print(result.text)

Dynamic Tool Loading

כלים מ-MCP servers נטענים דינמית בזמן ריצה. הסוכן מגלה אותם אוטומטית:

# mcp_dynamic.py
from claude_agent_sdk import Agent, MCPServer

# שרת שחושף כלים דינמיים
db_server = MCPServer(
    name="database",
    command="python",
    args=["my_db_mcp_server.py"],
    # כלים יטענו אוטומטית בעת חיבור
)

agent = Agent(
    name="data_analyst",
    system_prompt="You analyze data. Use the database tools to query and report.",
    model="claude-sonnet-4-20250514",
    mcp_servers=[db_server],
    # אפשר להגביל אילו כלים מ-MCP הסוכן רשאי להשתמש
    allowed_tools=["database:query", "database:list_tables"],
    disallowed_tools=["database:drop_table", "database:delete"]
)
טעות #5: חיבור MCP server בלי הגבלת כלים

MCP server יכול לחשוף כלים מסוכנים — כמו delete_file או drop_table. תמיד השתמשו ב-allowed_tools או disallowed_tools כדי להגביל את מה שהסוכן יכול לעשות. ב-production, עדיף whitelist (allowed_tools) על blacklist (disallowed_tools).

עשה עכשיו 8 דקות

חבר filesystem MCP server לסוכן:

  1. הגדר MCPServer עם @modelcontextprotocol/server-filesystem
  2. הגבל את הגישה לתיקייה אחת בלבד
  3. שאל את הסוכן "List the files and read the first one"
  4. הוסף disallowed_tools=["write_file", "delete_file"] — read-only access
בינוני 15 דקות פרקטי

Tracing ו-Debugging

כל הרצת סוכן ב-Claude Agent SDK מייצרת trace — רישום מפורט של כל מה שקרה: קריאות LLM, הפעלות כלים, handoffs, זמנים, שגיאות. Tracing הוא הכלי הכי חשוב שלכם כשסוכן לא עושה מה שציפיתם.

הפעלת Tracing

# tracing_basic.py
from claude_agent_sdk import ClaudeSDKClient, Agent, tool, Tracing

# הפעלת tracing עם כתיבה לקונסולה
Tracing.enable(output="console")

client = ClaudeSDKClient()

@tool
def lookup_product(product_id: str) -> str:
    """Looks up a product by ID."""
    products = {"P001": "Hummus - ₪15", "P002": "Tahini - ₪22", "P003": "Falafel - ₪12"}
    return products.get(product_id, f"Product {product_id} not found")

agent = Agent(
    name="product_agent",
    system_prompt="Help users find products. Use the lookup tool with product IDs.",
    model="claude-sonnet-4-20250514",
    tools=[lookup_product]
)

result = client.run(agent=agent, prompt="What is product P001?")

# ה-trace יודפס אוטומטית לקונסולה:
# [TRACE] Agent 'product_agent' started
# [TRACE] LLM call: 150 input tokens
# [TRACE] Tool call: lookup_product(product_id="P001")
# [TRACE] Tool result: "Hummus - ₪15"
# [TRACE] LLM call: 200 input tokens
# [TRACE] Agent 'product_agent' finished in 2.3s
# [TRACE] Total: 350 input + 45 output tokens, 1 tool call

Custom Trace Processor

# tracing_custom.py
from claude_agent_sdk import Tracing, TraceProcessor
import json

class JSONTraceProcessor(TraceProcessor):
    """Saves traces as JSON files for later analysis."""

    def __init__(self, output_dir: str = "./traces"):
        self.output_dir = output_dir
        os.makedirs(output_dir, exist_ok=True)

    def process(self, trace):
        filename = f"{self.output_dir}/trace_{trace.id}_{trace.timestamp}.json"
        with open(filename, 'w') as f:
            json.dump({
                "id": trace.id,
                "agent": trace.agent_name,
                "duration_ms": trace.duration_ms,
                "llm_calls": trace.llm_call_count,
                "tool_calls": [
                    {"name": tc.name, "input": tc.input, "output": tc.output, "duration_ms": tc.duration_ms}
                    for tc in trace.tool_calls
                ],
                "tokens": {"input": trace.total_input_tokens, "output": trace.total_output_tokens},
                "cost_estimate": trace.cost_estimate,
            }, f, indent=2)

# הפעלת tracing עם processor מותאם
Tracing.enable(processor=JSONTraceProcessor("./my_traces"))

Debugging בעיות נפוצות

בעיהמה לבדוק ב-traceפתרון
סוכן בלולאה אינסופית Tool calls חוזרים על אותו כלי הוסף max_turns ושפר את ה-system prompt
סוכן בוחר כלי לא נכון ה-tool input לא תואם את הכוונה שפר docstrings של הכלים — יותר ברורים ומפורטים
סוכן מתעלם מהוראות ה-system prompt לא ברור כתוב system prompt יותר מפורש, עם דוגמאות
תשובות ארוכות מדי Output tokens גבוה הוסף הנחיה "Keep responses under 200 words" ל-system prompt
עלויות גבוהות הרבה LLM calls בהרצה אחת הקטן max_turns, השתמש ב-claude-haiku למשימות פשוטות
עשה עכשיו 5 דקות

הפעל tracing וראה מה קורה:

  1. הוסף Tracing.enable(output="console") בתחילת הקוד
  2. הרץ את הסוכן עם כלים
  3. בדוק: כמה LLM calls? כמה tool calls? כמה שניות?
מתקדם 25 דקות פרקטי

תבניות מתקדמות — Production Patterns

עד עכשיו בנינו סוכנים בסיסיים. בואו נעבור לדפוסים שתראו ב-production אמיתי.

Multi-Turn Conversations — שיחה ממשית

# multi_turn.py
from claude_agent_sdk import ClaudeSDKClient, Agent, Conversation

client = ClaudeSDKClient()

agent = Agent(
    name="assistant",
    system_prompt="You are a helpful Hebrew-speaking assistant. Remember context from previous messages.",
    model="claude-sonnet-4-20250514"
)

# יוצרים שיחה שנשמרת
conversation = Conversation()

# תור 1
result = client.run(agent=agent, prompt="שמי יוסי ואני גר בתל אביב", conversation=conversation)
print(f"Turn 1: {result.text}")

# תור 2 — הסוכן זוכר את השם והעיר
result = client.run(agent=agent, prompt="מה השם שלי ואיפה אני גר?", conversation=conversation)
print(f"Turn 2: {result.text}")
# Output: "שמך יוסי ואתה גר בתל אביב."

# תור 3 — שאלה המשך
result = client.run(agent=agent, prompt="מה מזג האוויר שם בדרך כלל?", conversation=conversation)
print(f"Turn 3: {result.text}")
# Claude understands "שם" = Tel Aviv from context

Session Forking — ניסויים בלי סיכון

# session_fork.py
from claude_agent_sdk import ClaudeSDKClient, Agent, Conversation

client = ClaudeSDKClient()
agent = Agent(name="assistant", system_prompt="Helpful assistant.", model="claude-sonnet-4-20250514")

# שיחה ראשית
conversation = Conversation()
client.run(agent=agent, prompt="Let's plan a trip to Japan", conversation=conversation)

# Fork — יוצרים עותק של השיחה
fork1 = conversation.fork()
fork2 = conversation.fork()

# כל fork ממשיך בכיוון אחר — בלי להשפיע על המקור
r1 = client.run(agent=agent, prompt="Focus on Tokyo only", conversation=fork1)
r2 = client.run(agent=agent, prompt="Focus on Kyoto only", conversation=fork2)

# השיחה המקורית לא השתנתה
r_original = client.run(agent=agent, prompt="What were we planning?", conversation=conversation)
# Still talking about general Japan trip

Structured Output — פלט מובנה

# structured_output.py
from claude_agent_sdk import ClaudeSDKClient, Agent
from pydantic import BaseModel
from typing import List

class ProductReview(BaseModel):
    product_name: str
    rating: float  # 1-5
    pros: List[str]
    cons: List[str]
    summary: str
    recommended: bool

client = ClaudeSDKClient()

agent = Agent(
    name="reviewer",
    system_prompt="You analyze products and return structured reviews.",
    model="claude-sonnet-4-20250514",
    output_schema=ProductReview  # מגדיר schema מובנה
)

result = client.run(
    agent=agent,
    prompt="Review the MacBook Air M3 for a developer"
)

# result.parsed — אובייקט ProductReview מאומת
review: ProductReview = result.parsed
print(f"Product: {review.product_name}")
print(f"Rating: {review.rating}/5")
print(f"Recommended: {review.recommended}")
for pro in review.pros:
    print(f"  + {pro}")
for con in review.cons:
    print(f"  - {con}")

TypeScript — Structured Output

// structured_output.ts
import { ClaudeSDKClient, Agent } from '@anthropic-ai/claude-agent-sdk';
import { z } from 'zod';

const ProductReview = z.object({
  productName: z.string(),
  rating: z.number().min(1).max(5),
  pros: z.array(z.string()),
  cons: z.array(z.string()),
  summary: z.string(),
  recommended: z.boolean(),
});

type ProductReview = z.infer<typeof ProductReview>;

const client = new ClaudeSDKClient();

const agent = new Agent({
  name: "reviewer",
  systemPrompt: "You analyze products and return structured reviews.",
  model: "claude-sonnet-4-20250514",
  outputSchema: ProductReview,
});

async function main() {
  const result = await client.run({
    agent,
    prompt: "Review the MacBook Air M3 for a developer",
  });

  const review = result.parsed as ProductReview;
  console.log(`${review.productName}: ${review.rating}/5`);
  console.log(`Recommended: ${review.recommended}`);
}

main();

Extended Thinking — חשיבה מעמיקה

# extended_thinking.py
from claude_agent_sdk import ClaudeSDKClient, Agent

client = ClaudeSDKClient()

agent = Agent(
    name="deep_thinker",
    system_prompt="You solve complex problems. Think step by step.",
    model="claude-sonnet-4-20250514",
    extended_thinking=True,  # מפעיל חשיבה מורחבת
    thinking_budget=10000    # עד 10K טוקנים לחשיבה
)

result = client.run(
    agent=agent,
    prompt="A company has 3 products. Product A costs $10, Product B costs $20, Product C costs $30. "
           "If they sell A:B:C in ratio 5:3:2, and total revenue is $150,000, "
           "how many units of each product were sold?"
)

# אפשר לראות את תהליך החשיבה
if result.thinking:
    print("=== Thinking Process ===")
    print(result.thinking)
    print("========================")

print(result.text)

Max Turns ו-Cost Control

# cost_control.py
from claude_agent_sdk import ClaudeSDKClient, Agent

client = ClaudeSDKClient()

agent = Agent(
    name="controlled_agent",
    system_prompt="Help the user efficiently.",
    model="claude-sonnet-4-20250514",
    max_turns=5,           # מקסימום 5 tool calls לפני עצירה
    max_tokens=1000,       # מקסימום 1000 טוקנים בתשובה
)

result = client.run(
    agent=agent,
    prompt="Analyze this codebase thoroughly"
)

if result.truncated:
    print("Warning: Agent reached max_turns limit")
print(f"Turns used: {result.turns_used}/{agent.max_turns}")
print(f"Cost: ~${result.cost_estimate:.4f}")
תרגיל 3: סוכן עם Structured Output 15 דקות
  1. הגדר BaseModel (Pydantic) עם 5 שדות לתיאור מסעדה: שם, דירוג, כתובת, סוג אוכל, טווח מחירים
  2. צור סוכן עם output_schema שמחזיר RestaurantReview
  3. שלח 3 שאלות על מסעדות שונות
  4. ודא שכל תשובה מפורסרת כ-RestaurantReview תקין
  5. הדפס את הנתונים בפורמט טבלה

הצלחה: 3 reviews מפורסרים בהצלחה, כל השדות מלאים, אין שגיאות validation.

עלות: ~$0.15

תרגיל 4: Full Production Agent — הפרויקט המסכם 45 דקות

שלבו את הכל — סוכן production-ready מלא:

  1. צור קובץ production_agent.py
  2. הגדר 3 כלים: calculator, file reader, product lookup
  3. הגדר 2 sub-agents: technical + billing, עם router agent
  4. הוסף 2 guardrails: PII detection (input) + max length (output)
  5. הפעל streaming — הצג תשובות בזמן אמת
  6. הפעל tracing — שמור traces כ-JSON
  7. השתמש ב-RunContext לניהול state (user info, request count)
  8. הגדר max_turns=10 ו-cost limit
  9. הרץ 5 שאילתות מגוונות ובדוק שהכל עובד

הצלחה: סוכן מלא שמשלב tools + sub-agents + guardrails + streaming + tracing + context. כל 5 השאילתות מצליחות.

עלות: ~$2-5

בינוני 10 דקות תיאוריה

Claude Agent SDK מול Raw API — מתי להשתמש במה

עכשיו שאתם מכירים את ה-SDK, חשוב להבין מתי הוא הבחירה הנכונה ומתי עדיף להשתמש ב-Anthropic API ישירות.

מה ה-SDK מוסיף מעל ה-API

יכולתRaw APIClaude Agent SDK
Agent Loopצריך לבנות ידניתמובנה ומנוהל
Tool Orchestrationידני: parse → execute → send backאוטומטי עם @tool decorator
Handoffsלא קייםמובנה עם Handoff class
Guardrailsצריך לבנות ידניתInput/Output guardrails מובנים
Tracingצריך לבנות ידניתמובנה עם trace processor
StreamingSSE parsing ידניEvent types מובנים
Context/StateידניRunContext מובנה
Overhead0~5-10ms per loop iteration

כלל אצבע: אם אתם כותבים agent loop ידנית — עדיף להשתמש ב-SDK. אם אתם עושים API call אחד בלי לולאה — השתמשו ב-API ישירות.

מסגרת: "מדרגת המורכבות" — בחירת הכלי הנכון
מורכבות דוגמה כלי עלות/בקשה
1 — API Call יחיד תרגום טקסט, סיכום מאמר Anthropic API $0.01-0.05
2 — Agent עם כלים עוזר שיכול לחשב ולחפש Claude Agent SDK $0.05-0.30
3 — Multi-Agent מערכת שירות לקוחות Claude Agent SDK + Handoffs $0.30-2.00
4 — Multi-Provider מערכת שמשתמשת ב-Claude + GPT Vercel AI SDK (פרק 6) $0.10-1.00
5 — Orchestration מורכב pipeline עם 10+ שלבים ותנאים LangGraph (פרק 8) $1.00-10.00

Migration Path — מ-SDK ל-API ובחזרה

היתרון של Claude Agent SDK: הוא שכבה דקה. אם מחר תצטרכו יותר שליטה — קל לחלץ את הלוגיקה ל-raw API calls. ואם התחלתם עם raw API ומרגישים שאתם בונים מחדש את הגלגל — ה-SDK נמצא שם.

# migration_example.py

# === RAW API (manual agent loop) ===
import anthropic

client = anthropic.Anthropic()
messages = [{"role": "user", "content": "What is 2+2?"}]
tools = [{"name": "calculator", "description": "...", "input_schema": {...}}]

while True:
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        messages=messages,
        tools=tools,
        max_tokens=1000
    )

    if response.stop_reason == "end_turn":
        break

    # Manual tool execution...
    for block in response.content:
        if block.type == "tool_use":
            result = execute_tool(block.name, block.input)  # You build this
            messages.append({"role": "assistant", "content": response.content})
            messages.append({"role": "user", "content": [{"type": "tool_result", "tool_use_id": block.id, "content": result}]})

# === CLAUDE AGENT SDK (same thing, 5 lines) ===
from claude_agent_sdk import ClaudeSDKClient, tool

@tool
def calculator(expression: str) -> str:
    """Evaluates math expressions."""
    return str(eval(expression))

client = ClaudeSDKClient()
result = client.query(prompt="What is 2+2?", tools=[calculator])

ה-SDK חוסך 20-30 שורות קוד boilerplate בכל סוכן. בפרויקט עם 5 סוכנים — זה 100-150 שורות שלא צריך לתחזק.

טעויות נפוצות — סיכום

טעות #6: הרצת סוכן בלי max_turns

סוכן בלי max_turns יכול להיכנס ללולאה אינסופית — כלי שנכשל, הסוכן מנסה שוב, נכשל שוב, וכך עד שנגמר ה-budget. תמיד הגדירו max_turns — 5-10 לסוכנים פשוטים, 15-20 למורכבים. בלי זה, שגיאה אחת יכולה לעלות $50.

טעות #7: System prompt גנרי מדי

"You are a helpful assistant" הוא system prompt גרוע. Claude חזק — אבל הוא צריך הוראות ברורות. כתבו: (1) מה הסוכן עושה, (2) מה הוא לא עושה, (3) באיזה טון לדבר, (4) באיזו שפה, (5) מגבלות ספציפיות. system prompt ספציפי = סוכן שמתנהג כצפוי.

טעות #8: שימוש ב-Opus כשמספיק Haiku

Claude Opus עולה פי 30 מ-Haiku. אם הסוכן שלכם עושה ניתוב פשוט או עונה על FAQ — השתמשו ב-Haiku. שמרו את Opus למשימות שדורשות חשיבה עמוקה. כלל אצבע: התחילו עם Haiku, שדרגו ל-Sonnet אם לא מספיק, Opus רק כשבאמת צריך.

שגרת עבודה — פרק 5
תדירותמשימהזמן
יומיבדוק traces של הסוכנים שלך — חפש לולאות, עלויות חריגות, tool failures5 דק'
שבועיסקור guardrails — בדוק שה-PII detector תופס patterns חדשים10 דק'
שבועיבדוק עדכוני Claude Agent SDK — changelog ב-GitHub5 דק'
חודשיניתוח עלויות: tokens per query, cost per user, model distribution20 דק'
חודשיA/B testing: נסה model חדש (למשל Haiku במקום Sonnet) על סוכן ספציפי30 דק'
רבעוניסקירת ארכיטקטורה: האם ה-handoff patterns עדיין נכונים?60 דק'
אם אתם עושים רק דבר אחד מהפרק הזה 15 דקות

בנו סוכן עם כלי אחד. זה הכל. קובץ אחד, 20 שורות קוד: ClaudeSDKClient, פונקציה עם @tool, ו-client.query(). ברגע שראיתם את Claude בוחר להפעיל את הכלי שלכם ומשתמש בתוצאה — הבנתם את העיקרון הכי חשוב של סוכני AI. כל השאר (handoffs, guardrails, streaming, tracing) הם שיפורים על הבסיס הזה. אבל הבסיס — סוכן + כלי — הוא הכל.

בדוק את עצמך — 5 שאלות
  1. מה ההבדל בין client.query() ל-client.stream()? (רמז: synchronous vs real-time events)
  2. למה ה-docstring של כלי כל כך חשוב? (רמז: זה מה ש-Claude רואה כשהוא מחליט אם להשתמש בכלי)
  3. מה ההבדל בין Input Guardrail ל-Tripwire Guardrail? (רמז: עצירה מיידית)
  4. מתי עדיף להשתמש ב-Handoffs ומתי בסוכן אחד עם הרבה כלים? (רמז: 5+ כלים, תחומי ידע שונים)
  5. למה חייבים להגדיר max_turns ב-production? (רמז: לולאות אינסופיות, $$$)

עברתם 4 מתוך 5? מצוין — אתם מוכנים לפרק 6 (Vercel AI SDK).

סיכום הפרק

בפרק הזה בנינו את הסוכן הראשון שלנו עם Claude Agent SDK — ה-SDK הרשמי של Anthropic. התחלנו עם סוכן בסיסי (5 שורות קוד), הוספנו כלים מותאמים אישית עם @tool decorator, למדנו streaming לפלט בזמן אמת, ובנינו מערכת multi-agent עם handoffs בין סוכנים מומחים.

הוספנו guardrails לבטיחות — PII detection, content filtering, ו-tripwire guardrails לעצירה מיידית. למדנו לנהל state עם RunContext, חיברנו MCP servers לכלים חיצוניים, והפעלנו tracing לדיבאגינג. לסיום, ראינו production patterns: multi-turn conversations, structured output, extended thinking, ו-cost control.

בפרק הבא (פרק 6) נכיר את Vercel AI SDK — ה-SDK שתומך ב-providers מרובים (Claude, GPT, Gemini) ומתמחה בבניית UI עם React. נשווה אותו ל-Claude Agent SDK ונבין מתי להשתמש בכל אחד.

צ'קליסט — סיכום פרק 5