LangGraph实战:构建高效Agentic工作流,解锁AI应用开发新范式

用 Agentic 框架构建 AI 工作流

LangGraph实战:构建高效Agentic工作流,解锁AI应用开发新范式

随着 GPT-5、Gemini 2.5 Pro 等强大 AI 模型的涌现,旨在高效利用这些模型的 Agentic 框架也日益增多。这类框架通过抽象化诸多复杂环节,极大地简化了与 AI 模型的协作,例如处理工具调用、管理智能体状态以及集成人工反馈循环。

本文将深入探讨其中一个可用的 Agentic AI 框架:LangGraph。我们将用它来开发一个简单的智能体应用,通过具体步骤展示此类框架的优势,并在文末讨论使用 LangGraph 及其他类似框架的优缺点。

市面上有多种 Agentic 框架可供选择,例如:
* LangChain
* LlamaIndex
* CrewAI

LangGraph实战:构建高效Agentic工作流,解锁AI应用开发新范式

为什么需要 Agentic 框架?

尽管市面上存在许多旨在简化应用开发的库,但它们有时反而会使代码变得晦涩、影响生产环境性能,并增加调试难度。

关键在于找到那些能通过抽象样板代码来真正简化应用的库。这一原则在创业领域常被概括为:专注于解决你的核心问题,而将那些已被解决的问题交给成熟的工具去处理。

Agentic 框架的价值在于它能够抽象掉开发者通常不愿处理的复杂性:
* 状态管理:不仅管理对话历史,还包括在执行 RAG 等任务时收集的所有相关信息。
* 工具使用:开发者无需编写调用工具的具体逻辑,只需定义好工具,框架便能负责如何调用,尤其擅长处理并行与异步的工具调用。

因此,使用 Agentic 框架可以剥离大量底层细节,让开发者能够将精力集中于产品的核心逻辑。

LangGraph 基础

LangGraph 的核心思想是基于“图”来构建工作流。在每次处理请求时,系统都会执行这个图。图中主要包含三个要素:
* 状态:保存在内存中的当前信息。
* 节点:通常是执行特定操作的单元,例如调用 LLM 判断意图或调用工具执行任务。
* :定义节点之间的流转逻辑,通常基于条件判断来决定下一步执行哪个节点。

这些概念均源自基础的图论。

实现一个工作流

LangGraph实战:构建高效Agentic工作流,解锁AI应用开发新范式

我们将通过一个简单的文档处理应用来实践 LangGraph。该应用允许用户执行以下操作:
* 创建带文本的文档
* 删除文档
* 在文档中搜索

为此,我们将构建一个包含两个主要步骤的工作流:
1. 识别用户意图:判断用户是想创建、删除还是搜索文档。
2. 路由执行:根据识别的意图,将请求路由到对应的处理流程。

虽然也可以直接让智能体自由调用所有工具,但先进行意图分类的路由设计,能为后续根据意图执行更复杂的操作序列提供清晰的架构。

加载依赖与 LLM

首先,加载必要的库并初始化 LLM。本例中使用 AWS Bedrock 的 Claude 模型,你也可以替换为其他服务商。

from typing_extensions import TypedDict, Literal
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command, interrupt
from langchain_aws import ChatBedrockConverse
from langchain_core.messages import HumanMessage, SystemMessage
from pydantic import BaseModel, Field
from IPython.display import display, Image
from dotenv import load_dotenv
import os
load_dotenv()

aws_access_key_id = os.getenv("AWS_ACCESS_KEY_ID") or ""
aws_secret_access_key = os.getenv("AWS_SECRET_ACCESS_KEY") or ""

os.environ["AWS_ACCESS_KEY_ID"] = aws_access_key_id
os.environ["AWS_SECRET_ACCESS_KEY"] = aws_secret_access_key

llm = ChatBedrockConverse(
    model_id="us.anthropic.claude-3-5-haiku-20241022-v1:0",
    region_name="us-east-1",
    aws_access_key_id=aws_access_key_id,
    aws_secret_access_key=aws_secret_access_key,
)

# 使用字典模拟文档数据库,生产环境应替换为真实数据库
document_database: dict[str, str] = {}

定义图结构

接下来定义图。首先创建一个路由器,用于将用户输入分类为三种意图之一:add_documentdelete_documentask_document

# 定义状态结构
class State(TypedDict):
    input: str
    decision: str | None
    output: str | None

# 为路由逻辑定义结构化输出模式
class Route(BaseModel):
    step: Literal["add_document", "delete_document", "ask_document"] = Field(
        description="路由流程中的下一步"
    )

# 增强 LLM,使其支持结构化输出以用于路由
router = llm.with_structured_output(Route)

def llm_call_router(state: State):
    """将用户输入路由到适当的节点"""
    decision = router.invoke(
        [
            SystemMessage(
                content="""将用户输入路由到以下三种意图之一:
                - 'add_document'
                - 'delete_document'
                - 'ask_document'
                你只需返回意图,无需其他文本。
                """
            ),
            HumanMessage(content=state["input"]),
        ]
    )
    return {"decision": decision.step}

# 条件边函数,根据决策路由到对应节点
def route_decision(state: State):
    if state["decision"] == "add_document":
        return "add_document_to_database_tool"
    elif state["decision"] == "delete_document":
        return "delete_document_from_database_tool"
    elif state["decision"] == "ask_document":
        return "ask_document_tool"

这里定义了状态 State 来存储用户输入和路由决策。通过强制 LLM 进行结构化输出,我们确保了模型只会返回三种预定义的意图之一,从而实现了可靠的路由逻辑。

接着,我们定义本例中使用的三个工具,每个意图对应一个工具。

# 节点定义
def add_document_to_database_tool(state: State):
    """向数据库添加文档。根据用户查询,提取文档的文件名和内容。若未提供,则不添加。"""
    user_query = state["input"]
    # 从用户查询中提取文件名
    filename_prompt = f"根据以下用户查询,提取文档的文件名:{user_query}。只返回文件名,不要返回其他文本。"
    output = llm.invoke(filename_prompt)
    filename = output.content
    # 从用户查询中提取内容
    content_prompt = f"根据以下用户查询,提取文档的内容:{user_query}。只返回内容,不要返回其他文本。"
    output = llm.invoke(content_prompt)
    content = output.content
    # 将文档添加到数据库
    document_database[filename] = content
    return {"output": f"文档 {filename} 已添加到数据库"}

def delete_document_from_database_tool(state: State):
    """从数据库删除文档。根据用户查询,提取要删除的文档的文件名。若未提供,则不删除。"""
    user_query = state["input"]
    # 从用户查询中提取文件名
    filename_prompt = f"根据以下用户查询,提取要删除的文档的文件名:{user_query}。只返回文件名,不要返回其他文本。"
    output = llm.invoke(filename_prompt)
    filename = output.content
    # 如果文档存在则删除,否则返回失败信息
    if filename not in document_database:
        return {"output": f"数据库中未找到文档 {filename}"}
    document_database.pop(filename)
    return {"output": f"文档 {filename} 已从数据库删除"}

def ask_document_tool(state: State):
    """询问文档相关问题。根据用户查询,提取文档的文件名和问题。若未提供,则不提问。"""
    user_query = state["input"]
    # 从用户查询中提取文件名
    filename_prompt = f"根据以下用户查询,提取要提问的文档的文件名:{user_query}。只返回文件名,不要返回其他文本。"
    output = llm.invoke(filename_prompt)
    filename = output.content
    # 从用户查询中提取问题
    question_prompt = f"根据以下用户查询,提取要问文档的问题:{user_query}。只返回问题,不要返回其他文本。"
    output = llm.invoke(question_prompt)
    question = output.content
    # 对文档进行提问
    if filename not in document_database:
        return {"output": f"数据库中未找到文档 {filename}"}
    result = llm.invoke(f"文档:{document_database[filename]}nn问题:{question}")
    return {"output": f"文档查询结果:{result.content}"}

最后,我们通过添加节点和边来构建图:

# 构建工作流
router_builder = StateGraph(State)

# 添加节点
router_builder.add_node("add_document_to_database_tool", add_document_to_database_tool)
router_builder.add_node("delete_document_from_database_tool", delete_document_from_database_tool)
router_builder.add_node("ask_document_tool", ask_document_tool)
router_builder.add_node("llm_call_router", llm_call_router)

# 添加边以连接节点
router_builder.add_edge(START, "llm_call_router")
router_builder.add_conditional_edges(
    "llm_call_router",
    route_decision,
    {  # route_decision 返回的名称 : 要访问的下一个节点名称
        "add_document_to_database_tool": "add_document_to_database_tool",
        "delete_document_from_database_tool": "delete_document_from_database_tool",
        "ask_document_tool": "ask_document_tool",
    },
)
router_builder.add_edge("add_document_to_database_tool", END)
router_builder.add_edge("delete_document_from_database_tool", END)
router_builder.add_edge("ask_document_tool", END)

# 编译工作流
memory = InMemorySaver()
router_workflow = router_builder.compile(checkpointer=memory)
config = {"configurable": {"thread_id": "1"}}

# 可视化工作流
display(Image(router_workflow.get_graph().draw_mermaid_png()))

最后的显示函数会渲染出如下图所示的图结构:

LangGraph实战:构建高效Agentic工作流,解锁AI应用开发新范式

现在,我们可以按不同意图来运行这个工作流了。

添加文档:

user_input = "Add the document 'test.txt' with content 'This is a test document' to the database"
state = router_workflow.invoke({"input": user_input}, config)
print(state["output"])

# -> Document test.txt added to database

查询文档:

user_input = "Give me a summary of the document 'test.txt'"
state = router_workflow.invoke({"input": user_input}, config)
print(state["output"])

# -> A brief, generic test document with a simple descriptive sentence.

删除文档:

user_input = "Delete the document 'test.txt' from the database"
state = router_workflow.invoke({"input": user_input}, config)
print(state["output"])

# -> Document test.txt deleted from database

可以看到,工作流在不同的路由路径下都能正确运行。你可以根据需要轻松地增加更多意图,或为每个意图添加更多节点,从而构建更复杂的工作流。

更强的 Agentic 用例

“Agentic Workflows” 与完全的 “Agentic Applications” 之间的区别有时会令人困惑。为了区分这两个术语,这里引用 Anthropic 在《Building effective agents》一文中的观点:

工作流 是通过预定义的代码路径来编排 LLM 和工具的系统;而 智能体 是由 LLM 动态地指挥其自身流程与工具使用、并持续控制任务完成方式的系统。

大多数使用 LLM 解决的问题更适合采用工作流模式,因为多数问题(根据经验)是预定义的,并且应该遵循一套预设的防护栏。例如,在上述添加/删除/查询文档的场景中,最佳实践就是先定义意图分类器,并基于不同意图设定好后续的固定流程。

但在某些场景下,你可能需要构建更具自主性的智能体应用。例如,一个能够在你代码库中搜索、在线查阅最新文档并直接修改代码的编程助手。这类应用的潜在场景极为多样,很难预先定义固定的工作流。

若想构建具备更高自主性的智能体系统,可以深入探索其核心概念。

LangGraph 的优缺点

优点

我认为 LangGraph 主要有以下三个优点:

  • 上手简单:安装和快速启动非常便捷。参考官方文档,或利用 AI 助手(如 Cursor)基于文档实现特定工作流,都能轻松开始。
  • 开源:其代码完全开源。这意味着无论背后的公司如何变化,你都可以确保项目代码的长期可运行性,这对于生产环境至关重要。
  • 代码简洁:它显著简化了大量样板代码,并抽象掉了许多原本需要手动处理的复杂逻辑,让开发更聚焦于业务本身。

缺点

在实际使用中,我也发现了一些不足之处:

  • 仍有一定样板代码:在实现自定义工作流时,虽然比从零开始写代码量少,但仍需要编写一定量的配置和结构代码,以实现一个相对简单的流程。这部分源于 LangGraph 定位为一个比某些高层框架(如 LangChain)更底层的工具,旨在提供灵活性的同时避免过度抽象。
  • 特有的集成问题:如同许多第三方库,集成 LangGraph 时可能会遇到其特有的错误。例如,在尝试可视化工作流图时,我遇到了与 _draw_mermaid_png 函数相关的问题。使用外部库总是在“获得的便利抽象”和“可能遇到的特定 Bug”之间进行权衡。

总结

总体而言,我认为 LangGraph 是构建智能体系统的一个非常有用的工具。通过意图分类来路由到不同处理流程的方式,让我能够相对轻松地搭建所需的工作流。它在“避免过度抽象以保持代码透明和易于调试”与“封装不必要的复杂性”之间取得了良好的平衡。采用此类智能体框架有利有弊,而判断其是否适合你的最佳方式,就是亲自尝试实现几个简单的工作流。

原文地址:https://pub.towardsai.net/how-to-build-effective-agentic-systems-with-langgraph-e5433d7aa153


关注“鲸栖”小程序,掌握最新AI资讯

本文来自网络搜集,不代表鲸林向海立场,如有侵权,联系删除。转载请注明出处:https://www.itsolotime.com/archives/13403

(0)
上一篇 2025年11月20日 下午2:42
下一篇 2025年11月21日 上午11:30

相关推荐

  • 文心5.0正式版发布:2.4万亿参数原生全模态架构,登顶LMArena的“最强文科生”技术解析

    2.4万亿参数,原生全模态架构,在文本榜上“霸榜如喝水”。 在文心Moment大会上,文心大模型5.0正式版 上线。该模型参数量达2.4万亿,采用原生全模态统一建模技术,具备全模态理解与生成能力,支持文本、图像、音频、视频等多种信息的输入与输出。在40余项权威基准的综合评测中,文心5.0正式版的语言与多模态理解能力稳居国际第一梯队。其音频和视觉生成能力与垂直…

    2026年1月24日
    5900
  • 从Jupyter到Web应用:用Python、FastAPI与LangChain构建可部署的AI工具

    从Jupyter到Web应用:用Python、FastAPI与LangChain构建可部署的AI工具(第1/2部分) 为何需要将AI脚本转化为Web应用 在Jupyter Notebook中成功验证一个AI模型(如问答或文本摘要)后,其价值往往受限于本地环境。团队无法协作,用户无法访问,模型的价值难以释放。 核心在于:AI的价值不仅在于模型本身,更在于其可访…

    2025年11月30日
    9000
  • 构建可扩展、生产级的 Agentic RAG Pipeline:分层架构与六层核心设计详解

    面向大型数据集、符合行业标准的 Agentic RAG Pipeline 需要基于清晰、可扩展的分层架构进行构建。我们将系统结构化,使得 Agent 能够并行地进行推理、获取上下文、使用工具以及与数据库交互。每一层都承担明确的职责,涵盖从数据摄取、模型服务到 Agent 协调的全过程。这种分层方法有助于系统实现可预测的扩展,同时为终端用户保持较低的响应延迟。…

    2026年1月22日
    14800
  • TL编译器:自动映射Tile程序至空间数据流架构,性能超越厂商库1.03×/1.91×

    TL 证明,编译器驱动的映射可以在空间数据流加速器上提供厂商级别的性能。在 GEMM 和 FlashAttention 上,以最少的人工干预实现了与厂商库足以竞争甚至更好的性能,将手工设计的内核转变为可重用编译流水线的输出。同时,TL 的硬件抽象使得编译器过程在很大程度上可在不同加速器间重用,简化了对多样化空间数据流架构的支持,并为未来芯片的设计空间探索提供…

    3天前
    4400
  • JEPA-WM:Meta LeCun团队发布物理规划终极指南,让机器人拥有理性大脑

    长期以来,AI领域一直怀揣着一个宏大的梦想:创造出能够像人类一样直观理解物理世界,并在从未见过的任务和环境中游刃有余的智能体。 传统的强化学习方法往往比较笨拙,需要通过无数次的试错和海量的样本才能学到一点皮毛,这在奖励信号稀疏的现实环境中效率低下。 为了打破这一僵局,研究者们提出了“世界模型”这一概念,即让智能体在脑海中构建一个物理模拟器,通过预测未来状态来…

    2026年1月3日
    7100

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注