Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

ReAct Agents - Reasoning and Acting

ReAct (Reasoning and Acting) to wzorzec projektowy dla agentów AI, który łączy rozumowanie z podejmowaniem akcji. Agent na przemian "myśli" (reasoning) i "działa" (acting), co prowadzi do lepszych rezultatów!

Czym jest ReAct?

ReAct to paradygmat, w którym agent AI:

  1. Thought - analizuje sytuację, planuje
  2. Action - wykonuje akcję (np. wywołuje narzędzie)
  3. Observation - obserwuje wynik akcji
  4. Repeat - powtarza cykl aż do rozwiązania
1┌─────────────────────────────────────────────────────────┐
2│                    ReAct Loop                           │
3├─────────────────────────────────────────────────────────┤
4│                                                         │
5│  ┌──────────┐    ┌──────────┐    ┌─────────────┐       │
6│  │ Thought  │───▶│  Action  │───▶│ Observation │       │
7│  │ (Myśl)   │    │ (Działaj)│    │ (Obserwuj)  │       │
8│  └──────────┘    └──────────┘    └──────┬──────┘       │
9│       ▲                                  │              │
10│       └──────────────────────────────────┘              │
11│                    (Repeat)                             │
12│                                                         │
13│  Przykład:                                              │
14│  Thought: Muszę sprawdzić pogodę w Serengeti           │
15│  Action: get_weather("Serengeti")                       │
16│  Observation: Temperatura 28°C, słonecznie              │
17│  Thought: Teraz mogę odpowiedzieć użytkownikowi        │
18│  Action: final_answer("Pogoda w Serengeti...")          │
19│                                                         │
20└─────────────────────────────────────────────────────────┘

Podstawowa implementacja ReAct

1from openai import OpenAI
2from typing import Callable
3import json
4import re
5
6client = OpenAI()
7
8class ReActAgent:
9    """Agent implementujący wzorzec ReAct."""
10
11    def __init__(self, tools: dict[str, Callable], max_iterations: int = 10):
12        self.tools = tools
13        self.max_iterations = max_iterations
14        self.history: list[str] = []
15
16    def _create_system_prompt(self) -> str:
17        """Tworzy system prompt z opisem narzędzi."""
18        tool_descriptions = "\n".join([
19            f"- {name}: {func.__doc__}"
20            for name, func in self.tools.items()
21        ])
22
23        return f"""Jesteś pomocnym asystentem Safari, który rozwiązuje problemy krok po kroku.
24
25Dostępne narzędzia:
26{tool_descriptions}
27
28Używaj formatu:
29Thought: [twoje rozumowanie o tym, co robić dalej]
30Action: [nazwa_narzędzia(argumenty)]
31
32Gdy masz wystarczające informacje, aby odpowiedzieć:
33Thought: [rozumowanie końcowe]
34Answer: [twoja odpowiedź dla użytkownika]
35
36Zawsze myśl przed działaniem. Analizuj obserwacje zanim podejmiesz kolejny krok."""
37
38    def _parse_response(self, response: str) -> tuple[str, str, str]:
39        """Parsuje odpowiedź na thought, action i answer."""
40        thought_match = re.search(r'Thought:\s*(.+?)(?=Action:|Answer:|$)', response, re.DOTALL)
41        action_match = re.search(r'Action:\s*(.+?)(?=Thought:|Observation:|Answer:|$)', response, re.DOTALL)
42        answer_match = re.search(r'Answer:\s*(.+)', response, re.DOTALL)
43
44        thought = thought_match.group(1).strip() if thought_match else ""
45        action = action_match.group(1).strip() if action_match else ""
46        answer = answer_match.group(1).strip() if answer_match else ""
47
48        return thought, action, answer
49
50    def _execute_action(self, action_str: str) -> str:
51        """Wykonuje akcję i zwraca obserwację."""
52        # Parsuj nazwę funkcji i argumenty
53        match = re.match(r'(\w+)\((.*)\)', action_str)
54        if not match:
55            return f"Błąd: Nieprawidłowy format akcji: {action_str}"
56
57        func_name = match.group(1)
58        args_str = match.group(2)
59
60        if func_name not in self.tools:
61            return f"Błąd: Nieznane narzędzie: {func_name}"
62
63        try:
64            # Proste parsowanie argumentów
65            if args_str:
66                args = [arg.strip().strip('"').strip("'") for arg in args_str.split(',')]
67                result = self.tools[func_name](*args)
68            else:
69                result = self.tools[func_name]()
70            return str(result)
71        except Exception as e:
72            return f"Błąd wykonania: {str(e)}"
73
74    def run(self, query: str) -> str:
75        """Uruchamia agenta ReAct."""
76        self.history = [f"User: {query}"]
77
78        for i in range(self.max_iterations):
79            # Buduj kontekst
80            context = "\n".join(self.history)
81
82            # Wywołaj LLM
83            response = client.chat.completions.create(
84                model="gpt-5-mini",
85                messages=[
86                    {"role": "system", "content": self._create_system_prompt()},
87                    {"role": "user", "content": context}
88                ],
89                temperature=0
90            )
91
92            llm_response = response.choices[0].message.content
93            thought, action, answer = self._parse_response(llm_response)
94
95            # Zapisz thought
96            if thought:
97                self.history.append(f"Thought: {thought}")
98                print(f"🤔 Thought: {thought}")
99
100            # Jeśli jest odpowiedź końcowa
101            if answer:
102                print(f"✅ Answer: {answer}")
103                return answer
104
105            # Wykonaj akcję
106            if action:
107                self.history.append(f"Action: {action}")
108                print(f"⚡ Action: {action}")
109
110                observation = self._execute_action(action)
111                self.history.append(f"Observation: {observation}")
112                print(f"👁️ Observation: {observation}")
113
114        return "Przekroczono limit iteracji bez znalezienia odpowiedzi."

Przykład użycia ReAct

1# Definicja narzędzi
2def get_weather(location: str) -> str:
3    """Pobiera aktualną pogodę dla danej lokalizacji."""
4    weather_data = {
5        "Serengeti": "28°C, słonecznie, wilgotność 45%",
6        "Kilimandżaro": "5°C, pochmurnie, śnieg na szczycie",
7        "Nairobi": "22°C, częściowe zachmurzenie"
8    }
9    return weather_data.get(location, f"Brak danych pogodowych dla {location}")
10
11def search_animals(query: str) -> str:
12    """Wyszukuje informacje o zwierzętach Safari."""
13    animals = {
14        "lew": "Panthera leo - największy kot Afryki, żyje w stadach zwanych watahami",
15        "słoń": "Loxodonta africana - największe zwierzę lądowe, żyje 60-70 lat",
16        "żyrafa": "Giraffa camelopardalis - najwyższe zwierzę świata, do 5.5m wysokości"
17    }
18    for key, value in animals.items():
19        if key in query.lower():
20            return value
21    return f"Nie znaleziono informacji o: {query}"
22
23def calculate_distance(from_loc: str, to_loc: str) -> str:
24    """Oblicza odległość między lokalizacjami Safari."""
25    distances = {
26        ("Nairobi", "Serengeti"): 350,
27        ("Serengeti", "Kilimandżaro"): 280,
28        ("Nairobi", "Kilimandżaro"): 200
29    }
30    key = (from_loc, to_loc)
31    reverse_key = (to_loc, from_loc)
32
33    if key in distances:
34        return f"{distances[key]} km"
35    elif reverse_key in distances:
36        return f"{distances[reverse_key]} km"
37    return f"Nieznana odległość między {from_loc} a {to_loc}"
38
39# Utworzenie agenta
40tools = {
41    "get_weather": get_weather,
42    "search_animals": search_animals,
43    "calculate_distance": calculate_distance
44}
45
46agent = ReActAgent(tools=tools)
47
48# Uruchomienie
49result = agent.run("Jaka jest pogoda w Serengeti i jakie zwierzęta tam żyją?")
50print(f"\nKońcowa odpowiedź: {result}")

ReAct z LangChain

1from langchain.agents import create_react_agent, AgentExecutor
2from langchain_openai import ChatOpenAI
3from langchain.tools import tool
4from langchain import hub
5
6# Narzędzia jako tools LangChain
7@tool
8def safari_weather(location: str) -> str:
9    """Pobiera pogodę dla lokalizacji na Safari."""
10    return f"Pogoda w {location}: 26°C, słonecznie"
11
12@tool
13def animal_info(animal_name: str) -> str:
14    """Wyszukuje informacje o zwierzęciu Safari."""
15    return f"Informacje o {animal_name}: Wspaniałe zwierzę Afryki!"
16
17@tool
18def safari_distance(from_location: str, to_location: str) -> str:
19    """Oblicza odległość między punktami Safari."""
20    return f"Odległość z {from_location} do {to_location}: 150 km"
21
22# LLM i prompt
23llm = ChatOpenAI(model="gpt-5-mini", temperature=0)
24prompt = hub.pull("hwchase17/react")
25
26# Agent ReAct
27tools = [safari_weather, animal_info, safari_distance]
28agent = create_react_agent(llm, tools, prompt)
29
30# Executor
31agent_executor = AgentExecutor(
32    agent=agent,
33    tools=tools,
34    verbose=True,
35    handle_parsing_errors=True,
36    max_iterations=10
37)
38
39# Uruchomienie
40result = agent_executor.invoke({
41    "input": "Sprawdź pogodę w Serengeti i powiedz mi o lwach"
42})
43print(result["output"])

ReAct z OpenAI Function Calling

1from openai import OpenAI
2import json
3
4client = OpenAI()
5
6# Definicja tools dla OpenAI
7tools = [
8    {
9        "type": "function",
10        "function": {
11            "name": "get_safari_info",
12            "description": "Pobiera informacje o Safari",
13            "parameters": {
14                "type": "object",
15                "properties": {
16                    "topic": {
17                        "type": "string",
18                        "description": "Temat: weather, animals, locations"
19                    },
20                    "query": {
21                        "type": "string",
22                        "description": "Szczegółowe zapytanie"
23                    }
24                },
25                "required": ["topic", "query"]
26            }
27        }
28    }
29]
30
31def execute_safari_tool(topic: str, query: str) -> str:
32    """Wykonuje narzędzie Safari."""
33    if topic == "weather":
34        return f"Pogoda dla {query}: 28°C, słonecznie"
35    elif topic == "animals":
36        return f"Informacje o {query}: Fascynujące zwierzę Safari!"
37    elif topic == "locations":
38        return f"Lokalizacja {query}: Popularne miejsce na Safari"
39    return "Nieznany temat"
40
41def react_with_functions(query: str) -> str:
42    """ReAct używając OpenAI function calling."""
43    messages = [
44        {
45            "role": "system",
46            "content": "Jesteś ekspertem Safari. Odpowiadaj krok po kroku, używając dostępnych narzędzi."
47        },
48        {"role": "user", "content": query}
49    ]
50
51    for _ in range(5):  # Max iterations
52        response = client.chat.completions.create(
53            model="gpt-5-mini",
54            messages=messages,
55            tools=tools,
56            tool_choice="auto"
57        )
58
59        message = response.choices[0].message
60        messages.append(message)
61
62        # Sprawdź czy są tool calls
63        if message.tool_calls:
64            for tool_call in message.tool_calls:
65                args = json.loads(tool_call.function.arguments)
66                result = execute_safari_tool(**args)
67
68                messages.append({
69                    "role": "tool",
70                    "tool_call_id": tool_call.id,
71                    "content": result
72                })
73        else:
74            # Brak tool calls = finalna odpowiedź
75            return message.content
76
77    return "Przekroczono limit iteracji"
78
79# Użycie
80answer = react_with_functions("Jaka pogoda panuje w Serengeti?")
81print(answer)

Zaawansowany ReAct z pamięcią

1from dataclasses import dataclass, field
2from typing import Optional
3from datetime import datetime
4
5@dataclass
6class ThoughtStep:
7    """Krok rozumowania agenta."""
8    thought: str
9    action: Optional[str] = None
10    observation: Optional[str] = None
11    timestamp: datetime = field(default_factory=datetime.now)
12
13class AdvancedReActAgent:
14    """Zaawansowany agent ReAct z pamięcią i refleksją."""
15
16    def __init__(self, tools: dict, model: str = "gpt-5-mini"):
17        self.tools = tools
18        self.model = model
19        self.client = OpenAI()
20        self.thought_history: list[ThoughtStep] = []
21        self.long_term_memory: list[str] = []
22
23    def reflect(self) -> str:
24        """Refleksja nad dotychczasowymi krokami."""
25        if len(self.thought_history) < 2:
26            return ""
27
28        steps_summary = "\n".join([
29            f"- Thought: {s.thought}, Action: {s.action}, Result: {s.observation}"
30            for s in self.thought_history[-3:]
31        ])
32
33        response = self.client.chat.completions.create(
34            model=self.model,
35            messages=[
36                {
37                    "role": "system",
38                    "content": "Przeanalizuj dotychczasowe kroki i zasugeruj ulepszenia."
39                },
40                {
41                    "role": "user",
42                    "content": f"Dotychczasowe kroki:\n{steps_summary}"
43                }
44            ],
45            max_tokens=200
46        )
47
48        return response.choices[0].message.content
49
50    def should_reflect(self) -> bool:
51        """Decyduje czy agent powinien przeprowadzić refleksję."""
52        # Refleksja co 3 kroki lub gdy ostatnia akcja nie powiodła się
53        if len(self.thought_history) % 3 == 0 and len(self.thought_history) > 0:
54            return True
55        if self.thought_history and "błąd" in (self.thought_history[-1].observation or "").lower():
56            return True
57        return False
58
59    def run(self, query: str) -> str:
60        """Uruchamia agenta z refleksją."""
61        # ... implementacja podobna do podstawowej wersji
62        # ale z dodaną refleksją
63
64        for i in range(10):
65            if self.should_reflect():
66                reflection = self.reflect()
67                print(f"🔍 Refleksja: {reflection}")
68
69            # ... reszta logiki ReAct
70
71        return "Odpowiedź"

Wzorce ReAct

1"""
2Popularne wzorce używane w agentach ReAct:
3
41. BASIC ReAct
5   Thought -> Action -> Observation -> Repeat
6
72. ReAct with Reflection
8   Thought -> Action -> Observation -> Reflect -> Repeat
9
103. ReAct with Planning
11   Plan -> Thought -> Action -> Observation -> Replan -> Repeat
12
134. ReAct with Self-Consistency
14   Multiple ReAct traces -> Vote on best answer
15
165. ReAct with Retrieval (RAG-ReAct)
17   Thought -> Retrieve -> Augment -> Action -> Observation
18"""
19
20# Przykład ReAct with Planning
21class PlanningReActAgent:
22    def __init__(self, tools: dict):
23        self.tools = tools
24        self.plan: list[str] = []
25        self.current_step = 0
26
27    def create_plan(self, query: str) -> list[str]:
28        """Tworzy plan działania przed rozpoczęciem."""
29        # LLM tworzy plan kroków
30        response = client.chat.completions.create(
31            model="gpt-5-mini",
32            messages=[
33                {
34                    "role": "system",
35                    "content": "Stwórz plan kroków do rozwiązania zadania. Każdy krok w nowej linii."
36                },
37                {"role": "user", "content": query}
38            ]
39        )
40        plan = response.choices[0].message.content.split("\n")
41        return [step.strip() for step in plan if step.strip()]
42
43    def replan(self, remaining_steps: list[str], new_info: str) -> list[str]:
44        """Aktualizuje plan na podstawie nowych informacji."""
45        # LLM może zmodyfikować pozostałe kroki
46        return remaining_steps  # lub zmodyfikowane kroki

ReAct to potężny wzorzec dla agentów AI. Łączy rozumowanie z działaniem, co prowadzi do bardziej przemyślanych i dokładnych odpowiedzi. W następnym module poznasz jeszcze bardziej zaawansowane techniki - RAG i systemy Multi-Agent!

Ir a CodeWorlds