ReAct prompting הוא דפוס שבו מודל שפה עובר לסירוגין בין חשיבה לפעולה: הוא כותב מחשבה על מה לעשות, מבצע פעולה כמו חיפוש או קריאה לכלי, קורא את התוצאה, ורק אז מחליט על הצעד הבא. השם הוא קיצור של Reason + Act (לחשוב ולפעול) ומגיע מ-Yao et al. 2022, "ReAct: Synergizing Reasoning and Acting in Language Models". הוא לא קשור ל-React, ספריית ה-JavaScript לבניית ממשקי משתמש.
רוב סוכני ה-AI שגולשים, מריצים קוד או עורכים קבצים מריצים גרסה כלשהי של הלולאה הזו. אחרי שעוברים עליה ידנית, קל הרבה יותר לחזות את ההתנהגות של סוכן ולדבג אותה.
לולאת Thought, Action, Observation
פרומפט ReAct מבקש מהמודל לכתוב בשלושה סוגי שורות.
- Thought (מחשבה): נימוק על המצב הנוכחי ועל המידע שחסר.
- Action (פעולה): קריאה אחת לכלי בפורמט קבוע, כמו
search[query],read_file[path]אוfinish[answer]. - Observation (תצפית): התוצאה של הפעולה הזו. המודל לא כותב את השורה הזו. התוכנה שלכם (או אתם) מריצה את הפעולה ומוסיפה את הפלט האמיתי לתמליל.
אחר כך כל התמליל חוזר למודל, שכותב את ה-Thought הבא. הלולאה מסתיימת כשהמודל בוחר בפעולת הסיום. המודל אף פעם לא מריץ כלי בעצמו; הוא רק מבקש, והקוד שמסביבו מחליט אם לבצע את הבקשה.
למה חשיבה ופעולה ביחד מנצחות כל אחת לבד
המאמר השווה את ReAct לפרומפטים שרק חושבים (chain of thought) ולפרומפטים שרק פועלים (קריאות לכלים בלי נימוק כתוב). במענה על שאלות ובבדיקת עובדות עם כלי חיפוש בוויקיפדיה, חשיבה לבדה בנתה לעיתים קרובות טיעון בטוח בעצמו על עובדה שהמודל המציא, בעוד ש-ReAct יכול היה לחפש את העובדה, והתוצאות הטובות ביותר במענה על שאלות הגיעו משילוב של ReAct עם chain of thought, עם מעבר מאחד לשני כשהראשון לא הפיק תשובה בטוחה. פעולה לבדה התקשתה לפרק את המטרה ולאסוף את מה שמצאה לתשובה; המחשבות הכתובות שמרו על התוכנית גלויה. בשתי משימות אינטראקטיביות, משחק טקסט של מטלות בית וחנות מקוונת מדומה, ReAct עם דוגמה אחת או שתיים בפרומפט הצליח יותר ממערכות מאומנות שהחוקרים השוו אליהן.
המסקנה הכללית: chain of thought עוזר למודל לתכנן, כלים נותנים לו עובדות, ואף אחד מהם לא מספיק למשימה שצריכה את שניהם.
סוכן ReAct, תור אחרי תור
הבלוק למטה הוא סוכן תכנות קטן עם שני כלים. כל לשונית היא אותה שיחה צעד אחד מאוחר יותר: שורות ה-Observation הן מה שהתוכנה הדביקה בחזרה אחרי שהריצה את הפעולה הקודמת. התוויות Thought, Action ו-Observation נשארות באנגלית כי זה הפורמט שהקוד בסוף הדף מחפש. קראו את התשובות לפי הסדר.
Thought: אני לא יודע איזו בדיקה נכשלת או איך. הרצת הבדיקות תראה את ה-assertion המדויק לפני שאני קורא קוד כלשהו. Action: run_tests[tests/test_cart.py]
שימו לב לשני דברים. הפעולה הראשונה היא הרצת בדיקות, לא ניחוש: המודל אסף ראיות לפני שקרא קוד. וכל מחשבה מתייחסת לתצפית האחרונה, וזה החלק ש-chain of thought לא יכול לעשות.
לנסות ReAct באפליקציית צ'אט
לא צריך קוד כדי לראות את הלולאה. ב-ChatGPT, ב-Claude או ב-Gemini אתם יכולים להיות הכלי: המודל מבקש פעולה, אתם מבצעים אותה ומדביקים בחזרה את התוצאה.
מחשבה: מאוורר רועש במצב מנוחה פירושו בדרך כלל שמשהו משתמש במעבד ברקע, או שהמאוורר מתקשה לקרר (אבק, פתחי אוורור חסומים). בדיקה של השימוש במעבד קודם מפרידה בין שני המקרים האלה.
פעולה: פתחו את מנהל המשימות (Ctrl+Shift+Esc ב-Windows) או את Activity Monitor (Applications > Utilities ב-Mac). מיינו את התהליכים לפי CPU, חכו 30 שניות, ואמרו לי מה שלושת התהליכים המובילים ומה אחוזי ה-CPU שלהם.
אותן הוראות, עם פונקציה אמיתית מאחורי כל פעולה, הן סוכן.
לולאת ReAct מינימלית ב-Python
בקוד, התוכנה שולחת את התמליל, מוצאת את שורת ה-Action בתשובה, מריצה את הפונקציה המתאימה, מוסיפה את ה-Observation וחוזרת. השלד הזה משתמש ב-OpenAI Python SDK ובפרומפט המערכת של סוכן התכנות שלמעלה.
import re
import subprocess
from pathlib import Path
from openai import OpenAI
client = OpenAI()
MODEL = "your-model-id" # e.g. from your provider's model list
PROJECT = Path.cwd().resolve()
READABLE = {".py", ".md", ".txt", ".toml", ".cfg"}
def inside_project(arg):
# The model chooses arg: resolve it and refuse anything outside the project.
path = (PROJECT / arg).resolve()
if not path.is_relative_to(PROJECT):
raise ValueError(f"{arg} is outside the project")
return path
def run_tests(arg):
path = inside_project(arg)
if not (path.is_file() and path.name.startswith("test_") and path.suffix == ".py"):
raise ValueError(f"{arg} is not a test file")
result = subprocess.run(["pytest", str(path), "-q"], capture_output=True, text=True)
return result.stdout[-2000:]
def read_file(arg):
path = inside_project(arg)
if not path.is_file() or path.suffix not in READABLE:
raise ValueError(f"{arg} is not a file this agent may read")
return path.read_text()[:20000]
TOOLS = {"run_tests": run_tests, "read_file": read_file}
def react(task, system_prompt, max_steps=8):
transcript = f"Task: {task}\n"
for _ in range(max_steps):
response = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": transcript},
],
temperature=0,
)
text = response.choices[0].message.content
match = re.search(r"Action: (\w+)\[(.*)\]", text)
if not match:
return text
transcript += text[: match.end()] + "\n"
tool, arg = match.groups()
if tool == "finish":
return arg
try:
observation = TOOLS[tool](arg) if tool in TOOLS else f"Unknown tool: {tool}"
except Exception as error: # a missing file becomes an observation, not a crash
observation = f"Error: {error}"
transcript += f"Observation:\n{observation}\n"
return "Stopped after max_steps without an answer."
ארבעה פרטים חשובים. התמליל נחתך מיד אחרי ה-Action, כי מודלים ממשיכים לפעמים לכתוב וממציאים Observation משלהם. כלי שנכשל (למשל שם קובץ שגוי) הופך ל-Observation שהמודל יכול להגיב אליה, במקום להקריס את הלולאה. מגבלת הצעדים עוצרת מודל שנתקע בלולאה לנצח. ופלט הבדיקות מקוצץ, כי לוג בדיקות ענק היה ממלא את חלון ההקשר; ההחלטה מה נכנס לחלון הזה היא context engineering.
שני הכלים גם בודקים את הארגומנט שלהם לפני שהם עושים משהו. המודל בוחר את הארגומנט הזה, ולכן המגבלות שייכות לכלי: inside_project דוחה כל נתיב שמוביל אל מחוץ לתיקיית הפרויקט (כולל טריקים עם ../), run_tests מקבל רק קבצי test_*.py, ו-read_file פותח רק כמה סוגי קבצי קוד וטקסט ומחזיר לכל היותר 20,000 תווים, כך שקובץ .env או מפתח SSH אף פעם לא מגיעים לבקשה הבאה. הרצת pytest עדיין מריצה את הקוד של הפרויקט שלכם, אז הריצו סוכן כזה בקונטיינר או בעותק זמני, לעולם לא על מחשב שמחזיק סודות.
ה-API הנוכחיים של OpenAI, Anthropic ו-Google מציעים גם tool calling מובנה: מתארים כל כלי עם שם ו-JSON schema, והמודל מחזיר קריאה מובנית לכלי במקום שורת Action:. הלולאה זהה; רק הפענוח נעלם.
בטיחות בלולאת ReAct
סוכן קורא טקסט שהוא לא כתב: דפי אינטרנט, קבצים, פלט של כלים. כל אחד מהם יכול להכיל הוראות שמכוונות למודל, וזה prompt injection. תנו לכל כלי את הגישה המינימלית שהוא צריך (קריאה בלבד כשאפשר), בקשו מאדם לאשר כל דבר שמוחק, שולח או משלם, והתייחסו לפעולות שהמודל מבקש כקלט לא אמין שצריך לבדוק, לא כפקודות להרצה עיוורת.
שאלות נפוצות
מה זה ReAct prompting?
ReAct (קיצור של Reason + Act, לחשוב ולפעול) הוא דפוס prompting שבו מודל שפה כותב קטע נימוק קצר, אחר כך פעולה כמו חיפוש או קריאה לכלי, ואז קורא את התוצאה לפני שהוא חושב שוב. הוא מגיע מ-Yao et al. 2022, "ReAct: Synergizing Reasoning and Acting in Language Models". אין לו שום קשר לספריית React של JavaScript.
מה הם Thought, Action ו-Observation ב-ReAct?
Thought (מחשבה) הוא הנימוק של המודל לגבי מה לעשות הלאה. Action (פעולה) היא קריאה לכלי בפורמט קבוע, למשל search[python 3.13 release notes]. Observation (תצפית) היא הפלט של הכלי, שהתוכנה שלכם מקבלת על ידי הרצת הפעולה ומוסיפה לפרומפט. הלולאה חוזרת עד שהמודל מבצע פעולת סיום עם התשובה שלו.
מה ההבדל בין ReAct ל-chain of thought?
Chain of thought חושב מתוך מה שהמודל כבר יודע, כך שעובדה שגויה בתחילת השרשרת נשארת שגויה. ReAct משלב נימוק עם פעולות שמביאות מידע אמיתי, כך שהמודל יכול לבדוק עובדה, לראות בדיקה נכשלת או לקרוא קובץ לפני שהוא ממשיך. מאמר ה-ReAct מצא שעיגון הנימוק בתוצאות חיפוש צמצם את העובדות המומצאות ש-chain of thought לבדו הפיק.
האם סוכני AI עדיין משתמשים ב-ReAct?
בלולאה, כן. רוב הסוכנים היום חושבים, קוראים לכלי, קוראים את התוצאה ומחליטים שוב, וזה מחזור ה-ReAct. מה שהשתנה הוא הפורמט: במקום לפענח שורות Action: מתוך טקסט, ל-API הנוכחיים יש tool calling מובנה, שבו המודל מחזיר בקשת כלי מובנית והקוד שלכם שולח בחזרה את התוצאה.
אפשר להשתמש ב-ReAct ב-ChatGPT בלי קוד?
כן, אם אתם משחקים בעצמכם את תפקיד הכלי. בקשו מהמודל לענות עם Thought אחד ו-Action אחת ואז לעצור. אתם מריצים את הפעולה (חיפוש, פתיחת קובץ, הרצת פקודה), מדביקים את התוצאה כ-Observation, וחוזרים חלילה. זה איטי, אבל זה מראה בדיוק איך סוכן מחליט מה לעשות הלאה.