Tutorial

How to Build LangGraph Agent with Critic Loop

Build a LangGraph agent that searches, drafts, self-critiques, and re-searches until quality passes. Python implementation.

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.

Python
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.

Python
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.

Python
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

Python
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

JavaScript
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

JSON
=== 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...

Related Tutorials

Frequently Asked Questions

Most developers complete this tutorial in 15 to 30 minutes. You will need a Scavio API key (free tier works) and a working Python or JavaScript environment.

Python 3.8+. langgraph and langchain installed. A Scavio API key from scavio.dev. OpenAI or Anthropic API key for LLM. A Scavio API key gives you 250 free credits per month.

Yes. The free tier includes 250 credits per month, which is more than enough to complete this tutorial and prototype a working solution.

Scavio has a native LangChain package (langchain-scavio), an MCP server, and a plain REST API that works with any HTTP client. This tutorial uses the raw REST API, but you can adapt to your framework of choice.

Start Building

Build a LangGraph agent that searches, drafts, self-critiques, and re-searches until quality passes. Python implementation.