1. לולאת הסוכן

"לולאה אחת ו-Bash זה כל מה שצריך"

15 דקות קריאה
💡חדש בנושא?

מה זה API?

API (ממשק תכנות יישומים) הוא דרך לתוכנות לדבר זו עם זו. כשאנחנו קוראים ל-API של Claude, אנחנו שולחים טקסט ומקבלים תשובה — כמו לשלוח הודעה לחבר מאוד חכם.

מה זה 'while True'?

זו לולאה שרצה לנצח עד שמשהו אומר לה לעצור. בסוכן שלנו, היא ממשיכה לרוץ עד שהמודל מחליט שהוא סיים (stop_reason שונה מ-'tool_use').

מה זה קריאת כלי (tool call)?

כשמודל הבינה המלאכותית רוצה לעשות משהו בעולם האמיתי (להריץ פקודה, לקרוא קובץ), הוא שולח בחזרה הודעת 'tool_use' מיוחדת במקום טקסט רגיל. הקוד שלנו מבצע את הפעולה ושולח את התוצאה בחזרה.

הבעיה

איך מודל שפה עובר מייצור טקסט לעשייה בעולם האמיתי?

המודל יכול לחשוב, לתכנן ולייצר קוד — אבל אין לו ידיים. הוא לא יכול להריץ פקודה, לקרוא קובץ או לבדוק תוצאה. הוא מוח בצנצנת.

הפתרון

לולאה אחת. כלי אחד. זו כל הארכיטקטורה.

while True:
  response = LLM(messages, tools)
  if stop_reason != "tool_use": return
  execute tools
  append results
  loop back

המודל מחליט מתי לקרוא לכלים ומתי לעצור. הקוד רק מבצע את מה שהמודל מבקש.

הלולאה המרכזית

def agent_loop(messages):
    while True:
        response = client.messages.create(
            model=MODEL, system=SYSTEM,
            messages=messages, tools=TOOLS, max_tokens=8000,
        )
        messages.append({"role": "assistant", "content": response.content})

        if response.stop_reason != "tool_use":
            return

        results = []
        for block in response.content:
            if block.type == "tool_use":
                output = TOOL_HANDLERS[block.name](**block.input)
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": output,
                })
        messages.append({"role": "user", "content": results})

ארבעה שלבים, חוזרים על עצמם:

  1. הוסף את ההנחיה של המשתמש להיסטוריית ההודעות
  2. שלח הודעות + הגדרות כלים ל-LLM
  3. בדוק stop_reason — אם זה לא tool_use, המודל סיים
  4. בצע כל קריאת כלי, הוסף את התוצאות, חזור ללולאה

כלי ה-Bash

TOOLS = [{
    "name": "bash",
    "description": "Run a shell command.",
    "input_schema": {
        "type": "object",
        "properties": {"command": {"type": "string"}},
        "required": ["command"],
    },
}]

def run_bash(command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    r = subprocess.run(command, shell=True, cwd=os.getcwd(),
                       capture_output=True, text=True, timeout=120)
    out = (r.stdout + r.stderr).strip()
    return out[:50000] if out else "(no output)"

הגדרת כלי אחת. handler אחד. כעת יש למודל ידיים — הוא יכול להריץ כל פקודת shell ולקרוא את הפלט.

המימוש המלא

#!/usr/bin/env python3
"""s01_agent_loop.py - The Agent Loop"""

import os, subprocess
from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv(override=True)
client = Anthropic()
MODEL = os.environ["MODEL_ID"]
SYSTEM = f"You are a coding agent at {os.getcwd()}. Use bash to solve tasks."

TOOLS = [{
    "name": "bash",
    "description": "Run a shell command.",
    "input_schema": {
        "type": "object",
        "properties": {"command": {"type": "string"}},
        "required": ["command"],
    },
}]

def run_bash(command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    try:
        r = subprocess.run(command, shell=True, cwd=os.getcwd(),
                           capture_output=True, text=True, timeout=120)
        out = (r.stdout + r.stderr).strip()
        return out[:50000] if out else "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"

def agent_loop(messages: list):
    while True:
        response = client.messages.create(
            model=MODEL, system=SYSTEM, messages=messages,
            tools=TOOLS, max_tokens=8000,
        )
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return
        results = []
        for block in response.content:
            if block.type == "tool_use":
                print(f"\033[33m$ {block.input['command']}\033[0m")
                output = run_bash(block.input["command"])
                print(output[:200])
                results.append({"type": "tool_result",
                                "tool_use_id": block.id,
                                "content": output})
        messages.append({"role": "user", "content": results})

if __name__ == "__main__":
    history = []
    while True:
        try:
            query = input("\033[36ms01 >> \033[0m")
        except (EOFError, KeyboardInterrupt):
            break
        if query.strip().lower() in ("q", "exit", ""):
            break
        history.append({"role": "user", "content": query})
        agent_loop(history)

מסקנה מרכזית

הסוד השלם של סוכן קוד מבוסס AI הוא הלולאה הזו. המודל הוא הבינה — הוא מחליט מה לעשות. הקוד הוא הרתמה — הוא נותן למודל כלי ומזין חזרה תוצאות. בסשן הבא (שימוש בכלים), נוסיף עוד כלים מבלי לשנות את הלולאה כלל.

מדריך קוד אינטראקטיבי

לולאת הסוכן המרכזית
1def agent_loop(messages):
2 while True:
3 response = client.messages.create(
4 model=MODEL, system=SYSTEM,
5 messages=messages, tools=TOOLS,
6 max_tokens=8000,
7 )
8 messages.append({"role": "assistant",
9 "content": response.content})
10 
11 if response.stop_reason != "tool_use":
12 return
13 
14 results = []
15 for block in response.content:
16 if block.type == "tool_use":
17 output = TOOL_HANDLERS[block.name](**block.input)
18 results.append({
19 "type": "tool_result",
20 "tool_use_id": block.id,
21 "content": output,
22 })
23 messages.append({"role": "user", "content": results})
24 
הלולאה האינסופית. היא ממשיכה לרוץ עד שהמודל מחליט לעצור. זהו פעימת הלב של כל סוכן.
שלב 1 מתוך 6
🧪 נסו בעצמכם

שכפלו את הריפו, הריצו python agents/s01_agent_loop.py, ובקשו ממנו ליצור קובץ. עקבו אחר קריאות הכלים בטרמינל.

רמז

הגדירו MODEL_ID=claude-sonnet-4-20250514 בקובץ .env שלכם

מצאתם טעות? דווחו ←