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
Background Title LangGraph Core Architecture Outer big frame: StateGraph StateGraph (State Diagram) State layer - spanning the top State (shared state) - runs through all nodes Node area START Start node Node A Processing node Node B Decision node END End node Branch node Node C Alternative branch Edge: START -> Node A Edge: Node A -> Node B Edge: Node B -> END (conditional edge) Complete Edge: Node B -> Node C (conditional edge) Needs more processing Loop edge: Node C -> Node A Loop Arrow definitions

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                           # 步骤计数
Wichtig: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.
Background Title State data flow illustration Initial State State {msg: []} Arrow Node A Process input Arrow + Update State State update 1 State {msg: [A]} Arrow Node B Generate response Arrow State update 2 State {msg: [A,B]} Arrow END Bottom note Each node reads the State, performs operations, and returns the updated partial fields.Arrow definition

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
Hinweis:Die Beispiele in diesem Artikel unterstützen sowohl OpenAI als auch DeepSeek. Es wird empfohlen, dass inländische Benutzer DeepSeek verwenden und die API-Adresse wie folgt konfigurierenhttps://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 langgraph.graph import StateGraph, START, END
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

import os
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 时 -> 结束
    }
)
Background Title Conditional routing diagram START Arrow Router node Router Intent recognition Conditional branch Weather Agent weather_agent Weather query Code Agent code_agent Code assistance General Agent general_agent General conversation Farewell farewell Conditional edges "Weather" "Code" "Other" "Goodbye" END All branches go to END Arrow definitions

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

import os
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: 再见!期待下次与你交流。
--------------------------------------------------
Hinweis:Der Rückgabewert der bedingten Routing-Funktion muss ein bereits im Graphen registrierter Knotenname sein oderEND. 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

import os
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.

Background Title ReAct Agent Workflow START node START Arrow START -> Agent Agent node Agent LLM reasoning Conditional decision diamond Is there a tool call? Arrow Agent -> Conditional Tool node Tools Execute tool Arrow Conditional -> Tool (Yes) Yes Loop arrow Tool -> Agent Return the result and continue reasoning END node END Arrow Conditional -> END (No) no Arrow definition

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]
Sicherheitswarnung:Das calculate-Tool verwendet stattdessen AST-Parsing (Abstract Syntax Tree)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

import os
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 '(无内容)'}")
Hinweis: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

import os
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"],     # 执行该节点后暂停
)
Wichtig: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'])}")
Tipp: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.

Background Title Multi-Agent Collaboration Architecture (Supervisor Pattern) Supervisor Node Supervisor Supervisor - Task Assignment Research Agent Information Collection Writing Agent Content Creation Review Agent Quality Review Arrows from Supervisor to each Agent Arrows from each Agent back to Supervisor (dashed) Caption Assign tasks Assign tasks Return result Return result Bottom note Supervisor coordinates the expert Agents. After each Agent completes its task, it returns the result to Supervisor, who decides the next step. Arrow definitions

Supervisor-Muster

Beispiel

import os
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
Umgebungsanforderungen:LangGraph Studio erfordert Docker-Umgebungsunterstützung. Stellen Sie sicher, dass Docker Desktop installiert ist und ordnungsgemäß läuft, bevor Sie den Entwicklungsserver starten.

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.

Hinweis: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

Empfohlener Lernpfad

Background Step 1 Basic Concepts Arrow 1 Step 2 Simple Workflow Arrow 2 Step 3 ReAct Agent Arrow 3 Step 4 Human-Machine Collaboration Arrow 4 Step 5 Multi-Agent Arrow 5 Step 6 LangGraph Studio Arrow Definition
Andere Erweiterungen