Les agents LangGraph gagnent une valeur significative lorsqu'ils combinent une mémoire persistante avec un ancrage de recherche en direct. La mémoire permet à l'agent de se souvenir des interactions passées et des préférences de l'utilisateur, tandis que l'ancrage de recherche empêche les hallucinations en injectant des données en temps réel. Ce tutoriel construit un agent LangGraph qui maintient l'état de la conversation entre les tours, utilise l'API de recherche Scavio comme outil, et décide quand chercher par rapport à quand se fier à la mémoire. L'outil de recherche coûte $0.005 par appel, donc même les agents bavards restent sous quelques dollars par mois.
Prérequis
- Python 3.10+ installé
- paquets langgraph et langchain-openai installés
- Clé API OpenAI pour le LLM
- Clé API Scavio pour l'ancrage de recherche
Parcours
Étape 1: Définir le schéma d'état de l'agent
LangGraph utilise un état typé pour gérer le flux de conversation. Définissez un état qui contient les messages, les résultats de recherche et un magasin de mémoire.
from typing import TypedDict, Annotated, Sequence
from langchain_core.messages import BaseMessage
import operator
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], operator.add]
search_results: str
memory_context: strÉtape 2: Construire le nœud de l'outil de recherche
Créez un nœud qui appelle l'API Scavio lorsque l'agent décide qu'il a besoin d'informations actuelles. Le nœud reçoit l'état et renvoie les search_results mis à jour.
import requests, os
API_KEY = os.environ['SCAVIO_API_KEY']
def search_node(state: AgentState) -> dict:
last_msg = state['messages'][-1].content
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': API_KEY, 'Content-Type': 'application/json'},
json={'query': last_msg, 'country_code': 'us'})
resp.raise_for_status()
results = resp.json().get('organic_results', [])[:5]
context = '\n'.join(f'- {r["title"]}: {r.get("snippet", "")}' for r in results)
return {'search_results': context}Étape 3: Construire le nœud de gestion de la mémoire
Créez un magasin de mémoire simple qui persiste les faits clés de la conversation. L'agent peut les référencer dans les tours futurs sans refaire de recherche.
memory_store = {}
def memory_node(state: AgentState) -> dict:
# Extract facts from the latest exchange
messages = state['messages']
if len(messages) >= 2:
user_msg = messages[-2].content if len(messages) >= 2 else ''
ai_msg = messages[-1].content if messages else ''
# Store key topic as memory
key = user_msg[:50]
memory_store[key] = ai_msg[:200]
# Build memory context from recent entries
recent = list(memory_store.items())[-5:]
ctx = '\n'.join(f'Previously discussed: {k} -> {v[:80]}' for k, v in recent)
return {'memory_context': ctx}Étape 4: Construire le nœud de réponse LLM
Le nœud de réponse combine les résultats de recherche, le contexte de mémoire et le message de l'utilisateur pour générer une réponse ancrée et consciente du contexte.
from langchain_openai import ChatOpenAI
from langchain_core.messages import AIMessage, SystemMessage
llm = ChatOpenAI(model='gpt-4o', temperature=0)
def respond_node(state: AgentState) -> dict:
system = f"""You are a helpful assistant with web search and memory.
Memory from past conversations:
{state.get('memory_context', 'No prior context.')}
Current search results:
{state.get('search_results', 'No search performed.')}
Use search results for factual claims. Use memory to personalize responses."""
messages = [SystemMessage(content=system)] + list(state['messages'])
response = llm.invoke(messages)
return {'messages': [response]}Étape 5: Assembler le graphe avec un routage conditionnel
Connectez les nœuds dans un LangGraph avec un routeur qui décide de chercher ou de répondre directement. Les questions sur les événements actuels déclenchent une recherche ; les suivis utilisent la mémoire.
from langgraph.graph import StateGraph, END
def should_search(state: AgentState) -> str:
last_msg = state['messages'][-1].content.lower()
search_triggers = ['latest', 'current', 'price', '2026', 'today', 'news', 'best']
if any(t in last_msg for t in search_triggers):
return 'search'
return 'respond'
graph = StateGraph(AgentState)
graph.add_node('search', search_node)
graph.add_node('respond', respond_node)
graph.add_node('memory', memory_node)
graph.set_entry_point('search')
graph.add_conditional_edges('search', should_search, {
'search': 'search',
'respond': 'respond'
})
graph.add_edge('search', 'respond')
graph.add_edge('respond', 'memory')
graph.add_edge('memory', END)
app = graph.compile()
print('Agent graph compiled with search + memory nodes')Exemple Python
import os, requests
from typing import TypedDict, Annotated, Sequence
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
import operator
API_KEY = os.environ['SCAVIO_API_KEY']
llm = ChatOpenAI(model='gpt-4o', temperature=0)
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], operator.add]
search_results: str
def search_node(state):
last = state['messages'][-1].content
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': API_KEY, 'Content-Type': 'application/json'},
json={'query': last, 'country_code': 'us'})
results = resp.json().get('organic_results', [])[:5]
ctx = '\n'.join(f'- {r["title"]}: {r.get("snippet", "")}' for r in results)
return {'search_results': ctx}
def respond_node(state):
sys = f'Use these search results:\n{state.get("search_results", "")}'
msgs = [SystemMessage(content=sys)] + list(state['messages'])
return {'messages': [llm.invoke(msgs)]}
graph = StateGraph(AgentState)
graph.add_node('search', search_node)
graph.add_node('respond', respond_node)
graph.set_entry_point('search')
graph.add_edge('search', 'respond')
graph.add_edge('respond', END)
app = graph.compile()
result = app.invoke({'messages': [HumanMessage(content='Best Python frameworks 2026')], 'search_results': ''})
print(result['messages'][-1].content)Exemple JavaScript
// LangGraph is Python-only; this JS example shows the equivalent pattern
const API_KEY = process.env.SCAVIO_API_KEY;
async function searchGrounding(query) {
const resp = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, country_code: 'us' })
});
const data = await resp.json();
return (data.organic_results || []).slice(0, 5)
.map(r => `- ${r.title}: ${r.snippet || ''}`).join('\n');
}
const memory = [];
async function agentTurn(userMessage) {
const context = await searchGrounding(userMessage);
const memoryCtx = memory.slice(-3).join('\n');
const prompt = `Memory:\n${memoryCtx}\n\nSearch:\n${context}\n\nQ: ${userMessage}`;
// Pass prompt to your LLM of choice
console.log(prompt);
memory.push(`User asked: ${userMessage}`);
}
agentTurn('Best Python frameworks 2026').catch(console.error);Sortie attendue
Agent graph compiled with search + memory nodes
Based on current search results, the best Python frameworks in 2026 are:
1. FastAPI - async-first, ideal for APIs and microservices
2. Django - full-featured for complex web applications
3. Flask - lightweight and flexible for smaller projects
...
Search grounding cost: $0.005 per agent turn