Skip to content

Protect a LangGraph agent

Install with the LangGraph extra:

shell
pip install "nullrun[langgraph]" langgraph langchain-openai

nullrun.init() auto-instruments LangGraph — it attaches the NullRun callback to any compiled graph once init() runs. No manual callback wiring needed (the legacy direct-import path still works but is discouraged).

langgraph_agent.py
from langchain_openai import ChatOpenAI
from langgraph.graph import END, MessagesState, StateGraph

from nullrun import init

init(api_key="nr_live_...")

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

def chat(state: MessagesState):
    return {"messages": [llm.invoke(state["messages"])]}

# `StateGraph(MessagesState)` replaces the deprecated
# `langgraph.graph.MessageGraph` (removed in langgraph 1.0).
graph = StateGraph(MessagesState)
graph.add_node("chat", chat)
graph.add_edge("chat", END)
graph.set_entry_point("chat")
app = graph.compile()

result = app.invoke({"messages": [{"role": "user", "content": "Hi"}]})

Every LLM call inside the graph is now cost-attributed and gated by your workspace policy. The same auto-instrumentation path works for any LangChain Runnable and most LangGraph node types.

Manual wrapper (advanced)

If you need to attach the callback manually — e.g. inside a library that re-compiles graphs after init() ran — the canonical wrapper is:

langgraph_manual_wrapper.py
from nullrun.toolbox.langgraph import wrapper

app = wrapper(graph.compile())

wrapper wraps the compiled app's .invoke and .stream methods to inject the NullRun callback into the LangChain config["callbacks"] list per call. The control-plane kill/pause subscription is independent — it's started automatically by init() and works for every @protect call in the process regardless of whether you used wrapper() or relied on the auto-instrumentation path above.

See also