Designed for macOS with Docker Desktop - A practical demonstration of core agentic AI principles with enhanced observability.
Current Features:
- ✓ ReAct Pattern: Reason → Act → Observe loop with full visibility
- ✓ Enhanced Observability: Token breakdown (prompt vs completion), conversation history
- ✓ Tool Intelligence: Automatic type conversion, argument validation
- ✓ Per-step Metrics: Timing, tokens, model for every LLM call
- ✓ Language Tools: Translation, summarization, rewriting, file operations
- ✓ Clean Web UI: Collapsible sections, full transparency
Learn by doing: Run in 5 minutes, understand agentic AI through clear examples.
- macOS with Docker Desktop installed and running
- 8GB+ RAM available
- ~4GB disk space for the LLM model
Three fundamental concepts that power modern AI agents:
Think → Act → Observe → Think → Act → ...
The agent:
- Decides what to do next (reasoning)
- Uses tools when needed (acting)
- Processes results (observing)
- Continues until task complete
# Agent thinks: "I need to calculate this"
{"tool": "calculate", "arguments": {"expression": "25 * 4"}}
# Tool executes → returns result
{"result": 100.0}
# Agent continues with the result
"The answer is 100..."The agent decides WHEN to use tools (not hardcoded). This extends LLMs beyond text generation.
- Uses Ollama (local, no API costs)
- Maintains conversation context
- Iterative problem solving
cd /Volumes/external/code/agentic-ai
# One command setup and run
./run-simple.shThen open your browser to: http://localhost:8000
Or step by step:
# 1. Start both containers (Ollama + Web UI)
docker compose -f docker-compose-simple.yml up -d --build
# 2. Pull model (one-time, ~4GB)
docker exec simple-ollama ollama pull llama3.1
# 3. Open browser
open http://localhost:8000In the web interface, try:
Query: "Translate 'Hello, how are you?' to Spanish"
Agent Process:
Step 1 - Tool Call:
Tool: translate_text
Arguments: {
"text": "Hello, how are you?",
"target_language": "Spanish"
}
Result: {
"translated": "Hola, ¿cómo estás?"
}
Step 2 - Final Answer:
The Spanish translation is: "Hola, ¿cómo estás?"
The agent autonomously:
- Understood you wanted translation
- Called the translate tool with correct arguments
- Received the result
- Provided a clear answer
The agent operates in an iterative Reason → Act → Observe loop. This is what makes it "agentic" - it can adapt, recover from errors, and try different strategies.
File: simple-server.py (lines 228-289)
def run(self, user_query: str):
messages = [system_prompt, user_query]
for iteration in range(max_iterations):
# 1. REASONING: What should I do next?
response = call_llm(messages)
# 2. Check if agent wants to use a tool
if is_tool_call(response):
# 3. ACTING: Execute the tool
result = execute_tool(response)
# 4. OBSERVING: Add result to context
messages.append(result)
continue # Loop again with new info
# 5. DONE: Agent has final answer
return responseThe agent keeps looping until:
- ✓ LLM provides a final answer (no tool call detected)
- ⏱️ Max iterations reached (safety limit: 5 by default)
Query: "Summarize the US Constitution Preamble"
- Step 1: Agent calls
summarize_textbut forgetstextargument → Gets error - Step 2: Agent sees the error, corrects itself, includes the text → Tool succeeds
- Step 3: Agent realizes the tool output wasn't helpful, tries different parameters
- Step 4: Still not working as expected
- Step 5: Agent gives up on the tool and writes answer manually
This self-correction and adaptive behavior is the core value of agentic AI - it doesn't just execute one command, it reasons through problems.
Traditional approach:
User → LLM → One response (if wrong, user must retry)
Agentic approach:
User → Agent → [Try tool] → [See result] → [Try again] → [Adapt] → Final answer
The agent can:
- Recover from mistakes (Step 1 → Step 2)
- Try different strategies (Steps 2-4)
- Fall back gracefully (Step 5)
- Chain multiple tools (translate, then save to file)
This is the essence of agentic behavior.
File: simple-server.py (lines 15-80)
def translate_text(text: str, target_language: str) -> dict:
"""Translate text to Spanish, French, or German."""
return {"translated": translated_text}
def summarize_text(text: str, max_sentences: int = 3) -> dict:
"""Summarize longer text into key points."""
return {"summary": summary_text}
def rewrite_text(text: str, style: str) -> dict:
"""Rewrite text in formal, casual, or technical style."""
return {"rewritten": rewritten_text}
def save_file(filename: str, content: str) -> dict:
"""Save content to a file."""
with open(f"outputs/{filename}", 'w') as f:
f.write(content)
return {"status": "success"}Tools are just Python functions. The agent decides when and how to call them.
Important: The tools are built by the app, not provided by Ollama.
All tools are Python functions defined in the application code:
In simple-server.py (lines 21-105):
translate_text()- Translation (mock implementation)summarize_text()- Text summarizationrewrite_text()- Style rewritingsave_file()- File operations
In simple-agent.py (lines 24-84):
web_search()- Mock web searchcalculate()- Math evaluationsave_file()- File operations
These are regular Python functions that you can modify, extend, or replace with real API integrations.
Ollama is only used for LLM inference. It:
- Generates reasoning and decisions
- Produces tool calls in JSON format
- Provides final answers
Ollama does not execute tools - it only suggests when to use them.
1. User Query → Agent
2. Agent → Ollama (LLM) → "I should call translate_text tool"
3. Agent parses JSON: {"tool": "translate_text", "arguments": {...}}
4. Agent executes translate_text() locally (Python function)
5. Agent → Ollama (with tool result) → "The translation is..."
6. Agent returns final answer
The tools are custom Python functions that the agent calls based on Ollama's decisions. Ollama doesn't know how to execute them - it only suggests when to use them. This separation of concerns is key to the architecture:
- Ollama: Handles reasoning and language understanding
- Your App: Handles tool execution and business logic
This means you have full control over what tools are available and how they work.
File: simple-server.py (lines 120-135)
system_prompt = """You are a helpful AI agent with access to tools.
Available tools:
- web_search(query, max_results): Search the web
- calculate(expression): Evaluate math expressions
- save_file(filename, content): Save content to file
When you need to use a tool, respond with ONLY this JSON format:
{"tool": "tool_name", "arguments": {"arg1": "value1"}}
When you have enough information, provide your final answer without JSON.
Think step by step and use tools when needed."""The prompt defines agent behavior and available capabilities.
The implementation is in simple-server.py (~250 lines). Key parts:
Lines 15-80: Tool definitions (language-focused)
def translate_text(text, target_language):
# Translation implementation
return result
def summarize_text(text, max_sentences):
# Summarization implementation
return resultLines 90-180: Agent class
class SimpleAgent:
def call_ollama() # Talk to LLM
def parse_tool_call() # Extract JSON from response
def execute_tool() # Run the tool
def run() # Main ReAct loopLines 200-250: Flask web server + API endpoints
The code is well-commented and the web UI makes it easy to see the agent in action!
def weather(city: str) -> dict:
"""Get weather for a city."""
# Your implementation (e.g., API call)
return {"temp": 72, "condition": "sunny"}
# Register it
TOOLS["weather"] = {
"function": weather,
"description": "Get weather for a city",
"parameters": {"city": "str"}
}Then update the system prompt to tell the agent about the new tool. That's it!
- Lower temperature (line 90): More consistent tool calling
- Add examples to system prompt (few-shot learning)
- Better error handling in tools
- Validate tool outputs before returning to agent
class SimpleAgent:
def __init__(self):
self.memory = [] # Store past interactions
def run(self, query):
# Retrieve relevant memories
relevant = [m for m in self.memory if query in m]
# Add to context
messages.append({"role": "system", "content": str(relevant)})Token Breakdown: Every step shows prompt vs completion tokens
Prompt Tokens: 145
Completion Tokens: 89
Total: 234
Full Conversation History: Click to expand and see:
- Every message exchanged with the LLM
- Token counts per message (prompt + completion)
- Tool calls and results
- Complete message flow
Per-Step Metrics: Each action shows:
- LLM Time: How long the model took
- Tool Time: How long tool execution took
- Model: Which model was used (llama3.1)
Automatic Type Conversion:
# LLM generates: {"max_sentences": "2"}
# Agent converts: max_sentences = 2 (int)Error Handling: Clear error messages when tools fail
Argument Validation: Ensures correct types before execution
Every step shows:
- File:
simple-server.py - Line: Exact line number where AI call happens
- Function: Call chain (e.g.,
SimpleAgent.run() → call_ollama()) - Narrative: Plain English explanation of what happened
simple-server.py- Agent + web server (~380 lines)templates/index.html- Web UI with observability featuresdocker-compose-simple.yml- Ollama + Web containersDockerfile.simple- Web service containersimple-requirements.txt- Minimal dependencies (ollama, requests, Flask)run-simple.sh- One-command setupcleanup-simple.sh- Safe cleanup scriptSIMPLE-README.md- Extended documentation
Open http://localhost:8000 and try:
Translate 'Hello, how are you?' to Spanish
Rewrite 'hey whats up' in a formal style
Summarize the US Constitution Preamble
Translate 'Hello world' to Spanish and save it to greeting.txt
Make this more professional: 'wanna grab coffee l8r?'
Watch how the agent:
- Decides which tool to use
- Calls tools with correct arguments
- Processes results
- Provides clear answers
Docker Desktop not running:
# Make sure Docker Desktop is running on your Mac
# Check from menu bar or run:
docker psOllama not responding:
docker logs simple-ollama
docker restart simple-ollamaModel not found:
docker exec simple-ollama ollama pull llama3.1
docker exec simple-ollama ollama listAgent not using tools correctly:
- Check system prompt is clear
- Try lower temperature (0.1-0.3)
- Add examples of tool usage to prompt
Translation returns mock data:
- This is intentional for the demo
- To enable real translation, integrate a translation API in
simple-server.py
Can't access localhost:8000:
# Check containers are running
docker ps | grep simple
# Check logs
docker compose -f docker-compose-simple.yml logs webPort already in use:
# Find what's using port 8000
sudo lsof -i :8000
# Kill the process or change the port in docker-compose-simple.ymlQuick cleanup (keeps Ollama and model):
./cleanup-simple.shManual cleanup (same as script):
docker compose -f docker-compose-simple.yml downNuclear cleanup (removes everything including model):
docker compose -f docker-compose-simple.yml down -vWhen you run a query, the UI shows:
- Each Step - Collapsible sections for each iteration
- Tool Calls - Arguments sent and results received
- Token Metrics - Breakdown of every LLM call
- Timing - How long each step took
- Code Context - Where in the code each action happened
- Conversation History - Full message exchange (expandable)
Step 1 - Tool Call:
Tool: translate_text
Arguments: {"text": "Hello", "target_language": "Spanish"}
Result: {"translated": "[Spanish translation of: Hello]"}
Metrics:
- Prompt Tokens: 145
- Completion Tokens: 89
- Total: 234
- LLM Time: 2543ms
- Tool Time: 0.03ms
Step 2 - Final Answer:
The Spanish translation is: "Hola"
Summary:
- Total Tokens: 458
- Total Time: 4,832ms
- Iterations: 2
- Messages: 4
This simple demo teaches you:
Agentic AI Fundamentals:
- Autonomous decision making
- Tool use / function calling
- Iterative problem solving
- Context management
Implementation Patterns:
- Reasoning loops
- Structured outputs (JSON)
- LLM integration
- Error handling
Computer Science:
- State machines
- API design
- Process control
- Modular architecture
Simple Demo (30 min)
↓
Understand code (1 hr)
↓
Add custom tools (1 hr)
↓
Modify for your use case (2+ hrs)
Papers to Read:
- "ReAct: Synergizing Reasoning and Acting in Language Models" (Yao et al., 2022)
- "Chain-of-Thought Prompting" (Wei et al., 2022)
Production Frameworks:
- LangChain - Similar patterns at scale
- LlamaIndex - RAG-focused framework
This is a learning project. Improvements welcome:
- Better example tools
- Clearer documentation
- Additional examples
- Bug fixes
MIT License - Free for educational and commercial use
Get started:
./run-simple.shThen open http://localhost:8000 and watch the AI agent work!
Questions? Read SIMPLE-README.md for more details.