Skip to content

LangGraph: using the VIDAI Control Plane as the backend

LangGraph builds on LangChain's BaseChatModel contract. You configure a LangChain chat model pointed at the control plane (see langchain.md) and pass it into your graph. LangGraph itself needs no control-plane-specific configuration.

TL;DR

export OPENAI_API_BASE=https://your-vidaiserver.example.com/v1
export OPENAI_API_KEY=sk-your-vidai-key
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent

model = ChatOpenAI(model="gpt-4o-mini")    # reads env vars
agent = create_agent(model=model, tools=[])

result = agent.invoke(
    {"messages": [{"role": "user", "content": "hello"}]}
)
for msg in result["messages"]:
    print(msg.type, ":", getattr(msg, "content", "")[:80])

Prerequisites

  • langgraph >= 1.1 + langchain >= 1.0 (for langchain.agents.create_agent)
  • One of: langchain-openai, langchain-anthropic, langchain-google-genai
  • The control plane URL and an API key

API note: the ReAct-style prebuilt used to live at langgraph.prebuilt.create_react_agent. As of LangGraph 1.0, the recommended import is from langchain.agents import create_agent. Both drive the same underlying state machine; use the new import in new code.

New project: LangGraph

  1. Install:
    pip install langgraph langchain langchain-openai
    
  2. Configure the ChatModel (per langchain.md).
  3. Build your graph:
    from langchain_openai import ChatOpenAI
    from langchain.agents import create_agent
    from langchain_core.tools import tool
    
    @tool
    def get_weather(city: str) -> str:
        """Get current weather for a city."""
        return f"{city}: 20C"
    
    model = ChatOpenAI(model="gpt-4o-mini")
    agent = create_agent(model=model, tools=[get_weather])
    
    result = agent.invoke(
        {"messages": [{"role": "user", "content": "Weather in Paris?"}]}
    )
    

Existing project: LangGraph

LangGraph isn't where you wire the control plane; the LangChain ChatModel you pass to create_agent(...) (or to a custom StateGraph node) is. So:

  • If your graph passes a ChatOpenAI(...): see langchain.md "Existing project: ChatOpenAI". Usually just env-var changes.
  • If your graph passes a ChatAnthropic(...): same, see the ChatAnthropic section.
  • If your graph passes a ChatGoogleGenerativeAI(...): you need a constructor base_url= kwarg; centralise it in a factory.

The rest of your graph (nodes, edges, state schema, conditional routing) is unchanged.

Multi-turn conversations

Carry state between turns by appending to the messages list:

turn1 = agent.invoke(
    {"messages": [{"role": "user", "content": "say alpha"}]}
)
turn2 = agent.invoke(
    {"messages": list(turn1["messages"]) + [
        {"role": "user", "content": "now say beta"}
    ]}
)

For persistent state across process restarts, use LangGraph's checkpointer (e.g. MemorySaver or a SQLite / Postgres backend); that's a LangGraph concern, not a control plane one.

Streaming

for event in agent.stream(
    {"messages": [{"role": "user", "content": "hello"}]}
):
    print(event)

Each event is a dict of {node_name: state_update}: exactly how LangGraph streams state changes. The underlying ChatModel chunks are merged by LangChain before LangGraph emits them.

Tool execution: graph-visible

When a tool fires inside the agent loop, the result shows up as a ToolMessage in the state:

result = agent.invoke(
    {"messages": [{"role": "user", "content": "Weather in Tokyo?"}]}
)
for m in result["messages"]:
    if m.type == "tool":
        print("tool output:", m.content)

Cross-provider routing

LangGraph inherits whichever cross-provider capabilities the underlying ChatModel has. See the cross-provider matrix in Client integrations; column rules apply to the LangChain wrapper you choose.

Reading the control plane's response headers

LangGraph inherits LangChain's visibility here; response headers aren't surfaced through the graph's state updates. For debug requests, either: - Call the underlying vendor SDK directly (bypass LangGraph briefly; see e.g. openai-sdk.md "Reading the control plane's response headers"). - Check response_metadata on the AIMessages in the graph state (may or may not be populated depending on the LangChain version).

See routing-and-headers.md for what the x-vidai-* headers mean and when each one is set.

Verify it works: probe

from langchain_openai import ChatOpenAI
from langchain.agents import create_agent

model = ChatOpenAI(
    model="gpt-4o-mini",
    base_url="https://your-vidaiserver.example.com/v1",
    api_key="sk-your-vidai-key",
)
agent = create_agent(model=model, tools=[])
result = agent.invoke({"messages": [{"role": "user", "content": "say: ok"}]})

last_ai = next(
    (m for m in reversed(result["messages"]) if m.type == "ai"), None
)
print("final:", last_ai.content if last_ai else "<nothing>")

If this prints a response, your setup is correct. Any failure points at the underlying ChatModel configuration; go back to langchain.md and verify that first.