A ReAct (Reasoning and Acting) agent that can reason about tasks using Thought/Action/Observation loops, use built-in tools, and dynamically generate new tools when needed.
- ReAct Framework: Step-by-step reasoning with tool use
- Multi-Provider LLM Support: Anthropic, OpenAI, and Gemini
- Built-in Tools: Calculator, control operations
- Dynamic Tool Creation: Agent can create new tools at run time
- Secure Secrets Management: Tools can access API keys without exposing them to the agent
- Baseline Mode: Direct prompting without ReAct framework for comparison
-
Install the package:
uv pip install -e . -
Configure LLM API keys:
cp config/.env.example config/.env # Edit config/.env with your LLM provider API keys -
(Optional) Configure tool secrets:
cp config/.env.tools.example config/.env.tools # Edit config/.env.tools with API keys for generated tools -
(Optional) Edit
config/config.yamlto select provider and model settings.
Run the agent with a task:
uv run react -t "What is the square root of 144?"With verbose trace output:
uv run react -t "Calculate 2^10" -vDirect prompting without ReAct framework (for comparison):
uv run baseline -p "What is 2+2?"Interactive mode:
uv run baselineFrom file:
uv run baseline -f prompt.txt| Tool | Operation | Description |
|---|---|---|
control |
control.finish(answer) |
Complete task with final answer |
control |
control.create_tool(name, description, code) |
Create a new tool dynamically |
calculator |
calculator.evaluate(expression) |
Evaluate math expressions |
calculator |
calculator.sqrt(number) |
Square root |
calculator |
calculator.power(base, exponent) |
Exponentiation |
The agent can create new tools at run time. Example task:
uv run react -t "Create a tool to reverse strings, then reverse 'hello world'" -vThe agent will:
- Create a
StringToolwith areverseoperation - Save it to
src/react/tools/generated/stringtool.py - Use
string.reverseto reverse the string - Return the result
For the following task, the agent will generate a tool for suing the OpenWeather API. You need to pre-configure an OPENWEATHER_TOOL_API_KEY in config/.env.tools:
uv run react -t "Tell me how is the current weather in St. Gallen using the OpenWeather API"Generated tools are automatically loaded on subsequent runs.
Tools can access API keys without the agent ever seeing the values.
-
Add secrets to
config/.env.tools(must containTOOL_API_KEYin name):OPENWEATHER_TOOL_API_KEY=your_key_here -
The agent sees only the secret name in its system prompt:
Available secrets (use get_secret('NAME') in tools): - OPENWEATHER_TOOL_API_KEY -
Generated tools use
get_secret():from react.secrets import get_secret api_key = get_secret('OPENWEATHER_TOOL_API_KEY')
-
The actual value is only retrieved at tool execution time.
Security: The secrets manager blocks access to non-tool secrets (like LLM provider keys) even if the agent tries to access them.
Edit config/config.yaml:
# LLM provider: anthropic, openai, or gemini
provider: anthropic
# Provider-specific settings
anthropic:
model: claude-sonnet-4-20250514
max_tokens: 1024
temperature: 0.7
# ReAct agent settings
react:
max_iterations: 10
enable_tool_creation: true
generated_tools_dir: src/react/tools/generated
# Logging
logging:
output_dir: data/responses
format: jsonResponses are saved to data/responses/<provider>/:
| Mode | File Pattern | Contents |
|---|---|---|
| ReAct | react_<timestamp>.json |
Full trace with thoughts, actions, observations |
| Baseline | base_<timestamp>.json |
Direct response |
Example ReAct log:
{
"timestamp": "2026-03-27T10:00:00",
"provider": "anthropic",
"model": "claude-sonnet-4-20250514",
"task": "What is 2+2?",
"trace": [
{"type": "thought", "content": "I need to calculate 2+2"},
{"type": "action", "operation": "calculator.evaluate", "input": {"expression": "2+2"}},
{"type": "observation", "content": "4"}
],
"final_answer": "4",
"iterations": 2,
"tools_created": []
}prompting/
├── pyproject.toml # Package configuration
├── config/
│ ├── config.yaml # Main configuration
│ ├── .env # LLM provider API keys
│ └── .env.tools # Tool secrets (secure)
├── src/react/
│ ├── __init__.py # Package exports
│ ├── agent.py # ReAct agent
│ ├── baseline.py # Simple prompting script
│ ├── client.py # Multi-provider LLM client
│ ├── secrets.py # Secure secrets access
│ └── tools/
│ ├── __init__.py
│ ├── base.py # Tool base class and registry
│ ├── builtin.py # Built-in tools
│ └── generated/ # Dynamically generated tools
├── data/
│ └── responses/ # Logged responses
└── docs/
└── prompts/ # Prompt templates
ReAct agent:
from react import ReActAgent
from react.agent import load_config
config = load_config()
agent = ReActAgent(config)
result = agent.run("Calculate 15 * 7")
print(result["final_answer"])
print(result["iterations"])
print(result["tools_created"])Baseline prompting:
from react import prompt_llm
result = prompt_llm("Calculate 15 * 7")
print(result["response"])