DEVUP Docs
Back to Dashboard

Integrations

LangGraph

LangGraph builds agents as graphs of nodes and edges, with tool loops, streaming and memory built in. Connect its chat model to DEVUP AI with langchain-openai, and every node runs on DEVUP AI models.

Requirements

Install LangGraph

bash
pip install langgraph langchain-openai

Set your API key

The configuration reads the key from DEVUP_API_KEY. Set it in the terminal where you run your graphs:

PowerShell

powershell
$env:DEVUP_API_KEY = "YOUR_DEVUP_API_KEY"

Bash

bash
export DEVUP_API_KEY="YOUR_DEVUP_API_KEY"

Connect LangGraph to DEVUP AI

Save this as devup_config.py next to your graphs. Every example on this page imports devup_model from it.

devup_config.py

python
import os

from langchain_openai import ChatOpenAI


def devup_model(model: str = "deepseek-ai/DeepSeek-V4-Pro") -> ChatOpenAI:
    return ChatOpenAI(
        model=model,
        base_url="https://api.devupai.com/v1",
        api_key=os.environ["DEVUP_API_KEY"],
    )

Settings

SettingValueWhat it does
base_urlhttps://api.devupai.com/v1Sends every model call to DEVUP AI.
api_keyDEVUP_API_KEYYour DEVUP AI key, read from the environment.
modela catalog model IDWritten exactly as in the catalog, such as deepseek-ai/DeepSeek-V4-Pro or openai/gpt-5.4-mini.

Build your first graph

basic_graph.py

python
from langgraph.graph import END, START, MessagesState, StateGraph

from devup_config import devup_model

model = devup_model()


def chatbot(state: MessagesState):
    return {"messages": [model.invoke(state["messages"])]}


builder = StateGraph(MessagesState)
builder.add_node("chatbot", chatbot)
builder.add_edge(START, "chatbot")
builder.add_edge("chatbot", END)
graph = builder.compile()

result = graph.invoke({"messages": [{"role": "user", "content": "Reply with exactly: Salam DEVUP"}]})
print(result["messages"][-1].content)
bash
python basic_graph.py

It prints Salam DEVUP.

Stream tokens

Pass stream_mode="messages" to stream() to get the model's tokens as the node runs:

streaming.py

python
from langgraph.graph import END, START, MessagesState, StateGraph

from devup_config import devup_model

model = devup_model()


def chatbot(state: MessagesState):
    return {"messages": [model.invoke(state["messages"])]}


builder = StateGraph(MessagesState)
builder.add_node("chatbot", chatbot)
builder.add_edge(START, "chatbot")
builder.add_edge("chatbot", END)
graph = builder.compile()

for chunk, metadata in graph.stream(
    {"messages": [{"role": "user", "content": "Count from 1 to 5, separated by commas."}]},
    stream_mode="messages",
):
    print(chunk.content, end="", flush=True)
print()

It prints 1, 2, 3, 4, 5.

Add a tool loop

ToolNode runs the tools the model calls, and tools_condition sends the graph back to the model until it answers:

tools_graph.py

python
from langchain_core.tools import tool
from langgraph.graph import START, MessagesState, StateGraph
from langgraph.prebuilt import ToolNode, tools_condition

from devup_config import devup_model


@tool
def get_order_status(order_id: str) -> str:
    """Look up the shipping status and tracking number of an order."""
    return f"Order {order_id}: shipped, tracking number DZ-4471."


tools = [get_order_status]
model = devup_model().bind_tools(tools)


def agent(state: MessagesState):
    return {"messages": [model.invoke(state["messages"])]}


builder = StateGraph(MessagesState)
builder.add_node("agent", agent)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", tools_condition)
builder.add_edge("tools", "agent")
graph = builder.compile()

result = graph.invoke({"messages": [{"role": "user", "content": "What is the tracking number for order A-1001?"}]})
print(result["messages"][-1].content)

The model calls get_order_status and answers with the tracking number DZ-4471.

Remember a conversation

Compile the graph with a checkpointer and pass the same thread_id to each call. LangGraph saves the state after each step, so the second call sees the first:

memory.py

python
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import END, START, MessagesState, StateGraph

from devup_config import devup_model

model = devup_model()


def chatbot(state: MessagesState):
    return {"messages": [model.invoke(state["messages"])]}


builder = StateGraph(MessagesState)
builder.add_node("chatbot", chatbot)
builder.add_edge(START, "chatbot")
builder.add_edge("chatbot", END)
graph = builder.compile(checkpointer=InMemorySaver())

config = {"configurable": {"thread_id": "order-chat"}}
graph.invoke({"messages": [{"role": "user", "content": "My order number is A-1001. Reply with OK."}]}, config)
result = graph.invoke(
    {"messages": [{"role": "user", "content": "What is my order number? Reply with the number only."}]},
    config,
)
print(result["messages"][-1].content)

It prints A-1001. InMemorySaver keeps the state in memory; for production, use a database checkpointer such as SqliteSaver or PostgresSaver.

Use any catalog model

Pass another model ID to devup_model. The same tool loop runs on openai/gpt-5.4-mini:

model_switch.py

python
from langchain_core.tools import tool
from langgraph.graph import START, MessagesState, StateGraph
from langgraph.prebuilt import ToolNode, tools_condition

from devup_config import devup_model


@tool
def get_order_status(order_id: str) -> str:
    """Look up the shipping status and tracking number of an order."""
    return f"Order {order_id}: shipped, tracking number DZ-4471."


tools = [get_order_status]
model = devup_model("openai/gpt-5.4-mini").bind_tools(tools)


def agent(state: MessagesState):
    return {"messages": [model.invoke(state["messages"])]}


builder = StateGraph(MessagesState)
builder.add_node("agent", agent)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", tools_condition)
builder.add_edge("tools", "agent")
graph = builder.compile()

result = graph.invoke({"messages": [{"role": "user", "content": "What is the tracking number for order A-1001?"}]})
print(result["messages"][-1].content)

Troubleshooting

  • KeyError: 'DEVUP_API_KEY'devup_config.py reads the key from the environment. Set DEVUP_API_KEY in the same terminal that runs python.
  • Tokens arrive in one pieceA short answer can arrive as a single chunk; longer answers stream in several.

FAQ

Does LangGraph work with DEVUP AI?

Yes. Create ChatOpenAI from langchain-openai with base_url https://api.devupai.com/v1 and your DEVUP AI key. Graph nodes, tool loops, token streaming and checkpointer memory then run on DEVUP AI models.

Which API does LangGraph use with DEVUP AI?

ChatOpenAI sends its requests to the Chat Completions API, POST /v1/chat/completions, with the model ID exactly as written.

Which models can my graphs use?

Any model ID from the DEVUP AI catalog, written exactly as listed, such as deepseek-ai/DeepSeek-V4-Pro or openai/gpt-5.4-mini.

How does conversation memory work?

Compile the graph with a checkpointer and pass a thread_id in the config. LangGraph saves the state after each step, and a later call with the same thread_id continues from it. Use InMemorySaver while developing and a database checkpointer such as SqliteSaver or PostgresSaver in production.

Can a graph call tools?

Yes. Bind the tools to the model, add a ToolNode, and route with tools_condition. The graph runs each tool the model asks for and returns the result to the model.

How is this different from the LangChain guide?

The LangChain guide connects LangChain's chat model to DEVUP AI. LangGraph uses the same ChatOpenAI setup and adds graphs with state, tool loops and memory.

Related integrations

  • LangChain: Use ChatOpenAI with the DEVUP AI base URL in chains and agents.
  • Deep Agents: Run Deep Agents on DEVUP AI with langchain-openai's ChatOpenAI and your own tools.
  • OpenAI Agents SDK: Agents with tools, handoffs and streaming on DEVUP AI models.
  • Pydantic AI: Connect Pydantic AI agents to DEVUP AI with OpenAIChatModel and OpenAIProvider.