LangGraph agents process multi-step reasoning through a state graph, but they cannot access live web data without a search tool. This tutorial adds Scavio as a LangGraph tool node so your agent can search the web mid-reasoning, incorporate fresh results, and cite sources -- all within the standard LangGraph execution model.
Prerequisites
- Python 3.11+
- langgraph >= 0.2.0 and langchain-core installed
- A Scavio API key from https://scavio.dev
- An OpenAI or Anthropic API key for the LLM node
Walkthrough
Step 1: Define the Scavio search tool for LangGraph
Create a LangChain-compatible tool that wraps the Scavio Search API. This tool follows the standard BaseTool interface so LangGraph can invoke it in any tool node.
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)Step 2: Build the LangGraph state graph with a tool node
Create the agent graph with an LLM node and a tool node. The LLM decides when to call the search tool, and the tool node executes the call and returns results to the 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()Step 3: Run the agent with search and track costs
Execute the agent, count how many search calls it makes, and calculate the Scavio API cost. Each search costs $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]}...")Python Example
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])JavaScript Example
// 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));Expected Output
Search calls: 2
Cost: $0.010
Answer: Based on current web results, the top 3 LangGraph alternatives in May 2026 are...