---
name: react_loop
kind: function
lang: py
domain: core
version: "1.0.0"
purity: impure
signature: "def react_loop(llm_chat: Callable[[list[dict]], str], tools: dict[str, Callable[..., str]], system_prompt: str, user_prompt: str, max_iterations: int = 5, on_thought: Callable[[str], None] | None = None, on_action: Callable[[str, dict], None] | None = None, on_observation: Callable[[str], None] | None = None) -> str"
description: "Implementa el patron ReACT (Reasoning + Acting) para agentes LLM. El agente razona, decide usar herramientas, observa resultados, y repite hasta producir una respuesta final."
tags: [llm, agent, react, reasoning, tools, loop]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [json, re, typing]
params:
  - name: llm_chat
    desc: "Función que envía mensajes al LLM y retorna la respuesta"
  - name: tools
    desc: "Diccionario de funciones disponibles como tools {nombre: callable}"
  - name: system_prompt
    desc: "Instrucciones del sistema para el agente"
  - name: user_prompt
    desc: "Pregunta/tarea inicial del usuario"
  - name: max_iterations
    desc: "Máximo número de iteraciones antes de fallback"
  - name: on_thought
    desc: "Callback opcional para logging de pensamientos"
  - name: on_action
    desc: "Callback opcional para logging de acciones"
  - name: on_observation
    desc: "Callback opcional para logging de observaciones"
output: "String con la respuesta final del agente"
tested: true
tests:
  - "agente que usa 1 tool y da respuesta final"
  - "agente que usa multiples tools iterativamente"
  - "agente que da respuesta directa sin tools"
  - "max iterations alcanzado fallback"
  - "tool que falla error handling"
test_file_path: "python/functions/core/react_loop_test.py"
file_path: "python/functions/core/react_loop.py"
---

## Ejemplo

```python
import openai

client = openai.OpenAI()

def llm_chat(messages: list[dict]) -> str:
    resp = client.chat.completions.create(model="gpt-4o", messages=messages)
    return resp.choices[0].message.content

def search(query: str) -> str:
    """Search the knowledge base."""
    return f"Results for: {query}"

def analyze(entity_id: str) -> str:
    """Deep dive into an entity."""
    return f"Analysis of entity {entity_id}"

answer = react_loop(
    llm_chat=llm_chat,
    tools={"search": search, "analyze": analyze},
    system_prompt="You are a research assistant.",
    user_prompt="What is quantum entanglement?",
    max_iterations=5,
    on_thought=lambda t: print(f"[thought] {t}"),
    on_action=lambda a, i: print(f"[action] {a}({i})"),
    on_observation=lambda o: print(f"[obs] {o}"),
)
print(answer)
```

## Notas

El formato de parseo sigue el patron ReACT clasico (Thought/Action/Action Input/Observation/Final Answer), compatible con LangChain y agentes basados en este patron.

Los callbacks (on_thought, on_action, on_observation) permiten logging, UI y tracing sin acoplar al agente.

La funcion es completamente generica: funciona con cualquier LLM (OpenAI, Anthropic, litellm) y cualquier conjunto de tools. El tool description se extrae del primer renglon del docstring de cada callable.

Si max_iterations se agota sin Final Answer, retorna el ultimo response crudo del LLM como fallback.

Los errores de tools se capturan y se entregan como Observation al LLM para que pueda recuperarse. Los errores del LLM propio se propagan hacia arriba.
