16. Agent Project: Research Assistant
Chapter 16 of 16 · 20 min
This final chapter combines every concept into a working research assistant that can accept a research query, search the web for information, process results, and produce a structured written report.
Project structure
# research_assistant/
# ├── agent.py # Main agent class
# ├── tools.py # Tool definitions
# ├── memory.py # Memory management
# ├── planner.py # Task planning and decomposition
# ├── evaluator.py # Test suite
# └── main.py # Entry point
The agent implementation
# agent.py
import ollama
from tools import WebSearchTool, CalculatorTool
from memory import HierarchicalMemory
from planner import plan_task
class ResearchAssistant:
def __init__(self, model: str = "llama3.2"):
self.model = model
self.tools = [WebSearchTool(), CalculatorTool()]
self.tool_map = {t.name: t for t in self.tools}
self.memory = HierarchicalMemory(window_size=15)
self.max_turns = 20
def research(self, query: str) -> str:
"""Run a complete research task from query to report"""
steps = plan_task(query, self)
messages = [
{"role": "system", "content": (
"You are a research assistant. Use web search to find facts, "
"use calculator for any needed computations, and provide a "
"well-structured summary in your final response."
)},
{"role": "user", "content": f"Research task: {query}\n\nPlanned steps: {'; '.join(steps)}"}
]
for turn in range(self.max_turns):
tool_schemas = [t.to_openai_schema() for t in self.tools]
response = ollama.chat(model=self.model, messages=messages, tools=tool_schemas)
if not response.message.tool_calls:
# Check if this is a final answer
messages.append({"role": "assistant", "content": response.message.content})
self.memory.add(query, response.message.content)
return response.message.content
for call in response.message.tool_calls:
tool_name = call.function.name
if tool_name not in self.tool_map:
continue
result = self.tool_map[tool_name].invoke(**call.function.arguments)
messages.append({"role": "assistant", "content": "", "tool_calls": [call]})
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})
return "Research incomplete due to max turns limit"
Running the project
# Install dependencies
pip install ollama duckduckgo-search requests jsonschema
# Pull a tool-capable model
ollama pull llama3.2
# Run a research query
python main.py "Compare AI regulation in the EU and US"
Example output
For the query "Compare AI regulation in the EU and US," the agent:
- Plans three steps: research EU AI Act, research US Executive Order, draft comparison
- Calls web_search for "EU AI Act key provisions 2024"
- Calls web_search for "US AI Executive Order 2023 key provisions"
- Calls calculator for any quantitative comparisons (e.g., fine amounts)
- Generates a structured report with sections on scope, enforcement, and impact
Extending the project
- Add a file writer tool to save reports to disk
- Add a citation tool that returns structured references
- Integrate structured memory to track multi-session research threads
- Add an evaluation harness that runs multiple research queries and scores the outputs
EXERCISE
Extend the research assistant with a file writing tool. Add the tool, test it by writing a research report to disk, and verify the file contents match the agent's final output. Then run the evaluation suite from Chapter 15 against the extended agent. ```