Trace LangGraph and LangChain
Trace LangGraph and LangChain agents in Python with OpenInference's LangChain instrumentor.
The instrumentor connects the same way as every other OpenTelemetry library. To keep sending runs to LangSmith as well, see Keep your existing tool.
Register the OpenInference instrumentor
LangChainInstrumentor hooks LangChain’s callback system, which LangGraph runs on, and creates a span for each chain, model call, and tool call. Judgment’s ingest maps OpenInference attributes (span kind, input, output, model, tokens) to its own keys.
pip install judgeval langchain langchain-openai openinference-instrumentation-langchain
from judgeval import Tracer
from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from openinference.instrumentation.langchain import LangChainInstrumentor
Tracer.init(project_name="lunch-order-agent")
Tracer.registerOTELInstrumentation(LangChainInstrumentor())
@tool
def search_nearby_restaurants(cuisine: str) -> list[str]:
"""Find restaurants near the office that serve a cuisine."""
return ["Olive & Thyme", "Falafel House", "Cedar Grill"]
agent = create_agent(
ChatOpenAI(model="gpt-5.2"),
tools=[search_nearby_restaurants],
)
result = agent.invoke(
{"messages": [("user", "order me mediterranean food to feed 30 hungry engineers")]}
)
print(result["messages"][-1].content)
Tracer.force_flush()
Register the instrumentor once, right after Tracer.init, before any graph runs. Don’t also wrap() the OpenAI client underneath ChatOpenAI: the instrumentor already records that call.
Don’t use the Langgraph helper yet
judgeval also ships Langgraph.initialize() in judgeval.integrations.langgraph. It only sets environment variables that turn on LangSmith’s OpenTelemetry mode (LANGSMITH_OTEL_ENABLED, LANGSMITH_TRACING, and, by default, LANGSMITH_OTEL_ONLY).
In judgeval 1.3.3 those spans don’t reach Judgment, even after JudgmentTracerProvider.install_as_global_tracer_provider(). LangSmith creates its OpenTelemetry spans on a background thread. Judgment’s provider sends each span to the tracer activated in the current context, and a new thread has none, so the spans go to a no-op tracer. Use the instrumentor above.
Trace work outside the graph
Code that runs outside LangChain (a direct HTTP call, a database write after the graph returns) needs its own span. Decorate it:
from judgeval import Tracer
@Tracer.observe(span_type="tool")
def estimate_order(restaurant: str, headcount: int) -> float:
return 612.0
If the decorated function calls the graph, the graph’s spans nest under it.
If something looks wrong
- No spans after
Langgraph.initialize()is expected in judgeval 1.3.3. Switch to the instrumentor. - If you see duplicate model spans, remove
wrap()or any OpenAI instrumentor; LangChain already records the call. - If spans land in the wrong project, check the project name in
Tracer.init; it decides. See Set up tracing.