Skip to content

azizhalloul/ReAct-AI-Agent

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 

Repository files navigation

ReAct AI Agent

A smart home controller built from scratch using the ReAct (Reasoning + Acting) pattern with Gemma 3 and Ollama — no framework, no magic, pure Python.


Overview

Standard LLMs can only predict the next token. They cannot turn on a light or read a temperature sensor by themselves.

The ReAct pattern bridges this gap by having the model emit structured text commands that a Python runtime intercepts, executes against a real system, and feeds back as observations — all within the same conversation history.

This project implements a complete ReAct loop from scratch, controlling a simulated smart home without any agent framework (no LangChain, no LlamaIndex).


ReAct Cycle

┌─────────────────────────────────────────────────────────────┐
│                         USER REQUEST                        │
└────────────────────────────┬────────────────────────────────┘
                             │
                    ┌────────▼────────┐
                    │   1. THOUGHT    │  Gemma 3 analyses the request
                    │  (Gemma 3)      │  and decides what to do next
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │   2. ACTION     │  Gemma 3 emits a structured
                    │  (Gemma 3)      │  command:  Action: light_on
                    └────────┬────────┘  Action Input: salon
                             │
                    ┌────────▼────────┐
                    │  3. EXECUTION   │  Python parses the command
                    │   (Python)      │  and calls home.light_on("salon")
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │ 4. OBSERVATION  │  Result is injected back into
                    │  (→ history)    │  the conversation history
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │  Final Answer   │  Gemma 3 formulates a natural
                    │  (Gemma 3)      │  language response to the user
                    └─────────────────┘

Features

Feature Description
ReAct loop Thought → Action → Observation cycle up to 5 iterations
Conversation history Full multi-turn memory passed on every LLM call
Robust parser Handles parenthesis-style calls, extra whitespace, quotes
Whitelisted tools Only registered functions can be called — no arbitrary exec
Dynamic tool discovery Agent auto-discovers SmartHome methods via dir()
Dynamic system prompt Tool list auto-generated from docstrings — zero hardcoding
Color-coded terminal Visual distinction between thoughts, actions, observations

Project Structure

tp2-react-agent/
├── agent.py          # ReAct loop, prompt builder, parser, tool registry
├── smart_home.py     # Simulated smart home environment
└── README.md

Setup

Prerequisites: Ollama running with gemma3:4b installed (see TP n°1).

# Install dependencies
pip install ollama

# Start Ollama server (Terminal 1)
ollama serve

# Run the agent (Terminal 2)
python agent.py

Key Concepts

System Prompt Engineering

The system prompt is the contract between the LLM and the Python runtime. It must define:

  • The agent's role (smart home assistant)
  • The exact output format (Thought / Action / Action Input)
  • The available tools with their signatures
  • Examples so the model internalizes the pattern

A poorly written system prompt leads to unparseable responses and a broken agent loop.

Conversation History as Working Memory

The agent has no persistent memory across Python runs. Within a session, the full conversation is passed on every API call:

[system_prompt, user_msg_1, ai_msg_1, observation_1, user_msg_2, ...]

This is how the agent "remembers" that it already read the temperature before deciding to turn on the heater.

Tool Whitelisting (Security)

The agent can only call functions explicitly registered in the actions dict. If Gemma 3 were to hallucinate Action: os.system, the parser would find it, look it up in actions, not find it, and return an error observation — without ever executing anything dangerous.

Dynamic Tool Discovery

for name in dir(home):
    if not name.startswith("_") and callable(getattr(home, name)):
        tools[name] = getattr(home, name)

Any new public method added to SmartHome is automatically exposed to the agent with zero changes elsewhere.


🔬 Implementation Details

parse_action(ai_msg)(action, action_input)

Regex-based parser that handles:

  • Standard format: Action: light_on + Action Input: salon
  • Parenthesis style: Action: light_on(salon) — extracts just the function name
  • Case-insensitive, strips quotes and extra whitespace

get_dynamic_system_prompt(actions)

Reads the first line of each tool's docstring to auto-generate the tool list. Adding a new tool to SmartHome and registering it updates the prompt automatically.

run_agent(actions, system_prompt)

Main loop with two nested levels:

  • Outer loop — one iteration per user turn (until exit)
  • Inner loop — up to MAX_STEPS Thought→Action→Observation cycles per turn

Test Scenarios

Prompt Expected behavior
"Turn on the salon light." Single action: light_on(salon)
"Is the kitchen light on?" Single action: get_light_state(cuisine)
"Is it cold? If so, turn on the kitchen light." Two actions: get_temperature → conditional light_on
"What rooms do you control?" Single action: get_rooms()

Limits & Critical Analysis

1. Fragile Parsing

The parser relies on Gemma 3 following the format strictly. Edge cases:

  • Action: light_on(salon) → handled (parentheses stripped)
  • Action : light_on (space before colon) → handled (regex)
  • Action: light_on\nAction Input: le salon (articles) → may fail silently

Solution: MCP, which replaces this fragile text parsing with a standardized JSON protocol — the production-grade approach.

2. Security

If the actions dict were populated with {"os.system": os.system}, the agent could execute arbitrary shell commands. The current implementation is safe because:

  • get_available_tools() is manually curated
  • get_available_tools_auto() only exposes SmartHome methods — never OS-level functions

Never expose eval, exec, os.system, or subprocess as agent tools.

3. No Persistent Memory

History resets on every Python run. A production system would use a vector database (see TP n°1) to store and retrieve past interactions.

4. Single-model Architecture

Both reasoning and action formatting are handled by the same model. A more robust architecture would use a dedicated tool-calling model (e.g. with OpenAI function calling or Ollama's native tool format).


References


License

Academic project — Polytech Nantes, IDIA.

About

ReAct AI Agent from scratch ; Smart home controller with Gemma 3 & Ollama

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages