Strona główna / Artykuły / ReAct od zera: myśl, działanie, pauza, obserwacja bez ramy referencyjnej

ReAct od zera: myśl, działanie, pauza, obserwacja bez ramy referencyjnej

Stwórz ręcznie mały pętlę Groq ReAct — najpierw ręczne obserwacje, a następnie wywołania narzędzi opartych na regex — aby zrozumieć, dlaczego nowoczesne frameworki agentów funkcjonują w taki sposób.

1373 słów

Wprowadzenie

Zadaj modelowi mówiącemu prostym językiem pytanie o aktualną cenę akcji Nvidia oraz o to, ile akcji da się kupić za 100 000 dolarów. Bez dodatkowych narzędzi model wymyśla przestarzałą cenę na podstawie danych treningowych, a następnie błędnie dokonuje obliczeń przy niewłaściwych danych wejściowych. Nie posiada wbudowanej funkcji oznaczającej „Nie wiem – sprawdź to”, ponieważ wyszukiwanie informacji nie polega na pojedynczym przetworzeniu danych.

Gdy jakość odpowiedzi wzrosła, modele okazały się przydatne do rozumowania jednorazowego. Samo planowanie nadal nie zapewnia autonomii. Pytania, które definiują agenty, to: co znajduje się poza wiedzą modelu, która akcja ją zdobywa, jak osiągnąć rezultat, jak zmodyfikować plan oraz kiedy przestać. Decyzja → działanie → sprawdzenie → ponowne planowanie to pętla, a nie linia prosta. Ta pętla jest źródłem współczesnych agentów sztucznej inteligencji. Model ReAct (Rozumowanie + Działanie) ustalił ten wzorzec. Początkowe demonstracje nie były aplikacjami LangChain – składały się z modelu językowego, ustrukturyzowanego promptu oraz osoby (a później skryptu), która wykonywała działania i przekazywała informacje zwrotne.

Poznaj ReAct: Rozumowanie + Działanie

Przykład w praktyce, krok po kroku:

from groq import Groq
import re
from dotenv import load_dotenv

_ = load_dotenv()
client = Groq()
message = client.chat.completions.create(
    model="openai/gpt-oss-120b",  # free, fast open-weight model hosted on Groq
    max_tokens=1000,
    messages=[
        {"role": "user", "content": "Hello, GPT!"}
    ],
)
print(message.choices[0].message.content) # Check the client
class Agent:
    def __init__(self, system=""):
        self.system = system
        self.messages = []
        if self.system:
            self.messages.append({"role": "system", "content": system})

    def __call__(self, message):
        self.messages.append({"role": "user", "content": message})
        result = self.execute()
        self.messages.append({"role": "assistant", "content": result})
        return result

    def execute(self):
        response = client.chat.completions.create(
            model="openai/gpt-oss-120b",
            max_tokens=1000,
            messages=self.messages,
        ).choices[0].message.content
        return response
prompt = """
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer
Use Thought to describe your thoughts about the question you have been asked.
Use Action to run one of the actions available to you - then return PAUSE.
Observation will be the result of running those actions.

Your available actions are:

calculate:
e.g. calculate: 4 * 7 / 3
Runs a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary

fish_weight:
e.g. fish_weight: Shark
returns weight of a fish when given the breed

Example session:

Question: How much does a shark weigh?
Thought: I should look the fish weight using fish_weight
Action: fish_weight: Shark
PAUSE

You will be called again with this:

Observation: A Great white shark weights 41000 lbs

You then output:

Answer: A Great white shark weights 41000 lbs
""".strip()
def calculate(expression):
    return eval(expression)

def fish_weight(name):
    if "Shark" in name:
        return("Great white shark weighs 41000 lbs")
    elif "Puffer" in name:
        return("A puffer fish weighs 20 lbs")
    else:
        return("A fish can weight upto 47000 lbs")

known_actions = {
    "calculate": calculate,
    "fish_weight": fish_weight
}
abot = Agent(prompt)
result = abot("How much does a Shark weigh?")
print(result)
result = fish_weight("Shark")
result
next_prompt = "Observation: {}".format(result)
abot(next_prompt)
abot.messages # View the list of messages containing the conversation
# Present a compund query
abot = Agent(prompt)
# New query
question = """I have 2 fishes, a Shark and a puffer fish. \
What is their combined weight"""
abot(question)
next_prompt = "Observation: {}".format(fish_weight("Shark"))
print(next_prompt)
abot(next_prompt)
next_prompt = "Observation: {}".format(fish_weight("Puffer fish"))
print(next_prompt)
abot(next_prompt)
next_prompt = "Observation: {}".format(eval("41000 + 20"))
print(next_prompt)
abot(next_prompt)
## Add loop (automate the reasoning + act process)
action_re = re.compile('^Action: (\w+): (.*)
) # python regular expression to selection action
def query(question, max_turns=5):
    i = 0
    bot = Agent(prompt)
    next_prompt = question
    while i < max_turns:
        i += 1
        result = bot(next_prompt)
        print(result)
        actions = [
            action_re.match(a)
            for a in result.split('\n')
            if action_re.match(a)
        ]
        if actions:
            # There is an action to run
            action, action_input = actions[0].groups()
            if action not in known_actions:
                raise Exception("Unknown action: {}: {}".format(action, action_input))
            print(" -- running {} {}".format(action, action_input))
            observation = known_actions[action](action_input)
            print("Observation:", observation)
            next_prompt = "Observation: {}".format(observation)
        else:
            return
question = """I have 2 fishes, a Shark and a puffer fish. \
What is their combined weight"""
query(question)
  1. Imporuj klienta Groq, funkcję re do dopasowywania wzorów oraz load_dotenv; załaduj plik środowiskowy, aby klucz API nie był zapisany w sposób sztywny.
  2. Stwórz klienta Groq, który odczytuje wartość GROQ_API_KEY z środowiska.
  3. Wyślij proste wiadomość „Hello” do skonfigurowanego modelu w celu potwierdzenia połączenia.
  4. Zbuduj Agenta, który przechowuje instrukcje systemowe oraz rosnącą historię wiadomości: przy każdym wezwaniu dodaje kolejną wypowiedź użytkownika, wysyła pełną historię, dodaje odpowiedź modelu i zwraca tekst.
  • Napisz instrukcję systemową, która wymusza sekwencję Myśl → Działanie → PAUZA → Obserwacja.
  • Zarejestruj narzędzia takie jak calculate() do obliczeń arytmetycznych oraz fish_weight() do małej tabeli referencyjnej w known_actions.
  • Zainstaluj agenta przy użyciu instrukcji systemowej ReAct.
  • Zadaj pytanie „Ile waży rekin?”. Oczekuj proponowanego Działanie: fish_weight: Shark, a nie samego liczby.
  • Ręcznie uruchom fish_weight("Shark") i zapisz wynik (najpierw w trybie ręcznym).
  • Dostarcz z powrotem wartość Obserwacja: …, aby agent mógł odpowiedzieć.
  • Zobacz zawartość abot.messages, aby przejrzeć pełny zapis rozmowy widziany przez model.
  • Rozpocznij nowego agenta z złożonym pytaniem (waga rekina i ryby balonowej). 13–15. Ręcznie podawaj każdą obserwację, w tym łączną sumę w razie potrzeby.
  • Niech agent skomponuje ostateczną odpowiedź, gdy tylko będą dostępne obserwacje.
  • Dodaj regular expression, który automatycznie wykrywa Action: name: input.
  • Zamknij pętlę w funkcji query(): nowy agent, wysłanie pytania, wydrukowanie wyniku, analiza działania, weryfikacja względem known_actions, wykonanie, obserwacja – powtarzaj do uzyskania ostatecznej odpowiedzi lub osiągnięcia limitu rund.
  • Zaprowadź funkcję query() na złożone pytanie i obserwuj proces Thought → Action → Observation → Answer bez konieczności ręcznych kroków.
  • Wniosek

    Agenci zaczynają od pętli, a nie od ram roboczych. Modeli nauczane metodą Chain-of-thought uczyły się rozumować na głos; ReAct strukturyzuje proces myślenia–działania–obserwacji w sposób odpowiadający sposobowi pracy ludzi. Popularne ramy dla agentów przemysłowią tę koncepcję. Budowa pętli ręcznie trwale zmienia odczucia związane z ramami roboczymi. Oryginalny artykuł znajduje się pod adresem https://arxiv.org/abs/2210.03629.

    Lekcja z praktyki przemysłowej polega na tym, że frameworki ukrywają pętlę za elementami typu runner i węzłami narzędzi, ale umowa pozostaje identyczna: model musi emitować akcję możliwą do analizy, środowisko wykonawcze musi uruchamiać tylko dozwolone narzędzia, a obserwacje muszą ponownie trafić do transkrypcji jako wiadomości pierwszej klasy. Jeśli pominie się cokolwiek z tego, otrzymuje się chatbota z efektami ubocznymi, a nie agenta.

    Lekcja z praktyki przemysłowej polega na tym, że frameworki ukrywają pętlę za elementami typu runner i węzłami narzędzi, ale umowa pozostaje identyczna: model musi emitować akcję możliwą do analizy, środowisko wykonawcze musi uruchamiać tylko dozwolone narzędzia, a obserwacje muszą ponownie trafić do transkrypcji jako wiadomości pierwszej klasy. Jeśli pominie się cokolwiek z tego, otrzymuje się chatbota z efektami ubocznymi, a nie agenta.

    Lekcja z praktyki przemysłowej polega na tym, że frameworki ukrywają pętlę za elementami typu runner i węzłami narzędzi, ale umowa pozostaje identyczna: model musi emitować akcję możliwą do analizy, środowisko wykonawcze musi uruchamiać tylko dozwolone narzędzia, a obserwacje muszą ponownie trafić do transkrypcji jako wiadomości pierwszej klasy. Jeśli pominie się cokolwiek z tego, otrzymuje się chatbota z efektami ubocznymi, a nie agenta.

    Lekcja z praktyki przemysłowej polega na tym, że frameworki ukrywają pętlę za elementami typu runner i węzłami narzędzi, ale umowa pozostaje identyczna: model musi emitować akcję możliwą do analizy, środowisko wykonawcze musi uruchamiać tylko dozwolone narzędzia, a obserwacje muszą ponownie trafić do transkrypcji jako wiadomości pierwszej klasy. Jeśli pominie się cokolwiek z tego, otrzymuje się chatbota z efektami ubocznymi, a nie agenta.

    Literatura pokrewna

  • ReAct Agents w LangGraph: Myślenie, Działanie, Obserwacja krok po kroku — Implementacja pętli ReAct poprzez wyraźne węzły grafu z typowanym stanem, wywołaniami narzędzi oraz warunkami zatrzymania, które można przetestować.
  • Wzory promptów dla prac flow w stylu GPT-6 Astra — Strukturyzacja systemu, narzędzi i przykładów tak, aby długie prompty były możliwe do ponownego użycia w różnych interfejsach czatowych i hostach agentów.