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

相关推荐

  • 如何使用 Knowledge Graph 和 LLM 构建构建问答系统

    基于模拟 FAQ 文档构建的知识图谱 本文将介绍一个基于知识图谱(使用上一篇文章介绍的方法构建)和大型语言模型(LLM,此处使用 Gemma3-4b-it-qat)的简易问答系统。选择 Gemma3-4b 是因为其模型尺寸适中,可在普通笔记本电脑上运行,且具备出色的指令遵循能力。 我们将以一个虚构智能手机产品的 FAQ 文本为例,复用上一篇文章的代码为其构建…

    2025年11月13日
    31000
  • AGI的物理边界:计算本质与硬件极限下的AI未来

    大模型的通用性与泛化能力正日益增强。 尽管一些新模型(例如在某些专业任务和智能水平上表现已相当出色的模型)不断涌现,但它们距离我们通常所理解的通用人工智能(AGI)依然十分遥远。 然而,这恰恰说明业界对AGI的实现仍抱有巨大的热情与信心,或许下一款突破性的大模型就能初步触及AGI的构想。 不过,卡耐基梅隆大学教授、AI2研究所科学家Tim Dettmers近…

    2025年12月21日
    40500
  • GraphRAG革命:知识图谱与向量数据库的协同进化

    Knowledge graphs 和 vector databases 常被定位为彼此竞争的技术,但这种框架忽略了问题的本质。 它们是对立的吗?简短回答:不是。 它们解决的是根本不同的问题。事实上,它们最好的状态是协同,而不是对抗。如果你在构建现代 AI 系统,把它们当作对手是一种设计缺陷。 更好的理解方式是: Knowledge Graph = 结构化大脑…

    2025年12月28日
    36300
  • 从理论到实践:使用Model Context Protocol构建多工具AI代理的完整指南

    类比 我们都熟悉《Kaun Banega Crorepati(KBC)》节目中的“Phone a Friend(打电话求助)”环节。这是印度版的《Who Wants to Be a Millionaire?》。 现在,想象一下如果 KBC 节目诞生于“电话尚未发明”的时代。 在没有电话的世界里:如果节目想让选手“打电话”求助朋友,就必须为每一位求助的朋友进行…

    2025年11月25日
    57200
  • Cog-RAG:让RAG在检索前先思考,用双超图架构模拟人类认知过程

    Retrieval-Augmented Generation(RAG)已成为帮助大型语言模型保持信息“有根有据”的标准方法。其基本流程广为人知:将文档切分为片段,进行向量化嵌入,执行向量检索,最后将最匹配的前K个结果输入给大模型生成答案。 然而,这条标准流水线存在一些固有的局限性。 为什么多数 RAG 系统仍像支离破碎的记忆 图 1:图、超图与 Cog-RA…

    2026年2月23日
    45600

发表回复

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