8. משימות רקע
"הריצו פעולות איטיות ברקע; הסוכן ממשיך לחשוב"
חדש בנושא?
מה זה פעולה חוסמת?
פקודה שגורמת לתוכנית להמתין עד שתסיים לפני שעושה כל דבר אחר. '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-+
איך זה עובד
- תור משותף אוסף תוצאות משימות רקע שהושלמו.
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."
- לפני כל קריאת 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
- הלולאה הראשית קוראת ל-
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 ומקבל התראה כשהמשימה מסתיימת. ריקון התור הוא המפתח: זו נקודת הזרקה יחידה שמזינה השלמות חזרה לשיחה בדיוק ברגע הנכון.
מדריך קוד אינטראקטיבי
1bg_queue: queue.Queue = queue.Queue()2bg_counter = {"n": 0}3 4def run_in_background(command: str, label: str = "") -> str:5 bg_counter["n"] += 16 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 messages35 bg_queue הוא Queue בטוח לthreads המשותף בין ה-thread הראשי וכל threads הפועלים. bg_counter משתמש ב-dict (לא int) כדי ש-closures של workers יוכלו להגדיל אותו בהפניה.התחילו משימת רקע ארוכה (כמו sleep 30 && echo done) והמשיכו לשוחח עם הסוכן בזמן שהיא רצה.
רמז
ההתראה תוזרק ל-tool_result הבא אוטומטית