Les agents LangGraph traitent un raisonnement en plusieurs étapes via un graphe d'état, mais ils ne peuvent pas accéder aux données web en direct sans un outil de recherche. Ce tutoriel ajoute Scavio en tant que nœud d'outil LangGraph afin que votre agent puisse rechercher sur le web en cours de raisonnement, intégrer des résultats frais et citer ses sources -- le tout dans le modèle d'exécution standard de LangGraph.
Prérequis
- Python 3.11+
- langgraph >= 0.2.0 et langchain-core installés
- Une clé API Scavio depuis https://scavio.dev
- Une clé API OpenAI ou Anthropic pour le nœud LLM
Parcours
Étape 1: Définir l'outil de recherche Scavio pour LangGraph
Créer un outil compatible LangChain qui encapsule l'API Scavio Search. Cet outil suit l'interface standard BaseTool afin que LangGraph puisse l'invoquer dans n'importe quel nœud d'outil.
import httpx
from langchain_core.tools import tool
from typing import Optional
SCAVIO_API_KEY = "your-api-key"
@tool
def web_search(query: str, num_results: Optional[int] = 5) -> str:
"""Search the web for current information. Returns titles, URLs, and snippets.
Use this when you need up-to-date facts, recent news, or live data."""
with httpx.Client(timeout=15) as client:
resp = client.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": SCAVIO_API_KEY},
json={"query": query, "num_results": num_results}
)
resp.raise_for_status()
results = resp.json().get("results", [])
if not results:
return "No results found for this query."
formatted = []
for i, r in enumerate(results, 1):
formatted.append(
f"{i}. {r.get('title', 'No title')}\n"
f" URL: {r.get('url', '')}\n"
f" {r.get('description', '')[:200]}"
)
return "\n\n".join(formatted)Étape 2: Construire le graphe d'état LangGraph avec un nœud d'outil
Créer le graphe d'agent avec un nœud LLM et un nœud d'outil. Le LLM décide quand appeler l'outil de recherche, et le nœud d'outil exécute l'appel et retourne les résultats au LLM.
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
# LLM with tool binding
llm = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [web_search]
llm_with_tools = llm.bind_tools(tools)
# Define the agent node
def agent_node(state: MessagesState):
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
# Build the graph
graph = StateGraph(MessagesState)
graph.add_node("agent", agent_node)
graph.add_node("tools", ToolNode(tools))
# Routing: agent -> tools -> agent (loop until done)
graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", tools_condition)
graph.add_edge("tools", "agent")
# Compile
agent = graph.compile()Étape 3: Exécuter l'agent avec recherche et suivre les coûts
Exécuter l'agent, compter combien d'appels de recherche il effectue, et calculer le coût de l'API Scavio. Chaque recherche coûte $0.005.
from langchain_core.messages import HumanMessage
async def run_with_tracking(question: str) -> dict:
search_calls = 0
final_answer = ""
result = await agent.ainvoke({
"messages": [HumanMessage(content=question)]
})
for msg in result["messages"]:
if hasattr(msg, "tool_calls") and msg.tool_calls:
for tc in msg.tool_calls:
if tc["name"] == "web_search":
search_calls += 1
if hasattr(msg, "content") and msg.content and not hasattr(msg, "tool_calls"):
final_answer = msg.content
cost = search_calls * 0.005
return {
"answer": final_answer,
"search_calls": search_calls,
"cost_usd": cost
}
# Usage
import asyncio
result = asyncio.run(run_with_tracking(
"Compare the top 3 LangGraph alternatives in May 2026"
))
print(f"Search calls: {result['search_calls']}")
print(f"Cost: {result['cost_usd']:.3f}")
print(f"Answer: {result['answer'][:300]}...")Exemple Python
import asyncio
import httpx
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
SCAVIO_API_KEY = "your-api-key"
@tool
def web_search(query: str, num_results: int = 5) -> str:
"""Search the web for current information."""
with httpx.Client(timeout=15) as client:
resp = client.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": SCAVIO_API_KEY},
json={"query": query, "num_results": num_results}
)
resp.raise_for_status()
results = resp.json().get("results", [])
return "\n".join(
f"{i}. {r['title']} - {r['url']}" for i, r in enumerate(results, 1)
) or "No results found."
tools = [web_search]
llm = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
def agent_node(state: MessagesState):
return {"messages": [llm.invoke(state["messages"])]}
graph = StateGraph(MessagesState)
graph.add_node("agent", agent_node)
graph.add_node("tools", ToolNode(tools))
graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", tools_condition)
graph.add_edge("tools", "agent")
agent = graph.compile()
result = asyncio.run(agent.ainvoke({
"messages": [HumanMessage(content="Top LangGraph alternatives May 2026")]
}))
print(result["messages"][-1].content[:500])Exemple JavaScript
// LangGraph.js with Scavio search tool
import { ChatOpenAI } from "@langchain/openai";
import { tool } from "@langchain/core/tools";
import { StateGraph, MessagesAnnotation, START, END } from "@langchain/langgraph";
import { ToolNode } from "@langchain/langgraph/prebuilt";
import { z } from "zod";
const SCAVIO_API_KEY = "your-api-key";
const webSearch = tool(async ({ query }) => {
const resp = await fetch("https://api.scavio.dev/api/v1/search", {
method: "POST",
headers: { "x-api-key": SCAVIO_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ query, num_results: 5 })
});
const data = await resp.json();
return (data.results || []).map((r, i) => `${i + 1}. ${r.title} - ${r.url}`).join("\n") || "No results.";
}, {
name: "web_search",
description: "Search the web for current information",
schema: z.object({ query: z.string() })
});
const tools = [webSearch];
const llm = new ChatOpenAI({ model: "gpt-4o", temperature: 0 }).bindTools(tools);
const agentNode = async (state) => {
const response = await llm.invoke(state.messages);
return { messages: [response] };
};
const shouldContinue = (state) => {
const last = state.messages[state.messages.length - 1];
return last.tool_calls?.length ? "tools" : END;
};
const graph = new StateGraph(MessagesAnnotation)
.addNode("agent", agentNode)
.addNode("tools", new ToolNode(tools))
.addEdge(START, "agent")
.addConditionalEdges("agent", shouldContinue)
.addEdge("tools", "agent")
.compile();
const result = await graph.invoke({ messages: [{ role: "user", content: "Top LangGraph alternatives May 2026" }] });
console.log(result.messages.at(-1).content.slice(0, 500));Sortie attendue
Search calls: 2
Cost: $0.010
Answer: Based on current web results, the top 3 LangGraph alternatives in May 2026 are...