Skip to content

About

A minimal, dependency-free implementation of the ReAct (Reasoning + Acting) agent loop in ~110 lines of Python — no LangChain, no magic, just a transparent loop with Pydantic-validated tool calls, supporting OpenAI, Anthropic, and local Ollama models.

Topics

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

🤖 BareMetal ReAct

The agent loop, stripped to the atoms.

License: MIT Python OpenAI Anthropic Ollama

Welcome to BareMetal ReAct. This repository provides an educational, production-conscious, and extremely minimal implementation of the ReAct (Reasoning + Acting) agent loop in Python.

The goal of this project is radical clarity. Instead of using complex abstractions, decorators, or heavy frameworks (like LangChain, AutoGen, or LlamaIndex), this project implements the reasoning loop explicitly in ~110 lines of readable code.

ReAct Loop Concept


🎯 Why This Project Exists

Most modern agent frameworks hide the underlying mechanics of how Large Language Models (LLMs) reason and act. While abstraction is useful for rapid prototyping, it can lead to:

  • Hidden prompt injection vulnerabilities
  • Implicit and opaque tool discovery
  • "Adapter hell" when switching LLM backends
  • Confusing debugging when the agent gets stuck in a loop

BareMetal ReAct improves on these frameworks by:

  1. Zero Framework Dependency: You see the loop. You understand the loop.
  2. Type-Safe Tool Validation: We use pydantic to strictly enforce tool input schemas. If the LLM hallucinates arguments, it fails safely and deterministically, feeding the validation error back to the LLM to auto-correct.
  3. Local-First Support: First-class integration with Ollama for local LLM usage, alongside OpenAI and Anthropic.
  4. Resiliency: No dynamic exec() or eval() for tools (except safely constrained mock examples). Tools are explicitly registered and whitelisted.

⚙️ How The ReAct Loop Works

The entire agent logic lives in the agent.py file, which follows this explicit sequence:

  1. Prompt LLM: Feed the system prompt, available tools (with JSON schemas), and conversation history.
  2. Parse Reasoning & Tool Call: Expect the LLM to output a precise JSON contract containing its thought, action, and action_input.
  3. Validate: Check that the requested tool exists in the whitelist registry and that the arguments perfectly match the Pydantic schema structure.
  4. Execute Tool: Run the associated Python function securely.
  5. Return Result: Append the result as an Observation: message and repeat until maximum iterations or a final_answer is reached.

📜 The JSON Contract

The LLM is instructed to strictly return exactly one of the following JSON structures (no markdown wrappers):

When using a tool:

{
  "thought": "I need to get the temperature for Paris to do this math calculation...",
  "action": "get_weather",
  "action_input": {"city": "Paris", "units": "metric"}
}

When returning the final answer:

{
  "thought": "I now have all the necessary information to answer.",
  "final_answer": "The final calculated result is 88."
}

🚀 Setup & Running

  1. Install Dependencies:

    pip install -r requirements.txt
  2. Configure the Agent: Open config.py to set up your desired backend:

    • For OpenAI: ACTIVE_BACKEND = "openai", MODEL_NAME = "gpt-4o"
    • For Anthropic: ACTIVE_BACKEND = "anthropic", MODEL_NAME = "claude-3-haiku-20240307"
    • For Ollama (Local): ACTIVE_BACKEND = "ollama", MODEL_NAME = "llama3"
  3. Run the Implementation Example:

    python main.py

    Note: Because DEBUG_MODE=True is set in config by default, the terminal will sequentially stream each exact thought, registry action, and parser result dynamically on standard out to prove that the loop is working under the hood.


🛠 Defining Your Own Tools

To add a new tool, define a Pydantic BaseModel for its arguments, write the execution function, and register it explicitly in tools.py.

from pydantic import BaseModel
from tools import Tool, registry

class SearchArgs(BaseModel):
    query: str

def mock_search(query: str) -> str:
    return f"Mock search results for: {query}"

my_tool = Tool(
    name="internet_search",
    description="Search the web for real-time information.",
    args_schema=SearchArgs,
    func=mock_search
)

registry.register(my_tool)

By ensuring strict pydantic types, the agent engine will automatically validate inputs during the execution loop and return descriptive errors back to the LLM if it hallucinates invalid arguments.


📄 Documentation & Links

Built for transparency and educational empowerment.

About

A minimal, dependency-free implementation of the ReAct (Reasoning + Acting) agent loop in ~110 lines of Python — no LangChain, no magic, just a transparent loop with Pydantic-validated tool calls, supporting OpenAI, Anthropic, and local Ollama models.

Topics

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages