LangGraph-Tutorial für den Einstieg
LangGraph ist einLow-Level-Agent-Orchestrierungsframework, das vom LangChain-Team entwickelt wurdeund speziell für den Aufbau zustandsbehafteter, lang laufender KI-Workflows konzipiert wurde.
Im Gegensatz zu traditionellen linearen LLM-Aufrufketten modelliert LangGraph Workflows alsgerichteten Graphen:
- Knoten: Funktionen, die bestimmte Aktionen ausführen (z. B. Aufrufen von LLMs, Ausführen von Tools, Verarbeiten von Daten)
- Kante: Definiert Flusspfade zwischen Knoten und unterstützt bedingte Verzweigungen
- Zustand: Daten, die im gesamten Workflow gemeinsam genutzt und weitergegeben werden
Open-Source-Repository:https://github.com/langchain-ai/langgraph。

Stellen Sie sich vor, Sie dirigieren eine Symphonie: Eine traditionelle LLM-Kette ist wie das Abspielen eines Stücks von Anfang bis Ende, nur sequenziell; LangGraph hingegen ist wie ein Dirigent, der die Aufführungsreihenfolge jederzeit anhand der Reaktionen des Live-Publikums anpassen, einen bestimmten Satz wiederholen oder zu einer bestimmten Passage springen kann. Es verleiht KI-Workflows die Weisheit eines „Dirigenten“ – fähig zu Schleifen, Verzweigungen und Rückverfolgung, und ermöglicht so wirklich komplexe autonome Entscheidungsfindung.
Warum LangGraph wählen?
Die folgende Tabelle vergleicht die wichtigsten Unterschiede zwischen traditionellen LLM-Ketten und LangGraph:
| Merkmal | Traditionelle LLM-Kette | LangGraph |
|---|---|---|
| Workflow-Struktur | Lineare, unidirektionale Ausführung | Graphstruktur, unterstützt Schleifen |
| Zustandsverwaltung | Erfordert manuelle Verwaltung | Integrierte Zustandspersistenz |
| Bedingtes Routing | Komplex zu implementieren | Native Unterstützung |
| Human-in-the-loop | Erfordert zusätzliche Entwicklung | Integrierte Unterstützung für Unterbrechungen |
| Multi-Agent-Koordination | Schwierig zu implementieren | Erstklassige Unterstützung |
| Debugging-Tools | Eingeschränkt | LangGraph Studio |
Geeignete Szenarien
- Chatbots: Müssen sich den Kontext mehrrundiger Dialoge merken
- Autonomer Agent: Kann planen, Tools verwenden und Denkprozesse iterieren
- Multi-Agent-System: Mehrere KIs arbeiten zusammen, um komplexe Aufgaben zu erledigen
- Genehmigungsworkflow: Ein automatisierter Prozess, der eine menschliche Überprüfung erfordert
- Recherche-Assistent: Erfordert mehrstufiges Schlussfolgern und Informationsabruf
Kernkonzepte
Bevor Sie mit dem Schreiben von Code beginnen, sollten Sie zunächst die drei Kernkonzepte von LangGraph verstehen.
Graph
Der Graph ist der Bauplan des gesamten Workflows und definiert die vollständige logische Struktur des Agenten. Er besteht aus Knoten und Kanten:
StateGraph
|-- Nodes(节点)
| |-- node_a
| |-- node_b
| +-- node_c
+-- Edges(边)
|-- START -> node_a
|-- node_a -> node_b(条件边)
|-- node_a -> node_c(条件边)
+-- node_b -> END
State
Der Zustand ist diegemeinsam genutzte Datenstruktur, die durch den gesamten Graphen läuft. Jeder Knoten kann den Zustand lesen und aktualisieren, und der aktualisierte Zustand wird an den nächsten Knoten weitergegeben.
from typing import TypedDict, Annotated
from langgraph.graph import add_messages
class MyState(TypedDict):
messages: Annotated[list, add_messages] # 消息列表(自动追加)
user_name: str # 用户名称
step_count: int # 步骤计数
Annotated[list, add_messages]Gibt an, dass dieses Feld verwendetadd_messagesals Reducer – neue Nachrichten werden an die Liste angehängt statt überschrieben. Dies ist der Kernmechanismus der Zustandsverwaltung von LangGraph.Knoten
Knoten sind gewöhnliche Python-Funktionen, die den aktuellen Zustand empfangen und den aktualisierten Zustand (Teilfelder) zurückgeben.
def my_node(state: MyState) -> dict:
# 读取状态
messages = state["messages"]
# 执行操作...
result = "处理结果"
# 返回更新的字段(不需要返回所有字段)
return {"messages": [{"role": "ai", "content": result}]}
Kanten
Kanten definieren den Fluss zwischen Knoten:
- Normale Kante: fester Pfad,
node_a -> node_b - Bedingte Kante: leitet dynamisch basierend auf dem Zustand weiter,
node_a -> node_b oder node_c - Startkante:
START -> 第一个节点 - Endkante:
某节点 -> END
Umgebungs-Setup
Abhängigkeiten installieren
LangGraph und zugehörige Abhängigkeiten mit einem inländischen Mirror installieren:
# 创建虚拟环境(推荐) python -m venv venv source venv/bin/activate # macOS/Linux # venv\Scripts\activate # Windows # 安装 LangGraph 和 LangChain pip install langgraph langchain langchain-openai python-dotenv -i https://mirrors.aliyun.com/pypi/simple/ # 可选:安装开发工具 pip install langgraph-cli jupyter -i https://mirrors.aliyun.com/pypi/simple/
API-Schlüssel konfigurieren
Im Projektstammverzeichnis, erstellen Sie.enveine Datei, und konfigurieren Sie OpenAI und DeepSeek:
# .env 文件内容 # OpenAI 配置(国外用户) OPENAI_API_KEY=sk-xxx OPENAI_BASE_URL=https://api.openai.com/v1 # DeepSeek 配置(国内用户推荐) DEEPSEEK_API_KEY=sk-xxx DEEPSEEK_BASE_URL=https://api.deepseek.com DEEPSEEK_MODEL=deepseek-v4-pro
https://api.deepseek.com(ohne /v1); LangChain fügt den Pfad automatisch an. Der DeepSeek API-Schlüssel kann unterhttps://platform.deepseek.com/api_keyserstellt werden.Installation überprüfen
import langgraph
print(f"LangGraph 版本: {langgraph.__version__}")
Ihr erstes LangGraph-Programm
Beginnen wir mit dem einfachsten Beispiel – einem linearen Workflow mit nur zwei Knoten.
Beispiel
from typing import TypedDict
# Schritt 1: Zustand definieren
class SimpleState(TypedDict):
message: str
processed: bool
# Schritt 2: Knotenfunktionen definieren
def greet_node(state: SimpleState) -> dict:
"""Begrüßungs-Knoten: erzeugt eine Begrüßung"""
print(f"[greet_node] Empfangene Nachricht: {state['message']}")
return {"message": f"Hallo! {state['message']}"}
def process_node(state: SimpleState) -> dict:
"""Verarbeitungs-Knoten: als verarbeitet markieren"""
print(f"[process_node] Verarbeite Nachricht: {state['message']}")
return {"processed": True}
# Schritt 3: Graph erstellen
builder = StateGraph(SimpleState)
# Knoten hinzufügen
builder.add_node("greet", greet_node)
builder.add_node("process", process_node)
# Kanten hinzufügen
builder.add_edge(START, "greet")
builder.add_edge("greet", "process")
builder.add_edge("process", END)
# Schritt 4: Graph kompilieren
graph = builder.compile()
# Schritt 5: Ausführen
result = graph.invoke({
"message": "Welt",
"processed": False
})
print(f"\nEndergebnis: {result}")
Ausführungsergebnis:
[greet_node] 收到消息: 世界
[process_node] 处理消息: 你好!世界
最终结果: {'message': '你好!世界', 'processed': True}
Graphstruktur visualisieren
In Jupyter Notebook können Sie die Graphstruktur direkt visualisieren:
# 在 Jupyter Notebook 中可视化 from IPython.display import Image Image(graph.get_graph().draw_mermaid_png()) # 或者打印 Mermaid 格式 print(graph.get_graph().draw_mermaid())
Zustandsverwaltung
Zustand mit TypedDict definieren
from typing import TypedDict, Annotated, Optional
from langgraph.graph.message import add_messages
class AgentState(TypedDict):
# 消息历史(add_messages reducer 自动追加而非覆盖)
messages: Annotated[list, add_messages]
# 普通字段(直接覆盖)
user_id: str
session_id: str
# 可选字段
error: Optional[str]
# 计数器(使用 operator.add 作为 reducer)
retry_count: Annotated[int, lambda x, y: x + y]
Zustand mit Pydantic definieren (für Produktion empfohlen)
from pydantic import BaseModel, Field
from typing import Annotated
from langgraph.graph.message import add_messages
class ProductionState(BaseModel):
messages: Annotated[list, add_messages] = Field(default_factory=list)
user_id: str = ""
confidence_score: float = 0.0
class Config:
arbitrary_types_allowed = True
MessagesState (integrierter Komfort-Zustand)
LangGraph bietet integriertenMessagesState, speziell für Konversationsszenarien entwickelt:
from langgraph.graph import MessagesState # MessagesState 等价于: # class MessagesState(TypedDict): # messages: Annotated[list[AnyMessage], add_messages] # 直接使用,无需自定义 builder = StateGraph(MessagesState)
Knoten
Reguläre Funktionsknoten
def simple_node(state: AgentState) -> dict:
# 读取状态
last_message = state["messages"][-1]
# 执行操作
response = f"收到: {last_message.content}"
# 返回部分状态更新
return {
"messages": [{"role": "assistant", "content": response}]
}
LLM-Aufrufknoten
Beispiel
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage
load_dotenv()
# DeepSeek-Modell verwenden
llm = ChatOpenAI(
model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
temperature=0
)
def llm_node(state: dict) -> dict:
"""Knoten, der das LLM aufruft"""
system_prompt = SystemMessage(content="Sie sind ein hilfreicher Assistent.")
# System-Prompt mit Konversationsverlauf zusammenführen
messages = [system_prompt] + state["messages"]
# LLM aufrufen
response = llm.invoke(messages)
return {"messages": [response]}
Asynchrone Knoten
import asyncio
async def async_node(state: AgentState) -> dict:
"""异步节点,适合 I/O 密集型操作"""
# 模拟异步操作(如 API 调用、数据库查询)
await asyncio.sleep(0.1)
result = await some_async_api_call(state["messages"][-1].content)
return {"messages": [{"role": "assistant", "content": result}]}
# 使用异步图
result = await graph.ainvoke({"messages": [...]})
Klassen als Knoten verwenden
class RouterNode:
def __init__(self, llm, system_prompt: str):
self.llm = llm
self.system_prompt = system_prompt
def __call__(self, state: AgentState) -> dict:
"""类实例可以作为节点使用"""
messages = [
SystemMessage(content=self.system_prompt),
*state["messages"]
]
response = self.llm.invoke(messages)
return {"messages": [response]}
# 添加类节点
router = RouterNode(llm, "你是一个专业的路由助手。")
builder.add_node("router", router)
Kanten und bedingtes Routing
Reguläre Kanten
# 固定路径:node_a 完成后始终执行 node_b
builder.add_edge("node_a", "node_b")
# 结束:node_a 完成后图结束
builder.add_edge("node_a", END)
Bedingte Kanten
Bedingte Kanten sind ein Kernmerkmal von LangGraph und entscheiden dynamisch über den nächsten Schritt basierend auf dem aktuellen State.
def route_after_llm(state: AgentState) -> str:
"""
路由函数:根据 LLM 的最新输出决定走哪条路径
返回值必须是已注册节点名称或 END
"""
last_message = state["messages"][-1]
# 如果 LLM 请求使用工具
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
return "tools"
# 否则结束
return END
# 添加条件边
builder.add_conditional_edges(
"llm", # 源节点
route_after_llm, # 路由函数
{
"tools": "tool_executor", # 返回 "tools" 时 -> tool_executor 节点
END: END # 返回 END 时 -> 结束
}
)
Parallele Ausführung (Fan-out)
# 从一个节点并行分叉到多个节点
builder.add_edge("start_node", "branch_a")
builder.add_edge("start_node", "branch_b")
builder.add_edge("start_node", "branch_c")
# 多个节点汇聚到一个节点(Fan-in)
builder.add_edge("branch_a", "merge_node")
builder.add_edge("branch_b", "merge_node")
builder.add_edge("branch_c", "merge_node")
Vollständiges Beispiel für bedingtes Routing
Beispiel
from dotenv import load_dotenv
from langgraph.graph import StateGraph, START, END, MessagesState
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
load_dotenv()
# LLM initialisieren
llm = ChatOpenAI(
model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
temperature=0.7
)
# Routing-Funktion
def classify_intent(state: MessagesState) -> str:
"""Weiterleitung an verschiedene Agenten basierend auf Benutzerabsicht"""
last_message = state["messages"][-1]
content = last_message.content.lower()
if "Weather" in content or "Temperature" in content:
return "weather_agent"
elif "Code" in content or "Programming" in content:
return "code_agent"
elif "Goodbye" in content or "Exit" in content:
return "farewell"
else:
return "general_agent"
# Verschiedene Agent-Knoten definieren
def router_node(state: MessagesState) -> dict:
"""Router-Knoten: führt keine Verarbeitung durch, wird nur verwendet, um Routing-Entscheidungen auszulösen"""
return {}
def weather_node(state: MessagesState) -> dict:
"""Wetter-Agent"""
response = llm.invoke([
SystemMessage(content="Du bist ein Wetterassistent und antwortest freundlich auf wetterbezogene Fragen. Wenn keine Echtzeitdaten vorliegen, kannst du allgemeine Ratschläge geben."),
*state["messages"]
])
return {"messages": [response]}
def code_node(state: MessagesState) -> dict:
"""Code-Agent"""
response = llm.invoke([
SystemMessage(content="Du bist ein Programmierassistent, geschickt darin, Codefragen zu beantworten und klare Codebeispiele zu liefern."),
*state["messages"]
])
return {"messages": [response]}
def general_node(state: MessagesState) -> dict:
"""Allgemeiner Agent"""
response = llm.invoke([
SystemMessage(content="Du bist ein freundlicher KI-Assistent, der verschiedene Fragen beantworten kann."),
*state["messages"]
])
return {"messages": [response]}
def farewell_node(state: MessagesState) -> dict:
"""Abschiedsknoten"""
return {"messages": [{"role": "assistant", "content": "Auf Wiedersehen! Ich freue mich auf unser nächstes Gespräch."}]}
# Graph erstellen
builder = StateGraph(MessagesState)
# Knoten hinzufügen
builder.add_node("router", router_node)
builder.add_node("weather_agent", weather_node)
builder.add_node("code_agent", code_node)
builder.add_node("general_agent", general_node)
builder.add_node("farewell", farewell_node)
# Kanten hinzufügen
builder.add_edge(START, "router")
builder.add_conditional_edges(
"router",
classify_intent,
{
"weather_agent": "weather_agent",
"code_agent": "code_agent",
"general_agent": "general_agent",
"farewell": "farewell",
}
)
# Nach Verarbeitung aller Agent-Knoten beenden
for node in ["weather_agent", "code_agent", "general_agent", "farewell"]:
builder.add_edge(node, END)
# Graph kompilieren
graph = builder.compile()
# Verschiedene Absichten testen
test_inputs = [
"Wie ist das Wetter heute in Peking?",
"Hilf mir, einen Python-Quicksort zu schreiben",
"Hallo, stell dich vor",
"Tschüss!"
]
for user_input in test_inputs:
print(f"\nBenutzer: {user_input}")
result = graph.invoke({"messages": [HumanMessage(content=user_input)]})
print(f"Assistent: {result['messages'][-1].content[:100]}...")
print("-" * 50)
Beispiel für ein Ausführungsergebnis:
User: 北京今天天气怎么样?
Assistant: 很抱歉,我没有实时天气数据。不过北京现在是春季,建议您出门时关注天气预报...
--------------------------------------------------
User: 帮我写一个 Python 快速排序
Assistant: 好的,下面是一个 Python 实现的快速排序算法:
```python
def quicksort(arr):
if len(arr) <= 1:
return arr
...
--------------------------------------------------
User: 你好,介绍一下你自己
Assistant: 你好!我是一个 AI 助手,很高兴为你服务。我可以帮你回答各种问题...
--------------------------------------------------
User: 再见啦!
Assistant: 再见!期待下次与你交流。
--------------------------------------------------
END. Wenn ein nicht registrierter Name zurückgegeben wird, wirft der Graph bei der Ausführung einen Fehler.Einen Chatbot erstellen
Nutze nun das Gelernte, um einen Roboter zu bauen, der Multi-Turn-Konversationen unterstützt. Dieser Roboter kann den Gesprächskontext behalten und ein kontinuierliches interaktives Erlebnis bieten.
Beispiel
from dotenv import load_dotenv
from langgraph.graph import StateGraph, MessagesState, START, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
load_dotenv()
# LLM initialisieren (mit DeepSeek)
llm = ChatOpenAI(
model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
temperature=0.7
)
SYSTEM_PROMPT = """Du bist ein freundlicher und professioneller KI-Assistent.
Deine Antworten sollten:
- Knapp und klar sein
- Auf Chinesisch antworten
- Bei Unsicherheit aktiv den Benutzer fragen
"""
def chatbot_node(state: MessagesState) -> dict:
"""Zentraler Gesprächsknoten"""
system = SystemMessage(content=SYSTEM_PROMPT)
messages = [system] + state["messages"]
response = llm.invoke(messages)
return {"messages": [response]}
# Graph erstellen
builder = StateGraph(MessagesState)
builder.add_node("chatbot", chatbot_node)
builder.add_edge(START, "chatbot")
builder.add_edge("chatbot", END)
graph = builder.compile()
# Funktion für Multi-Turn-Konversation
def chat(conversation_history: list, user_input: str) -> tuple[str, list]:
"""Eine einzelne Gesprächsrunde verarbeiten, die KI-Antwort und den aktualisierten Verlauf zurückgeben"""
conversation_history.append(HumanMessage(content=user_input))
result = graph.invoke({"messages": conversation_history})
conversation_history = result["messages"]
ai_response = conversation_history[-1].content
return ai_response, conversation_history
# Beispiel für Multi-Turn-Konversation
history = []
while True:
user_input = input("Du: ")
if user_input.lower() in ["Exit", "exit", "quit"]:
print("Auf Wiedersehen!")
break
response, history = chat(history, user_input)
print(f"Assistent: {response}\n")
Beispiel für ein Ausführungsergebnis:
You: 你好,我叫小明 Assistant: 你好,小明!很高兴认识你。有什么我可以帮助你的吗? You: 我想学习 Python 编程 Assistant: 太好了!Python 是一门很适合初学者的编程语言。我可以帮你从基础开始: 1. 首先了解变量和数据类型 2. 学习条件语句和循环 3. 掌握函数的定义和使用 你想从哪个部分开始呢? You: 你还记得我叫什么吗? Assistant: 当然记得,你叫小明!你刚才说想学习 Python 编程,我们可以继续这个话题。 You: 退出 再见!
Tool-Aufruf - ReAct-Agent
Dem Agenten zu ermöglichen, externe Tools zu verwenden, ist eine der leistungsstärksten Funktionen von LangGraph.ReAct(Reason + Act) ist das gebräuchlichste Agentenmuster: LLM denkt -> wählt ein Tool aus -> führt das Tool aus -> beobachtet das Ergebnis -> denkt weiter.
Tools definieren
Definiere zunächst die Tools, die der Agent verwenden kann:
from langchain_core.tools import tool
@tool
def search_web(query: str) -> str:
"""搜索网络获取最新信息。
Args:
query: 搜索关键词
Returns:
搜索结果摘要
"""
# 实际项目中替换为真实搜索 API
return f"关于 '{query}' 的搜索结果:这是模拟的搜索结果..."
@tool
def calculate(expression: str) -> str:
"""计算数学表达式。
Args:
expression: 数学表达式,如 '2 + 2' oder '100 * 0.8'
Returns:
计算结果
"""
import ast
import operator
# 安全的运算符映射
ops = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Pow: operator.pow,
ast.USub: operator.neg,
}
def safe_eval(node):
if isinstance(node, ast.Expression):
return safe_eval(node.body)
elif isinstance(node, ast.Constant):
return node.value
elif isinstance(node, ast.BinOp):
left = safe_eval(node.left)
right = safe_eval(node.right)
return ops[type(node.op)](left, right)
elif isinstance(node, ast.UnaryOp):
operand = safe_eval(node.operand)
return ops[type(node.op)](operand)
else:
raise ValueError(f"不支持的表达式类型: {type(node)}")
try:
tree = ast.parse(expression, mode='eval')
result = safe_eval(tree)
return f"计算结果: {expression} = {result}"
except Exception as e:
return f"计算错误: {str(e)}"
@tool
def get_weather(city: str) -> str:
"""获取指定城市的天气信息。
Args:
city: 城市名称
Returns:
天气信息
"""
# 实际项目中替换为真实天气 API
return f"{city} 今日天气:晴,温度 22C,湿度 60%"
tools = [search_web, calculate, get_weather]
eval(). Die direkte Verwendung voneval()birgt ernste Sicherheitsrisiken, da beliebiger Python-Code ausgeführt werden kann. In Produktionsumgebungen sollte unbedingt eine sichere Methode zum Parsen von Ausdrücken verwendet werden.Einen ReAct-Agenten erstellen
Beispiel
from dotenv import load_dotenv
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage
import ast
import operator
load_dotenv()
# Werkzeuge definieren
@tool
def search_web(query: str) -> str:
"""Durchsuche das Web, um die neuesten Informationen zu erhalten."""
return f"Suchergebnisse für '{query}': Dies ist ein simuliertes Suchergebnis..."
@tool
def calculate(expression: str) -> str:
"""Berechne einen mathematischen Ausdruck."""
ops = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Pow: operator.pow,
ast.USub: operator.neg,
}
def safe_eval(node):
if isinstance(node, ast.Expression):
return safe_eval(node.body)
elif isinstance(node, ast.Constant):
return node.value
elif isinstance(node, ast.BinOp):
left = safe_eval(node.left)
right = safe_eval(node.right)
return ops[type(node.op)](left, right)
elif isinstance(node, ast.UnaryOp):
operand = safe_eval(node.operand)
return ops[type(node.op)](operand)
else:
raise ValueError(f"Nicht unterstützter Ausdruckstyp: {type(node)}")
try:
tree = ast.parse(expression, mode='eval')
result = safe_eval(tree)
return f"Berechnungsergebnis: {expression} = {result}"
except Exception as e:
return f"Berechnungsfehler: {str(e)}"
@tool
def get_weather(city: str) -> str:
"""Rufe Wetterinformationen für eine angegebene Stadt ab."""
return f"{city} Heutiges Wetter: sonnig, 22C, Luftfeuchtigkeit 60%"
tools = [search_web, calculate, get_weather]
# LLM initialisieren und Tools binden
llm = ChatOpenAI(
model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
temperature=0
)
llm_with_tools = llm.bind_tools(tools)
def agent_node(state: MessagesState) -> dict:
"""Agent-Reasoning-Knoten: Rufe das LLM auf, um die nächste Aktion zu entscheiden"""
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
# ReAct-Graph aufbauen
builder = StateGraph(MessagesState)
# Knoten hinzufügen
builder.add_node("agent", agent_node)
builder.add_node("tools", ToolNode(tools)) # Der integrierte ToolNode verarbeitet Tool-Aufrufe automatisch
# Kanten hinzufügen
builder.add_edge(START, "agent")
# Bedingtes Routing: Wenn das LLM ein Tool anfordert, führe das Tool aus; andernfalls beenden
builder.add_conditional_edges(
"agent",
tools_condition, # Integrierte Routing-Funktion
{
"tools": "tools",
END: END
}
)
# Nachdem das Tool ausgeführt wurde, kehre zum Agenten zurück, um mit dem Reasoning fortzufahren
builder.add_edge("tools", "agent")
graph = builder.compile()
# Test
result = graph.invoke({
"messages": [HumanMessage(content="Wie ist das Wetter heute in Peking? Hilf mir außerdem, 1234 * 5678 zu berechnen")]
})
for message in result["messages"]:
print(f"[{message.type}]: {message.content[:200] if message.content else '(Tool-Aufruf)'}")
Streaming des Tool-Aufrufprozesses
# 流式观察 Agent 的每一步
for chunk in graph.stream(
{"messages": [HumanMessage(content="搜索 LangGraph 的最新特性")]},
stream_mode="updates"
):
for node_name, updates in chunk.items():
print(f"\n=== 节点: {node_name} ===")
for msg in updates.get("messages", []):
if hasattr(msg, "tool_calls") and msg.tool_calls:
for tc in msg.tool_calls:
print(f" -> 调用工具: {tc['name']}({tc['args']})")
else:
print(f" -> 输出: {msg.content[:200] if msg.content else '(无内容)'}")
ToolNodeundtools_conditionist eine integrierte Komponente von LangGraph.ToolNodeTool-Aufrufe, die vom LLM zurückgegeben werden, automatisch analysieren und die entsprechenden Tool-Funktionen ausführen.tools_conditionEs ist eine Routing-Funktion, die prüft, ob die LLM-Ausgabe eine Tool-Aufrufanforderung enthält. Wenn ja, gibt sie "tools" zurück; andernfalls gibt sie END zurück.Human-in-the-Loop-Zusammenarbeit
LangGraph unterstützt nativ das Pausieren während der Workflow-Ausführung, um auf menschliche Überprüfung oder Eingabe zu warten. Dies ist sehr nützlich für sensible Vorgänge, die eine menschliche Bestätigung erfordern.
interrupt verwenden, um die Ausführung zu pausieren
from langgraph.types import interrupt
from langgraph.checkpoint.memory import MemorySaver
def sensitive_action_node(state: MessagesState) -> dict:
"""执行敏感操作前请求人工审批"""
last_msg = state["messages"][-1].content
# 暂停图的执行,等待人工决策
human_decision = interrupt({
"question": "是否批准执行以下操作?",
"action": last_msg,
"risk_level": "中等"
})
if human_decision == "approve":
return {"messages": [{"role": "assistant", "content": "操作已批准并执行完毕。"}]}
else:
return {"messages": [{"role": "assistant", "content": "操作已取消。"}]}
# 必须使用 checkpointer 才能支持 interrupt
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
Vollständiger Genehmigungs-Workflow
Beispiel
from dotenv import load_dotenv
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.types import Command, interrupt
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.messages import HumanMessage
load_dotenv()
def request_node(state: MessagesState) -> dict:
"""Benutzeranfrage empfangen"""
return {"messages": state["messages"]}
def approval_node(state: MessagesState) -> dict:
"""Genehmigungsknoten: pausieren und auf menschliche Genehmigung warten"""
last_msg = state["messages"][-1].content
# Ausführung pausieren und auf menschliche Entscheidung warten
human_decision = interrupt({
"question": "Folgenden Vorgang genehmigen?",
"action": last_msg
})
if human_decision == "approve":
return {"messages": [{"role": "assistant", "content": f"Vorgang genehmigt: {last_msg}"}]}
else:
return {"messages": [{"role": "assistant", "content": "Vorgang abgelehnt."}]}
def execute_node(state: MessagesState) -> dict:
"""Ausführungsknoten"""
return {"messages": [{"role": "assistant", "content": "Aufgabenausführung abgeschlossen!"}]}
# Graph aufbauen
builder = StateGraph(MessagesState)
builder.add_node("request", request_node)
builder.add_node("approval", approval_node)
builder.add_node("execute", execute_node)
builder.add_edge(START, "request")
builder.add_edge("request", "approval")
builder.add_edge("approval", "execute")
builder.add_edge("execute", END)
# Den Graphen mit einem Checkpointer kompilieren
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
# Verwende für jede Konversation eine eindeutige thread_id
config = {"configurable": {"thread_id": "approval-session-001"}}
# Schritt 1: Starte den Graphen; er wird am Interrupt pausieren
print("=== Schritt 1: Anfrage senden ===")
result = graph.invoke(
{"messages": [HumanMessage(content="Bitte lösche alle Testdaten in der Datenbank")]},
config=config
)
print("Graph pausiert, wartet auf Genehmigung...")
# Schritt 2: Nach menschlicher Genehmigung die Ausführung mit Command fortsetzen
print("\n"=== Schritt 2: Menschliche Genehmigung ===")
# Den Vorgang genehmigen
result = graph.invoke(
Command(resume="approve"),
config=config
)
print(f"Endergebnis: {result['messages'][-1].content}")
# Wenn du ablehnen möchtest, verwende:
# graph.invoke(Command(resume="reject"), config=config)
Breakpoints auf Kanten setzen
# 另一种方式:在编译时指定断点
graph = builder.compile(
checkpointer=checkpointer,
interrupt_before=["sensitive_node"], # 执行该节点前暂停
# interrupt_after=["review_node"], # 执行该节点后暂停
)
checkpointerist zu implementiereninterruptDie Voraussetzung für die Funktionalität. Ohne einen Checkpointer kann der Graph seinen pausierten Zustand nicht speichern und daher die Ausführung nicht fortsetzen. MemorySaver eignet sich für Entwicklung und Tests; für Produktionsumgebungen wird empfohlen, SqliteSaver oder anderen persistenten Speicher zu verwenden.Persistenter Speicher
LangGraph bietet einen integrierten Mechanismus zur Zustandspersistenz, der es Agenten ermöglicht, den Gesprächsverlauf über Sitzungen hinweg zu behalten.
In-Memory-Speicherung (geeignet für Entwicklung und Tests)
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
# 使用 thread_id 区分不同会话
config_user_a = {"configurable": {"thread_id": "user-alice"}}
config_user_b = {"configurable": {"thread_id": "user-bob"}}
# Alice 的对话
graph.invoke({"messages": [HumanMessage(content="我叫 Alice")]}, config=config_user_a)
graph.invoke({"messages": [HumanMessage(content="我叫什么名字?")]}, config=config_user_a)
# Agent 能记住:你叫 Alice
# Bob 的对话完全独立
graph.invoke({"messages": [HumanMessage(content="我叫什么名字?")]}, config=config_user_b)
# Agent 不知道 Bob 的名字(不同 thread_id)
Persistente SQLite-Speicherung (geeignet für lokale Projekte)
# 安装依赖
# pip install langgraph-checkpoint-sqlite
from langgraph.checkpoint.sqlite import SqliteSaver
# 数据持久化到文件,程序重启后对话历史仍存在
with SqliteSaver.from_conn_string("./chat_memory.db") as checkpointer:
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "persistent-chat"}}
# 第一次运行
graph.invoke({"messages": [HumanMessage(content="我叫张三")]}, config=config)
# 程序重启后再次运行,记忆仍然存在
result = graph.invoke(
{"messages": [HumanMessage(content="你还记得我叫什么吗?")]},
config=config
)
Gesprächsverlauf anzeigen
# 获取某个 thread 的完整状态历史
history = list(graph.get_state_history(config))
for snapshot in history:
print(f"时间: {snapshot.created_at}")
print(f"消息数: {len(snapshot.values['messages'])}")
print("---")
# 获取当前状态
current_state = graph.get_state(config)
print(f"当前消息数: {len(current_state.values['messages'])}")
thread_idEs ist die eindeutige Kennung der Sitzung, die verwendet wird, um den Zustand verschiedener Benutzer oder verschiedener Gespräche zu isolieren. Alle Aufrufe mit derselben thread_id teilen den Gesprächsverlauf, und verschiedene thread_ids sind völlig unabhängig. In Mehrbenutzerszenarien wird normalerweise eine Benutzer-ID oder Sitzungs-ID als thread_id verwendet.Multi-Agent-Systeme
LangGraph zeichnet sich durch die Koordination mehrerer spezialisierter Agenten aus, die zusammenarbeiten. Durch die Zerlegung komplexer Aufgaben und deren Zuweisung an verschiedene Experten-Agenten können leistungsfähigere Problemlösungsfähigkeiten erreicht werden.
Supervisor-Muster
Beispiel
from dotenv import load_dotenv
from langgraph.graph import StateGraph, MessagesState, START, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
load_dotenv()
# LLM initialisieren
llm = ChatOpenAI(
model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
temperature=0
)
# Experten-Agenten definieren
def research_agent(state: MessagesState) -> dict:
"""Recherche-Agent: verantwortlich für die Informationssammlung"""
system = SystemMessage(content="Du bist ein professioneller Rechercheur und dafür verantwortlich, Informationen zu sammeln und zu organisieren. Bitte fasse die wichtigsten Informationen prägnant zusammen.")
response = llm.invoke([system] + state["messages"])
return {"messages": [response]}
def writing_agent(state: MessagesState) -> dict:
"""Schreib-Agent: verantwortlich für die Erstellung von Inhalten"""
system = SystemMessage(content="Du bist ein professioneller Autor und dafür verantwortlich, Inhalte auf Grundlage der vorhandenen Informationen zu schreiben. Bitte halte den Inhalt klar und flüssig.")
response = llm.invoke([system] + state["messages"])
return {"messages": [response]}
def review_agent(state: MessagesState) -> dict:
"""Review-Agent: verantwortlich für die Qualitätskontrolle"""
system = SystemMessage(content="Du bist ein professioneller Redakteur und dafür verantwortlich, die Inhaltsqualität zu prüfen und zu verbessern. Bitte weise auf Probleme hin und gib Verbesserungsvorschläge.")
response = llm.invoke([system] + state["messages"])
return {"messages": [response]}
# Supervisor-Agent entscheidet über den Workflow
def supervisor_node(state: MessagesState) -> dict:
"""Supervisor: koordiniert die Arbeit jedes Experten-Agenten"""
system = SystemMessage(content="""Du bist ein Workflow-Supervisor.
Bestimme anhand des Aufgabenfortschritts, welcher Agent den nächsten Schritt bearbeiten soll.
Analysiere den Gesprächsverlauf und gib nur eines der Folgenden zurück: RESEARCH, WRITING, REVIEW, FINISH
- RESEARCH: Es müssen mehr Informationen gesammelt werden
- WRITING: Informationen sind ausreichend, mit dem Schreiben kann begonnen werden
REVIEW: Schreiben abgeschlossen, muss überprüft werden
FINISH: Aufgabe abgeschlossen
""")
response = llm.invoke([system] + state["messages"])
return {"messages": [response]}
def route_by_supervisor(state: MessagesState) -> str:
"""Weiterleitung basierend auf Supervisor-Entscheidung"""
last_msg = state["messages"][-1].content.strip().upper()
if "RESEARCH" in last_msg:
return "research"
elif "WRITING" in last_msg:
return "writing"
elif "REVIEW" in last_msg:
return "review"
else:
return END
# Multi-Agenten-Graph erstellen
builder = StateGraph(MessagesState)
builder.add_node("supervisor", supervisor_node)
builder.add_node("research", research_agent)
builder.add_node("writing", writing_agent)
builder.add_node("review", review_agent)
builder.add_edge(START, "supervisor")
builder.add_conditional_edges("supervisor", route_by_supervisor)
# Nach Abschluss jedes Experten zum Supervisor zurückkehren
for agent in ["research", "writing", "review"]:
builder.add_edge(agent, "supervisor")
graph = builder.compile()
# Multi-Agenten-Zusammenarbeit testen
result = graph.invoke({
"messages": [HumanMessage(content="Bitte hilf mir, einen kurzen Einführungsartikel über Python-Dekoratoren zu schreiben")]
})
print("=== Multi-Agenten-Zusammenarbeit abgeschlossen ===")
for i, msg in enumerate(result["messages"]):
print(f"\n[{i+1}] {msg.type}: {msg.content[:150]}...")
Teilgraph
Komplexe Teilprozesse als Teilgraphen kapseln und im Hauptgraphen wiederverwenden:
# 将复杂子流程封装为子图,在主图中复用
sub_builder = StateGraph(MessagesState)
sub_builder.add_node("step1", step1_node)
sub_builder.add_node("step2", step2_node)
sub_builder.add_edge(START, "step1")
sub_builder.add_edge("step1", "step2")
sub_builder.add_edge("step2", END)
sub_graph = sub_builder.compile()
# 在主图中使用子图
main_builder = StateGraph(MessagesState)
main_builder.add_node("preprocessing", preprocess_node)
main_builder.add_node("sub_workflow", sub_graph) # 直接使用编译好的子图
main_builder.add_node("postprocessing", postprocess_node)
main_builder.add_edge(START, "preprocessing")
main_builder.add_edge("preprocessing", "sub_workflow")
main_builder.add_edge("sub_workflow", "postprocessing")
main_builder.add_edge("postprocessing", END)
main_graph = main_builder.compile()
Visuelles Debugging mit LangGraph Studio
LangGraph Studio ist die offizielle visuelle Entwicklungsumgebung, mit der Sie den Ausführungsprozess des Agenten in Echtzeit verfolgen können, was die Entwicklungs- und Debugging-Effizienz erheblich verbessert.
LangGraph CLI installieren
pip install langgraph-cli -i https://mirrors.aliyun.com/pypi/simple/
Projektkonfigurationsdatei erstellen
Im Projektstammverzeichnis erstellenlanggraph.jsonKonfigurationsdatei:
{
"dependencies": ["."],
"graphs": {
"my_agent": "./my_agent.py:graph"
},
"env": ".env"
}
Entwicklungsserver starten
langgraph dev
Nach dem Start besuchen Siehttp://localhost:8123Anschließend können Sie LangGraph Studio in Ihrem Browser verwenden.
Hauptfunktionen von Studio
- Echtzeit-Visualisierung: Zeigt den Ausführungsprozess der Knoten grafisch an, um den Workflow-Status intuitiv zu verstehen
- State-Inspektion: An jedem Knoten anhalten, um den aktuellen State anzuzeigen, was die Fehlersuche erleichtert
- Zeitreise: Historische Ausführungsschritte erneut abspielen, um Statusänderungen bei jedem Schritt zu verfolgen
- Hot Reload: Aktualisiert die Graphstruktur nach Codeänderungen automatisch, kein Neustart des Dienstes erforderlich
Best Practices und FAQs
Best Practices
Wichtige Punkte beim State-Design
- State knapp halten und nur notwendige Felder enthalten
- Explizite Reducer für komplexe Felder definieren (z. B.
add_messages) - Pydantic-Modelle verwenden, um State-Typen in Produktionsumgebungen zu validieren
- Vermeiden Sie das Speichern übergroßer Objekte im State; ziehen Sie externen Speicher in Betracht
Wichtige Punkte beim Knoten-Design
- Jeder Knoten hat eine einzige Verantwortung, was Tests und Wiederverwendung erleichtert
- Knotenfunktionen sollten idempotent sein (gleiche Eingabe erzeugt gleiche Ausgabe)
- Vermeiden Sie das direkte Ändern des übergebenen States in Knoten; geben Sie stattdessen neue Werte zurück
- Async-Knoten sinnvoll für I/O-intensive Operationen einsetzen
Fehlerbehandlung
def robust_node(state: AgentState) -> dict:
try:
result = risky_operation(state)
return {"messages": [result], "error": None}
except Exception as e:
return {
"error": str(e),
"messages": [{"role": "assistant", "content": f"操作失败: {e}"}]
}
Endlosschleifen vermeiden
def route_with_limit(state: AgentState) -> str:
# 设置最大重试次数,防止无限循环
if state.get("retry_count", 0) >= 3:
return END
if needs_retry(state):
return "retry_node"
return END
Häufig gestellte Fragen (FAQ)
F1: Was ist, wenn das Format des Rückgabewerts eines Knotens falsch ist?
# 错误:直接修改 state 对象
def bad_node(state):
state["messages"].append(...) # 不要直接修改
return state
# 正确:返回需要更新的字段
def good_node(state):
return {"messages": [new_message]} # 只返回变更字段
F2: Wie übergebe ich temporäre Daten zwischen Knoten?
Fügen Sie temporäre Daten zur State-Definition hinzu oder verwenden Sie eine Unterstrich-Präfix-Konvention für interne Felder:
from typing import TypedDict
class PublicState(TypedDict):
messages: list # 对外暴露
class PrivateState(TypedDict):
messages: list
_internal_cache: dict # 以下划线开头约定为内部使用
F3: Wie debuggt man den Knotenausführungsprozess?
# 使用 stream 模式观察每个节点的输出
for event in graph.stream(initial_state, stream_mode="updates"):
for node_name, state_update in event.items():
print(f"\n[节点: {node_name}]")
print(f"更新: {state_update}")
F4: Was ist der Unterschied zwischen StateGraph und MessageGraph?
MessageGraphEs ist eine frühere Version der API mit relativ begrenzten Funktionen. Jetzt wird empfohlen, einheitlich zu verwendenStateGraph, was flexibler und funktionsreicher ist. Für die Verarbeitung von Nachrichten verwenden SieStateGraph(MessagesState)oder passen Sie eine an, diemessagesFelder im State enthält.
MessageGraphwurde als veraltet markiert, bitte verwenden Sie einheitlichStateGraph. Wenn Ihr Code noch MessageGraph verwendet, wird empfohlen, so bald wie möglich zu StateGraph(MessagesState) zu migrieren, um bessere Unterstützung und mehr Funktionen zu erhalten.Zusammenfassung
Dieser Artikel führt systematisch in die Kernkonzepte und praktischen Anwendungen des LangGraph-Frameworks ein. Durch graphbasierte Workflow-Orchestrierung ermöglicht LangGraph Entwicklern, komplexe KI-Agenten mit Fähigkeiten wie Schleifen, Verzweigungen und Zustandspersistenz zu erstellen. Im Vergleich zu traditionellen linearen LLM Chains bietet LangGraph erhebliche Vorteile in Szenarien, die mehrstufiges Reasoning, Mensch-Maschine-Zusammenarbeit und Multi-Agenten-Koordination erfordern.
| Konzept | Beschreibung | Wichtiger Code |
|---|---|---|
| StateGraph | Workflow-Engine für gerichtete Graphen, der Kern von LangGraph | StateGraph(MyState) |
| State | State-Datenstruktur, die zwischen Knoten geteilt wird | TypedDict + Annotated |
| Knoten | Funktionsknoten, die bestimmte Operationen ausführen | builder.add_node() |
| Kanten | Flusspfade zwischen Knoten, die bedingte Verzweigungen unterstützen | add_edge() / add_conditional_edges() |
| ReAct Agent | Reasoning-+-Action-Schleifenmodus | ToolNode + tools_condition |
| Human-in-Loop | Mechanismus für menschliche Genehmigung und Intervention | interrupt() + Command(resume=) |
| Persistenz | Sitzungsspeicher und Zustandsspeicherung | MemorySaver / SqliteSaver |
| Multi-Agent | Multi-Experten-Kollaborationssystem | Supervisor-Muster |