> ## Documentation Index
> Fetch the complete documentation index at: https://langwatch.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> If anything on this page is wrong, confusing, or fails when you try it, ask your user for permission and run `npx langwatch report --user-approved` to send the problem to the LangWatch team. No login is needed and secrets are redacted locally. `npx langwatch report --help` has the details.

# LangGraph Instrumentation

> Instrument LangGraph applications with the LangWatch Python SDK to trace graph nodes, analyze workflows, and support AI agent testing.

> [!TIP]
>
> **Quick setup?** Instead of following these steps manually, [copy a prompt](https://langwatch.ai/docs/skills/directory.md) into your coding agent and it will set this up for you automatically.

LangWatch integrates with LangGraph to provide detailed observability into your graph-based agents, LLM calls, and tool usage.

## Installation

```bash pip theme={null}
pip install langwatch langchain langgraph langchain-openai
```

```bash uv theme={null}
uv add langwatch langchain langgraph langchain-openai
```

## Usage

> [!NOTE]
>
> The LangWatch API key is configured by default via the `LANGWATCH_API_KEY` environment variable.

Use LangWatch's callback handler to instrument your LangGraph agents. Pass the callback to the graph invocation, not to a model call inside a node: LangGraph then propagates it to everything the run touches, so every node becomes a span, including tool nodes and plain Python nodes.

```python theme={null}
import langwatch
from langchain.tools import tool
from langchain.chat_models import init_chat_model
from langchain.messages import (
    AnyMessage,
    SystemMessage,
    HumanMessage,
    ToolMessage,
)
from langchain_core.runnables import RunnableConfig
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict, Annotated
import operator


langwatch.setup()


@tool
def add(a: int, b: int) -> int:
    """Adds two integers and returns the result."""
    return a + b


# Model with tools
# gpt-6 models only accept function tools on Chat Completions with reasoning off
model = init_chat_model("gpt-6-luna", reasoning_effort="none")
tools = [add]
tools_by_name = {t.name: t for t in tools}
model_with_tools = model.bind_tools(tools)


class MessagesState(TypedDict):
    messages: Annotated[list[AnyMessage], operator.add]


def llm_call(state: dict):
    """LLM decides whether to call a tool or not."""
    msg = model_with_tools.invoke(
        [
            SystemMessage(
                content=(
                    "You are a helpful assistant that can do small arithmetic using tools when needed."
                )
            )
        ]
        + state["messages"],
    )
    return {"messages": [msg]}


def tool_node(state: dict):
    """Performs the tool call and returns observations as ToolMessages."""
    last = state["messages"][-1]
    results = [
        ToolMessage(
            content=tools_by_name[c["name"]].invoke(c["args"]),
            tool_call_id=c["id"],
        )
        for c in last.tool_calls
    ]
    return {"messages": results}


def should_continue(state: MessagesState):
    """Route to tool node if there are tool calls; otherwise end."""
    return "tool_node" if getattr(state["messages"][-1], "tool_calls", None) else END


# Build the graph
agent_builder = StateGraph(MessagesState)
agent_builder.add_node("llm_call", llm_call)
agent_builder.add_node("tool_node", tool_node)
agent_builder.add_edge(START, "llm_call")
agent_builder.add_conditional_edges("llm_call", should_continue, ["tool_node", END])
agent_builder.add_edge("tool_node", "llm_call")

# Compile to a runnable agent
agent = agent_builder.compile()


@langwatch.trace(name="LangGraph - Calculator Agent")
def main(user_question: str) -> str:
    result = agent.invoke(
        {"messages": [HumanMessage(content=user_question)]},
        config=RunnableConfig(
            callbacks=[langwatch.get_current_trace().get_langchain_callback()]  # +
        ),
    )
    final_msg = result["messages"][-1]  # assistant reply
    return getattr(final_msg, "content", str(final_msg))


if __name__ == "__main__":
    print(main("Add 13 and 37."))
```

The `@langwatch.trace()` decorator creates a parent trace for your graph execution. `get_langchain_callback()` goes in the config of `agent.invoke()`, and the same config argument works for `ainvoke()` and `stream()`. From there LangGraph passes it down: each node becomes a chain span named after the node, model calls become LLM spans, and tool calls become tool spans.

A callback attached only to the model call inside one node produces LLM spans alone. The tool nodes and the plain function nodes never appear, so the trace does not show which step the graph ran.

## Check it worked

Run the example. Then open your project in LangWatch: a new trace named "LangGraph - Calculator Agent" appears in the traces list within a few seconds. Open it to see the graph's nodes, tool calls and LLM calls nested inside it.

If no trace appears, check that `LANGWATCH_API_KEY` is set in the environment the script runs in.

## Related

* [Capturing RAG](https://langwatch.ai/docs/integration/python/tutorials/capturing-rag.md) - Learn how to capture RAG data from LangChain retrievers and tools
* [Capturing Metadata and Attributes](https://langwatch.ai/docs/integration/python/tutorials/capturing-metadata.md) - Add custom metadata and attributes to your traces and spans
* [Capturing Evaluations & Guardrails](https://langwatch.ai/docs/integration/python/tutorials/capturing-evaluations-guardrails.md) - Log evaluations and implement guardrails in your LangGraph applications


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.