Menu

ReAct Prompting: לולאת חשיבה ופעולה ל-LLM

ReAct prompting גורם למודל לעבור לסירוגין בין חשיבה על משימה לבין קריאה לכלי, ולקרוא כל תוצאה לפני שהוא מחליט על הצעד הבא. זו הלולאה שמאחורי רוב סוכני ה-AI.

כל פרומפט למטה ניתן לעריכה: שנו אותו, ואז פתחו אותו ב-ChatGPT, ב-Claude או באפליקציית AI אחרת.

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 נשארות באנגלית כי זה הפורמט שהקוד בסוף הדף מחפש. קראו את התשובות לפי הסדר.

System prompt
פתור את המשימה על ידי חזרה על Thought, Action ו-Observation. Thought: חשוב על מה שאתה יודע ועל מה לעשות הלאה. Action: בדיוק אחת מאלה run_tests[path] מריץ pytest על קובץ ומחזיר את הפלט read_file[path] מחזיר את התוכן של קובץ finish[answer] מסיים את המשימה עם התשובה שלך כתוב Thought אחד ו-Action אחת, ואז עצור. ה-Observation תינתן לך.
משימה: tests/test_cart.py התחיל להיכשל אחרי המיזוג של אתמול. מצא את הסיבה.
Try it
Chat apps receive the system prompt as the start of your message, so this tries its wording, not its priority. To test it as standing instructions, put it in the app's custom instructions or the API's system field.
Example replyReplies vary between models and runs.

Thought: אני לא יודע איזו בדיקה נכשלת או איך. הרצת הבדיקות תראה את ה-assertion המדויק לפני שאני קורא קוד כלשהו. Action: run_tests[tests/test_cart.py]

שימו לב לשני דברים. הפעולה הראשונה היא הרצת בדיקות, לא ניחוש: המודל אסף ראיות לפני שקרא קוד. וכל מחשבה מתייחסת לתצפית האחרונה, וזה החלק ש-chain of thought לא יכול לעשות.

לנסות ReAct באפליקציית צ'אט

לא צריך קוד כדי לראות את הלולאה. ב-ChatGPT, ב-Claude או ב-Gemini אתם יכולים להיות הכלי: המודל מבקש פעולה, אתם מבצעים אותה ומדביקים בחזרה את התוצאה.

להיות הכלים של המודל
Fill in
אני רוצה לענות על השאלה הזו: למה המאוורר של הלפטופ שלי רועש גם כשאני לא עושה כלום? עזור לי בעבודה בלולאה. כל תשובה מכילה בדיוק מחשבה אחת (מה שאתה יודע עד עכשיו ומה חסר) ופעולה אחת: דבר מסוים שעליי לבדוק, להריץ או לחפש, עם השלבים המדויקים. אחר כך עצור וחכה. אענה עם התוצאה בצורה "תצפית: ...". כשיהיו לך מספיק ראיות, ענה עם "תשובה:" והמסקנה שלך. אל תנחש תשובה לפני שיש לך ראיות.
Try it
Example replyReplies vary between models and runs.

מחשבה: מאוורר רועש במצב מנוחה פירושו בדרך כלל שמשהו משתמש במעבד ברקע, או שהמאוורר מתקשה לקרר (אבק, פתחי אוורור חסומים). בדיקה של השימוש במעבד קודם מפרידה בין שני המקרים האלה.

פעולה: פתחו את מנהל המשימות (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, וחוזרים חלילה. זה איטי, אבל זה מראה בדיוק איך סוכן מחליט מה לעשות הלאה.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל