Skip to content

devxjitin/autourgos-react-agent

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

autourgos-react-agent

A self-contained ReAct (Reasoning + Acting) agent for the Autourgos framework.

ReAct is an agent pattern where the model alternates between Thought (reasoning about what to do next) and Action (calling a tool), looping until it has enough information to give a Final Answer.

Fully self-contained — no autourgos-core dependency. Zero required dependencies beyond Python 3.10+. Plug in any LLM wrapper you already have.


Why use this?

Almost every major LLM provider today exposes an OpenAI-compatible API. autourgos-react-agent was designed with this in mind — it works with any LLM that has .invoke() and .ainvoke() methods. You are not locked to a single provider.

OpenAI (gpt-4o, gpt-4o-mini, ...) ─────────┐
Groq (Llama 3, Mixtral, Gemma) ─────────────┤
Together AI (100+ open-source models) ──────┤  autourgos-react-agent
Mistral AI (mistral-large, codestral) ──────┤  (one agent, any LLM)
DeepSeek (deepseek-chat, reasoner) ─────────┤
Perplexity (sonar, web-connected) ──────────┤
Ollama — local models, no internet ─────────┤
LM Studio — local models, GUI-based ────────┤
vLLM — self-hosted, high throughput ────────┘

What does it do?

The agent receives a task and a list of tools. It then iterates:

  1. Think — what information do I need? which tool should I call?
  2. Act — call the tool, get the result
  3. Observe — add the result to the scratchpad, repeat

This continues until the agent has a final answer or hits the iteration/time limit.


Table of Contents


Install

pip install autourgos-react-agent

No required runtime dependencies. Bring your own LLM wrapper:

pip install autourgos-openaichat   # Chat Completions API
# or
pip install autourgos-responses    # OpenAI Responses API

Requires Python 3.10+.


Quick Start

from autourgos_react_agent import ReactAgent
from autourgos_openaichat  import OpenAIChatModel

# 1. Define a tool
def add(a: float, b: float) -> float:
    return a + b

calculator_tool = {
    "name": "calculator",
    "description": "Add two numbers together.",
    "parameters": {
        "type": "object",
        "properties": {
            "a": {"type": "number", "description": "First number"},
            "b": {"type": "number", "description": "Second number"},
        },
        "required": ["a", "b"],
    },
    "func": add,
}

# 2. Create the agent
agent = ReactAgent(
    llm=OpenAIChatModel(model="gpt-4o"),
    verbose=True,
)
agent.add_tools(calculator_tool)

# 3. Run
result = agent.invoke("What is 123 + 456?")
print(result)
# 579

Expected verbose output:

[ReactAgent] Step 1 | Thought: I need to add 123 and 456. I'll use the calculator tool.
[ReactAgent] Step 1 | Action: calculator({'a': 123, 'b': 456})
[ReactAgent] Step 1 | Observation [calculator]: 579.0
[ReactAgent] Step 2 | Thought: I have the result from the calculator.
[ReactAgent] Final Answer: 123 + 456 = 579

How the ReAct Loop Works

Each iteration the agent produces a JSON object:

{
  "thought": "I need to search for the latest Python version.",
  "actions": [
    {"action": "search", "action_input": {"query": "latest Python version 2025"}}
  ],
  "final_answer": null
}

Rules the LLM must follow (enforced by the prompt):

  • If tools are needed → fill actions, set final_answer to null
  • If the answer is ready → fill final_answer, set actions to []
  • Never set both actions and final_answer at the same time
  • Multiple tools can be called in one step if they are independent

The agent collects tool results into a scratchpad that is passed back to the LLM at each step so it always has full context of what was tried.


Defining Tools

A tool is a plain Python dict with these keys:

Key Type Required Description
name str yes Tool name (used by the LLM to call it)
description str yes What the tool does — shown to the LLM
parameters dict recommended JSON-Schema object describing the inputs
func callable yes Python function to call

Minimal tool

tool = {
    "name": "greet",
    "description": "Return a greeting for a given name.",
    "parameters": {
        "type": "object",
        "properties": {
            "name": {"type": "string", "description": "Person's name"},
        },
        "required": ["name"],
    },
    "func": lambda name: f"Hello, {name}!",
}

Tool with multiple parameters

def get_weather(city: str, unit: str = "celsius") -> str:
    # Replace with real API call
    return f"The weather in {city} is 22°{unit[0].upper()} and sunny."

weather_tool = {
    "name": "get_weather",
    "description": "Get the current weather for a city.",
    "parameters": {
        "type": "object",
        "properties": {
            "city": {"type": "string",  "description": "City name, e.g. Tokyo"},
            "unit": {"type": "string",  "description": "celsius or fahrenheit"},
        },
        "required": ["city"],
    },
    "func": get_weather,
}

Adding tools

# One at a time
agent.add_tools(weather_tool)

# Multiple at once
agent.add_tools(weather_tool, calculator_tool, search_tool)

# From a list
agent.add_tools([weather_tool, calculator_tool])

# Via constructor
agent = ReactAgent(llm=llm, tools=[weather_tool, calculator_tool])

Works With Any LLM

Change the llm= argument to switch providers. Everything else stays the same.

OpenAI

from autourgos_openaichat import OpenAIChatModel

agent = ReactAgent(llm=OpenAIChatModel(model="gpt-4o", api_key="sk-..."))

Groq (very fast, free tier)

from autourgos_openaichat import OpenAIChatModel

agent = ReactAgent(
    llm=OpenAIChatModel(
        model="llama3-70b-8192",
        api_key="gsk_...",
        base_url="https://api.groq.com/openai/v1",
    )
)

Ollama (fully local, no internet, no API key)

ollama pull llama3
from autourgos_openaichat import OpenAIChatModel

agent = ReactAgent(
    llm=OpenAIChatModel(
        model="llama3",
        api_key="ollama",
        base_url="http://localhost:11434/v1",
    )
)

Together AI

from autourgos_openaichat import OpenAIChatModel

agent = ReactAgent(
    llm=OpenAIChatModel(
        model="meta-llama/Llama-3-70b-chat-hf",
        api_key="...",
        base_url="https://api.together.xyz/v1",
    )
)

Mistral AI

from autourgos_openaichat import OpenAIChatModel

agent = ReactAgent(
    llm=OpenAIChatModel(
        model="mistral-large-latest",
        api_key="...",
        base_url="https://api.mistral.ai/v1",
    )
)

DeepSeek

from autourgos_openaichat import OpenAIChatModel

agent = ReactAgent(
    llm=OpenAIChatModel(
        model="deepseek-chat",
        api_key="...",
        base_url="https://api.deepseek.com/v1",
    )
)

OpenAI Responses API (autourgos-responses)

from autourgos_responses import OpenAIResponse

agent = ReactAgent(llm=OpenAIResponse(model="gpt-4o"))

LM Studio (local GUI)

from autourgos_openaichat import OpenAIChatModel

agent = ReactAgent(
    llm=OpenAIChatModel(
        model="local-model",
        api_key="lm-studio",
        base_url="http://localhost:1234/v1",
    )
)

vLLM (self-hosted)

from autourgos_openaichat import OpenAIChatModel

agent = ReactAgent(
    llm=OpenAIChatModel(
        model="meta-llama/Meta-Llama-3-8B-Instruct",
        api_key="EMPTY",
        base_url="http://your-server:8000/v1",
    )
)

Async Agent

All agent methods have an async counterpart.

import asyncio
from autourgos_react_agent import ReactAgent
from autourgos_openaichat  import OpenAIChatModel

agent = ReactAgent(llm=OpenAIChatModel(model="gpt-4o"))
agent.add_tools(weather_tool, calculator_tool)

async def main():
    result = await agent.ainvoke("What is the weather in Tokyo and what is 99 * 3?")
    print(result)
    # The weather in Tokyo is 22°C and sunny. 99 × 3 = 297.

asyncio.run(main())

Async tools (coroutine functions) are also supported:

import httpx

async def async_search(query: str) -> str:
    async with httpx.AsyncClient() as client:
        r = await client.get(f"https://api.example.com/search?q={query}")
        return r.text

search_tool = {
    "name": "search",
    "description": "Search the web.",
    "parameters": {
        "type": "object",
        "properties": {
            "query": {"type": "string", "description": "Search terms"},
        },
        "required": ["query"],
    },
    "func": async_search,   # async function — works with ainvoke()
}

Parallel Tool Calls

The LLM can call multiple tools in a single step when they don't depend on each other. The agent executes them sequentially and collects all results before the next LLM call.

agent = ReactAgent(llm=OpenAIChatModel(model="gpt-4o"))
agent.add_tools(weather_tool, calculator_tool, search_tool)

result = agent.invoke(
    "What is the weather in Paris and London, and what is 250 * 4?"
)
print(result)
# The weather in Paris is 18°C cloudy, London is 15°C rainy. 250 × 4 = 1000.

The LLM produces three tool calls in one step:

{
  "thought": "I can fetch both cities' weather and compute the multiplication in parallel.",
  "actions": [
    {"action": "get_weather", "action_input": {"city": "Paris"}},
    {"action": "get_weather", "action_input": {"city": "London"}},
    {"action": "calculator",  "action_input": {"a": 250, "b": 4}}
  ],
  "final_answer": null
}

Verbose Mode

Enable verbose=True to print every step to stdout.

agent = ReactAgent(
    llm=OpenAIChatModel(model="gpt-4o"),
    verbose=True,
)
agent.add_tools(weather_tool)
result = agent.invoke("What is the weather in Sydney?")

Output:

[ReactAgent] Step 1 | Thought: I need to get the weather in Sydney using the get_weather tool.
[ReactAgent] Step 1 | Action: get_weather({'city': 'Sydney'})
[ReactAgent] Step 1 | Observation [get_weather]: The weather in Sydney is 25°C and sunny.
[ReactAgent] Step 2 | Thought: I have the weather information for Sydney.
[ReactAgent] Final Answer: The weather in Sydney is 25°C and sunny.

Enable full_output=True to also print the raw LLM JSON at each step — useful for debugging prompt or parse issues:

agent = ReactAgent(llm=llm, verbose=True, full_output=True)

Memory

Attach a memory backend to persist conversation history across calls.

from autourgos_react_agent import ReactAgent, MemoryProtocol
from typing import Dict, List

class SimpleMemory(MemoryProtocol):
    def __init__(self):
        self._history: List[Dict[str, str]] = []

    def add_user_message(self, message: str) -> None:
        self._history.append({"role": "user", "content": message})

    def add_assistant_message(self, message: str) -> None:
        self._history.append({"role": "assistant", "content": message})

    def get_history(self) -> List[Dict[str, str]]:
        return list(self._history)

memory = SimpleMemory()
agent  = ReactAgent(llm=llm, memory=memory)
agent.add_tools(search_tool)

result1 = agent.invoke("Search for the capital of France.")
print(result1)
# The capital of France is Paris.

result2 = agent.invoke("What city did I just ask about?")
print(result2)
# You asked about Paris, the capital of France.

Approval Callback

Require human (or programmatic) approval before any tool is executed.

def require_approval(tool_name: str, tool_input: dict) -> bool:
    print(f"\n[Approval required] Tool: {tool_name}")
    print(f"Input: {tool_input}")
    answer = input("Allow? (y/n): ").strip().lower()
    return answer == "y"

agent = ReactAgent(
    llm=OpenAIChatModel(model="gpt-4o"),
    approval_callback=require_approval,
)
agent.add_tools(delete_file_tool)
result = agent.invoke("Delete the temp folder.")

If the callback returns a falsy value, the tool is skipped and the agent sees:

Observation: Tool call was denied by the approval callback.

Use this to implement human-in-the-loop, audit logging, or safety checks for destructive tools.


Middleware / Callbacks

Register event hooks to observe the agent without modifying it.

from autourgos_react_agent import ReactAgent, CallbackHandler

class MyLogger(CallbackHandler):

    def on_agent_start(self, query: str, **kwargs) -> None:
        print(f"Agent started with query: {query}")

    def on_agent_end(self, result: str, **kwargs) -> None:
        print(f"Agent finished: {result}")

    def on_tool_start(self, tool_name: str, tool_input: dict, **kwargs) -> None:
        print(f"Calling tool: {tool_name} with {tool_input}")

    def on_tool_end(self, tool_name: str, result: str, **kwargs) -> None:
        print(f"Tool {tool_name} returned: {result[:100]}")

    def on_iteration(self, iteration: int, thought: str, **kwargs) -> None:
        print(f"Iteration {iteration} — thought: {thought}")

    def on_parse_error(self, iteration: int, raw_response: str, **kwargs) -> None:
        print(f"Parse error at step {iteration}: {raw_response[:100]}")


agent = ReactAgent(llm=llm, middleware=[MyLogger()])
agent.add_tools(weather_tool)
result = agent.invoke("Weather in Berlin?")

You can also add middleware after construction:

agent.add_middleware(MyLogger())

Context Manager

The agent implements both sync and async context managers. They automatically close the LLM's HTTP client when the block exits.

with ReactAgent(llm=OpenAIChatModel(model="gpt-4o")) as agent:
    agent.add_tools(calculator_tool)
    result = agent.invoke("What is 7 * 8?")
    print(result)
    # 56
# LLM client closed here

Async:

import asyncio
from autourgos_openaichat import OpenAIChatModel

async def main():
    async with ReactAgent(llm=OpenAIChatModel(model="gpt-4o")) as agent:
        agent.add_tools(calculator_tool)
        result = await agent.ainvoke("What is 12 ** 2?")
        print(result)
        # 144

asyncio.run(main())

Time and Iteration Limits

Prevent runaway agents with hard limits.

agent = ReactAgent(
    llm=OpenAIChatModel(model="gpt-4o"),
    max_iterations=10,       # stop after 10 Thought → Action → Observe cycles
    max_execution_time=30.0, # stop after 30 seconds wall-clock time
)
agent.add_tools(search_tool)

result = agent.invoke("Research the entire history of the internet.")
# If the agent hasn't finished: "[Timeout] Agent stopped after 30.0s."
# or:                           "[Max Iterations] Agent stopped after 10 iterations..."

You can also override max_iterations per call:

result = agent.invoke("Quick question: capital of Japan?", max_iterations=3)

Custom System Prompt

Add extra instructions that persist across all steps.

agent = ReactAgent(
    llm=OpenAIChatModel(model="gpt-4o"),
    system_prompt=(
        "You are a helpful financial analyst. "
        "Always cite your sources. "
        "Never speculate without data."
    ),
)
agent.add_tools(search_tool, calculator_tool)
result = agent.invoke("What is the P/E ratio of Apple?")

Constructor Reference

Parameter Type Default Description
llm any None LLM wrapper with .invoke() / .ainvoke(). Works with OpenAIChatModel, OpenAIResponse, or any compatible object
verbose bool False Print step-by-step execution to stdout
full_output bool False Also print raw LLM responses (implies verbose)
memory MemoryProtocol None Memory backend for conversation history
max_iterations int 15 Max Thought → Action → Observe cycles before stopping
max_execution_time float None Wall-clock time limit in seconds
approval_callback callable None Called as fn(tool_name, tool_input) before each tool. Return truthy to allow
middleware list[CallbackHandler] None Event hooks for lifecycle events
max_consecutive_parse_errors int 3 Stop after this many back-to-back JSON parse failures
tools list[dict] None Initial tool list (more can be added with add_tools())
system_prompt str "" Extra system-level instruction added to every prompt

Tool Dict Reference

Key Type Required Description
name str yes Identifier used by the LLM. Use snake_case
description str yes Plain-English description of what the tool does and when to use it
parameters dict recommended JSON-Schema object describing the function's inputs
func callable yes The Python function to call. Can be sync or async

parameters format (JSON Schema):

"parameters": {
    "type": "object",
    "properties": {
        "param_name": {
            "type": "string",       # string | number | integer | boolean | array | object
            "description": "...",   # shown to the LLM — make it clear
            "enum": ["a", "b"],     # optional: restrict to specific values
        },
    },
    "required": ["param_name"],     # list required params
}

What the Agent Returns

invoke() and ainvoke() always return a str.

  • Normal completion — the final answer extracted from the LLM's final_answer field
  • Error / limit reached — a string starting with one of the tags below

Error Tags

Tag Meaning
[Timeout] max_execution_time was exceeded
[Max Iterations] max_iterations reached without a final answer
[Parse Error] LLM failed to produce valid JSON max_consecutive_parse_errors times in a row
[LLM Error] LLM raised an exception (network, rate limit, etc.)

v1 Backward Compatibility

The old Create_ReAct_Agent factory function still works but emits a DeprecationWarning:

from autourgos_react_agent import Create_ReAct_Agent  # DeprecationWarning

agent = Create_ReAct_Agent(llm=llm)  # same as ReactAgent(llm=llm)

Update your code to use ReactAgent directly.


License

MIT — Copyright (c) 2026 Jitin Kumar Sengar

About

Self-contained ReAct (Reasoning + Acting) agent for the Autourgos framework — works with any OpenAI-compatible LLM

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages