Sep 10, 2026
/
By Ariffud M.
/
17 min Read
To build an AI supplier successful Python pinch LangGraph, specify its shared authorities and exemplary node, link them successful a graph, past adhd tools, memory, streaming, and quality approval.
You’ll build the LangGraph supplier successful 8 stages:
- Set up the task and instal LangGraph.
- Define the shared authorities your nodes use.
- Create the exemplary node that calls the AI.
- Connect and tally your first graph.
- Add devices and determine erstwhile the supplier should usage them.
- Add representation crossed abstracted supplier calls.
- Stream the agent’s responses arsenic they’re generated.
- Pause instrumentality calls for quality approval.
After processing your LangGraph supplier locally, you’ll deploy it to a Linux virtual backstage server (VPS) truthful it keeps moving moreover aft you unopen down your computer.
What is LangGraph?
LangGraph is simply a Python model from LangChain for building stateful AI agents arsenic multi-step graphs of nodes and edges. Stateful intends the workflow keeps and updates accusation arsenic it runs alternatively of treating each action arsenic isolated.
It gives you nonstop power complete what your supplier does next. You tin create workflows pinch branching, loops, instrumentality use, memory, streaming, and quality support alternatively of forcing each petition done the aforesaid fixed sequence.
In a LangGraph workflow, nodes execute tasks, edges power which measurement runs next, and shared authorities carries accusation betwixt those steps. The chart tin return to an earlier node, letting your supplier repetition an action until it reaches a stopping condition.
For example, you tin build a investigation adjunct that moves betwixt reasoning and accusation retrieval until it has capable grounds to answer.
This looping behaviour is communal successful agentic AI, wherever an supplier chooses its adjacent action based connected the accusation it has gathered – the aforesaid shape down astir AI supplier examples that request branching, repeated actions, aliases quality review.
Use LangGraph erstwhile your supplier needs to loop, branch, telephone tools, aliases region for approval. If a azygous punctual and consequence solves your problem, telephone the exemplary API directly.
What’s the quality betwixt LangGraph and LangChain?
The quality betwixt LangGraph and LangChain is the level of power you person complete your agent’s workflow.
LangChain provides higher-level APIs and integrations for communal supplier patterns, while LangGraph gives you nonstop power complete state, routing, loops, and execution flow.
| Area | LangChain | LangGraph |
| Abstraction level | Higher-level supplier APIs and integrations | Lower-level power complete workflow execution |
| State management | Provides built-in patterns for communal supplier state | Lets you specify and update chart authorities directly |
Use LangChain erstwhile its built-in supplier patterns already fresh your app. Use LangGraph erstwhile you request civilization routing, persistent authorities crossed turns, repeated workflow steps, aliases quality review.
You don’t request LangChain’s higher-level supplier abstractions to usage LangGraph. LangGraph useful pinch LangChain components erstwhile you want ready-made exemplary and instrumentality integrations, but it doesn’t require them.
The 2 aren’t competitors – LangChain’s ain supplier abstraction runs connected LangGraph underneath, truthful you’re choosing really overmuch of the workflow to constitute yourself.
How to build a LangGraph supplier successful Python
To build a LangGraph supplier successful Python, define its state, create nodes that execute tasks, link them pinch edges, adhd devices and conditional routing, and compile the chart into a runnable app.
By the end, you’ll person 1 moving LangGraph supplier that remembers conversations, decides erstwhile to telephone tools, streams responses, and pauses selected actions for your approval.
1. Set up the task and instal LangGraph
To group up a LangGraph project, create the task folder, activate a Python virtual environment, and instal LangGraph pinch pip.
You’ll request Python 3.10 aliases later, an API cardinal for an AI model, and the required Python packages. We’ll usage DeepSeek, but you tin usage different supplier specified arsenic OpenAI aliases Anthropic by installing its corresponding LangChain package.
Open your terminal, create a files named langgraph-agent, and move into it:
mkdir langgraph-agent cd langgraph-agentNext, create a Python virtual environment truthful this project’s packages don’t impact your different Python projects. On macOS aliases Linux, run:
python3 -m venv .venv source .venv/bin/activate python -m pip instal --upgrade pipYour terminal should show (.venv) astatine the opening of the punctual aft activation.
Install LangGraph, the DeepSeek integration, and the different limitations utilized successful the project:
pip instal langgraph==1.2.11 langchain-deepseek==1.1.0 langchain-core==1.6.1 python-dotenv==1.2.3Pinning these versions keeps the project’s nonstop limitations accordant while you travel along.
Create the task files:
touch main.py .env .gitignore requirements.txtYour langgraph-agent files should now look for illustration this:
langgraph-agent/ .venv/ main.py .env .gitignore requirements.txtKeep the terminal open, past unfastened langgraph-agent successful your codification editor. In VS Code, prime File → Open Folder, past take langgraph-agent.

Open requirements.txt and adhd the aforesaid dependencies:
langgraph==1.2.11 langchain-deepseek==1.1.0 langchain-core==1.6.1 python-dotenv==1.2.3This record lets you recreate the situation later pinch pip instal -r requirements.txt.
Next, unfastened .env and adhd your DeepSeek API key:
DEEPSEEK_API_KEY=your-deepseek-api-keyReplace your-deepseek-api-key pinch the API cardinal from your DeepSeek account. Don’t wrap the cardinal successful quotation marks.
Open .gitignore and add:
.venv/ .env __pycache__/These entries support your virtual environment, API credentials, and Python cache files retired of Git.
Finally, verify that Python loads the installed packages. Open main.py and add:
from dotenv import load_dotenv from langchain_deepseek import ChatDeepSeek from langgraph.graph import StateGraph load_dotenv() print("LangGraph and DeepSeek imports OK")Save main.py. Then, return to the terminal and run:
python main.pyYour LangGraph setup is fresh aft the terminal prints LangGraph and DeepSeek imports OK without an import error. Keep main.py unfastened because you’ll proceed building the supplier successful the aforesaid file.

2. Define the supplier state
Define the supplier authorities by penning a TypedDict people that lists the fields each node tin publication and update.
This gives your LangGraph workflow a shared spot to shop and update speech history while it runs. For this project, you only request 1 section called messages.
In main.py, adhd these imports pinch the existing imports:
from typing import Annotated from langchain_core.messages import AnyMessage from langgraph.graph.message import add_messages from typing_extensions import TypedDictBelow the imports, adhd the AgentState schema:
class AgentState(TypedDict): messages: Annotated[list[AnyMessage], add_messages]TypedDict defines the fields disposable successful your authorities schema. Here, AgentState contains a messages database that each node tin publication and update arsenic the chart runs.
Annotated attaches the add_messages reducer to the list. A reducer controls really LangGraph combines a node’s update pinch the existing worth alternatively of replacing it outright.
Without add_messages, a caller messages worth would switch the existing list. With add_messages, LangGraph merges caller messages into the speech history and updates an existing connection erstwhile some messages usage the aforesaid ID.
Return updates alternatively of changing authorities straight
Don't edit the incoming state entity wrong a node. Return only the fields that changed, for illustration {"messages": [response]}, truthful LangGraph tin merge the update into the chart authorities correctly.
For now, LangGraph doesn’t prevention this authorities crossed abstracted chart runs. You’ll adhd a checkpointer later truthful the supplier tin clasp speech history betwixt runs.
3. Create the exemplary node
Create the exemplary node arsenic a Python usability that sounds your agent’s messages, sends them to DeepSeek, and returns the model’s consequence arsenic a authorities update. You’ll later registry this usability arsenic a node successful the LangGraph workflow.
In main.py, beneath the AgentState definition, initialize the model:
model = ChatDeepSeek( model="deepseek-v4-flash", extra_body={ "thinking": { "type": "disabled" } }, )Important
Important! DeepSeek V4 Flash uses reasoning mode by default. We disable it present because DeepSeek requires reasoning_content from tool-calling responses to beryllium passed backmost successful consequent requests. A LangChain GitHub issue reports that langchain-deepseek==1.1.0 drops this section successful multi-turn instrumentality calls, causing DeepSeek to return a 400 error.
Below the exemplary initialization, adhd model_node:
def model_node(state: AgentState): consequence = model.invoke(state["messages"]) return {"messages": [response]}The usability receives the afloat AgentState but returns only the section it changes. It sounds messages, sends the speech to DeepSeek, and returns the caller AI connection nether the aforesaid key.
Add HumanMessage pinch the existing imports:
from langchain_core.messages import HumanMessageThen, beneath model_node, adhd this impermanent test:
test_state = { "messages": [ HumanMessage(content="Reply pinch precisely OK.") ] } result = model_node(test_state) print(result["messages"][-1].content)You should spot OK successful the terminal. You now person a moving exemplary node that sends speech authorities to DeepSeek and returns nonstop responses.

4. Connect and tally the first graph
To link and tally your first chart successful LangGraph, adhd model_node to StateGraph, link it betwixt START and END, past compile and invoke the graph.
Update the existing LangGraph import successful main.py to see START and END:
from langgraph.graph import END, START, StateGraphSTART marks wherever execution enters the graph, while END marks wherever it stops.
Below the impermanent model-node test, create the chart builder pinch your AgentState schema. Then, registry model_node arsenic the model node:
builder = StateGraph(AgentState) builder.add_node("model", model_node)The builder stores the nodes and edges that specify your workflow earlier you compile it.
Connect START to model, past link model to END:
builder.add_edge(START, "model") builder.add_edge("model", END)These edges create the way START → exemplary → END. Every petition follows it because you haven’t added conditional routing yet.
Then, compile the chart truthful you tin tally it:
graph = builder.compile()compile() turns the StateGraph builder into an executable chart that you tin tally pinch methods specified arsenic invoke().
Below graph = builder.compile(), adhd this chart trial pinch invoke(), past people DeepSeek’s response. Remove the impermanent test_state codification and the HumanMessage import because you nary longer request them.
result = graph.invoke( { "messages": [ { "role": "user", "content": "Give maine 1 use of VPS hosting." } ] } ) print(result["messages"][-1].content)LangGraph sends the personification connection to the model node, adds DeepSeek’s reply to the messages state, and past reaches END. The result adaptable contains the last state, including the original personification connection and DeepSeek’s response.

5. Add devices and conditional routing
Add devices and conditional routing to LangGraph by defining a Python tool, binding it to DeepSeek, and routing the chart based connected the model’s response. For this agent, you’ll adhd a shipping calculator and tally it only for shipping-price questions.
In main.py, adhd tool pinch the existing imports:
from langchain_core.tools import toolBelow the exemplary initialization and supra model_node, specify the shipping calculator:
@tool def calculate_shipping(weight_kg: float, zone: str) -> str: """Calculate this demo store's shipping value successful USD. Use this instrumentality for each shipping-price request. Supported zones are US and EU. """ if weight_kg <= 0: return "Weight must beryllium greater than 0 kg." area = zone.upper() base_rates = { "US": 5.00, "EU": 8.00, } per_kg_rates = { "US": 1.25, "EU": 1.75, } if area not successful base_rates: return "Supported zones are US and EU." full = base_rates[zone] + per_kg_rates[zone] * weight_kg return f"${total:.2f}"The @tool decorator turns the Python usability into a instrumentality that the exemplary tin request. Its name, description, and typed arguments show DeepSeek what the instrumentality does and what input values it expects.
Immediately beneath calculate_shipping, create the instrumentality database and hindrance it to the existing model:
tools = [calculate_shipping] model_with_tools = model.bind_tools(tools)bind_tools() makes the instrumentality meaning disposable to DeepSeek but doesn’t execute the Python function. DeepSeek adds a petition to the AI message’s tool_calls section aft it decides to usage the calculator.
Replace the existing model_node usability pinch this tool-enabled version:
def model_node(state: AgentState): consequence = model_with_tools.invoke(state["messages"]) return {"messages": [response]}Add ToolNode and tools_condition pinch the existing imports successful main.py:
from langgraph.prebuilt import ToolNode, tools_conditionReplace the existent graph-building code, from builder = StateGraph(AgentState) to graph = builder.compile(), with:
builder = StateGraph(AgentState) builder.add_node("model", model_node) builder.add_node("tools", ToolNode(tools)) builder.add_edge(START, "model") builder.add_conditional_edges( "model", tools_condition, { "tools": "tools", "__end__": END, }, ) builder.add_edge("tools", "model") graph = builder.compile()tools_condition checks the latest AI connection aft model runs. It routes execution to tools aft DeepSeek requests a instrumentality and routes it to END aft DeepSeek returns a last consequence without instrumentality calls.
Use fixed routing rules erstwhile you tin
Use regular Python logic erstwhile your app already knows which way should tally next. Let the exemplary take a way only erstwhile the determination depends connected knowing the user's connection aliases context.
ToolNode executes the requested usability and adds its consequence to the speech state. The separator from tools backmost to model lets DeepSeek publication the instrumentality consequence and make a last response.
Your chart now has 2 execution paths:
- START → exemplary → END for a nonstop response.
- START → exemplary → devices → exemplary → END for a tool-assisted response.
The 2nd way creates a rhythm because the chart returns to model aft the instrumentality runs. This loop lets DeepSeek usage the instrumentality consequence earlier producing its last answer.
Warning
Warning! Every loop needs a measurement to stop, specified arsenic a way to END. A chart that keeps looping without reaching an exit yet hits LangGraph's recursion limit and raises GraphRecursionError.
Replace the erstwhile chart trial pinch this direct-response test:
result = graph.invoke( { "messages": [ { "role": "user", "content": "What does a virtual backstage server do?" } ] } ) print(result["messages"][-1].content)DeepSeek should reply straight because the punctual doesn’t require the shipping calculator.
Add this 2nd trial instantly aft the direct-response trial to verify the instrumentality path:
result = graph.invoke( { "messages": [ { "role": "user", "content": "What is the shipping value for 3 kg to EU?" } ] } ) print(result["messages"][-1].content)DeepSeek should petition calculate_shipping, which sends execution done ToolNode and past backmost to model. The calculator returns $13.25 based connected $8.00 + 3 × $1.75.

6. Add representation and persistence
To adhd representation and persistence to LangGraph, compile your chart pinch a checkpointer and delegate each speech a thread_id. InMemorySaver keeps each thread’s checkpoints disposable crossed abstracted invoke() calls while Python is running.
Your existent chart successful main.py doesn’t clasp messages betwixt abstracted invocations. To spot the problem, switch some trial blocks astatine the extremity of main.py pinch this impermanent test:
graph.invoke( { "messages": [ { "role": "user", "content": "My sanction is Jack." } ] } ) result = graph.invoke( { "messages": [ { "role": "user", "content": "What is my name?" } ] } ) print(result["messages"][-1].content)The 2nd invocation only receives “What is my name?”, truthful DeepSeek doesn’t person the earlier connection that identifies the personification arsenic Jack.
Add InMemorySaver pinch the existing imports successful main.py:
from langgraph.checkpoint.memory import InMemorySaverThen find the existent chart compilation line:
graph = builder.compile()Replace it with:
memory = InMemorySaver() graph = builder.compile(checkpointer=memory)Next, switch the impermanent trial astatine the extremity of main.py pinch this multi-turn conversation:
config = { "configurable": { "thread_id": "conversation-1" } } graph.invoke( { "messages": [ { "role": "user", "content": "My sanction is Jack." } ] }, config=config, ) result = graph.invoke( { "messages": [ { "role": "user", "content": "What is my name?" } ] }, config=config, ) print(result["messages"][-1].content)Both calls usage conversation-1, truthful LangGraph loads the checkpoint history for that thread during the 2nd invocation. DeepSeek now receives the earlier connection and has the discourse needed to reply that the user’s sanction is Jack.
Add this 2nd trial instantly aft the first speech to corroborate that a different thread_id keeps its history separate:
new_config = { "configurable": { "thread_id": "conversation-2" } } result = graph.invoke( { "messages": [ { "role": "user", "content": "What is my name?" } ] }, config=new_config, ) print(result["messages"][-1].content)DeepSeek doesn’t person Jack’s connection because conversation-2 has abstracted checkpoint history.

7. Stream the agent’s outputs
Stream your LangGraph supplier pinch graph.stream() to person output while the chart is still moving alternatively of waiting for the full workflow to finish. You tin watercourse each node’s authorities updates aliases show DeepSeek’s consequence token by token.
Keep the graph-building codification successful main.py unchanged. Replace the representation tests astatine the extremity of the file, starting pinch config = {…} and ending pinch the conversation-2 test, with:
stream_config = { "configurable": { "thread_id": "stream-demo" } } for chunk successful graph.stream( { "messages": [ { "role": "user", "content": "What is the shipping value for 5 kg to US?" } ] }, config=stream_config, stream_mode="updates", version="v2", ): for node_name, update successful chunk["data"].items(): print(node_name, update)In updates mode, the loop prints each node’s authorities update aft that node finishes. The intended way for this shipping petition is model → devices → model, truthful you’ll spot really the chart progresses alternatively of receiving only its last state.

This trial uses a caller thread_id truthful the speech history from the representation examples doesn’t impact the result.
To watercourse DeepSeek’s consequence token by token, switch the stream-demo trial astatine the extremity of main.py with:
for chunk successful graph.stream( { "messages": [ { "role": "user", "content": "Explain LangGraph authorities successful 2 sentences." } ] }, config={ "configurable": { "thread_id": "token-stream" } }, stream_mode="messages", version="v2", ): token, _ = chunk["data"] if token.content: print(token.content, end="", flush=True)In messages mode, LangGraph streams the model’s consequence token by token while DeepSeek generates it. Use this mode for a chat interface that should show the consequence progressively alternatively of waiting for the complete answer.

8. Add quality support pinch interrupts
To adhd quality support pinch interrupts successful LangGraph, region the chart earlier a instrumentality runs, past o.k. aliases cull the petition earlier execution continues. You’ll usage interrupt() to region the workflow and Command(resume=…) to nonstop your determination back.
Update the existing typing import successful main.py:
from typing import Annotated, LiteralThen update the existing LangChain connection import:
from langchain_core.messages import AIMessage, AnyMessage, ToolMessageAdd Command and interrupt pinch the different LangGraph imports:
from langgraph.types import Command, interruptBelow model_node, adhd the support node:
def approval_node( state: AgentState, ) -> Command[Literal["tools", "__end__"]]: tool_calls = state["messages"][-1].tool_calls approved = interrupt( { "question": "Approve these instrumentality calls?", "tool_calls": tool_calls, } ) if approved is True: return Command(goto="tools") rejected_messages = [ ToolMessage( content="Tool execution was rejected by the user.", tool_call_id=tool_call["id"], ) for tool_call successful tool_calls ] rejected_messages.append( AIMessage( content="The instrumentality telephone was rejected, truthful nary action was taken." ) ) return Command( update={"messages": rejected_messages}, goto=END, )interrupt() pauses the chart and returns the support mobility and pending instrumentality calls to your Python code.
You past resume it by passing Command(resume=True) backmost into the graph, which sends execution to tools, aliases Command(resume=False), which follows the rejection path.
The rejection way adds a ToolMessage for each pending telephone without executing the tool. This keeps the speech history valid earlier the chart ends.
Next, switch the existent graph-building code, from builder = StateGraph(AgentState) to graph = builder.compile(checkpointer=memory), with:
builder = StateGraph(AgentState) builder.add_node("model", model_node) builder.add_node("approval", approval_node) builder.add_node("tools", ToolNode(tools)) builder.add_edge(START, "model") builder.add_conditional_edges( "model", tools_condition, { "tools": "approval", "__end__": END, }, ) builder.add_edge("tools", "model") graph = builder.compile(checkpointer=memory)Tool requests now walk done approval earlier they scope tools. Direct responses still travel START → exemplary → END, while instrumentality requests region astatine START → exemplary → approval until you nonstop a decision.
Replace the streaming trial astatine the extremity of main.py with:
approval_config = { "configurable": { "thread_id": "approval-demo" } } pending = graph.invoke( { "messages": [ { "role": "user", "content": "What is the shipping value for 3 kg to EU?" } ] }, config=approval_config, ) print(pending["__interrupt__"][0].value)The chart stops wrong approval_node earlier the shipping calculator runs. The worth nether pending[“__interrupt__”] contains the support mobility and requested instrumentality call.

Add this instantly aft the trial to o.k. the request:
approved_result = graph.invoke( Command(resume=True), config=approval_config, ) print(approved_result["messages"][-1].content)Both calls usage approval-demo because LangGraph needs the aforesaid thread_id to resume the paused run. Passing True sends execution to tools, wherever the calculator returns $13.25 from $8.00 + 3 × $1.75.
Save main.py, spell to the terminal, past run:
python main.pyYou should spot the pending instrumentality telephone first, followed by DeepSeek’s last consequence aft approval.

To trial rejection, switch the support trial astatine the extremity of main.py with:
reject_config = { "configurable": { "thread_id": "reject-demo" } } pending = graph.invoke( { "messages": [ { "role": "user", "content": "What is the shipping value for 3 kg to EU?" } ] }, config=reject_config, ) print(pending["__interrupt__"][0].value) rejected_result = graph.invoke( Command(resume=False), config=reject_config, ) print(rejected_result["messages"][-1].content)Return to the terminal and tally python main.py again. LangGraph follows the rejection way and finishes without moving calculate_shipping.

Prevent copy actions aft an interrupt
LangGraph restarts an interrupted node from the opening aft you resume it, truthful support codification earlier interrupt() safe to tally much than once. Put one-time actions, specified arsenic sending an email aliases updating a database, aft the support constituent aliases successful a abstracted node. For debugging, you tin usage interrupt_before aliases interrupt_after to region earlier aliases aft a circumstantial node.
How to deploy a LangGraph supplier connected a VPS
To deploy a LangGraph supplier connected a VPS, move your section task to a Linux server, switch the in-memory checkpointer pinch persistent checkpoint storage, service the supplier done FastAPI, and usage systemd to support it running.
This illustration uses a Hostinger VPS and the aforesaid DeepSeek-based task you built successful the erstwhile section.
1. Prepare the VPS
Prepare the VPS by choosing a suitable scheme and installing Python and the different devices your LangGraph supplier needs. You don’t request to instal DeepSeek because the supplier sends exemplary requests to DeepSeek’s API.
For your LangGraph deployment, the KVM 1 VPS hosting plan pinch 1 vCPU, 4 GB RAM, and 50 GB NVMe retention for $6.49/month is a applicable starting point.
After purchasing the plan, prime Ubuntu 26.04 LTS arsenic the operating strategy and group a beardown password for your VPS.
Once Hostinger finishes setting up your VPS, spell to VPS → Manage and prime Web console to unfastened the browser-based terminal.

Update the server earlier installing the required packages:
apt update apt upgrade -yNext, instal Python, virtual situation support, pip, Git, and curl:
apt instal -y python3 python3-venv python3-pip git curl python3 --versionThe output should show Python 3.10 aliases later.

2. Upload and configure the LangGraph project
To upload and configure your LangGraph project, transcript the section files to the VPS, create a virtual environment, instal the other packages needed for deployment, and adhd your DeepSeek API key.
On your section computer, unfastened requirements.txt and adhd these lines aft the existing dependencies:
langgraph-checkpoint-sqlite==3.1.1 fastapi==0.141.1 uvicorn==0.52.4Go to your VPS terminal and create the app directory:
mkdir -p /opt/langgraph-agentNext, unfastened your computer’s terminal and move into the langgraph-agent task folder. Use its existent way if you created the files location else.
cd ~/langgraph-agentCopy main.py and requirements.txt to the VPS pinch scp:
scp main.py requirements.txt root@your-vps-ip:/opt/langgraph-agent/Replace your-vps-ip pinch your ain VPS IP address. You tin find it successful the VPS Overview page.
Enter your VPS password erstwhile prompted.

Switch to your VPS terminal, move into the app directory, and create a Python virtual environment:
cd /opt/langgraph-agent python3 -m venv .venv source .venv/bin/activateYour terminal should now show (.venv) astatine the opening of the prompt. Install the pinned dependencies:
python -m pip instal --upgrade pip pip instal -r requirements.txtNext, create .env connected the VPS pinch the nano matter editor:
nano .envAdd your DeepSeek API key:
DEEPSEEK_API_KEY=your-deepseek-api-keySave the record and exit nano pinch Ctrl + X → Y → Enter.
Modify the record permissions truthful only the proprietor tin publication aliases edit it:
chmod 600 .envFinally, verify that Python loads the packages:
python -c "import fastapi, uvicorn; from langchain_deepseek import ChatDeepSeek; from langgraph.checkpoint.sqlite import SqliteSaver; print('Deployment limitations OK')"The terminal should people Deployment limitations OK without an import error.

3. Add persistent checkpoint storage
Add persistent checkpoint retention by replacing InMemorySaver pinch SqliteSaver, which saves speech history and paused support requests to a database record alternatively of keeping them only successful memory.
In your VPS terminal, unfastened main.py:
nano main.pyAt the apical of the file, add:
import sqlite3Then switch the existing InMemorySaver import:
from langgraph.checkpoint.memory import InMemorySaverwith:
from langgraph.checkpoint.sqlite import SqliteSaverFind the existent checkpointer setup:
memory = InMemorySaver() graph = builder.compile(checkpointer=memory)Replace it with:
connection = sqlite3.connect( "/opt/langgraph-agent/checkpoints.sqlite", check_same_thread=False, ) checkpointer = SqliteSaver(connection) graph = builder.compile(checkpointer=checkpointer)SqliteSaver now saves the agent’s checkpoints successful /opt/langgraph-agent/checkpoints.sqlite, truthful they stay disposable aft the Python process restarts.
Keep check_same_thread=False truthful the FastAPI work you’ll adhd adjacent tin grip requests successful different threads.
Save main.py, past tally the agent:
python main.pyYou should spot the pending shipping request, followed by The instrumentality telephone was rejected, truthful nary action was taken.
Confirm that the checkpoint database was created:
ls -lh /opt/langgraph-agent/checkpoints.sqliteThe output should database checkpoints.sqlite.

4. Serve and trial the supplier pinch FastAPI
Serve and trial your LangGraph supplier pinch FastAPI by adding API endpoints, starting the app pinch Uvicorn, and sending trial requests from the VPS.
Open main.py successful your VPS terminal, past adhd these imports pinch the existing imports:
from fastapi import FastAPI from pydantic import BaseModelRemove the rejection trial astatine the extremity of main.py, starting pinch reject_config = { and ending pinch the last print(rejected_result…) line.
Below graph = builder.compile(checkpointer=checkpointer), adhd the FastAPI app and the petition models for chat and approval:
app = FastAPI() class ChatRequest(BaseModel): message: str thread_id: str class ApprovalRequest(BaseModel): thread_id: str approved: boolThen adhd the /chat endpoint to expose your app done an API:
@app.post("/chat") def chat(request: ChatRequest): config = { "configurable": { "thread_id": request.thread_id } } consequence = graph.invoke( { "messages": [ { "role": "user", "content": request.message, } ] }, config=config, ) if "__interrupt__" successful result: return { "status": "needs_approval", "request": result["__interrupt__"][0].value, } return { "status": "complete", "message": result["messages"][-1].content, }The /chat endpoint passes the connection to your graph. It returns DeepSeek’s reply for a nonstop consequence aliases needs_approval erstwhile a instrumentality telephone pauses for your decision.
Immediately beneath the /chat endpoint, adhd /approve:
@app.post("/approve") def approve(request: ApprovalRequest): config = { "configurable": { "thread_id": request.thread_id } } consequence = graph.invoke( Command(resume=request.approved), config=config, ) return { "status": "complete", "message": result["messages"][-1].content, }The /approve endpoint resumes the paused petition pinch the aforesaid thread_id. Set approved to true to tally the instrumentality aliases false to cull it.
After redeeming main.py, commencement the FastAPI app pinch Uvicorn:
cd /opt/langgraph-agent source .venv/bin/activate uvicorn main:app --host 127.0.0.1 --port 8000 --workers 1Using 127.0.0.1 keeps the API accessible only from the VPS. Keep this terminal unfastened while Uvicorn runs, past unfastened a caller VPS terminal window.
There, nonstop a petition that doesn’t request a tool:
curl -X POST http://127.0.0.1:8000/chat \ -H "Content-Type: application/json" \ -d '{"message":"What does LangGraph authorities do?","thread_id":"api-test-1"}'The consequence should incorporate “status”:”complete” and DeepSeek’s answer.
Next, nonstop a shipping petition to trial the support flow:
curl -X POST http://127.0.0.1:8000/chat \ -H "Content-Type: application/json" \ -d '{"message":"What is the shipping value for 3 kg to EU?","thread_id":"api-test-2"}'The consequence should incorporate “status”:”needs_approval” and the pending calculate_shipping instrumentality call.
Approve the petition done /approve:
curl -X POST http://127.0.0.1:8000/approve \ -H "Content-Type: application/json" \ -d '{"thread_id":"api-test-2","approved":true}'The consequence should incorporate “status”:”complete”, and DeepSeek’s reply should see the shipping value of $13.25.

5. Keep the supplier moving pinch systemd
Keep your LangGraph supplier moving pinch systemd truthful the API starts automatically aft a server reboot and restarts if Uvicorn fails.
Return to the VPS terminal wherever Uvicorn is moving and property Ctrl + C to extremity it.
Create a abstracted Linux personification for the work and springiness it ownership of the app directory:
useradd --system --home /opt/langgraph-agent --shell /usr/sbin/nologin langgraph chown -R langgraph:langgraph /opt/langgraph-agentNext, create the systemd work file:
nano /etc/systemd/system/langgraph-agent.serviceAdd:
[Unit] Description=LangGraph supplier API After=network-online.target Wants=network-online.target [Service] Type=simple User=langgraph Group=langgraph WorkingDirectory=/opt/langgraph-agent EnvironmentFile=/opt/langgraph-agent/.env Environment=LANGGRAPH_STRICT_MSGPACK=true ExecStart=/opt/langgraph-agent/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000 --workers 1 Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.targetThe work sounds your DeepSeek API cardinal from .env. LANGGRAPH_STRICT_MSGPACK=true limits what LangGraph is allowed to load from saved checkpoints.
Save the file. Then, reload systemd, commencement the service, and alteration it astatine boot:
systemctl daemon-reload systemctl alteration --now langgraph-agent systemctl position langgraph-agentThe position should show active (running).

Check the work logs pinch journalctl if systemctl status displays a failure:
journalctl -u langgraph-agent -n 50 --no-pagerIn the different VPS terminal, verify that the API still responds:
curl -X POST http://127.0.0.1:8000/chat \ -H "Content-Type: application/json" \ -d '{"message":"What does LangGraph authorities do?","thread_id":"service-test"}'The consequence should incorporate “status”:”complete” and DeepSeek’s answer.
How to amended your LangGraph supplier aft deployment
Improve your LangGraph supplier aft deployment by retesting each chart way aft changes, keeping conversations connected abstracted thread IDs, utilizing support only for delicate actions, backing up checkpoints, and updating pinned packages carefully.
- Retest each chart way aft changes. Check the direct-response, tool, approval, and rejection paths aft you update the agent. This helps you drawback surgery routes aliases instrumentality calls earlier deployment. Reuse the aforesaid trial prompts and curl requests you already used, past adhd caller tests arsenic you present much routes aliases grow into workflows pinch aggregate AI agents.
- Keep conversations connected abstracted thread IDs. Use a different thread_id for each caller speech truthful unrelated messages don’t extremity up successful the aforesaid saved history. Create a caller thread_id astatine the commencement of a conversation, past reuse it only for later messages successful that aforesaid conversation.
- Use support for delicate actions. Add an support interrupt earlier devices that execute actions you want to reappraisal earlier they run, specified arsenic sending messages, creating orders, deleting data, aliases changing records. Skip support for read-only actions that don’t modify information aliases trigger an outer action, specified arsenic retrieving information
- Back up the checkpoint database. Keep a transcript of checkpoints.sqlite earlier changing thing that affects saved state, specified arsenic the checkpoint setup aliases the deployed app. Losing this record removes the speech history your supplier relies on, truthful shop the backup extracurricular the VPS, for illustration connected your section machine aliases different backup server.
- Update pinned packages carefully. Change the versions successful requirements.txt 1 astatine a clip alternatively of upgrading each package astatine once, because updates tin impact really LangGraph, LangChain, FastAPI, aliases Uvicorn works. Test the updated supplier locally earlier redeploying it to your VPS.
All of the tutorial contented connected this website is taxable to Hostinger's rigorous editorial standards and values.
English (US) ·
Indonesian (ID) ·