第 03 期 | LCEL 语法精讲:现代化链式编排

更新于 2026/4/18

🎯 本期学习目标

嘿,各位未来的 AI 架构师们!欢迎来到《LangChain 全栈大师课》的第五期。前几期我们把基础打得挺扎实了,链 (Chains) 的组合拳也玩得有模有样。但你有没有觉得,我们的智能客服助手虽然能回答问题,却总有点“死板”?它只能按照我们预设的流程走,遇到点意料之外的情况就抓瞎了。

这就像你给了一个实习生一份详细的操作手册,他能照着做,但遇到手册上没写的事,他就得跑来问你。我们想要的是一个能自己思考、自己找工具解决问题的“高级实习生”!

本期,我们就来把我们客服助手的大脑升级一下,让它拥有自主决策的能力。学完本期,你将:

  1. 掌握 LangChain Agent 的核心组成: 彻底搞懂 ToolsLLMAgentExecutor 这三驾马车如何协同工作。
  2. 理解 Agent 的工作原理: 深入洞察 Agent 如何进行“观察-思考-行动-反思”的循环,赋予 LLM 智能推理能力。
  3. 学会为智能客服助手设计并集成自定义工具: 让你的客服助手不仅能回答问题,还能查询数据库、调用 API、甚至解决更复杂的问题。
  4. 能够构建一个具备自主决策和问题解决能力的客服 Agent: 从此告别“死板”的固定流程,迈向真正的智能应用。

准备好了吗?系好安全带,我们准备起飞!

📖 原理解析

Agent:让 LLM 拥有“手”和“脚”的大脑

还记得我们之前讲的 Chains 吗?它们很强大,能把不同的 LLM 调用和处理步骤串联起来。比如,我们可以链式地先摘要,再翻译,最后生成回复。但 Chains 的核心问题在于:流程是固定的。你告诉它怎么走,它就怎么走。

设想一下,你的智能客服助手收到一个问题:“我的订单号 XYZ123 的状态是什么?”

如果用 Chains,你可能需要:

  1. 判断这是一个“订单查询”意图。
  2. 提取订单号。
  3. 调用一个固定的“订单查询”API。
  4. 返回结果。

但如果用户问:“你们最新的手机型号有哪些?它们的特点是什么?我该怎么选?” 这可能需要:

  1. 判断这是“产品查询”和“推荐”意图。
  2. 查询产品数据库。
  3. 获取产品特点。
  4. 根据用户偏好(如果能获取到)进行推荐。

看到了吗?不同的问题需要不同的工具和不同的解决路径。如果用 Chains,你可能要为每一种情况预设一条链,这不仅复杂,而且一旦遇到没预设过的问题,Chain 就彻底懵圈了。

Agent 的出现,就是为了解决 Chains 的这种局限性。 Agent 的核心思想是:赋予 LLM 自主决策的能力。 不再是你告诉 LLM 怎么做,而是你给 LLM 一些工具,让它自己决定在什么时候、用什么工具、以及如何使用工具来解决问题。

这就像你给一个高级工程师一个任务,他会自己分析任务,根据情况选择使用 IDE、调试器、文档、搜索引擎等各种工具,最终完成任务。Agent 就是 LLM 的这个“高级工程师”模式。

Agent 的核心三要素:工具、大脑与执行官

LangChain Agent 的强大之处,在于它巧妙地结合了三个核心组件:

  1. Tools (工具):

    • 是什么? 它们是 LLM 能够调用的外部功能或数据源。想象一下,这些就是 LLM 的“手”和“脚”,让它能够与真实世界互动。
    • 例子: 搜索引擎、计算器、数据库查询接口、API 调用(如天气 API、订单查询 API)、代码解释器、甚至我们自定义的内部知识库查询函数。
    • 如何定义? 在 LangChain 中,一个 Tool 通常包含 name (工具名称)、description (工具用途的详细描述) 和 func (实际执行功能的 Python 函数)。description 至关重要,因为 LLM 会根据这个描述来决定是否使用以及如何使用工具。
  2. LLM (语言模型):

    • 是什么? 这是 Agent 的“大脑”,负责所有的推理、决策和规划。它接收用户的请求、可用的工具描述、以及之前执行步骤的结果(观察 Observation),然后思考下一步该做什么。
    • 功能:
      • 理解问题: 解析用户的意图。
      • 选择工具: 根据问题和可用的工具描述,决定使用哪个工具。
      • 生成参数: 为选定的工具生成正确的输入参数。
      • 规划步骤: 在多步推理中,决定下一步是继续使用工具,还是已经得到最终答案。
      • 生成最终答案: 当问题解决后,将结果整理成自然语言返回给用户。
  3. AgentExecutor (执行器):

    • 是什么? 它是 Agent 的“执行官”,负责协调 LLM 和 Tools 之间的交互。它是一个循环,不断地将 LLM 的输出(行动)传递给相应的工具执行,再将工具的输出(观察)反馈给 LLM,直到 LLM 给出最终答案。
    • 循环机制:
      • 用户输入 -> AgentExecutor 接收。
      • AgentExecutor 将输入和工具列表发给 LLM。
      • LLM 思考 (Thought) 后,决定一个行动 (Action) 和行动的输入 (Action Input)。
      • AgentExecutor 接收 LLM 的行动指令,找到对应的工具并执行。
      • 工具执行后产生一个结果 (Observation)。
      • AgentExecutor 将 Observation 反馈给 LLM。
      • LLM 根据新的 Observation 再次思考,决定是继续行动,还是给出最终答案。
      • 这个循环持续进行,直到 LLM 输出一个“最终答案 (Final Answer)”。

ReAct 框架:思考与行动的交替舞

理解 AgentExecutor 的循环机制,就不得不提 ReAct (Reasoning and Acting) 框架。这是目前最常用、最直观的 Agent 工作模式之一。

ReAct 的核心思想是:LLM 交替进行 Reasoning (思考)Acting (行动)

  • Reasoning (思考): LLM 会先“想一想” (Thought)。它会分析当前的问题、已有的信息、以及可用的工具,然后规划下一步应该做什么。这个“思考”过程通常会被打印出来,让我们看到 LLM 的内部逻辑。
  • Acting (行动): 在思考之后,LLM 会决定一个具体的“行动” (Action)。这个行动包括:
    • Action: 要调用的工具的名称。
    • Action Input: 传递给该工具的参数。

然后,AgentExecutor 会执行这个 Action,得到一个 Observation (观察结果)。这个 Observation 又会作为新的信息,反馈给 LLM,让它进行下一轮的 Reasoning。

这个过程就像一个侦探:

  1. 用户提问 (Observation): “谁是凶手?”
  2. 侦探思考 (Thought): “嗯,我需要线索。我可以查看案发现场照片,或者询问目击者。”
  3. 侦探行动 (Action): “查看案发现场照片。” (调用工具)
  4. 案发现场照片 (Observation): “发现一把带血的刀。”
  5. 侦探思考 (Thought): “刀上有指纹吗?我可以送去化验。”
  6. 侦探行动 (Action): “送刀去化验指纹。” (调用工具)
  7. 化验报告 (Observation): “指纹属于张三。”
  8. 侦探思考 (Thought): “张三有不在场证明吗?我需要询问他。”
  9. 侦探行动 (Action): “询问张三。” (调用工具)
  10. 询问结果 (Observation): “张三承认是他。”
  11. 侦探思考 (Thought): “所有线索都指向张三,我找到了凶手。”
  12. 侦探结论 (Final Answer): “凶手是张三。”

ReAct 框架通过这种交替的思考和行动,让 LLM 能够进行多步推理,并动态地选择和使用工具,极大地增强了其解决复杂问题的能力。

Mermaid 图解:Agent 工作流

让我们用一个 Mermaid 图来直观地看一下 Agent 的工作流程:

graph TD
    A[用户输入: 问题] --> B(AgentExecutor)
    B --> C{LLM (Agent 大脑)}
    C -- "Thought: 思考下一步" --> D1[Action: 工具名称]
    C -- "Action Input: 工具参数" --> D2[Action: 工具参数]

    D1 & D2 --> E{Tools (外部工具集)}
    E -- "执行工具" --> F[Observation: 工具执行结果]

    F --> C
    C -- "如果问题解决" --> G[Final Answer: 最终答案]
    G --> B
    B --> H[用户输出: 答案]

    subgraph Agent 内部循环
        C -- "Thought, Action, Action Input" --> E
        E -- "Observation" --> C
    end

图解说明:

  • 用户输入: 用户向 Agent 提出问题。
  • AgentExecutor: 作为总指挥,接收用户输入,并协调 LLM 和 Tools。
  • LLM (Agent 大脑):
    • 接收当前上下文(用户问题、可用工具描述、历史对话、之前的 Observation)。
    • 进行 Thought (思考),决定下一步做什么。
    • 输出 Action (要调用的工具名称) 和 Action Input (工具参数)。
  • Tools (外部工具集): 包含所有可供 Agent 调用的函数或 API。
  • 执行工具: AgentExecutor 根据 LLM 的 ActionAction Input,从 Tools 中找到对应工具并执行。
  • Observation (工具执行结果): 工具执行后返回的结果,被 AgentExecutor 收集。
  • 循环反馈: Observation 被再次送回 LLM,作为新的上下文,让 LLM 进行下一轮的 ThoughtAction
  • Final Answer (最终答案): 当 LLM 判断问题已经解决,并且能够给出最终答案时,它会输出 Final Answer
  • 用户输出: AgentExecutor 将 Final Answer 返回给用户。

这个循环会一直持续,直到 LLM 确信它已经找到了解决问题的最终答案。

💻 实战代码演练 (客服项目中的具体应用)

好了,原理讲得够透彻了,现在是时候让我们的智能客服助手真正动起来了!

场景设定: 我们的智能客服助手需要处理多种类型的客户请求:

  1. 查询产品目录: 用户想知道我们有哪些产品,或者某个产品的特点。
  2. 查询订单状态: 用户提供订单号,想知道订单的实时状态。
  3. 搜索知识库: 用户提出一个通用问题(如技术故障排除),需要从内部知识库中查找答案。

我们将为这三种场景分别创建工具,并让 Agent 自主选择使用。

import os
from dotenv import load_dotenv
from langchain_openai import OpenAI
from langchain.agents import AgentExecutor, create_react_agent
from langchain.tools import Tool
from langchain import hub # 用于获取 ReAct 提示模板
from langchain_core.prompts import PromptTemplate

# 加载环境变量,确保你的 OPENAI_API_KEY 已设置
load_dotenv()

# 1. 定义我们客服助手可用的工具集

# 模拟一个产品目录查询工具
def search_product_catalog(query: str) -> str:
    """根据产品名称或关键词查询产品目录,返回产品信息。
    例如:'查询手机', '最新款耳机', '笔记本电脑特点'
    """
    products_db = {
        "智能手机": "最新款智能手机,配备A17芯片,120Hz刷新率屏幕,AI拍照功能。",
        "无线耳机": "高品质无线耳机,支持主动降噪,续航24小时,Type-C充电。",
        "笔记本电脑": "轻薄高性能笔记本电脑,搭载M3芯片,视网膜显示屏,适合专业人士。",
        "智能手表": "健康监测智能手表,支持心率、血氧监测,多种运动模式,NFC支付。",
        "平板电脑": "娱乐学习两用平板电脑,大尺寸屏幕,支持手写笔,视听体验极佳。"
    }
    query = query.lower()
    results = [
        f"{name}: {desc}"
        for name, desc in products_db.items()
        if query in name.lower() or query in desc.lower()
    ]
    if results:
        return "\n".join(results)
    else:
        return f"抱歉,未能找到与 '{query}' 相关的产品信息。"

# 模拟一个订单状态查询工具
def check_order_status(order_id: str) -> str:
    """根据订单ID查询订单的当前状态。
    订单ID必须是数字,例如:'1001', '2003'
    """
    order_db = {
        "1001": "订单1001已发货,预计3天内送达。",
        "1002": "订单1002正在处理中,预计今天下午打包。",
        "1003": "订单1003已签收,感谢您的购买。",
        "2001": "订单2001已取消。",
        "2002": "订单2002支付失败,请重新尝试。",
    }
    if order_id in order_db:
        return order_db[order_id]
    else:
        return f"抱歉,未能找到订单号 '{order_id}' 的信息,请确认订单号是否正确。"

# 模拟一个知识库查询工具
def search_knowledge_base(query: str) -> str:
    """在内部知识库中搜索常见问题和解决方案。
    例如:'如何连接蓝牙耳机', 'Wi-Fi连接问题', '手机死机怎么办'
    """
    kb_data = {
        "如何连接蓝牙耳机": "请打开手机蓝牙,长按耳机电源键进入配对模式,在手机蓝牙设置中选择耳机名称进行连接。",
        "Wi-Fi连接问题": "尝试重启路由器和手机,检查Wi-Fi密码是否正确,确保信号良好。如果问题依旧,请联系客服。",
        "手机死机怎么办": "长按电源键10秒强制重启手机。如果频繁死机,建议备份数据后恢复出厂设置。",
        "退换货政策": "商品在签收后7天内可无理由退货,15天内可换货。请保持商品完好,并联系客服办理。",
        "发票申请": "购买成功后,可在订单详情页申请电子发票,通常在1-3个工作日内开具。"
    }
    query = query.lower()
    results = [
        f"问题: {q}\n答案: {a}"
        for q, a in kb_data.items()
        if query in q.lower() or query in a.lower()
    ]
    if results:
        return "\n---\n".join(results)
    else:
        return f"抱歉,知识库中未能找到与 '{query}' 相关的信息。"

# 将这些函数封装成 LangChain 的 Tool 对象
tools = [
    Tool(
        name="SearchProductCatalog",
        func=search_product_catalog,
        description="""
        当你需要查询产品信息、产品特点、产品推荐等与产品目录相关的问题时使用。
        输入应该是用户查询的产品关键词或产品类型。
        例如:'最新手机', '耳机特点', '笔记本电脑有哪些'
        """,
    ),
    Tool(
        name="CheckOrderStatus",
        func=check_order_status,
        description="""
        当你需要查询用户订单的实时状态时使用。
        输入必须是用户提供的具体订单号 (纯数字)。
        例如:'1001', '2003'
        """,
    ),
    Tool(
        name="SearchKnowledgeBase",
        func=search_knowledge_base,
        description="""
        当你需要查询通用问题、常见故障排除、服务条款、操作指南等内部知识库信息时使用。
        输入应该是用户查询的具体问题或关键词。
        例如:'如何连接蓝牙', 'Wi-Fi问题', '退货政策'
        """,
    ),
]

# 2. 初始化 LLM (Agent 的大脑)
# 推荐使用 gpt-4 或 gpt-3.5-turbo 等模型,它们在推理能力上表现更佳
llm = OpenAI(model_name="gpt-3.5-turbo-instruct", temperature=0) # temperature设为0,减少随机性

# 3. 获取 ReAct Agent 的提示模板
# LangChain Hub 提供了很多预设的提示模板,ReAct 是其中一种
# 如果你想自定义提示,也可以自己创建一个 PromptTemplate
prompt = hub.pull("hwchase17/react")

# 4. 创建 Agent
# create_react_agent 是一个便捷函数,用于创建基于 ReAct 框架的 Agent
# 它需要 LLM、Tools 和一个 PromptTemplate
agent = create_react_agent(llm, tools, prompt)

# 5. 创建 AgentExecutor (Agent 的执行官)
# verbose=True 会打印出 Agent 的思考过程 (Thought, Action, Observation),这对于调试至关重要!
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)

# 6. 运行 Agent,看看它如何自主决策!

print("--- 场景一:查询产品信息 ---")
# 用户询问产品信息,Agent 应该选择 SearchProductCatalog 工具
response = agent_executor.invoke({"input": "你们最新的智能手机型号有哪些特点?"})
print(f"\n客服助手回答: {response['output']}\n")

print("--- 场景二:查询订单状态 ---")
# 用户询问订单状态,Agent 应该选择 CheckOrderStatus 工具
response = agent_executor.invoke({"input": "我的订单号 1002 现在是什么状态?"})
print(f"\n客服助手回答: {response['output']}\n")

print("--- 场景三:查询知识库 ---")
# 用户询问通用问题,Agent 应该选择 SearchKnowledgeBase 工具
response = agent_executor.invoke({"input": "我的手机Wi-Fi连接有问题怎么办?"})
print(f"\n客服助手回答: {response['output']}\n")

print("--- 场景四:不确定或无工具处理 ---")
# 用户询问一个 Agent 没有工具处理的问题,Agent 应该直接回答
response = agent_executor.invoke({"input": "请帮我写一首关于秋天的诗。"})
print(f"\n客服助手回答: {response['output']}\n")

print("--- 场景五:多步推理 (如果工具设计允许) ---")
# 这是一个更复杂的例子,如果Agent能理解并分解任务
# 当前ReActAgent可能无法直接执行复杂的多步推理,但高级Agent可以
# 假设用户问:"我的订单号1001发货了吗?如果发货了,你们最新的耳机有什么推荐?"
# 对于基础的ReAct Agent,它可能会优先处理一个明确的工具调用,或者直接回答它不能同时处理两个不相关的任务。
# 更高级的Agent(如OpenAI Functions Agent)或结合规划的Agent会处理得更好。
# 这里我们演示一个稍微复杂一点的,但依然是单工具为主的场景
response = agent_executor.invoke({"input": "我的订单号1003已经签收了,我想了解一下你们的退换货政策。"})
print(f"\n客服助手回答: {response['output']}\n")

代码解析:

  1. 加载环境变量: 确保你的 OPENAI_API_KEY.env 文件中配置,或者直接设置为环境变量。
  2. 定义工具 (Tools):
    • 我们创建了三个 Python 函数:search_product_catalogcheck_order_statussearch_knowledge_base。它们模拟了与外部系统(产品数据库、订单系统、知识库)的交互。
    • 关键点: 每个函数都被封装成 langchain.tools.Tool 对象。name 属性是工具的唯一标识,func 属性是实际执行的函数。最最最重要的是 description 属性! 这个描述会直接喂给 LLM,LLM 完全依赖这个描述来理解工具的用途和何时使用。所以,description 必须清晰、准确、包含使用示例。
  3. 初始化 LLM: 我们使用 OpenAI(model_name="gpt-3.5-turbo-instruct", temperature=0) 作为 Agent 的大脑。temperature=0 会让 LLM 的输出更确定、更少创造性,这对于需要精确决策的 Agent 来说是更好的选择。
  4. 获取 ReAct 提示模板: hub.pull("hwchase17/react") 从 LangChain Hub 下载了一个标准的 ReAct 提示模板。这个模板会告诉 LLM 如何进行 Thought (思考) 和 Action (行动) 的输出格式。
  5. 创建 Agent: create_react_agent(llm, tools, prompt) 是一个工厂函数,它将 LLM、我们定义的工具列表和 ReAct 提示模板组合起来,创建出一个 ReAct 风格的 Agent 对象。
  6. 创建 AgentExecutor: AgentExecutor 是 Agent 的执行器。
    • agent=agent:指定要执行的 Agent。
    • tools=tools:再次传入工具列表,供 AgentExecutor 实际调用。
    • verbose=True划重点! 这是调试 Agent 的“神兵利器”。它会打印出 Agent 每一步的 ThoughtActionAction InputObservation,让你清晰地看到 Agent 的思考过程。
    • handle_parsing_errors=True: 允许 AgentExecutor 尝试处理 LLM 输出格式不正确导致的解析错误,提高健壮性。
  7. 运行 Agent: agent_executor.invoke({"input": "..."}) 会启动 Agent 的推理和执行循环。你会看到 Agent 如何根据你的问题,自主选择合适的工具,执行,然后给出答案。

通过运行上面的代码,你会清晰地看到 Agent 如何根据用户输入,自主地决策调用哪个工具,并最终给出答案。verbose=True 的输出会让你对 Agent 的内部工作机制有更直观的理解。

坑与避坑指南

Agent 虽好,但也不是魔法棒,使用过程中会遇到不少“坑”。作为高级讲师,我必须给你打好预防针,并授你避坑之术。

1. 工具描述的艺术:Agent 的“说明书”

  • 坑: 工具描述模糊不清,或者描述与实际功能不符。例如,你的 check_order_status 工具描述写成“查询订单”,但没说明输入必须是订单号。结果 LLM 可能会把“我的物流到哪了”也发给它,导致工具调用失败。
  • 避坑指南:
    • 像写 API 文档一样严谨: 清晰、准确、简洁。
    • 明确输入输出: 明确告诉 LLM 工具的输入是什么类型、什么格式,输出大概是什么。
    • 包含使用示例: 用一两个具体的例子指导 LLM 如何使用。
    • 区分相似工具: 如果有多个功能相似的工具,务必在描述中突出它们之间的差异,让 LLM 知道何时选择哪一个。

2. Token 限制与上下文管理:Agent 的“记忆力”与“脑容量”

  • 坑: Agent 的每次循环都会将历史的 ThoughtActionAction InputObservation 加入到 LLM 的上下文 (context) 中。如果 Agent 进行了多步推理,或者对话很长,上下文会迅速膨胀,很快达到 LLM 的 Token 限制,导致推理中断或成本飙升。
  • 避坑指南:
    • 限制最大步数 (max_iterations):AgentExecutor 中设置 max_iterations 参数,防止 Agent 无限循环或步数过多。
    • 记忆管理 (Memory): 对于长对话,结合 LangChain 的 Memory 模块。例如,使用 ConversationBufferMemory 存储对话历史,但更高级的如 ConversationSummaryMemory 可以对历史对话进行摘要,减少 Token 消耗。
    • Prompt Engineering: 在 Agent 的 Prompt 中加入指令,引导 LLM 在适当时候给出最终答案,而不是无休止地尝试工具。

3. 幻觉与错误推理:Agent 的“脑洞”

  • 坑: LLM 可能会“想象”出不存在的工具、错误的工具参数,或者进行错误的推理路径。这通常发生在 LLM 对问题理解不准确,或者工具描述不够清晰时。
  • 避坑指南:
    • verbose=True 是你的好朋友: 运行 Agent 时始终开启 verbose=True,仔细观察 LLM 的 Thought 过程。如果发现它的思考逻辑不对劲,那么问题很可能出在 Prompt 或工具描述上。
    • 改进 Prompt: 优化 Agent 的 Prompt,明确指令,引导 LLM 更加严谨地思考。例如,可以要求它在调用工具前先进行一步确认性思考:“我应该使用哪个工具?为什么?”
    • 工具描述的精准度: 回到第一点,确保工具描述足够精确,不给 LLM 留下想象空间。
    • 错误处理: 在工具函数内部做好错误处理,即使 LLM 传了错误的参数,工具也能优雅地返回错误信息,而不是崩溃。handle_parsing_errors=True 也能捕获一部分 LLM 输出格式问题。

4. 工具的幂等性与副作用:Agent 的“影响力”

  • 坑: 如果你的工具会修改外部状态(比如“更新订单状态”、“发送邮件”),并且不是幂等的(重复调用会导致不同结果),那么 Agent 的错误或重复调用可能会带来真实世界的负面影响。
  • 避坑指南:
    • 设计幂等工具: 尽可能让你的工具是幂等的,即多次调用同一个工具,结果都是一样的。
    • 确认机制: 对于有副作用的工具,在 Agent 调用前加入用户确认环节,或者在工具内部实现二次确认机制。
    • 权限与安全: 严格控制 Agent 可调用的工具权限,避免它执行不应有的敏感操作。

5. 性能优化:Agent 的“效率”

  • 坑: Agent 的每一次 ThoughtAction 循环都可能涉及一次 LLM 调用和一次工具调用,这会带来显著的延迟。如果用户问题需要多步推理,响应时间会更长。
  • 避坑指南:
    • 选择高效 LLM: 使用响应速度快、Token 消耗低的 LLM 模型。
    • 工具优化: 确保你的工具函数本身执行效率高,特别是涉及到数据库查询或外部 API 调用时。
    • 缓存: 对频繁查询且结果变化不大的工具调用进行缓存。