LangGraph
Auto-instrumentation for LangGraph agents.
Risicare provides deep integration with LangGraph for graph-based agent observability.
Version Compatibility
langgraph >= 0.1.0 and langchain-core >= 0.2.0.Installation
pip install risicare[langgraph]
# or
pip install risicare langgraphBasic Usage
import risicare
from langgraph.graph import StateGraph, END
risicare.init()
# Define your graph as usual - it's automatically traced
def agent_node(state):
return {"messages": state["messages"] + ["Agent response"]}
graph = StateGraph(dict)
graph.add_node("agent", agent_node)
graph.set_entry_point("agent")
graph.add_edge("agent", END)
app = graph.compile()
result = app.invoke({"messages": ["Hello"]})What's Captured
On the graph span
invoke, ainvoke and stream on the compiled graph are wrapped, producing a
langgraph.graph/<name> span carrying:
| Attribute | Meaning |
|---|---|
framework.langgraph.graph_name | Compiled graph name |
framework.langgraph.state_keys | Keys of the input state, recorded once |
framework.langgraph.result_keys | Keys of the final result state |
framework.langgraph.thread_id | Thread ID, when one is set in config |
framework.langgraph.streaming / streamed_events | Set on the stream path |
Per-node spans
Each node execution produces its own child span named after the node
(langchain.chain/<node_name>), with timing. These come from the LangChain
integration rather than the LangGraph one, so they appear whenever
langchain_core is also instrumented. A node that runs several times in a loop
produces one span per execution.
Graph topology is not captured
Risicare records what the graph did, not how it is wired. There are no
attributes for edge connections, entry/exit points, or node types, and no
loop-iteration counter — repeated execution is visible only as repeated node
spans, which you would have to count yourself. state_keys is recorded once
from the input state, not at each step. If you need node-level state diffs or
an explicit graph topology, Risicare does not provide them today.
Agent Identity
Annotate nodes with agent identity:
from risicare import agent_context
def researcher_node(state):
with agent_context("researcher-001", agent_name="researcher", agent_role="specialist"):
# Research logic
return {"findings": research_results}
def writer_node(state):
with agent_context("writer-001", agent_name="writer", agent_role="specialist"):
# Writing logic
return {"draft": written_content}Decision Phases
Track Think/Decide/Act within nodes:
from risicare import SemanticPhase, phase_context
def planning_node(state):
with phase_context(SemanticPhase.THINK):
analysis = analyze_task(state["task"])
with phase_context(SemanticPhase.DECIDE):
plan = create_plan(analysis)
return {"plan": plan}
def execution_node(state):
with phase_context(SemanticPhase.ACT):
result = execute_plan(state["plan"])
return {"result": result}Conditional Routing
Conditional edges are visible through the span hierarchy:
def should_continue(state):
if state["iteration"] >= 3:
return "end"
return "continue"
graph.add_conditional_edges(
"agent",
should_continue,
{"continue": "agent", "end": END}
)Routing decisions are reflected in which node spans execute next. The span tree shows the path taken through the graph, so you can infer routing outcomes from the execution sequence.
Subgraphs
Nested subgraphs maintain trace hierarchy:
# Parent graph
parent = StateGraph(dict)
parent.add_node("child_graph", child_app) # Subgraph as node
# Child traces are nested under parentProvider Spans
LangGraph instrumentation creates agent/framework-level spans. Underlying LLM calls (e.g., OpenAI, Anthropic) are traced separately by provider instrumentation, giving you both framework-level and LLM-level visibility. Note that LangChain callbacks handle deduplication when LangChain is also instrumented.
Visualization
View graph execution in the dashboard:
- Timeline View: Node execution waterfall
- Graph View: Visual graph with execution path highlighted
JavaScript / TypeScript
The JS SDK provides instrumentLangGraph() for LangGraph.js:
import { instrumentLangGraph } from 'risicare/langgraph';
const tracedGraph = instrumentLangGraph(compiledGraph);
const result = await tracedGraph.invoke(input);Wraps invoke() and stream() methods with tracing spans.