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
- Python 3.10 or later.
- a DEVUP AI API key from https://devupai.com/dashboard/api-keys. Create a dedicated key for your graphs so you can revoke it on its own.
Install LangGraph
pip install langgraph langchain-openaiSet your API key
The configuration reads the key from DEVUP_API_KEY. Set it in the terminal where you run your graphs:
PowerShell
$env:DEVUP_API_KEY = "YOUR_DEVUP_API_KEY"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
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
| Setting | Value | What it does |
|---|---|---|
| base_url | https://api.devupai.com/v1 | Sends every model call to DEVUP AI. |
| api_key | DEVUP_API_KEY | Your DEVUP AI key, read from the environment. |
| model | a catalog model ID | Written 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
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)python basic_graph.pyIt prints Salam DEVUP.
Stream tokens
Pass stream_mode="messages" to stream() to get the model's tokens as the node runs:
streaming.py
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
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
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
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.