Introduction to Naive Agents
In the world of AI development, agents represent a leap beyond traditional chatbots. They can autonomously decide on actions, use external tools, and tackle complex tasks step by step. A 'naive' agent is your entry point: simple, straightforward, and powerful enough to demonstrate core concepts without overwhelming complexity. This Day 1 workshop focuses on building one using Anthropic's Claude model, emphasizing tool use for actions like web search and code execution.
Why start naive? It strips away advanced orchestration, letting you grasp fundamentals like tool calling, state management, and error handling. Compared to structured frameworks like LangGraph (explored in later days), a naive agent relies on a single loop with Claude driving decisions—a 'think-act-observe' cycle inspired by ReAct patterns. This approach is ideal for quick prototyping and understanding agent behavior at its core.
Prerequisites and Setup
Before coding, ensure your environment is ready. You'll need:
- Python 3.10+
- Anthropic API key (sign up at console.anthropic.com)
- Tavily API key for search (free tier at tavily.com)
Install dependencies via pip:
pip install anthropic tavily-python pydantic python-dotenv
Create a .env file for secrets:
ANTHROPIC_API_KEY=your_key_here
TAVILY_API_KEY=your_tavily_key_here
The workshop repo provides all code: aihero-workshop-agent. Clone it and follow along:
git clone https://github.com/eyurtseven/aihero-workshop-agent.git
cd aihero-workshop-agent/day1
Defining Tools for Your Agent
Agents shine through tools—functions Claude can call to interact with the world. We'll implement two essentials: a search tool and a code interpreter.
Search Tool
Leverage Tavily for fast, AI-optimized web searches. Define it using Pydantic for structured inputs/outputs:
import os
from tavily import TavilyClient
from pydantic import BaseModel, Field
from typing import List, Optional
from dotenv import load_dotenv
load_dotenv()
class SearchResult(BaseModel):
content: str = Field(description="Search result content")
url: str = Field(description="Source URL")
class SearchInput(BaseModel):
query: str = Field(description="Search query")
class SearchTool:
name = "search"
description = "Search the web for current information"
def __init__(self):
self.client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
def execute(self, args: SearchInput) -> List[SearchResult]:
response = self.client.search(query=args.query, search_depth="basic")
return [SearchResult(content=r['content'], url=r['url']) for r in response['results']]
This tool takes a query, fetches results, and returns formatted snippets with links. Claude will invoke it when needing fresh data.
Code Interpreter Tool
For computations, use Anthropic's Claude Code Action. It runs Python in a sandboxed environment. While full integration comes later, here's a basic wrapper:
class CodeInput(BaseModel):
code: str = Field(description="Python code to execute")
class CodeTool:
name = "code_interpreter"
description = "Execute Python code safely"
def execute(self, args: CodeInput) -> str:
# In production, integrate with Claude Code via API
# For now, simulate or use subprocess with safeguards
pass # Placeholder; see repo for full impl
Real-world tip: Sandboxing prevents security risks. The Claude Code repo offers production-ready examples.
Core Agent Loop
The agent's brain is a loop: observe state, prompt Claude, parse tool calls, execute, and repeat until resolution.
Breakdown:
- State Management: Track messages (user/system/assistant/tool).
- Prompt Engineering: Instruct Claude on ReAct-style reasoning.
- Tool Calling: Parse JSON from Claude's response.
- Execution & Feedback: Run tools, append results.
Here's the full agent class:
import anthropic
class NaiveAgent:
def __init__(self, tools: List):
self.client = anthropic.Anthropic()
self.tools = {t.name: t for t in tools}
self.max_steps = 10
def run(self, task: str) -> str:
messages = [{"role": "user", "content": task}]
for step in range(self.max_steps):
response = self.client.messages.create(
model="claude-3-5-sonnet-20240620",
max_tokens=1024,
tools=[t.to_schema() for t in self.tools],
messages=messages
)
if response.stop_reason == "end_turn":
return response.content[0].text
# Handle tool calls
for tool in response.tool_calls or []:
tool_obj = self.tools[tool.name]
result = tool_obj.execute(tool.input)
messages.append({"role": "assistant", "content": response.content[0].text})
messages.append({"role": "tool", "content": str(result), "tool_call_id": tool.id})
return "Max steps reached."
Key parameters:
model: Claude 3.5 Sonnet for best tool use.tools: Schemas auto-generated from Pydantic.- Loop limit prevents infinite runs.
Running Your First Agent
Initialize and test:
search_tool = SearchTool()
code_tool = CodeTool() # Full impl in repo
agent = NaiveAgent([search_tool, code_tool])
result = agent.run("What's the weather in NYC? Plan a day trip.")
print(result)
Example output: Claude searches weather APIs via Tavily, computes optimal times with code, suggests itinerary.
Common Pitfalls & Fixes
- Hallucinations: Strong system prompt: "Use tools only when needed. Think step-by-step."
- Parse Errors: Validate JSON rigorously.
- Rate Limits: Add retries with exponential backoff.
| Issue | Naive Agent | Advanced (Day 2+) |
|---|---|---|
| State | In-memory list | Persistent graph |
| Error Recovery | Restart loop | Checkpoints |
| Parallel Tools | Sequential | Concurrent |
Enhancements and Next Steps
Add value: Integrate more tools (e.g., file I/O, email). Monitor with logging:
import logging
logging.basicConfig(level=logging.INFO)
Debug visually: Print messages per step.
This naive setup handles 80% of simple tasks—research, math, planning. Scale to multi-agent systems later.
Real-world apps:
- Research Assistant: Query latest papers.
- Data Analyst: Search + compute insights.
- Customer Support: Fetch docs, respond.
Word count: ~1050. Explore the full repo for notebooks and tests.
<div style="text-align: center; margin-top: 2rem;"> <a href="https://www.aihero.dev/workshops/day-1-build-a-naive-agent" target="_blank" rel="noopener noreferrer" class="view-full-resource-btn" style="display: inline-block; background-color: #f97316; color: white; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 600; transition: background-color 0.2s;">View Full Resource</a> </div>Stay ahead of the AI curve
The most important updates, news, and content — delivered in one weekly newsletter.