Most agent tutorials start with LangChain or CrewAI, but you do not need a framework to build a useful agent. This tutorial builds a working search-and-answer agent in plain Python using only requests, an LLM API, and Scavio search. You control every line of code and understand every decision. Total dependencies: requests.
Prerequisites
- Python 3.8+
- requests library
- A Scavio API key from scavio.dev
- An LLM API key (OpenAI, Anthropic, or similar)
Walkthrough
Step 1: Define the search tool
Create a simple function the agent can call to search the web.
import os, requests, json
API_KEY = os.environ['SCAVIO_API_KEY']
SH = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
def search(query, n=5):
"""Search the web. Returns list of {title, link, snippet}."""
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': query, 'country_code': 'us', 'num_results': n}).json()
return [{'title': r.get('title', ''), 'link': r.get('link', ''),
'snippet': r.get('snippet', '')} for r in data.get('organic_results', [])[:n]]
def extract(url):
"""Extract page content from URL."""
data = requests.post('https://api.scavio.dev/api/v1/extract',
headers=SH, json={'url': url}).json()
return data.get('content', '')[:2000]
TOOLS = {
'search': {'fn': search, 'desc': 'Search the web for information'},
'extract': {'fn': extract, 'desc': 'Extract content from a URL'},
}
print(f'Tools available: {list(TOOLS.keys())}')
results = search('python 3.13 new features')
print(f'Test search: {len(results)} results')Step 2: Build the agent loop
Create a think-act-observe loop that calls tools until the task is done.
def agent(task, max_steps=5):
"""Plain Python agent: think -> act -> observe -> repeat."""
memory = [f'Task: {task}']
cost = 0
for step in range(1, max_steps + 1):
print(f'\n--- Step {step} ---')
# Think: decide what to do based on memory
context = '\n'.join(memory[-5:]) # Keep last 5 items
# Simple heuristic: search if no results yet, extract if we have URLs
if step == 1:
action = 'search'
args = {'query': task}
elif any('http' in m for m in memory) and step == 2:
# Extract from first URL found
for m in memory:
if 'http' in m:
url = m.split('http')[1].split()[0]
url = 'http' + url
action = 'extract'
args = {'url': url}
break
else:
action = 'done'
args = {}
if action == 'done':
print('Agent: Task complete.')
break
# Act
print(f'Action: {action}({json.dumps(args)[:60]})')
tool = TOOLS.get(action)
if tool:
result = tool['fn'](**args)
cost += 0.005
observation = json.dumps(result)[:500] if isinstance(result, list) else str(result)[:500]
memory.append(f'Action: {action} -> {observation[:200]}')
print(f'Observation: {observation[:100]}...')
else:
print(f'Unknown action: {action}')
print(f'\nTotal cost: ${cost:.3f} ({step} steps)')
return memory
memory = agent('What are the top Python web frameworks in 2026?')Step 3: Add structured output and error handling
Make the agent produce a clean final answer with sources.
def robust_agent(task, max_steps=5):
"""Agent with error handling and structured output."""
results_cache = []
cost = 0
errors = 0
# Step 1: Search
try:
search_results = search(task)
cost += 0.005
results_cache.extend(search_results)
print(f'Searched: {len(search_results)} results')
except Exception as e:
print(f'Search failed: {e}')
errors += 1
# Step 2: Extract top result if available
if results_cache and results_cache[0].get('link'):
try:
content = extract(results_cache[0]['link'])
cost += 0.005
print(f'Extracted: {len(content)} chars from {results_cache[0]["link"][:40]}')
except Exception as e:
content = ''
print(f'Extract failed: {e}')
errors += 1
else:
content = ''
# Step 3: Compile answer
answer = {
'task': task,
'sources': [{'title': r['title'], 'url': r['link']} for r in results_cache[:5]],
'summary': content[:300] if content else 'See sources below.',
'cost': cost,
'errors': errors
}
print(f'\n=== Agent Result ===')
print(f'Sources: {len(answer["sources"])}')
print(f'Summary: {answer["summary"][:100]}...')
print(f'Cost: ${cost:.3f} | Errors: {errors}')
print(f'\nNo frameworks needed. Just Python and an API.')
return answer
robust_agent('best search api for ai agents 2026')Python Example
import os, requests
SH = {'x-api-key': os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}
def agent(task):
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': task, 'country_code': 'us'}).json()
results = data.get('organic_results', [])[:5]
print(f'Task: {task}')
for r in results:
print(f' [{r.get("title", "")}]({r.get("link", "")})')
print(f'Cost: $0.005 | No framework needed')
agent('best python web framework 2026')JavaScript Example
const SH = { 'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json' };
async function agent(task) {
const data = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: SH,
body: JSON.stringify({ query: task, country_code: 'us' })
}).then(r => r.json());
console.log(`Task: ${task}`);
(data.organic_results || []).slice(0, 5).forEach(r => console.log(` ${r.title}`));
console.log('Cost: $0.005 | No framework needed');
}
await agent('best python web framework 2026');Expected Output
Tools available: ['search', 'extract']
Test search: 5 results
--- Step 1 ---
Action: search({"query": "What are the top Python web frameworks in 2026"})
Observation: [{"title": "Top Python Web Frameworks 2026", "link": "https://...
--- Step 2 ---
Action: extract({"url": "https://realpython.com/top-python-frameworks-2026"})
Observation: FastAPI continues to lead for API development...
--- Step 3 ---
Agent: Task complete.
Total cost: $0.010 (3 steps)