Introducing confident-trace — our new tracing SDK

LangGraph

Use Confident AI for LLM observability and evals for LangGraph

Overview

LangGraph is a framework for building reactive, multi-agent systems. Confident AI traces and evaluates your LangGraph agents automatically through confident-trace, Confident AI's OpenTelemetry-native tracing SDK for Python and TypeScript — call init() once and your graph code stays exactly as it is.

The integration captures the following spans from your LangGraph agent:

  • Graph and node spans — the root span for each invoke / stream call (including subgraphs), plus one span per node and intermediate runnable
  • LLM spans — model name, token usage, finish reasons, and input/output messages (including tool calls made by the model)
  • Tool spans — tool name, input parameters, and output, nested under the node that ran them
  • Retriever spans — query input and retrieved document text
RuntimeRequirementsSetup
PythonPython 3.10+, LangGraph 1.xCall init() before invoking the graph
TypeScriptNode.js 22+, @langchain/langgraph >=1.4.14 <2, @langchain/core >=1.2.9 <2Call init() and launch your entry point with the preload

Auto-Instrument

  1. Install Dependencies

    Run the following command to install confident-trace alongside LangGraph:

    pip install confident-trace 'langgraph>=1,<2' 'langchain-openai>=1,<2'
  2. Set Your API Keys

    Get your Confident AI Project API key and set it as an environment variable, along with your model provider's key:

    export CONFIDENT_API_KEY="<your-confident-project-key>"
    export OPENAI_API_KEY="<your-openai-key>"
  3. Instrument LangGraph

    Call init() once before invoking your graph. It detects LangGraph automatically and attaches its callback handler for you — there's no handler to pass in config, and no tracing extra to install.

    main.py
    from confident_trace import init, shutdown
    from langchain_openai import ChatOpenAI
    from langgraph.graph import END, START, MessagesState, StateGraph
    
    init()
    model = ChatOpenAI(model="gpt-4.1-mini")
    
    def assistant(state: MessagesState):
        return {"messages": [model.invoke(state["messages"])]}
    
    graph = (
        StateGraph(MessagesState)
        .add_node("assistant", assistant)
        .add_edge(START, "assistant")
        .add_edge("assistant", END)
        .compile()
    )
    
    try:
        result = graph.invoke({"messages": [{"role": "user", "content": "what is the weather in sf"}]})
        print(result["messages"][-1].content)
    finally:
        shutdown()
  4. Run LangGraph

    Run your script to send the trace to Confident AI:

    python main.py

    Done ✅. Open the Observatory in your Confident AI project to inspect the trace and its graph, node, and model spans.

What Gets Captured

The integration mirrors the hierarchy LangGraph reports through its callbacks:

  • Graph invocation — the root span for each invoke / stream call, including subgraphs.
  • Nodes and runnables — one span per node and each intermediate runnable inside it.
  • Model callsLLM spans with model name, token usage, finish reasons, and normalized input/output messages.
  • Tool executionstool spans, parented under the node that ran them, normally alongside the model that requested them.
  • Retrieversretriever spans with retrieved document text.

Graph state, tool values, and document text follow the content policy. Size limits are disabled by default, but you can configure a limit or redact content before export.

Trace a LangGraph Server Deployment

If you deploy your graph with the LangGraph server (langgraph dev or LangGraph Platform), the server executes the graph in its own process — so tracing has to be initialized inside that process, not in whatever client is calling it. The pattern is the same as the quickstart: call init() once in the module that exports your graph.

  1. Initialize tracing in your graph module

    Call init() at the top of the file that builds and exports the graph. The server imports this module once at startup, so init() runs once and every run the server executes is traced.

    agent.py
    from confident_trace import init
    from langchain.agents import create_agent
    from langchain_openai import ChatOpenAI
    
    init()
    
    def get_weather(city: str) -> str:
        """Returns the weather in a city"""
        return f"It's always sunny in {city}!"
    
    graph = create_agent(
        model=ChatOpenAI(model="gpt-4.1-mini"),
        tools=[get_weather],
        system_prompt="You are a helpful assistant",
    )
  2. Register the graph in langgraph.json

    Point the graphs entry at the exported graph variable, and make sure CONFIDENT_API_KEY is in the env file the server loads.

    {
      "dependencies": ["."],
      "graphs": { "agent": "./agent.py:graph" },
      "env": ".env"
    }
  3. Start the LangGraph server

    Run the server. Every request it runs against the graph is traced to Confident AI.

    pip install -U "langgraph-cli[inmem]"
    langgraph dev

Conversations and Checkpoints

If your graph is compiled with a checkpointer, you're already passing a thread_id — but it's worth being clear that there are two different thread_ids here, belonging to two different systems:

  • configurable.thread_id is LangGraph's. It selects the checkpoint so the graph remembers earlier turns. Tracing has no say in it.
  • The trace's thread ID is Confident AI's. It groups each turn's trace into one thread in the Observatory so you can view and evaluate the whole conversation.

Use the same string for both, so the memory the graph sees and the conversation you inspect line up. Each invocation is still its own trace; the thread just groups them. A checkpoint resume after an interrupt starts a new trace rather than continuing the previous one.

These snippets replace the graph.invoke call inside the quickstart's try block and assume the graph was compiled with a checkpointer.

The callback bridge reads configurable.thread_id from LangGraph's run metadata and stamps it on the graph's spans as the conversation ID, so a bare graph.invoke is enough:

thread_id = "conversation-42"
config = {"configurable": {"thread_id": thread_id}}
for prompt in ("Hello", "What did I just say?"):
    result = graph.invoke(
        {"messages": [{"role": "user", "content": prompt}]}, config
    )
    print(result["messages"][-1].content)

Also supported: ainvoke, stream / astream, batch / abatch, and astream_events v2.

Set Trace Span Properties

Use a trace context to add properties you know before the call starts. It creates no extra span; the trace started by graph.invoke() inherits the tags, metadata, and user ID.

main.py
from confident_trace import init, trace_context

init()

with trace_context(
    tags=["support"],
    metadata={"release": "2026-09"},
    user_id="user-42",
):
    result = graph.invoke({"messages": [{"role": "user", "content": "Hello"}]})

See trace context for every supported trace property and update behavior.

Instrumenting Multi-Turn

You do not need turn() when one LangGraph entry-point call is already one conversational turn—the integration creates that turn's trace automatically. Use turn() when you want to define the boundary yourself, such as grouping two sequential LangGraph calls into one turn. Reuse the same thread ID on later turns to group them into one conversation.

main.py
from confident_trace import init, turn

init()

with turn("support-turn", thread_id="chat-42"):
    first = graph.invoke({"messages": [{"role": "user", "content": "Find my account."}]})
    second = graph.invoke({"messages": first["messages"] + [{"role": "user", "content": "Summarize it."}]})

See threads for thread I/O, turn IDs, and user IDs.

Troubleshooting

  • No trace: make sure init() runs before the graph executes, and that the process reaches shutdown() so buffered spans are flushed.
  • Duplicate spans: you have two instrumentors on the same graph. Let confident-trace manage its own handlers — don't add a manual ConfidentLangGraphCallbackHandler alongside automatic mode, and don't attach a second provider instrumentor.
  • Incomplete streams: consume or close graph and model streams before shutdown().
  • Separate traces per turn: expected. Turns sharing a thread ID are grouped as a thread, and a checkpoint resume is a new trace.
  • Missing spans in thread pools: submit work with copy_context().run so the active context reaches the worker.

For general setup issues, see troubleshooting.

Disable LangGraph Instrumentation

Pass init() a list of integration identifiers to opt in to only those integrations. The identifier for LangGraph is "langgraph" in Python and TypeScript; omit it to disable this integration. An empty list disables all automatic instrumentation:

main.py
from confident_trace import init
init(instrumentations=())
# Use ("langgraph",) to opt in; omit "langgraph" to disable it.

This turns off Confident AI's automatic instrumentation; calls made after initialization are not instrumented by this integration.

Next Steps

Need help instrumenting your application?Connect your model calls and agent workflows to Confident AITalk to an expert

Last updated on

Built byConfident AI