Noveum.ai
Noveum Docs
IntegrationsLangGraphLangGraph

LangGraph

Trace a complete LangGraph execution with callback propagation and stable parent relationships.

LangGraph uses the same callback protocol as LangChain. Pass NoveumTraceCallbackHandler when invoking the compiled graph to capture the graph execution and supported model, retriever, and tool operations beneath it.

Install the tracing package

This guide assumes the application already has LangGraph and its model-provider packages installed.

pip install noveum-trace

Initialize Noveum Trace

import os

import noveum_trace

noveum_trace.init(
    api_key=os.environ["NOVEUM_API_KEY"],
    project=os.environ["NOVEUM_PROJECT"],
    environment=os.getenv("NOVEUM_ENVIRONMENT", "production"),
    service_version=os.environ["NOVEUM_SERVICE_VERSION"],
)

Add the callback to graph invocation

The example below defines every node and edge, passes callback config into the model invocation, and traces one complete graph run:

from typing import TypedDict

from langchain_core.runnables import RunnableConfig
from langchain_openai import ChatOpenAI
from langgraph.graph import END, START, StateGraph
from noveum_trace.integrations.langchain import NoveumTraceCallbackHandler


class AgentState(TypedDict):
    question: str
    answer: str


model = ChatOpenAI(model="gpt-4o-mini")


def answer_question(state: AgentState, config: RunnableConfig) -> AgentState:
    response = model.invoke(state["question"], config=config)
    return {
        "question": state["question"],
        "answer": str(response.content),
    }


workflow = StateGraph(AgentState)
workflow.add_node("answer", answer_question)
workflow.add_edge(START, "answer")
workflow.add_edge("answer", END)
app = workflow.compile()

result = app.invoke(
    {"question": user_question, "answer": ""},
    config={"callbacks": [NoveumTraceCallbackHandler()]},
)

use_langchain_assigned_parent is enabled by default. The handler uses LangChain parent run IDs to preserve the graph hierarchy, so normal integrations do not need to set it explicitly.

For a short-lived process, flush after the graph finishes:

noveum_trace.flush()

Capture graph decisions

The callback handler captures events the graph and its components emit. Normal graph callbacks do not automatically produce a dedicated routing-decision span with reasoning, confidence, or alternative routes.

When routing evidence matters for evaluation, preserve it in graph state and attach it through a traced runnable or a manual child span. Capture:

  • the state values used to choose a route
  • the selected route
  • the resulting state change
  • terminal output and exit status
  • errors from nodes, models, retrievers, and tools

Do not store credentials or unbounded state snapshots. Retain only the input, output, and decision evidence needed to debug or evaluate the workflow.

Tools and nested runnables

Pass the node's RunnableConfig to nested model, retriever, chain, and tool invocations. LangChain tools created with @tool should be executed with .invoke() or .ainvoke(), not called as normal Python functions.

result = search_web.invoke(
    {"query": state["question"]},
    config=config,
)

Verify the integration

Run representative paths through the graph and confirm:

  1. each invocation produces one complete trace
  2. node and nested runnable spans have the expected parents
  3. each conditional path records enough state to explain the route
  4. model, retrieval, and tool payloads satisfy the evaluation capture contract
  5. the terminal state contains the final response and exit status
  6. failures identify the node and operation that failed

Use the trace design guide for span boundaries and the LangChain guide for callback payload behavior.

Source