- סוכן מינימלי עובד ב-20 שורות Python (ללא SDK, רק API calls) — ותבינו כל שורה
- אותו הסוכן גם ב-TypeScript — כדי לראות את הדמיון בין השפות
- דיאגרמת ארכיטקטורה של Agent Stack ב-7 שכבות שתשמש אתכם לאורך כל הקורס
- הבנה מלאה של דפוס ReAct — הדפוס שמאחורי כל SDK בעולם
- Framework לבחירת ארכיטקטורה נכונה (Single Agent, Router, Orchestrator, Collaborative)
- 10 בדיקות (Test Cases) לסוכן המינימלי שלכם עם מדידת אחוזי הצלחה
- רשימת 10 כללים לעיצוב כלים (Tool Design) שמשפיעים ישירות על דיוק הסוכן
- הבנת דפוסי זיכרון — מתי להשתמש בכל סוג ואיך לבנות אותו
- להסביר את דפוס ReAct (Reason + Act) ולזהות אותו בכל SDK ובכל כלי
- לבנות סוכן מינימלי מאפס ב-Python וב-TypeScript — ללא תלות ב-SDK
- לבחור ארכיטקטורה נכונה (Single, Router, Orchestrator, Collaborative) בהתאם למשימה
- לכתוב הגדרות כלים (Tool Definitions) שהמודל בוחר נכון ב-90%+ מהמקרים
- לתכנן מערכת זיכרון מתאימה — קצר-טווח, ארוך-טווח, ואפיזודי
- לזהות טעויות ארכיטקטורה נפוצות ולמנוע אותן לפני שמתחילים לכתוב קוד
- פרק 1 — מה זה AI Agents: טקסונומיה, סקירת השוק, ההבדל בין סוכן לצ'טבוט
- ידע בסיסי ב-Python או TypeScript (הבנת פונקציות, לולאות, JSON)
- מפתח API אחד לפחות (Anthropic, OpenAI, או Google) — חינם לרישום, $5-10 לתרגילים
- זמן מוערך: 3-4 שעות קריאה + תרגילים
בפרק 1 הבנתם מה סוכנים עושים. בפרק הזה אתם מבינים איך הם עובדים מבפנים. זה הפרק הכי חשוב בחלק הבסיס — כי כל SDK שתלמדו בפרקים 5-10 מבוסס על אותם דפוסים בדיוק.
הסוכן המינימלי שתבנו כאן (20 שורות, ללא SDK) ישמש אתכם כ-benchmark — כשתעבדו עם Claude Agent SDK, OpenAI Agents SDK או Vercel AI SDK, תוכלו תמיד לחזור לכאן ולשאול: "מה ה-SDK הזה עושה בשבילי שאני לא עושה בעצמי?"
הפרק הזה גם מניח את הבסיס לפרויקט הגמר: סוכן תמיכת לקוחות מלא (פרק 15) שישתמש ב-Router Agent, זיכרון ארוך-טווח, ו-Error Handling — כל הדפוסים שנלמד כאן.
הפרק הזה עמוס במונחים טכניים. לא צריך לשנן — הם יחזרו שוב ושוב. אבל טוב שיהיה לכם מפתח:
| מונח | תרגום / הסבר |
|---|---|
| ReAct | Reason + Act — הדפוס המרכזי בסוכני AI. הסוכן חושב (Reason), מבצע פעולה (Act), מתבונן בתוצאה (Observe), וחוזר על הלולאה עד שמסיים |
| Chain of Thought (CoT) | שרשרת חשיבה — טכניקה שבה המודל "חושב בקול רם" צעד אחרי צעד לפני שנותן תשובה. משפר דיוק ב-20-40% |
| Tool Use / Function Calling | שימוש בכלים — היכולת של מודל לבקש הרצת פונקציה חיצונית (חיפוש, שאילתת DB, שליחת מייל) ולקבל תוצאה |
| State Machine | מכונת מצבים — מודל שמגדיר מצבים אפשריים ומעברים ביניהם. דרך ליצור סוכנים צפויים יותר |
| Context Window | חלון הקשר — כמות הטקסט שהמודל יכול "לזכור" בשיחה אחת. Claude: 1M טוקנים, GPT-5: 1M טוקנים |
| System Prompt | פרומפט מערכת — ההוראות שמגדירות את האישיות, הכללים והמגבלות של הסוכן. "מי אתה ומה מותר/אסור לך" |
| Short-term Memory | זיכרון קצר-טווח — היסטוריית השיחה שנמצאת בתוך ה-Context Window. נמחק כשהשיחה נגמרת |
| Long-term Memory | זיכרון ארוך-טווח — מידע שנשמר בין שיחות: Vector Store, מסד נתונים, קבצים. שורד Restart |
| Episodic Memory | זיכרון אפיזודי — זכרונות של אירועים ספציפיים: "ב-15/3 הלקוח ביקש החזר וקיבל אותו". שימושי לפרסונליזציה |
| Orchestrator | תזמורן — סוכן מנהל שמחלק משימות לסוכני-משנה (Workers) ואוסף תוצאות |
| Router Agent | סוכן ניתוב — סוכן שמנתח את הבקשה ומעביר אותה לסוכן המתמחה הנכון |
| Handoff | העברה — כשסוכן מעביר את השיחה לסוכן אחר (או לאדם). מנגנון מרכזי ב-Multi-Agent |
| MCP | Model Context Protocol — פרוטוקול סטנדרטי לחיבור סוכנים לכלים. "ה-USB-C של AI Agents" |
| Guardrails | מעקות בטיחות — בדיקות קלט ופלט שמוודאות שהסוכן לא עושה דברים שהוא לא אמור |
| Max Iterations | מגבלת איטרציות — מספר מקסימלי של סיבובי לולאה. מונע סוכן שרץ לנצח |
| Token Budget | תקציב טוקנים — מגבלת עלות לריצה בודדת. מונע חשבונות של $500 על באג בלולאה |
| Durable Execution | הרצה עמידה — סוכן ששורד Crash, Restart, ותהליכים ארוכים. קריטי ל-Production |
לולאת ReAct — הדפוס המרכזי
כל סוכן AI בעולם — לא משנה באיזה SDK הוא בנוי, באיזה מודל הוא משתמש, או מה הוא עושה — פועל לפי אותו דפוס בסיסי. הדפוס הזה נקרא ReAct (קיצור של Reason + Act), ואם תבינו אותו לעומק, תבינו כל SDK שתפגשו בקורס הזה.
הרעיון פשוט להפליא: הסוכן מקבל קלט, חושב מה לעשות, מבצע פעולה, מסתכל על התוצאה, וחוזר על הלולאה עד שהוא מסיים. חמישה צעדים, שוב ושוב:
| שלב | שם באנגלית | מה קורה | דוגמה |
|---|---|---|---|
| 1 | Input (קלט) | הסוכן מקבל את הבקשה מהמשתמש | "הזמן לי טיסה לתל אביב ב-15 באפריל" |
| 2 | Reason (חשיבה) | המודל חושב מה הצעד הבא שצריך לעשות | "אני צריך לחפש טיסות לתל אביב ב-15/4. אשתמש בכלי search_flights" |
| 3 | Act (פעולה) | הסוכן מבצע פעולה — בדרך כלל קריאה לכלי (Tool) | search_flights(destination="TLV", date="2026-04-15") |
| 4 | Observe (צפייה) | הסוכן מקבל את תוצאת הכלי ומנתח אותה | "נמצאו 3 טיסות. הזולה ביותר: $450 ב-Turkish Airlines" |
| 5 | Repeat / Stop | הסוכן מחליט: האם סיימתי? אם לא — חוזר לשלב 2 | "צריך לשאול את המשתמש אם לאשר את ההזמנה" → עצירה |
זה הכול. כל סוכן AI שתראו — מ-Claude Code ועד CrewAI עם 10 סוכנים — הוא וריאציה על הלולאה הזו. הבנתם אותה? הבנתם 80% מארכיטקטורת סוכנים.
הלולאה הזו יכולה לרוץ לנצח. אם הסוכן לא מגיע למצב של "סיימתי", הוא ימשיך לחשוב ולפעול שוב ושוב — ויעלה לכם כסף. תמיד צריך מנגנוני עצירה:
- Max Iterations — מגבלת סיבובים (בדרך כלל 10-25)
- Token Budget — מגבלת טוקנים (למשל: 50,000 טוקנים לריצה)
- Timeout — מגבלת זמן (2-5 דקות למשימה רגילה)
- Cost Cap — מגבלת עלות ($1-5 לריצה, תלוי במשימה)
באג שגורם ללולאה אינסופית יכול לייצר חשבון API של $500 בלילה אחד. זה קורה. הגדירו מגבלות לפני שאתם מפעילים סוכן.
חשבו על לולאת ReAct בחיים שלכם. נניח שמישהו מבקש מכם "מצא לי מסעדה טובה ליום שישי". מה הצעדים שלכם?
- Reason: "אני צריך לדעת מה הוא אוהב, באיזה אזור, ותקציב"
- Act: שואלים אותו (כמו כלי — input מהמשתמש)
- Observe: "אוהב סושי, מרכז תל אביב, עד 200 ש"ח לזוג"
- Reason: "אחפש מסעדות סושי בתל אביב"
- Act: מחפשים ב-Google Maps
- Observe: 5 תוצאות, בודקים ביקורות
- Stop: "ממליץ על X — 4.7 כוכבים, 180 ש"ח לזוג"
ברכות — אתם סוכן ReAct. עכשיו תבינו מה הקוד עושה.
Pseudocode של לולאת ReAct
לפני שנכתוב קוד אמיתי — הנה הלוגיקה במילים פשוטות:
function agent_loop(user_message, tools, max_iterations=10):
messages = [system_prompt, user_message]
for i in range(max_iterations):
response = llm.call(messages) // שלב Reason
if response.has_tool_calls:
for tool_call in response.tool_calls:
result = execute_tool(tool_call) // שלב Act
messages.append(result) // שלב Observe
else:
return response.text // עצירה — התשובה מוכנה
return "הגעתי למגבלת הסיבובים" // Failsafe
ReAct בפועל — Python עם Streaming ו-Token Tracking
הנה גרסה מתקדמת יותר של לולאת ReAct, עם מעקב אחרי טוקנים ו-streaming — שני דברים קריטיים בפרודקשן:
# Python — ReAct Loop עם Token Tracking
import anthropic
import json
import time
client = anthropic.Anthropic()
def react_loop_with_tracking(
user_msg: str,
tools: list,
tool_fns: dict,
max_iterations: int = 10,
max_tokens_budget: int = 50000,
system_prompt: str = "You are a helpful assistant."
) -> dict:
"""
ReAct loop מתקדם עם:
- Token budget tracking (מונע חשבונות גבוהים)
- Iteration logging (לדיבוג)
- Timing per step (לזיהוי צווארי בקבוק)
"""
messages = [{"role": "user", "content": user_msg}]
total_tokens = 0
steps_log = []
for i in range(max_iterations):
step_start = time.time()
resp = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system=system_prompt,
tools=tools,
messages=messages
)
# עדכון תקציב טוקנים
step_tokens = resp.usage.input_tokens + resp.usage.output_tokens
total_tokens += step_tokens
step_time = time.time() - step_start
steps_log.append({
"step": i + 1,
"tokens": step_tokens,
"time_sec": round(step_time, 2),
"stop_reason": resp.stop_reason
})
# בדיקת תקציב
if total_tokens > max_tokens_budget:
return {
"result": "חרגתי מתקציב הטוקנים",
"total_tokens": total_tokens,
"steps": steps_log
}
# בדיקת tool calls
tool_calls = [b for b in resp.content if b.type == "tool_use"]
if not tool_calls:
return {
"result": "".join(
b.text for b in resp.content if b.type == "text"
),
"total_tokens": total_tokens,
"steps": steps_log
}
# ביצוע כלים
messages.append({"role": "assistant", "content": resp.content})
for tc in tool_calls:
fn = tool_fns.get(tc.name)
if fn:
result = fn(**tc.input)
else:
result = f"Error: Unknown tool '{tc.name}'"
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tc.id,
"content": str(result)
}]
})
return {
"result": "הגעתי למגבלת הסיבובים",
"total_tokens": total_tokens,
"steps": steps_log
}
אותו הדבר ב-TypeScript — עם Streaming
// TypeScript — ReAct Loop עם Token Tracking ו-Streaming
import Anthropic from "@anthropic-ai/sdk";
interface StepLog {
step: number;
tokens: number;
timeSec: number;
stopReason: string;
}
interface AgentResult {
result: string;
totalTokens: number;
steps: StepLog[];
}
async function reactLoopWithTracking(
userMsg: string,
tools: Anthropic.Tool[],
toolFns: Record<string, (args: any) => Promise<string>>,
maxIterations = 10,
maxTokensBudget = 50000,
systemPrompt = "You are a helpful assistant."
): Promise<AgentResult> {
const client = new Anthropic();
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: userMsg }
];
let totalTokens = 0;
const stepsLog: StepLog[] = [];
for (let i = 0; i < maxIterations; i++) {
const stepStart = Date.now();
const resp = await client.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
system: systemPrompt,
tools,
messages,
});
const stepTokens =
resp.usage.input_tokens + resp.usage.output_tokens;
totalTokens += stepTokens;
stepsLog.push({
step: i + 1,
tokens: stepTokens,
timeSec: Math.round((Date.now() - stepStart) / 100) / 10,
stopReason: resp.stop_reason ?? "unknown",
});
// בדיקת תקציב
if (totalTokens > maxTokensBudget) {
return {
result: "חרגתי מתקציב הטוקנים",
totalTokens,
steps: stepsLog,
};
}
const toolCalls = resp.content.filter(
(b) => b.type === "tool_use"
);
if (toolCalls.length === 0) {
return {
result: resp.content
.filter((b) => b.type === "text")
.map((b) => (b as Anthropic.TextBlock).text)
.join(""),
totalTokens,
steps: stepsLog,
};
}
messages.push({ role: "assistant", content: resp.content });
for (const tc of toolCalls) {
const block = tc as Anthropic.ToolUseBlock;
const fn = toolFns[block.name];
const result = fn
? await fn(block.input as any)
: `Error: Unknown tool '${block.name}'`;
messages.push({
role: "user",
content: [{
type: "tool_result",
tool_use_id: block.id,
content: result,
}],
});
}
}
return {
result: "הגעתי למגבלת הסיבובים",
totalTokens,
steps: stepsLog,
};
}
בלי מעקב אחרי טוקנים, אתם עיוורים. סוכן שנראה "עובד" יכול לצרוך 10x יותר טוקנים ממה שחשבתם — בגלל לולאות מיותרות, context window שגדל, או tool results ארוכים. ה-steps_log שבדוגמה למעלה נותן לכם visibility מלאה: כמה טוקנים כל צעד צרך, כמה זמן הוא לקח, ומה הסיבה שהוא עצר. בפרודקשן — שלחו את הלוג הזה ל-monitoring system.
שימו לב: המודל הוא זה שמחליט מתי לעצור. כשהוא לא מבקש לקרוא לכלי (no tool calls), הלולאה נגמרת והתשובה הטקסטואלית חוזרת למשתמש. זה הדפוס שכל SDK מיישם — רק עם יותר אבטחה, Streaming, ו-Error Handling.
נקודה חשובה: כל SDK שנלמד בקורס — Claude Agent SDK, OpenAI Agents SDK, Vercel AI SDK, LangGraph, CrewAI, Google ADK — מסתיר את הלולאה הזו מאחורי API יפה. אבל כשמבינים את ה-Pseudocode למעלה, מבינים בדיוק מה כל SDK עושה מתחת לפני השטח. ב-LangGraph, למשל, הלולאה הזו מיוצגת כ-graph עם nodes ו-edges. ב-Vercel AI SDK, היא מיוצגת כ-generateText + tools + stopWhen. התוצאה זהה — רק ה-syntax שונה.
שרטטו את לולאת ReAct על דף נייר. ריבוע = שלב. חץ = מעבר. ציירו: Input → Reason → Act → Observe → (חזרה ל-Reason או Stop). סמנו איפה ה-LLM עובד ואיפה הכלים עובדים. שמרו את הציור — תשתמשו בו שוב.
Chain of Thought — שרשרת חשיבה ווריאציות
Chain of Thought (CoT) היא טכניקה שבה המודל "חושב בקול רם" — פורט את תהליך החשיבה שלו צעד אחרי צעד לפני שהוא נותן תשובה סופית. זה לא טריק — זה מנגנון שמשפר דיוק באופן מדיד. מחקרים מראים שיפור של 20-40% בדיוק על משימות מורכבות כשמשתמשים ב-CoT.
למה זה עובד? כי בלי CoT, המודל "קופץ" ישר לתשובה. עם CoT, הוא מפרק את הבעיה ליחידות קטנות. ההבדל:
| ללא CoT | עם CoT |
|---|---|
| "כמה עולה להריץ 1,000 שאילתות ב-Claude Sonnet?" → "$4.50" (עשוי לטעות) |
"כמה עולה להריץ 1,000 שאילתות ב-Claude Sonnet?" → "בוא נחשב: כל שאילתה ~500 טוקנים input, ~200 output. סה"כ: 500K input + 200K output. מחיר: $3/M input = $1.50. $15/M output = $3.00. סה"כ: $4.50" (מדויק + ניתן לביקורת) |
וריאציות של Chain of Thought
CoT הוא הבסיס, אבל יש כמה גרסאות שחשוב להכיר:
| שם | איך עובד | מתי להשתמש |
|---|---|---|
| Zero-shot CoT | מוסיפים "חשוב צעד אחרי צעד" (Think step by step) לפרומפט. המודל עושה את השאר | כל משימה מורכבת. הכי פשוט להפעיל |
| Few-shot CoT | נותנים 2-3 דוגמאות של חשיבה מפורטת, והמודל מחקה את הסגנון | כשרוצים פורמט מסוים של חשיבה |
| Tree of Thought (ToT) | המודל בודק מספר נתיבי פתרון במקביל ובוחר את הטוב ביותר | משימות עם כמה פתרונות אפשריים, כמו תכנון אסטרטגיה |
| Inner Monologue | הסוכן כותב "הערות לעצמו" שהמשתמש לא רואה, לפני כל פעולה | סוכנים שצריכים לחשוב בלי להציף את המשתמש |
| ReAct + CoT | שילוב: הסוכן חושב בקול רם (CoT) וגם מבצע פעולות (Act) בתוך אותה לולאה | הדפוס הנפוץ ביותר לסוכני AI. ברירת המחדל של רוב ה-SDKs |
סוכנים צריכים לעבוד עם Temperature נמוך: 0.0-0.3. למה? כי אתם רוצים שהסוכן יקבל את אותה החלטה כל פעם שהוא רואה את אותו מצב. Temperature גבוה (0.7-1.0) מתאים ליצירת תוכן — לא לקבלת החלטות.
חריג: סוכן שמייצר תוכן יצירתי (למשל: כותב פוסטים) יכול להשתמש ב-Temperature גבוה בשלב הכתיבה, אבל Temperature נמוך בשלב קבלת ההחלטות (איזה כלי להפעיל, מתי לעצור).
נסו את ההבדל בעצמכם. פתחו את claude.ai ושאלו: "כמה יעלה להריץ סוכן שעושה 50 קריאות API ביום למשך חודש, עם Claude Sonnet, כשכל קריאה כוללת ~1,000 טוקנים input ו-~500 output?"
פעם ראשונה: שאלו ישר. פעם שנייה: הוסיפו "Think step by step, show all calculations." שימו לב להבדל בדיוק ובביטחון שלכם בתשובה.
ארכיטקטורת Tool Calling — הידיים של הסוכן
בלי כלים, סוכן הוא בסך הכול צ'טבוט מתקדם. הכלים (Tools / Functions) הם מה שהופכים אותו לסוכן אמיתי — הם הידיים שמאפשרות לו לפעול בעולם: לחפש באינטרנט, לשלוח מייל, לקרוא קובץ, לשאול בסיס נתונים, או לבצע חישוב.
האנטומיה של כלי (Tool)
כל כלי מורכב מ-5 חלקים:
| חלק | שם באנגלית | תפקיד | דוגמה |
|---|---|---|---|
| 1 | Name | שם ייחודי שהמודל משתמש בו | search_flights |
| 2 | Description | הסבר מתי ולמה להשתמש בכלי. הגורם מספר 1 לדיוק הסוכן | "Search available flights by destination and date. Use when user wants to book or compare flights." |
| 3 | Input Schema | הגדרת הפרמטרים שהכלי מקבל (JSON Schema) | { destination: string, date: string, max_price?: number } |
| 4 | Output | מה הכלי מחזיר למודל | JSON עם רשימת טיסות, מחירים, וזמנים |
| 5 | Execution Function | הקוד שבאמת רץ כשהמודל קורא לכלי | פונקציית Python/TS שמתשאלת API חיצוני |
איך המודל בוחר כלי?
זה עובד ככה: כשאתם שולחים הודעה לסוכן, אתם גם שולחים את רשימת הכלים הזמינים (עם השמות, התיאורים וה-Schemas). המודל קורא את כל זה ומחליט: "בהתחשב בבקשת המשתמש ובכלים שיש לי, הכלי הנכון הוא X עם הפרמטרים Y."
מה זה אומר בפועל? שתיאור הכלי (Description) הוא הדבר הכי חשוב שתכתבו. תיאור גרוע = הסוכן בוחר את הכלי הלא נכון = תוצאה גרועה. תיאור טוב = דיוק של 90%+.
ראו את ההבדל:
| תיאור גרוע | תיאור טוב |
|---|---|
"Search stuff" |
"Search the company's internal knowledge base for product documentation, FAQs, and troubleshooting guides. Use when user asks about product features, pricing, or technical issues. Do NOT use for general web search — use web_search for that." |
שימו לב: התיאור הטוב מסביר מתי להשתמש, מתי לא, ומה הכלי מחזיר. 3 המשפטים האלה הם ההבדל בין סוכן שעובד לסוכן שמתסכל.
Static Tools מול Dynamic Tools
יש שני סוגים של כלים:
- Static Tools (כלים סטטיים) — מוגדרים מראש בקוד. אתם יודעים בדיוק אילו כלים יש לסוכן לפני שהוא רץ. דוגמה:
calculate,send_email,query_database. - Dynamic Tools (כלים דינמיים) — נטענים בזמן ריצה, בדרך כלל ממשרת MCP. הסוכן מתחבר לשרת ומגלה אילו כלים זמינים. דוגמה: סוכן שמתחבר לשרת MCP של GitHub ומקבל כלים כמו
create_issue,list_repos.
בפרק 3 נעמיק ב-MCP ובכלים דינמיים. כרגע, הדבר החשוב להבין: כלים סטטיים פשוטים יותר ומהירים יותר, כלים דינמיים גמישים יותר ו-reusable.
Tool Calling — Python Example
הנה איך מגדירים כלי ושולחים אותו ל-API של Anthropic (Claude). שימו לב — זה בלי SDK, רק API ישיר:
import anthropic
client = anthropic.Anthropic()
# הגדרת כלי
tools = [{
"name": "get_weather",
"description": "Get current weather for a city. Use when user asks "
"about weather, temperature, or if they need an umbrella. "
"Returns temperature in Celsius and conditions.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name, e.g. 'Tel Aviv', 'Jerusalem'"
}
},
"required": ["city"]
}
}]
# שליחת בקשה עם כלים
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "מה מזג האוויר בתל אביב?"}]
)
# המודל מחליט לקרוא ל-get_weather
print(response.content)
# [ToolUse(type='tool_use', name='get_weather', input={'city': 'Tel Aviv'})]
Tool Calling — TypeScript Example
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
// הגדרת כלי
const tools = [{
name: "get_weather",
description: "Get current weather for a city. Use when user asks " +
"about weather, temperature, or if they need an umbrella. " +
"Returns temperature in Celsius and conditions.",
input_schema: {
type: "object" as const,
properties: {
city: {
type: "string" as const,
description: "City name, e.g. 'Tel Aviv', 'Jerusalem'"
}
},
required: ["city"]
}
}];
// שליחת בקשה עם כלים
const response = await client.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
tools,
messages: [{ role: "user", content: "מה מזג האוויר בתל אביב?" }]
});
console.log(response.content);
// [{ type: 'tool_use', name: 'get_weather', input: { city: 'Tel Aviv' } }]
כתבו הגדרת כלי (Tool Definition) עבור הכלי הבא: כלי ששולח הודעת WhatsApp ללקוח. חשבו על:
- מה השם? (רמז:
send_whatsapp_message) - מה ה-Description? (כללו: מתי להשתמש, מתי לא, ומה הכלי מחזיר)
- מה ה-Input Schema? (אילו פרמטרים הכלי צריך?)
אל תכתבו קוד — רק את ההגדרה ב-JSON או במילים. זה התרגיל הכי חשוב בפרק הזה.
State Management — ניהול מצב
סוכן צריך "לזכור" באיזה שלב הוא נמצא. האם הוא כבר חיפש טיסות? האם המשתמש אישר את ההזמנה? האם הייתה שגיאה שצריך לטפל בה? State Management (ניהול מצב) הוא האופן שבו הסוכן עוקב אחרי ההתקדמות שלו.
שלושה דפוסים של ניהול מצב
| דפוס | איך עובד | יתרונות | חסרונות | מתי להשתמש |
|---|---|---|---|---|
| Implicit State (מצב מרומז) |
כל ההיסטוריה ב-Context Window. המודל "זוכר" את מה שקרה דרך ההודעות הקודמות | פשוט, אין קוד נוסף | Context Window מתמלא, אין persistency | סוכנים פשוטים, שיחות קצרות |
| Explicit State (מצב מפורש) |
אובייקט State שעוקב אחרי משתנים: { step: "search_done", results: [...], approved: false } |
ברור, ניתן ל-debug, ניתן לשמירה | צריך לכתוב לוגיקה, מסובך יותר | סוכנים עם Workflow מוגדר |
| State Machine (מכונת מצבים) |
הגדרה מראש של מצבים אפשריים ומעברים ביניהם. כמו תרשים זרימה | צפוי מאוד, בטוח, ניתן ל-audit | פחות גמיש, לא מתאים למשימות פתוחות | Production, Compliance, תהליכים רגולטוריים |
השוואה מעשית — מתי להשתמש באיזה דפוס
כל שלושת הדפוסים עובדים. הבחירה תלויה בסוג המשימה:
| קריטריון | Implicit State | Explicit State | State Machine |
|---|---|---|---|
| קושי Implementation | אפס — ברירת מחדל | נמוך-בינוני | בינוני-גבוה |
| Debugging | קשה — צריך לקרוא Context | בינוני — מדפיסים State | קל — רואים בדיוק באיזה מצב |
| Persistence | אין — נמחק בסוף שיחה | אפשרי — שומרים לקובץ/DB | מובנה — Checkpointing |
| Auditability | נמוכה | בינונית | גבוהה — כל מעבר מתועד |
| דוגמה ל-SDK | Claude Agent SDK, OpenAI Agents SDK | Vercel AI SDK (ToolLoopAgent) | LangGraph (StateGraph), Google ADK |
| מתאים ל | Chatbots, שאלות פשוטות | Workflows בינוניים | Compliance, Finance, Healthcare |
שאלו את עצמכם שלוש שאלות: (1) האם צריך לשחזר את מצב הסוכן אחרי Crash? אם כן — State Machine. (2) האם צריך audit trail (למשל: רגולציה פיננסית)? אם כן — State Machine. (3) האם הסוכן פשוט (2-3 כלים, שיחה קצרה)? אם כן — Implicit State מספיק. ברוב המקרים, Implicit State + כלים טובים הם כל מה שצריך.
State Machine — דוגמה מעשית
נניח שבונים סוכן שמטפל בבקשת החזר כספי. הנה מכונת המצבים:
מצבים אפשריים:
┌──────────────────────────────────────────────────────────┐
│ │
│ [START] ──> [COLLECTING_INFO] ──> [CHECKING_POLICY] │
│ │ │
│ ┌────┴────┐ │
│ [APPROVED] [DENIED] │
│ │ │ │
│ [PROCESSING] [EXPLAINING] │
│ │ │ │
│ [DONE] [ESCALATE] │
│ │
└──────────────────────────────────────────────────────────┘
מעברים:
START → COLLECTING_INFO: כשמגיע בקשה חדשה
COLLECTING_INFO → CHECKING_POLICY: כשיש את כל הפרטים
CHECKING_POLICY → APPROVED: כשהבקשה עומדת במדיניות
CHECKING_POLICY → DENIED: כשהבקשה לא עומדת במדיניות
APPROVED → PROCESSING: אוטומטי
DENIED → EXPLAINING: מסביר למשתמש
DENIED → ESCALATE: אם המשתמש מבקש מנהל
PROCESSING → DONE: החזר בוצע
למה זה חשוב? כי ב-State Machine אתם שולטים במה הסוכן יכול לעשות בכל רגע. הוא לא יכול "לקפוץ" ממצב START ישר ל-PROCESSING — הוא חייב לעבור דרך כל השלבים. זה מה שהופך סוכנים ל-Production-ready.
SDKs כמו LangGraph בנויים סביב State Machines — כל ה-Architecture שלהם היא גרף של מצבים ומעברים. Google ADK מציע SequentialAgent, ParallelAgent, ו-LoopAgent שהם State Machines מוכנים. ב-OpenAI Agents SDK וב-Claude Agent SDK, הדפוס הוא יותר Implicit — אבל אפשר לבנות State Machine מעל.
צרו State Machine פשוט לסוכן שמזמין פיצה. רשמו:
- 4-6 מצבים (States) — מ-START ועד DELIVERED
- המעברים (Transitions) בין המצבים
- באיזה מצב הסוכן צריך אישור מהמשתמש? (Human-in-the-Loop)
Memory — זיכרון קצר-טווח, ארוך-טווח ואפיזודי
סוכן ללא זיכרון הוא כמו עוזר שמתחיל כל יום מאפס — לא זוכר שדיברתם אתמול, לא יודע שאתם אלרגיים לבוטנים, לא מכיר את ההעדפות שלכם. זיכרון הוא מה שהופך סוכן ממועיל ל-valuable.
אפשר לחשוב על הזיכרון של סוכן בדיוק כמו הזיכרון של מחשב — בהיררכיה של מהירות, גודל, ועלות:
| סוג זיכרון | אנלוגיה למחשב | בסוכן AI | גודל | מהירות | עלות | שורד Restart? |
|---|---|---|---|---|---|---|
| Working Memory | Registers / Cache | התור הנוכחי — מה הסוכן חושב עכשיו | קטן | מיידי | $0 | לא |
| Short-term Memory | RAM | Context Window — כל היסטוריית השיחה | עד 1M טוקנים | מהיר | $$ | לא |
| Episodic Memory | SSD Cache | זכרונות של אירועים: "ב-15/3 הלקוח ביקש החזר" | בינוני | בינוני | $ | כן |
| Long-term Memory | Hard Disk | Vector Store, מסד נתונים, קבצי זיכרון | ללא הגבלה | איטי | $ | כן |
Short-term Memory — Context Window
הזיכרון קצר-הטווח של סוכן הוא פשוט כל ההודעות שנשלחו עד כה — ההודעה של המשתמש, התשובה של המודל, קריאות הכלים, והתוצאות שחזרו. הכול נכנס ל-Context Window ונשלח למודל בכל סיבוב.
הבעיה? Context Window מוגבל. Claude: 1M טוקנים (כ-750,000 מילים). GPT-5: 1M טוקנים. Gemini 2.5 Pro: 1M טוקנים. נשמע הרבה, אבל כשסוכן מבצע 50 קריאות כלים ומקבל תוצאות ארוכות — זה מתמלא מהר.
שלוש אסטרטגיות לניהול Context Window:
- Summarization (סיכום) — כל X הודעות, מסכמים את מה שהיה ומחליפים את ההודעות המקוריות בסיכום. חוסך טוקנים, מאבד פרטים
- Sliding Window (חלון נע) — שומרים רק את N ההודעות האחרונות. פשוט, אבל "שוכח" את ההתחלה
- Priority-based Eviction (פינוי לפי חשיבות) — מסמנים הודעות חשובות ומוחקים רק את הפחות חשובות. הכי מתוחכם, הכי מדויק
Episodic Memory — זיכרון אירועים
זיכרון אפיזודי שומר אירועים ספציפיים ומשלב אותם בהמשך. זה הרעיון שמאחורי מה שנקרא Agentic Memory (מגמה חדשה ב-2026) — הסוכן עצמו מחליט מה שווה לזכור ומה לשכוח, במקום שהמפתח יגדיר הכול מראש.
דוגמאות לזיכרון אפיזודי:
- "הלקוח ביקש שלא ישלחו לו הודעות אחרי 21:00" — נשמר ומשפיע על כל אינטראקציה עתידית
- "הפעם האחרונה שהסוכן ניסה להשתמש ב-API של Stripe, הוא קיבל Error 429 (Rate Limit)" — הסוכן לומד לחכות לפני ניסיון נוסף
- "הלקוח מעדיף תקשורת בעברית" — נשמר כהעדפה
Long-term Memory — Vector Store ומעבר
לזיכרון ארוך-טווח יש כמה פתרונות, מהפשוט למורכב:
| פתרון | איך עובד | מתאים ל | דוגמה |
|---|---|---|---|
| Key-Value Store | שומרים נתונים מובנים: user_prefs = { lang: "he", timezone: "Asia/Jerusalem" } |
העדפות, הגדרות, נתונים מובנים | Redis, Cloudflare KV |
| Vector Store (RAG) | ממירים טקסט ל-Embeddings (וקטורים), שומרים, ומחפשים לפי דמיון סמנטי | בסיסי ידע, מסמכים, שיחות קודמות | Pinecone, Weaviate, pgvector |
| File-based Memory | קבצי Markdown/JSON שהסוכן קורא וכותב. דפוס CLAUDE.md |
סוכני פיתוח, זיכרון פרויקטים | Claude Code, Cursor |
| Database | מסד נתונים רלציוני עם שאילתות SQL | היסטוריית אינטראקציות, לוגים, ניתוח | PostgreSQL, SQLite, D1 |
התחילו בפשוט. רוב הסוכנים לא צריכים Vector Store מהיום הראשון. התחילו עם Context Window (חינם, מיידי). הוסיפו Key-Value Store כשצריך לזכור העדפות בין שיחות. הוסיפו Vector Store רק כשבסיס הידע גדול מדי ל-Context Window (מעל 100K מילים).
דפוסים מעשיים לניהול זיכרון
לכל סוג זיכרון יש דפוסים שעובדים בפרודקשן. הנה שלושה דפוסים שתשתמשו בהם שוב ושוב:
דפוס 1: Summarization Buffer — סיכום אוטומטי
כשהשיחה מתארכת, מסכמים את ההיסטוריה ומחליפים אותה בסיכום קומפקטי. חוסך טוקנים, שומר את המידע החשוב:
# Python — Summarization Buffer Pattern
def manage_context_with_summary(
messages: list,
model,
max_messages: int = 20,
summary_so_far: str = ""
) -> tuple[list, str]:
"""
כשמספר ההודעות חורג מ-max_messages:
1. מסכם את ההודעות הישנות
2. שומר רק את ה-summary + הודעות אחרונות
"""
if len(messages) <= max_messages:
return messages, summary_so_far
# ההודעות שנסכם (החצי הראשון)
to_summarize = messages[:max_messages // 2]
to_keep = messages[max_messages // 2:]
# בניית prompt לסיכום
summary_prompt = f"""Previous summary: {summary_so_far}
New messages to incorporate:
{format_messages(to_summarize)}
Write a concise summary of the full conversation so far.
Focus on: user preferences, decisions made, pending items."""
new_summary = model.invoke(summary_prompt).content
# מחזירים: הודעת summary + הודעות שנשארו
summary_msg = {"role": "system",
"content": f"Conversation summary: {new_summary}"}
return [summary_msg] + to_keep, new_summary
דפוס 2: User Profile Memory — זיכרון משתמש
// TypeScript — User Profile Pattern
interface UserProfile {
userId: string;
name?: string;
language: "he" | "en" | "auto";
preferences: Record<string, string>;
recentTopics: string[];
lastInteraction: string;
}
class UserMemory {
private profiles: Map<string, UserProfile> = new Map();
getSystemPromptAddition(userId: string): string {
const profile = this.profiles.get(userId);
if (!profile) return "";
const parts: string[] = [];
if (profile.name) parts.push(`User's name: ${profile.name}`);
if (profile.language !== "auto")
parts.push(`Preferred language: ${profile.language}`);
if (Object.keys(profile.preferences).length > 0)
parts.push(
`Preferences: ${JSON.stringify(profile.preferences)}`
);
if (profile.recentTopics.length > 0)
parts.push(
`Recent topics: ${profile.recentTopics.slice(-3).join(", ")}`
);
return parts.length > 0
? `\n\nKnown user info:\n${parts.join("\n")}`
: "";
}
updateFromConversation(
userId: string,
topic: string,
extractedPrefs: Record<string, string>
) {
const profile = this.profiles.get(userId) ?? {
userId,
language: "auto",
preferences: {},
recentTopics: [],
lastInteraction: new Date().toISOString(),
};
profile.recentTopics.push(topic);
if (profile.recentTopics.length > 10)
profile.recentTopics.shift(); // Sliding window
Object.assign(profile.preferences, extractedPrefs);
profile.lastInteraction = new Date().toISOString();
this.profiles.set(userId, profile);
}
}
דפוס 3: Tool Result Cache — קאש תוצאות כלים
כשכלי מחזיר תוצאה זהה לאותם פרמטרים, אין טעם לקרוא לו שוב. Cache פשוט חוסך tokens, זמן, ועלויות API חיצוניים:
# Python — Tool Result Cache
import hashlib
import json
from datetime import datetime, timedelta
class ToolCache:
def __init__(self, ttl_minutes: int = 30):
self.cache = {}
self.ttl = timedelta(minutes=ttl_minutes)
def _key(self, tool_name: str, args: dict) -> str:
raw = f"{tool_name}:{json.dumps(args, sort_keys=True)}"
return hashlib.md5(raw.encode()).hexdigest()
def get(self, tool_name: str, args: dict):
key = self._key(tool_name, args)
if key in self.cache:
result, timestamp = self.cache[key]
if datetime.now() - timestamp < self.ttl:
return result # Cache hit!
return None # Cache miss
def set(self, tool_name: str, args: dict, result):
key = self._key(tool_name, args)
self.cache[key] = (result, datetime.now())
# שימוש בלולאת ReAct
cache = ToolCache(ttl_minutes=15)
def execute_tool_cached(tool_name, args, tool_fn):
cached = cache.get(tool_name, args)
if cached:
print(f" [Cache HIT] {tool_name}")
return cached
result = tool_fn(**args)
cache.set(tool_name, args, result)
print(f" [Cache MISS] {tool_name} — called API")
return result
בפרודקשן, תשתמשו בשלושת הדפוסים במקביל: Summarization Buffer מנהל את גודל ה-Context Window, User Profile Memory שומר העדפות בין שיחות, ו-Tool Cache חוסך קריאות API כפולות. השילוב הזה לבד יכול להוריד עלויות ב-30-50% ולשפר UX משמעותית.
Scratchpad Pattern — פנקס טיוטה
דפוס שימושי: הסוכן כותב "הערות לעצמו" במהלך משימה מורכבת. למשל, סוכן מחקר שקורא 10 מאמרים — הוא כותב סיכום ביניים אחרי כל מאמר, ובסוף משתמש בכל הסיכומים כדי לכתוב דוח אחד.
# Python — Scratchpad Pattern
scratchpad = []
for article in articles:
summary = agent.summarize(article)
scratchpad.append({
"source": article.url,
"key_points": summary,
"relevance": agent.rate_relevance(summary, user_query)
})
# הסוכן משתמש ב-scratchpad כ-input לכתיבת הדוח
final_report = agent.write_report(
query=user_query,
notes=scratchpad
)
לאיזה סוג זיכרון מתאים כל מקרה? רשמו ליד כל אחד: Short-term, Episodic, או Long-term.
- הסוכן צריך לזכור מה המשתמש אמר לפני 5 הודעות → _______
- הסוכן צריך לדעת שהלקוח מעדיף אנגלית → _______
- הסוכן צריך גישה ל-500 עמודי תיעוד מוצר → _______
- הסוכן צריך לדעת שאתמול הוא ניסה API X וזה נכשל → _______
תשובות: 1=Short-term, 2=Long-term (KV), 3=Long-term (Vector), 4=Episodic
Multi-Agent Architecture — דפוסי ריבוי סוכנים
סוכן יחיד עם כלים טובים יכול לעשות הרבה. אבל לפעמים צריך יותר — מספר סוכנים שעובדים יחד, כל אחד עם תפקיד ומומחיות שונה. זה Multi-Agent Architecture.
אחת הטעויות הנפוצות ביותר: לקפוץ ישר לארכיטקטורת Multi-Agent כי "זה נשמע מגניב". המציאות: סוכן יחיד עם כלים טובים מנצח מערכת Multi-Agent בינונית ברוב המקרים. Multi-Agent קשה יותר ל-debug, יקר יותר להרצה, ומורכב יותר לתחזוקה.
כלל האצבע: "האם הייתם שוכרים צוות של 5 אנשים למשימה הזו, או שעובד אחד טוב מספיק?" אם עובד אחד מספיק — Single Agent.
ארבעת הדפוסים הגדולים
השתמשו בשאלות הבאות כדי לבחור את הדפוס הנכון:
| דפוס | מתי להשתמש | כמה כלים? | כמה דומיינים? | דוגמה | עלות יחסית |
|---|---|---|---|---|---|
| Pattern 1: Single Agent | משימה ברורה, תחום אחד | < 10 | 1 | סוכן שעונה על שאלות ובודק סטטוס הזמנה | $ |
| Pattern 2: Router | בקשות מגוונות, כל אחת פשוטה | 5-20 | 3-5 | Help Desk שמנתב ל-billing / tech / sales | $$ |
| Pattern 3: Orchestrator-Worker | משימה מורכבת שמתפרקת לתתי-משימות | 10-30 | 3-10 | מחקר שוק: חיפוש + ניתוח + כתיבת דוח | $$$ |
| Pattern 4: Collaborative | משימות יצירתיות, ביקורת, ולידציה | 10+ | 2-5 | צוות כתיבה: חוקר + כותב + עורך + Fact-checker | $$$$ |
שאלון מהיר:
- האם למשימה יש תחום אחד ופשוט? → Single Agent
- האם צריך לנתב בקשות לתחומים שונים? → Router
- האם המשימה מתפרקת לתתי-משימות עצמאיות? → Orchestrator-Worker
- האם צריך "דיון" או ביקורת בין סוכנים? → Collaborative
Pattern 1: Single Agent — סוכן יחיד
סוכן אחד, LLM אחד, כמה כלים. הדפוס הפשוט ביותר:
┌─────────────────────────────┐
│ USER │
│ "בדוק סטטוס הזמנה 12345" │
└──────────┬──────────────────┘
│
┌──────▼──────┐
│ SINGLE │
│ AGENT │──── [check_order] ──── [send_email]
│ (LLM) │──── [lookup_user] ──── [update_db]
└──────┬──────┘
│
┌──────▼──────────────────┐
│ "ההזמנה שלך בדרך! │
│ צפי הגעה: מחר 14:00" │
└─────────────────────────┘
Pattern 2: Router Agent — ניתוב
┌─────────────────────────────┐
│ USER │
│ "יש לי בעיה עם החיוב" │
└──────────┬──────────────────┘
│
┌──────▼──────┐
│ ROUTER │ ← מודל זול (Haiku/Flash)
│ AGENT │ מנתח ומחליט לאן לנתב
└──┬───┬───┬──┘
│ │ │
┌────▼┐ ┌▼───┐ ┌▼────┐
│BILL-│ │TECH│ │SALES│ ← סוכנים מתמחים
│ING │ │ │ │ │ (מודלים חזקים)
└─────┘ └────┘ └─────┘
טיפ עלויות: ב-Router Pattern, הסוכן שמנתב (Router) לא צריך מודל חזק — הוא רק מחליט "זה billing או tech?". השתמשו במודל זול כמו Claude Haiku ($0.25/M input) או Gemini Flash ($0.30/M input) לניתוב, ובמודל חזק כמו Claude Sonnet או GPT-5 לסוכנים המתמחים. זה חוסך 80-90% בעלויות.
Pattern 3: Orchestrator-Worker
┌──────────────────────────────────┐
│ USER: "כתוב דוח מחקר שוק │
│ על שוק ה-AI בישראל" │
└───────────┬──────────────────────┘
│
┌──────▼──────┐
│ ORCHESTRATOR│ ← מתכנן, מחלק משימות, אוסף
│ (מנהל) │
└──┬───┬───┬──┘
│ │ │
┌────▼┐ ┌▼───┐ ┌▼─────┐
│SEARCH│ │DATA│ │WRITE │ ← Workers
│WORKER│ │ │ │ │ עובדים במקביל
└──┬───┘ └─┬──┘ └──┬───┘
│ │ │
┌──▼───────▼───────▼──┐
│ ORCHESTRATOR │ ← אוסף תוצאות, מרכיב
│ אוסף ומרכיב │
└──────────┬───────────┘
│
┌──────▼──────┐
│ דוח מחקר │
│ מלא ומעוצב │
└─────────────┘
Pattern 4: Collaborative — שיתופי
┌─────────┐ ┌──────────┐
│ WRITER │────▶│ EDITOR │
│ כותב │◀────│ עורך │
└────┬────┘ └────┬─────┘
│ │
▼ ▼
┌─────────────────────────┐
│ SHARED STATE │ ← מסמך משותף
│ (מצב משותף לכל │ שכולם רואים
│ הסוכנים) │ ועובדים עליו
└─────────────────────────┘
▲ ▲
│ │
┌────┴────┐ ┌────┴─────┐
│RESEARCH │ │FACT- │
│חוקר │ │CHECKER │
└─────────┘ └──────────┘
תקשורת בין סוכנים
כשיש מספר סוכנים, הם צריכים דרך לתקשר. ארבע שיטות עיקריות:
| שיטה | איך עובד | SDK שמשתמש |
|---|---|---|
| Handoff | סוכן A מעביר את השיחה לסוכן B עם Context | OpenAI Agents SDK, Claude Agent SDK |
| Shared State | כל הסוכנים קוראים וכותבים ל-State משותף | LangGraph, Google ADK |
| Message Passing | סוכנים שולחים הודעות אחד לשני דרך Queue | AG2, Microsoft Agent Framework |
| Event-driven | סוכנים מגיבים לאירועים (Events) שסוכנים אחרים מייצרים | Mastra, מערכות מותאמות |
לאיזה Pattern מתאימה כל משימה?
- סוכן שעונה על שאלות על מוצר → _______
- Help Desk עם 4 מחלקות → _______
- כתיבת בלוג פוסט עם מחקר, כתיבה ועריכה → _______
- Code Review עם הצעות שיפור → _______
תשובות: 1=Single, 2=Router, 3=Orchestrator-Worker, 4=Collaborative
Error Handling & Retry — טיפול בשגיאות
סוכני AI נכשלים. לא "אולי" — בוודאות. על משימות מורכבות, סוכנים נכשלים ב-10-30% מהמקרים. הבדל בין סוכן חובבני לסוכן Production הוא לא שהסוכן לא נכשל — אלא מה קורה כשהוא נכשל.
סוגי כשלונות
| סוג | דוגמה | תגובה נכונה |
|---|---|---|
| Tool Failure | API חיצוני מחזיר Error 500 | Retry עם Exponential Backoff (1s, 2s, 4s). אחרי 3 ניסיונות — Fallback או הודעה למשתמש |
| Rate Limit | Error 429 — Too Many Requests | חכה לפי ה-Retry-After Header ונסה שוב. הפחת קצב |
| Invalid Output | המודל מחזיר JSON שבור או תשובה לא רלוונטית | בקש מהמודל לנסות שוב עם הנחיה ברורה יותר |
| Infinite Loop | הסוכן חוזר על אותה פעולה שוב ושוב | Max Iterations עוצר אותו. בדקו את ה-prompt |
| Context Overflow | יותר מדי מידע ב-Context Window | סכמו את ההיסטוריה, הסירו מידע לא רלוונטי |
| Hallucination | הסוכן "ממציא" מידע שלא קיים | Guardrails — בדיקת הפלט. בקשו ציטוטים ומקורות |
Retry Strategy — Python
import time
import random
def call_tool_with_retry(tool_fn, args, max_retries=3):
"""
קורא לכלי עם Exponential Backoff.
ניסיון 1: מיידי, ניסיון 2: 1-2 שניות, ניסיון 3: 2-4 שניות.
"""
for attempt in range(max_retries):
try:
result = tool_fn(**args)
return result
except RateLimitError:
wait = (2 ** attempt) + random.uniform(0, 1)
print(f"Rate limited. Waiting {wait:.1f}s (attempt {attempt + 1})")
time.sleep(wait)
except ToolError as e:
if attempt == max_retries - 1:
return {"error": f"Tool failed after {max_retries} attempts: {e}"}
time.sleep(1)
return {"error": "Max retries exceeded"}
Retry Strategy — TypeScript
async function callToolWithRetry(
toolFn: Function,
args: Record<string, unknown>,
maxRetries = 3
): Promise<unknown> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await toolFn(args);
} catch (error) {
if (error instanceof RateLimitError) {
const wait = 2 ** attempt + Math.random();
console.log(`Rate limited. Waiting ${wait.toFixed(1)}s (attempt ${attempt + 1})`);
await new Promise(r => setTimeout(r, wait * 1000));
} else if (attempt === maxRetries - 1) {
return { error: `Tool failed after ${maxRetries} attempts: ${error}` };
} else {
await new Promise(r => setTimeout(r, 1000));
}
}
}
return { error: "Max retries exceeded" };
}
Fallback Strategies
כשכלי נכשל אחרי כל הניסיונות, יש כמה אפשרויות:
- Alternative Tool — נסה כלי אחר שעושה משהו דומה. למשל: חיפוש ב-Google נכשל? נסה Bing
- Cached Result — השתמש בתוצאה ישנה מה-cache. לא אידיאלי, אבל טוב מכלום
- Graceful Degradation — תן תשובה חלקית. "מצאתי 3 מתוך 5 תוצאות, 2 מקורות לא זמינים כרגע"
- Human Escalation — העבר לאדם. "לא הצלחתי לטפל בבקשה — מעביר לנציג"
- Retry בלי Backoff — שולחים 100 בקשות בשנייה, ה-API חוסם אתכם לשעה. תמיד השתמשו ב-Exponential Backoff
- Retry לנצח — הגדירו מגבלה (3-5 ניסיונות). אחרי זה — Fallback או הודעה למשתמש
- הסתרת שגיאות — הסוכן נכשל בשקט ונותן תשובה שגויה. תמיד תיעדו (Log) שגיאות
The Agent Stack — שכבות ההפשטה
כמו שבפיתוח Web יש את ה-"Tech Stack" (React + Node + PostgreSQL + AWS), גם לסוכני AI יש Stack משלהם. הבנת שבע השכבות תעזור לכם לבחור SDK, לתכנן ארכיטקטורה, ולהבין מה בדיוק כל SDK נותן ומה צריך לבנות בעצמכם.
| שכבה | תפקיד | דוגמאות | מי בונה? |
|---|---|---|---|
| 7. Observability שקיפות |
Logging, Tracing, Monitoring, Cost Tracking | LangSmith, Braintrust, Helicone, custom logs | אתם + כלי צד שלישי |
| 6. Interface ממשק |
איך המשתמש מתקשר עם הסוכן | CLI, Web UI, Slack bot, API endpoint, WhatsApp | אתם |
| 5. Orchestration תזמור |
ניהול ריבוי סוכנים, ניתוב, Workflows | LangGraph, CrewAI, Google ADK Workflows | SDK + אתם |
| 4. Memory & State זיכרון ומצב |
שמירת מידע בין סיבובים ובין שיחות | Vector stores, Redis, PostgreSQL, קבצים | SDK (חלקי) + אתם |
| 3. Tools & MCP כלים |
פעולות שהסוכן יכול לבצע | MCP servers, Custom tools, API integrations | SDK + MCP + אתם |
| 2. SDK / Framework | הספרייה שמנהלת את הלולאה, הכלים, וה-State | Claude Agent SDK, OpenAI Agents SDK, Vercel AI SDK, LangGraph, CrewAI, Google ADK | SDK Provider |
| 1. Model Provider ספק מודל |
ה-LLM עצמו — "המוח" | Anthropic API, OpenAI API, Google API, DeepSeek, Mistral | Model Provider |
כל SDK מכסה שכבות אחרות. למשל:
- Claude Agent SDK / OpenAI Agents SDK — שכבות 1-3 מובנות. 4-7 צריך להוסיף
- LangGraph — שכבות 2-5 חזקות מאוד. 1 (כל מודל), 6-7 חלקי
- Vercel AI SDK v6 — שכבות 1-3 + 6 (React hooks מובנים)
- CrewAI — שכבות 1-5 מובנות. 6 (Studio) + 7 (CrewAI AMP)
עיצבו Agent Stack לסוכן תמיכת לקוחות. בחרו טכנולוגיה לכל שכבה:
- Model Provider: _________ (איזה מודל?)
- SDK: _________ (איזה Framework?)
- Tools: _________ (אילו כלים? 3 לפחות)
- Memory: _________ (איזה סוג?)
- Orchestration: _________ (Single/Router/Orchestrator?)
- Interface: _________ (Web? Slack? WhatsApp?)
- Observability: _________ (מה אתם מנטרים?)
Agent Development Lifecycle — מחזור חיי פיתוח
בניית סוכן AI זה לא "כתוב קוד, Deploy, תשכח." סוכנים הם מערכות שדורשות שיפור מתמיד — כי הם לא דטרמיניסטיים. אותה שאלה יכולה לתת תשובות שונות ביומים שונים. לכן יש מחזור חיים ייחודי לפיתוח סוכנים:
| שלב | מה עושים | כלים | זמן מוערך |
|---|---|---|---|
| 1. Define הגדרה |
מגדירים 10 תרחישים ספציפיים. "מה הסוכן צריך לעשות? ב-10 מקרים קונקרטיים." | מסמך דרישות, User Stories | 2-4 שעות |
| 2. Prototype אב-טיפוס |
סוכן מינימלי שעובד על 1-2 מקרים. ללא Production — רק הוכחת היתכנות | API ישיר או SDK פשוט | 4-8 שעות |
| 3. Evaluate הערכה |
בונים Golden Dataset — 50-100 מקרי בדיקה עם תשובות מצופות. מודדים Pass Rate | Braintrust, LangSmith, סקריפט Python | 4-8 שעות |
| 4. Iterate איטרציה |
משפרים Prompts, כלים, ו-Logic. מריצים Evals אחרי כל שינוי | Git (גרסאות prompts), Eval suite | 8-20 שעות |
| 5. Harden חיזוק |
מוסיפים Guardrails, Error Handling, Rate Limits, Cost Caps | Code review, Penetration testing | 4-8 שעות |
| 6. Deploy פריסה |
Canary deployment — 5% מהתעבורה, ניטור, הרחבה בהדרגה | CI/CD, Monitoring, Alerts | 2-4 שעות |
| 7. Monitor & Refine ניטור ושיפור |
מנטרים ביצועים בזמן אמת. Prompt Tuning מתמשך | Dashboards, Logs, Cost tracking | שוטף |
סוכנים הם לא תוכנה רגילה. אי אפשר לפרוס ולשכוח. המודלים משתנים, ה-APIs משתנים, והמשתמשים מפתיעים. תכננו מראש זמן שבועי לניטור ושיפור — 1-2 שעות בשבוע מספיקות לסוכן בינוני.
תרגילים מעשיים
בנו סוכן ReAct מינימלי שיכול לענות על שאלות ולהשתמש בכלי מזג אוויר. ללא SDK — רק API ישיר.
Python:
import anthropic, json
client = anthropic.Anthropic() # ANTHROPIC_API_KEY from env
tools = [{
"name": "get_weather",
"description": "Get weather for a city. Returns temp (C) and conditions.",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}]
def get_weather(city: str) -> str:
# בפרויקט אמיתי — קריאה ל-API. כאן: Mock
data = {"Tel Aviv": "28°C, Sunny", "Jerusalem": "22°C, Partly Cloudy"}
return data.get(city, f"No data for {city}")
def run_agent(user_msg: str, max_loops: int = 10) -> str:
msgs = [{"role": "user", "content": user_msg}]
for _ in range(max_loops):
resp = client.messages.create(
model="claude-sonnet-4-20250514", max_tokens=1024,
system="You are a helpful weather assistant. Answer in Hebrew.",
tools=tools, messages=msgs
)
# בודקים אם יש Tool Use
tool_calls = [b for b in resp.content if b.type == "tool_use"]
if not tool_calls:
# אין קריאות כלים — התשובה מוכנה
return "".join(b.text for b in resp.content if b.type == "text")
# מוסיפים את תגובת המודל ומבצעים כלים
msgs.append({"role": "assistant", "content": resp.content})
for tc in tool_calls:
result = get_weather(**tc.input)
msgs.append({
"role": "user",
"content": [{"type": "tool_result", "tool_use_id": tc.id,
"content": result}]
})
return "הגעתי למגבלת הסיבובים"
# הרצה
print(run_agent("מה מזג האוויר בתל אביב ובירושלים?"))
TypeScript:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic(); // ANTHROPIC_API_KEY from env
const tools: Anthropic.Tool[] = [{
name: "get_weather",
description: "Get weather for a city. Returns temp (C) and conditions.",
input_schema: {
type: "object" as const,
properties: { city: { type: "string" as const } },
required: ["city"]
}
}];
function getWeather(city: string): string {
const data: Record<string, string> = {
"Tel Aviv": "28°C, Sunny", "Jerusalem": "22°C, Partly Cloudy"
};
return data[city] ?? `No data for ${city}`;
}
async function runAgent(userMsg: string, maxLoops = 10): Promise<string> {
const msgs: Anthropic.MessageParam[] = [{ role: "user", content: userMsg }];
for (let i = 0; i < maxLoops; i++) {
const resp = await client.messages.create({
model: "claude-sonnet-4-20250514", max_tokens: 1024,
system: "You are a helpful weather assistant. Answer in Hebrew.",
tools, messages: msgs
});
const toolCalls = resp.content.filter(b => b.type === "tool_use");
if (toolCalls.length === 0) {
return resp.content
.filter(b => b.type === "text")
.map(b => (b as Anthropic.TextBlock).text)
.join("");
}
msgs.push({ role: "assistant", content: resp.content });
for (const tc of toolCalls) {
const result = getWeather((tc as Anthropic.ToolUseBlock).input.city as string);
msgs.push({
role: "user",
content: [{
type: "tool_result", tool_use_id: (tc as Anthropic.ToolUseBlock).id,
content: result
}]
});
}
}
return "הגעתי למגבלת הסיבובים";
}
runAgent("מה מזג האוויר בתל אביב ובירושלים?").then(console.log);
מה לשים לב:
- שימו לב ללולאת ה-ReAct:
forעם בדיקה אם יש Tool Calls - ה-
max_loopsהוא ה-Safety Net שמונע ריצה אינסופית - הקוד זהה בלוגיקה בין Python ו-TypeScript — רק ה-syntax שונה
קחו את הסוכן מתרגיל 1 והוסיפו לו זיכרון פשוט — כך שהוא "יזכור" מידע בין שיחות.
Python — הוספת זיכרון מבוסס קובץ:
import json
from pathlib import Path
MEMORY_FILE = Path("agent_memory.json")
def load_memory() -> dict:
if MEMORY_FILE.exists():
return json.loads(MEMORY_FILE.read_text())
return {"preferences": {}, "history": []}
def save_memory(memory: dict):
MEMORY_FILE.write_text(json.dumps(memory, ensure_ascii=False, indent=2))
def run_agent_with_memory(user_msg: str) -> str:
memory = load_memory()
# הוספת context מהזיכרון ל-System Prompt
system = "You are a helpful assistant. Answer in Hebrew.\n"
if memory["preferences"]:
system += f"User preferences: {json.dumps(memory['preferences'])}\n"
if memory["history"]:
system += f"Recent interactions: {json.dumps(memory['history'][-5:])}\n"
# הרצת הסוכן (כמו קודם, עם system מעודכן)
result = run_agent_with_system(user_msg, system)
# שמירת האינטראקציה בזיכרון
memory["history"].append({
"query": user_msg,
"response": result[:200], # שומרים רק 200 תווים ראשונים
"timestamp": "2026-03-24"
})
save_memory(memory)
return result
משימות:
- הריצו את הסוכן 3 פעמים ברצף ובדקו שה-memory.json גדל
- הוסיפו יכולת לשמור העדפות ("אני מעדיף תשובות קצרות" → נשמר ומשפיע)
- הוסיפו מגבלה: שמרו רק 20 אינטראקציות אחרונות (Sliding Window)
כתבו הגדרות כלים (Tool Definitions) עבור סוכן תמיכת לקוחות של חנות אונליין. הסוכן צריך 4 כלים:
lookup_order— חיפוש הזמנה לפי מספר הזמנהcheck_inventory— בדיקת מלאי של מוצרissue_refund— ביצוע החזר כספי (פעולה הרסנית — צריכה אישור!)send_notification— שליחת הודעה ללקוח
לכל כלי כתבו: name, description (3 משפטים לפחות: מה, מתי, מתי לא), input_schema.
בונוס: כתבו את זה בפורמט JSON Schema תקני. נסו את ההגדרות ב-Claude ובדקו: האם המודל בוחר את הכלי הנכון?
קחו את הסוכן מתרגיל 1 (מזג אוויר) וכתבו 10 מקרי בדיקה:
| # | שאלה | תוצאה מצופה | Pass/Fail |
|---|---|---|---|
| 1 | "מה הטמפרטורה בתל אביב?" | קורא ל-get_weather עם "Tel Aviv" | |
| 2 | "האם צריך מטריה בירושלים?" | קורא ל-get_weather עם "Jerusalem" | |
| 3 | "מה מזג האוויר?" | שואל באיזו עיר (אין מספיק מידע) | |
| 4 | "מה הבירה של ישראל?" | עונה בלי לקרוא לכלי (שאלה לא קשורה) | |
| 5 | "השווה מזג אוויר בת"א וירושלים" | קורא ל-get_weather פעמיים | |
| 6-10 | הוסיפו 5 שאלות נוספות בעצמכם — כולל Edge Cases | ||
הריצו את הסוכן על כל 10 המקרים ורשמו Pass/Fail. אם Pass Rate מתחת ל-80% — שפרו את ה-System Prompt או את ה-Tool Description.
טעויות נפוצות — וידוי ארכיטקטורי
| # | הטעות | למה זה בעיה | מה לעשות במקום |
|---|---|---|---|
| 1 | Building a "God Agent" — סוכן שמנסה לעשות הכול | נכשל בהכול. יותר מדי כלים = בלבול. יותר מדי תפקידים = דיוק נמוך | התחילו עם Single Agent לתפקיד אחד. הוסיפו יכולות בהדרגה |
| 2 | No stop conditions — בלי מגבלות עצירה | לולאה אינסופית = חשבון API של $500 בלילה | תמיד: max_iterations, token_budget, timeout, cost_cap |
| 3 | Vague tool descriptions — תיאורי כלים עמומים | המודל בוחר את הכלי הלא נכון ב-30-50% מהמקרים | 3 משפטים לפחות: מה, מתי, מתי לא. דוגמאות בתיאור |
| 4 | Context flooding — שופכים את כל המידע ל-prompt | יותר מידע = יותר עלות + ירידה באיכות. המודל "טובע" | שלחו רק מידע רלוונטי. השתמשו ב-RAG לבסיסי ידע גדולים |
| 5 | Jumping to Multi-Agent — ריבוי סוכנים מהיום הראשון | מורכבות x3, עלות x3, debugging x10. בדרך כלל מיותר | בדקו: "האם עובד אחד טוב מספיק?" רק אם לא — Multi-Agent |
| תדירות | משימה | זמן |
|---|---|---|
| חד-פעמי | בנו את הסוכן המינימלי מתרגיל 1 ושמרו אותו כ-reference | 30 דק' |
| חד-פעמי | בחרו Architecture Pattern לפרויקט שלכם (Single/Router/Orchestrator) ותעדו | 15 דק' |
| בכל סוכן חדש | כתבו 10 Test Cases לפני שכותבים שורת קוד. הגדירו מה "הצלחה" | 20 דק' |
| בכל סוכן חדש | מלאו את Agent Stack Template: 7 שכבות עם בחירות טכנולוגיות | 10 דק' |
| שבועי (בסוכן פעיל) | בדקו Eval Pass Rate — אם ירד, חקרו למה | 15 דק' |
| שבועי (בסוכן פעיל) | בדקו עלויות API ולוגים — חפשו לולאות מיותרות | 10 דק' |
בנו את הסוכן המינימלי מתרגיל 1. 20 שורות Python (או TypeScript). ללא SDK. הריצו אותו, ראו את לולאת ReAct בפעולה, ותבינו שכל SDK בעולם — מ-LangGraph עם גרפים מסובכים ועד CrewAI עם צוותים שלמים — מיישם את אותה לולאה בדיוק. הקוד הזה שווה יותר מ-10 שעות קריאת תיעוד.
- מהם 5 השלבים של לולאת ReAct, ומתי הלולאה נעצרת?
- למה תיאור הכלי (Tool Description) חשוב יותר מהקוד של הכלי? מה חייב להיות בתיאור טוב?
- מה ההבדל בין Router Agent ל-Orchestrator-Worker? תנו דוגמה לכל אחד.
- אילו 4 מנגנוני עצירה (Stop Conditions) חייבים להיות בכל סוכן, ולמה?
- מה ההבדל בין Episodic Memory ל-Long-term Memory? מתי משתמשים בכל אחד?
ענו על 4 מתוך 5? מצוין — יש לכם את הבסיס הארכיטקטורי. אפשר להמשיך לפרק 3.
בפרק הזה למדנו את כל הבסיס הארכיטקטורי של סוכני AI:
- לולאת ReAct — Input → Reason → Act → Observe → Repeat/Stop. כל סוכן AI בעולם מיישם את הלולאה הזו
- Chain of Thought — "חשיבה בקול רם" שמשפרת דיוק ב-20-40%. וריאציות: Zero-shot, Few-shot, Tree of Thought, Inner Monologue
- Tool Calling — הכלים הם ה"ידיים" של הסוכן. תיאור הכלי הוא הגורם מספר 1 לדיוק
- State Management — שלושה דפוסים: Implicit (Context), Explicit (אובייקט), State Machine (גרף מוגדר). State Machine = הכי בטוח ל-Production
- Memory — היררכיה: Working (רגע) → Short-term (שיחה) → Episodic (אירועים) → Long-term (תמיד). התחילו בפשוט, הוסיפו כשצריך
- Multi-Agent — ארבעה דפוסים: Single, Router, Orchestrator-Worker, Collaborative. אל תקפצו ל-Multi-Agent — Single Agent מספיק ברוב המקרים
- Error Handling — Retry עם Exponential Backoff, Fallback strategies, Graceful Degradation. סוכנים נכשלים 10-30% — תכננו לזה
- Agent Stack — 7 שכבות מ-Model Provider ועד Observability. כל SDK מכסה שכבות אחרות
- Development Lifecycle — Define → Prototype → Evaluate → Iterate → Harden → Deploy → Monitor. לא "כתוב ושכח"
בפרק הבא — The Tool Ecosystem: MCP, Function Calling, APIs — נלמד איך מחברים סוכנים לעולם האמיתי דרך פרוטוקול MCP, נבנה שרת MCP ראשון, ונבין את ההבדל בין כלים סטטיים לדינמיים.
צ'קליסט — סיכום פרק 2
- הבנתי את לולאת ReAct ויכול/ה לצייר אותה מזיכרון (Input → Reason → Act → Observe → Stop)
- הבנתי מה Chain of Thought עושה ומתי להשתמש בכל וריאציה
- יודע/ת לכתוב Tool Definition עם שם, תיאור מפורט, ו-Input Schema
- מכיר/ה את 3 דפוסי ניהול המצב (Implicit, Explicit, State Machine) ומתי להשתמש בכל אחד
- מכיר/ה את ההיררכיה של זיכרון סוכנים: Working → Short-term → Episodic → Long-term
- מכיר/ה את 4 דפוסי Multi-Agent ויודע/ת לבחור את הנכון לכל מצב
- הבנתי את חשיבות Stop Conditions (max iterations, token budget, timeout, cost cap)
- בניתי סוכן מינימלי ב-20 שורות Python או TypeScript
- הוספתי Memory לסוכן המינימלי (קובץ JSON)
- כתבתי 4 הגדרות כלים לסוכן תמיכת לקוחות
- כתבתי 10 Test Cases והרצתי אותם על הסוכן
- מילאתי Agent Stack Template עם 7 שכבות לפרויקט שלי
- הבנתי למה "Deploy and Forget" לא עובד לסוכנים
- יודע/ת להסביר את ההבדל בין Router ל-Orchestrator-Worker
- מכיר/ה את 5 הטעויות הנפוצות ויודע/ת איך למנוע כל אחת