HOW-TO · RAG
How to Build Core AI Agent Architecture
Target environment
Ubuntu 24.04 · Ollama 0.4.x
PREREQUISITES
Python, LLM with function calling, Python 3.10+
What this does
A core agent architecture defines the loop: receive input, decide which tool to call, execute the tool, observe the result, and decide the next action — repeating until the task is complete.
Steps
- Define the agent loop. A simple
whileloop drives the process.
import json
from openai import OpenAI
client = OpenAI(api_key="ollama", base_url="http://localhost:11434/v1")
def agent_loop(system_prompt: str, user_input: str, tools: list, max_turns=5):
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_input}
]
for turn in range(max_turns):
response = client.chat.completions.create(
model="llama3.2",
messages=messages,
tools=tools
)
msg = response.choices[0].message
messages.append(msg)
if msg.finish_reason == "stop":
return msg.content
elif msg.finish_reason == "tool_calls":
for tc in msg.tool_calls:
result = execute_tool(tc.function.name, json.loads(tc.function.arguments))
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps(result)
})
return "Max turns reached."
- Build a tool registry. Centralize tool lookup.
class ToolRegistry:
def __init__(self):
self._tools = {}
def register(self, func, schema):
self._tools[func.__name__] = func
return schema
def execute(self, name: str, args: dict):
if name not in self._tools:
return {"error": f"Tool {name} not found"}
try:
return {"result": self._tools[name](**args)}
except Exception as e:
return {"error": str(e)}
- Separate concerns into modules. Organize as: agent loop, tool registry, tools, and memory.
# main.py — entry point
from agent import Agent
from tools.registry import ToolRegistry
from tools.web import search, fetch_page
from tools.files import read_file, write_file
registry = ToolRegistry()
agent = Agent(llm_client=client, registry=registry)
response = agent.run("Search for RAG papers and save results")
- Add observation processing. Let the agent reflect on tool outputs before deciding the next action.
def agent_step(state: dict) -> str:
last_message = state["messages"][-1]
if last_message["role"] == "tool":
# Optional: process/compress tool output
state["context"].append(last_message["content"])
return decide_next(state)
- Include a stop condition. Check for task completion signals.
def is_task_complete(messages: list) -> bool:
last = messages[-1]
if last.get("role") == "assistant" and "FINAL ANSWER:" in last.get("content", ""):
return True
return "task_complete" in str(last)
Verification
python -c "
# Test tool registry
registry = ToolRegistry()
registry.register(lambda x: x*2, {'type':'function','function':{'name':'double'}})
result = registry.execute('double', {'x': 5})
print(result['result'])
# Expected: 10
"
Common failures
- No termination condition. The agent loops indefinitely if
finish_reasonis neverstopand no max_turns guard is set. - Tool output too large. The LLM's context window fills with verbose tool results. Summarize or truncate before feeding back.
- Lost tool call IDs. Each tool response must include the exact
tool_call_id. A mismatch causes the model to ignore the response. - Version mismatch - The installed package or runtime differs from the command shown; check the version first and rerun the smallest verification command.
- Local environment drift - Another service, virtual environment, model, or path is being used; print the active binary path and configuration before changing the guide steps.
Related guides
- How to Implement Tool Use in AI Agents
- How to Create Agent Decision-Making Logic