8. משימות רקע

"הריצו פעולות איטיות ברקע; הסוכן ממשיך לחשוב"

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

מה זה פעולה חוסמת?

פקודה שגורמת לתוכנית להמתין עד שתסיים לפני שעושה כל דבר אחר. 'npm install' יכול לקחת 2 דקות. עם ביצוע חוסם, הסוכן יושב בלא פעולה כל הזמן הזה — מבזבז זמן קיר ואת סבלנות המשתמש.

מה זה daemon thread?

Thread רקע שרץ באופן עצמאי מהתוכנית הראשית. כשמוגדר כ-daemon=True ב-Python, הוא עוצר אוטומטית כשהתוכנית הראשית יוצאת, כך שאין צורך לנהל ניקוי.

איך הסוכן לומד שמשימת רקע הסתיימה?

ה-thread הרקע דוחף תוצאה לתור משותף. לפני כל קריאת LLM, הסוכן מרוקן את התור ומזריק כל תוצאות שהושלמו כהודעות. המודל קורא אותן בסיבוב הבא שלו.

הבעיה

חלק מהפקודות לוקחות דקות: npm install, pytest, docker build. עם לולאה חוסמת, המודל יושב בלא פעולה ומחכה. אם המשתמש מבקש “התקן תלויות ובינתיים צור את קובץ התצורה,” הסוכן עושה אותם עוקבת, לא במקביל.

הפתרון

Main thread                Background thread
+-----------------+        +-----------------+
| agent loop      |        | subprocess runs |
| ...             |        | ...             |
| [LLM call] <---+------- | enqueue(result) |
|  ^drain queue   |        +-----------------+
+-----------------+

Timeline:
Agent --[spawn A]--[spawn B]--[other work]--[drain]--
             |          |                       ^
             v          v                       |
          [A runs]   [B runs]      (parallel)   |
             |          |                       |
             +----------+----- results injected-+

איך זה עובד

  1. תור משותף אוסף תוצאות משימות רקע שהושלמו.
import threading
import subprocess
import queue

bg_queue: queue.Queue = queue.Queue()
bg_counter = {"n": 0}

def run_in_background(command: str, label: str = "") -> str:
    bg_counter["n"] += 1
    task_id = bg_counter["n"]
    label = label or f"bg-{task_id}"

    def worker():
        try:
            result = subprocess.run(
                command, shell=True, capture_output=True,
                text=True, timeout=300,
            )
            output = (result.stdout + result.stderr).strip()
            status = "done" if result.returncode == 0 else "failed"
        except subprocess.TimeoutExpired:
            output = "Timeout after 300s"
            status = "failed"
        bg_queue.put({
            "task_id": task_id,
            "label": label,
            "status": status,
            "output": output[:5000],
        })

    t = threading.Thread(target=worker, daemon=True)
    t.start()
    return f"Background task {task_id} ({label}) started. You'll be notified when done."
  1. לפני כל קריאת LLM, רוקנו את התור והזריקו תוצאות שהושלמו.
def drain_bg_queue(messages: list) -> list:
    results = []
    while not bg_queue.empty():
        completed = bg_queue.get_nowait()
        results.append({
            "type": "text",
            "text": (
                f"<background_complete>\n"
                f"Task {completed['task_id']} ({completed['label']}): "
                f"{completed['status']}\n"
                f"{completed['output']}\n"
                f"</background_complete>"
            ),
        })
    if results:
        messages.append({"role": "user", "content": results})
    return messages
  1. הלולאה הראשית קוראת ל-drain_bg_queue לפני כל קריאת LLM.
def agent_loop(messages: list):
    while True:
        messages = drain_bg_queue(messages)  # inject any completions
        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})

מה השתנה מ-מערכת משימות

רכיבלפני (מערכת משימות)אחרי (משימות רקע)
ביצועעוקב בלבדמשימות רקע מקביליות
המתנהחוסם את לולאת הסוכןהסוכן ממשיך בזמן שהרקע רץ
הודעהN/Aריקון תור לפני כל קריאת LLM
כליאיןrun_in_background(command, label)

מסקנה מרכזית

משימות רקע הן תבנית מקביליות לרתמת הסוכן. המודל לא צריך להבין threads — הוא פשוט קורא ל-run_in_background ומקבל התראה כשהמשימה מסתיימת. ריקון התור הוא המפתח: זו נקודת הזרקה יחידה שמזינה השלמות חזרה לשיחה בדיוק ברגע הנכון.

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

Background Runner ותור ההתראות
1bg_queue: queue.Queue = queue.Queue()
2bg_counter = {"n": 0}
3 
4def run_in_background(command: str, label: str = "") -> str:
5 bg_counter["n"] += 1
6 task_id = bg_counter["n"]
7 label = label or f"bg-{task_id}"
8 
9 def worker():
10 result = subprocess.run(
11 command, shell=True, capture_output=True,
12 text=True, timeout=300,
13 )
14 output = (result.stdout + result.stderr).strip()
15 status = "done" if result.returncode == 0 else "failed"
16 bg_queue.put({"task_id": task_id, "label": label,
17 "status": status, "output": output[:5000]})
18 
19 t = threading.Thread(target=worker, daemon=True)
20 t.start()
21 return f"Background task {task_id} ({label}) started."
22 
23def drain_bg_queue(messages: list) -> list:
24 results = []
25 while not bg_queue.empty():
26 completed = bg_queue.get_nowait()
27 results.append({"type": "text", "text": (
28 f"<background_complete>\nTask {completed['task_id']} "
29 f"({completed['label']}): {completed['status']}\n"
30 f"{completed['output']}\n</background_complete>"
31 )})
32 if results:
33 messages.append({"role": "user", "content": results})
34 return messages
35 
bg_queue הוא Queue בטוח לthreads המשותף בין ה-thread הראשי וכל threads הפועלים. bg_counter משתמש ב-dict (לא int) כדי ש-closures של workers יוכלו להגדיל אותו בהפניה.
שלב 1 מתוך 5
🧪 נסו בעצמכם

התחילו משימת רקע ארוכה (כמו sleep 30 && echo done) והמשיכו לשוחח עם הסוכן בזמן שהיא רצה.

רמז

ההתראה תוזרק ל-tool_result הבא אוטומטית

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