第 24 期 | LangServe 初探:一键部署你的 LangChain 应用

⏱ 预计阅读 20 分钟 更新于 2026/5/7
💡 进群学习加 wx: agentupdate
(申请发送: agentupdate)

🎯 本期学习目标

各位未来的 AI 大师们,欢迎来到《LangChain 全栈大师课》的第五站!今天我们要攻克的是 LangChain 中最激动人心、也最具魔力的概念之一——Agents (智能体)。学完本期,你将:

  1. 彻底理解 Agent 的核心工作机制: 从 LLM 的“大脑”到工具的“手脚”,掌握 Agent 如何进行思考、决策和行动。
  2. 熟练掌握自定义工具的创建与集成: 为你的智能客服小助手配备查询订单、修改资料等强大技能,让它不再是“纸上谈兵”。
  3. 构建一个能够动态决策、解决复杂问题的智能客服Agent: 让你的助手从固定脚本的“傻瓜”蜕变为能根据用户意图灵活应对的“智多星”。
  4. 识别并规避 Agent 设计中的常见陷阱: 避免智能体陷入“幻觉”、无效循环或错误工具调用,确保其稳定可靠地运行。

📖 原理解析

好了,伙计们,系好安全带,我们准备深入 LangChain 的智能体世界。

在之前的课程中,我们学习了如何构建 Chain,将 LLM 的能力串联起来。Chain 就像一条预设好的生产线,每一步做什么,输入输出是什么,都规划得清清楚楚。这对于完成明确、固定的任务非常有效。

但等等,现实世界是这样的吗?你的智能客服小助手,用户的问题总是那么规规矩矩、一成不变吗?

“我想查一下我上周买的那个蓝牙耳机的订单状态。” “我的收货地址填错了,能帮我改一下吗?” “这款新品鼠标什么时候有货?”

这些问题,哪个能用一个简单的 LLM 调用或一个固定 Chain 搞定?不行!它需要:

  1. 理解用户意图: 是要查订单,还是要改地址,还是要问库存?
  2. 获取外部信息: 订单状态、用户地址、商品库存,这些信息都在公司的数据库或第三方 API 里。
  3. 执行外部操作: 查询数据库、调用 API、更新记录。

这就是 Chain 的局限性——它没有“思考”和“行动”的能力。它只能按部就班。而 Agent,正是来解决这个问题的!

Agent 的核心思想:LLM 的“思考”与工具的“行动”

Agent 的核心理念是:让大型语言模型(LLM)成为一个“推理引擎”,它能够根据用户的输入和可用的“工具”来决定下一步该做什么。 简单来说,LLM 扮演了大脑的角色,而“工具”则扮演了手脚的角色。

Agent 的工作流程是一个动态的、迭代的**“思考-行动”循环 (Reasoning-Action Loop)**:

  1. 观察 (Observation): Agent 接收到用户的输入,或者上一步工具执行的结果。
  2. 思考 (Thought): LLM 根据当前观察到的信息,结合它被赋予的“人设”和“工具箱”,进行推理,决定下一步应该采取什么行动。它会思考:“我应该用哪个工具?工具的输入参数是什么?”
  3. 行动 (Action): LLM 决定好行动后,就会调用一个或多个工具,并提供相应的输入。
  4. 循环: 工具执行完毕,返回结果(Observation),Agent 再次进入“思考”阶段,直到它认为任务已经完成,或者无法继续。

ReAct 范式:Agent 的黄金法则

在 Agent 的世界里,有一个非常著名的范式叫做 ReAct (Reasoning and Acting),它完美地诠释了上述的“思考-行动”循环。

ReAct 的核心思想是:LLM 不仅要生成最终答案,还要在生成答案的过程中,清晰地展示它的**“思考过程 (Thought)”、它决定采取的“行动 (Action)”,以及行动后得到的“观察结果 (Observation)”**。这个过程反复进行,直到 LLM 认为可以给出最终的“回答 (Final Answer)”。

graph TD
    A[用户请求: "查询订单号XYZ的物流状态"] --> B(Agent Executor)
    B --> C{LLM (大语言模型)}
    C -- "Thought: 用户想查订单状态,我需要一个查询订单的工具。" --> D[Action: 调用查询订单工具]
    D --> E(Tool: query_order_status(订单号=XYZ))
    E -- "Observation: 订单状态为:已发货,预计明天送达。" --> C
    C -- "Thought: 我已获取订单状态,现在可以回复用户了。" --> F[Action: Final Answer]
    F -- "Final Answer: 您的订单XYZ已发货,预计明天送达。" --> G(用户)
    C -- "如果需要更多信息或用户后续追问" -- C

ReAct 流程图解析:

  1. 用户请求: Agent 接收到用户的原始问题。
  2. LLM (Thought): LLM 分析用户请求,结合其被赋予的系统提示和可用工具列表,进行推理。它会生成一个“思考”文本,表明它理解了什么,以及下一步打算做什么。
  3. Action (工具调用): 基于“思考”,LLM 决定调用哪个工具,并构造工具的输入参数。LangChain 的 Agent Executor 会解析这个 Action,并实际去执行对应的工具。
  4. Tool (工具执行): 注册好的工具被调用,执行其内部逻辑(例如:查询数据库、调用外部 API)。
  5. Observation (观察结果): 工具执行完毕,返回结果。这个结果将作为新的输入,再次传递给 LLM。
  6. LLM (Thought) & Action (循环): LLM 再次进行思考,根据新的 Observation 决定是继续调用其他工具,还是已经可以给出最终答案。这个循环会一直持续,直到 LLM 生成一个“Final Answer”的 Action。
  7. Final Answer: Agent 最终将这个答案返回给用户。

这种迭代的 ReAct 模式赋予了 Agent 强大的动态决策能力,让它能够像人类一样,一步步地思考、尝试、观察,最终解决复杂问题。

Agent 的构成要素

一个 LangChain Agent 主要由以下几部分组成:

  • LLM (Large Language Model): 这是 Agent 的“大脑”,负责所有的推理和决策。
  • Tools (工具): 这是 Agent 的“手脚”,封装了各种外部能力,如查询数据库、调用 API、执行代码等。每个工具都有一个描述,告诉 LLM 它的功能、输入参数等。
  • Agent Executor (代理执行器): 这是 Agent 的“调度中心”,它负责驱动整个“思考-行动”循环。它会接收 LLM 的 Action,执行对应的 Tool,然后将 Tool 的 Observation 返回给 LLM。
  • Agent Type (代理类型): LangChain 提供了多种预设的 Agent 类型,它们定义了 LLM 如何被提示,以及如何解析 LLM 的输出以提取 Thought、Action 和 Final Answer。最常用的是 zero-shot-react-description (基于 ReAct 范式) 和 OpenAIFunctionsAgent (利用 OpenAI 函数调用能力)。

在我们的智能客服项目中,Agent 将是核心大脑,它将协调各种工具来响应用户的复杂需求。

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

现在,我们把 Agent 的理论落地到我们的“智能客服知识库”项目。

设想一下,我们的智能客服小助手,除了能回答基于知识库的常见问题外,用户还希望它能处理以下事务:

  1. 查询商品库存: 用户询问某款商品是否有货。
  2. 查询订单状态: 用户提供订单号,查询物流信息。

这些都需要调用我们后端系统的 API。好,我们来为 Agent 构建这些“手脚”!

我们将使用 Python 进行演示,因为 LangChain 的 Python 生态更为成熟和广泛。

import os
from dotenv import load_dotenv

# 加载环境变量,确保安全地使用 API 密钥
load_dotenv()

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# 导入必要的模块
from langchain.agents import AgentExecutor, create_react_agent
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI

# 为了演示,我们先模拟一些外部 API
# 实际项目中,这些会是调用你的后端服务或数据库的函数

@tool
def search_product_inventory(product_name: str) -> str:
    """
    根据商品名称查询商品的当前库存量。
    输入参数:
    - product_name (str): 要查询的商品名称。
    返回:
    - str: 包含商品库存信息的字符串,例如 "iPhone 15 Pro Max 当前库存 50 件。"
    """
    print(f"\n--- 调用工具: search_product_inventory(product_name='{product_name}') ---")
    # 模拟数据库查询或 API 调用
    inventory_data = {
        "iPhone 15 Pro Max": "50",
        "华为 MateBook X Pro": "20",
        "小米手环 8 Pro": "120",
        "索尼 WH-1000XM5 耳机": "35",
        "戴森吹风机": "缺货",
        "Apple Watch Ultra 2": "15"
    }
    result = inventory_data.get(product_name, "抱歉,未找到该商品或无法获取库存信息。")
    if result == "缺货":
        return f"抱歉,{product_name} 目前缺货。"
    elif "未找到" in result:
        return result
    else:
        return f"{product_name} 当前库存 {result} 件。"

@tool
def get_order_status(order_id: str) -> str:
    """
    根据订单ID查询订单的当前状态和物流信息。
    输入参数:
    - order_id (str): 要查询的订单编号。
    返回:
    - str: 包含订单状态和物流信息的字符串。
    """
    print(f"\n--- 调用工具: get_order_status(order_id='{order_id}') ---")
    # 模拟外部物流 API 调用
    order_data = {
        "20231026001": "您的订单已于10月26日发货,快递单号SF123456789,预计10月28日送达。",
        "20231025002": "您的订单正在打包中,预计今天下午发出。",
        "20231024003": "抱歉,该订单已取消,请联系客服处理。",
        "20231023004": "您的订单已于10月23日签收,感谢您的购买!",
    }
    return order_data.get(order_id, "抱歉,未找到该订单号或订单信息。")

# 将工具列表化
tools = [search_product_inventory, get_order_status]

# 初始化 LLM
# 建议使用支持函数调用的模型,如 gpt-3.5-turbo 或 gpt-4
# 确保你的 OPENAI_API_KEY 已经设置在环境变量中
llm = ChatOpenAI(model="gpt-3.5-turbo-1106", temperature=0) # temperature=0 让模型更“理性”

# 1. 定义 Agent 的 Prompt
# LangChain 提供了一个 create_react_agent 函数,它会帮我们构建一个符合 ReAct 范式的 prompt
# 但我们也可以自定义,这里我们用 create_react_agent 推荐的结构,并稍作修改以适应中文语境和客服场景
prompt_template = ChatPromptTemplate.from_messages(
    [
        ("system", "你是一个智能客服助手,你的任务是高效、准确地回答用户的问题并处理他们的请求。你可以使用提供的工具来获取实时信息或执行操作。如果问题无法通过工具解决,请尽量给出友好且有帮助的回复。"),
        MessagesPlaceholder("chat_history"), # 预留给后续的聊天历史,本期暂不涉及
        ("human", "{input}"),
        MessagesPlaceholder("agent_scratchpad"), # 这是 Agent 内部思考和行动的“草稿本”
    ]
)

# 2. 创建 Agent
# create_react_agent 函数会结合 LLM、Tools 和 Prompt 来创建一个 Agent Runnable
agent = create_react_agent(llm, tools, prompt_template)

# 3. 创建 Agent Executor
# Agent Executor 是实际运行 Agent 的引擎,它会驱动 Thought-Action-Observation 循环
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)

print("--- 智能客服 Agent 启动!请开始提问(输入 'exit' 退出)---")

# 模拟与智能客服的交互
while True:
    user_query = input("\n用户:")
    if user_query.lower() == 'exit':
        print("客服助手:再见!")
        break

    try:
        # 调用 Agent Executor 来处理用户查询
        # chat_history 暂时为空,后续课程会加入内存管理
        response = agent_executor.invoke({"input": user_query, "chat_history": []})
        print(f"客服助手:{response['output']}")
    except Exception as e:
        print(f"客服助手:抱歉,处理您的请求时遇到问题:{e}")
        print("请尝试换一种方式提问,或稍后再试。")

代码解析:

  1. 工具定义 (@tool 装饰器): 我们定义了 search_product_inventoryget_order_status 两个函数,并用 LangChain 的 @tool 装饰器将其转换为 Agent 可识别的工具。
    • 关键点: 每个工具函数必须有清晰的 docstring (文档字符串)。这个 docstring 会被 LLM 用来理解工具的功能、输入参数以及何时使用它。这是 Agent 正确决策的基础!参数类型注解也很重要。
    • 模拟外部系统: 在实际项目中,这些函数会调用你的数据库、微服务 API 等。这里我们用 Python 字典模拟了数据。
  2. LLM 初始化: 我们使用 ChatOpenAI 作为 LLM。推荐使用支持函数调用的模型(如 gpt-3.5-turbo-1106gpt-4),它们在工具调用方面表现更佳。
  3. Prompt 定义 (ChatPromptTemplate):
    • 我们构建了一个 ChatPromptTemplate,它包含了系统指令 (system) 和用户输入 (human)。
    • MessagesPlaceholder("agent_scratchpad") 是一个非常关键的占位符。Agent Executor 会将每次 Thought、Action 和 Observation 的历史记录填充到这里,从而形成完整的 ReAct 循环。
    • MessagesPlaceholder("chat_history") 预留给后续的内存管理,本期我们暂时留空。
  4. 创建 Agent (create_react_agent): LangChain 提供了 create_react_agent 这个便捷函数,它会根据 ReAct 范式,将 LLM、工具和 Prompt 组装成一个 Agent 的“Runnable”。
  5. 创建 Agent Executor (AgentExecutor): 这是 Agent 的运行引擎。
    • agent: 传入我们创建好的 Agent Runnable。
    • tools: 传入 Agent 可以使用的工具列表。
    • verbose=True: 这个非常重要! 在开发和调试阶段,务必设置为 True。它会打印出 Agent 内部详细的 Thought、Action、Observation 过程,让你清晰地看到 LLM 是如何思考和调用工具的。
    • handle_parsing_errors=True: 允许 Agent 在解析 LLM 输出失败时进行重试或处理。

运行演示:

当你运行上述代码并输入以下问题时,你会看到 Agent 的思考过程:

用户:苹果手机现在还有货吗?

你会看到 LLM 思考:“用户想查库存,我应该用 search_product_inventory 工具,参数是 product_name='iPhone 15 Pro Max'。”然后工具被调用,返回库存信息。

用户:查一下20231026001这个订单的物流

LLM 思考:“用户想查订单,我应该用 get_order_status 工具,参数是 order_id='20231026001'。”然后工具被调用,返回物流信息。

用户:你好,请问你们公司是做什么的?

LLM 会发现这个问题不需要调用任何工具,直接回答。

通过 verbose=True,你将亲眼见证 Agent 如何从一个问题,经过思考,选择合适的工具,执行工具,再根据工具结果继续思考,最终给出答案。这就像给 LLM 装上了“眼睛”和“双手”,让它真正地“活”了起来!

坑与避坑指南

恭喜你,已经掌握了 Agent 的基本构建!但别高兴得太早,Agent 虽然强大,但也并非万能,它是一个“熊孩子”,需要你精心调教。这里有几个常见的“坑”和“避坑指南”,请务必牢记:

1. 工具描述模糊或不准确

  • 坑: LLM 完全依赖工具的 docstring 来理解工具的功能和何时使用。如果 docstring 写得含糊不清、有歧义,或者参数描述不准确,LLM 很容易错误地调用工具,或者根本不知道何时使用。例如,def search(query: str) 这样的工具,LLM 不知道 query 是指商品名、订单号还是别的什么。
  • 避坑指南:
    • 像写 API 文档一样写工具描述: 详细说明工具的作用、它能解决什么问题、它的输入参数(类型、示例)以及返回结果的含义。
    • 明确边界: 指出工具不能做什么,或者在什么情况下不应该使用。
    • 使用具体示例: 在描述中加入一些使用场景的示例,帮助 LLM 更好地理解。
    • 参数命名清晰: product_namename 更明确。

2. Prompt 工程不足,Agent “人设”崩塌

  • 坑: 如果系统提示词 (System Prompt) 写得不好,Agent 可能不知道自己的角色、目标,或者在遇到工具无法解决的问题时表现得不知所措。它可能会“幻觉”出不存在的工具,或者在不该使用工具时乱用。
  • 避坑指南:
    • 明确角色与目标: 在 System Prompt 中清晰地定义 Agent 的身份(“你是一个智能客服助手”)、它的主要任务(“高效、准确地回答用户问题”),以及优先级(“优先使用工具获取信息”)。
    • 指明处理未知情况: 告诉 Agent 在无法使用工具时应该如何应对,例如“如果问题无法通过工具解决,请尽量给出友好且有帮助的回复。”
    • 引导工具使用: 可以通过示例或指令,轻微引导 LLM 在特定场景下考虑使用哪些工具。

3. 工具粒度不当

  • 坑:
    • 工具粒度过粗: 一个工具包含太多功能,导致 LLM 难以精准控制。例如,一个 handle_customer_request(request_type: str, details: str) 工具,LLM 很难在 details 中正确地构造复杂请求。
    • 工具粒度过细: 过多的细小工具会让 LLM 在决策时负担过重,增加思考的复杂度和成本。
  • 避坑指南:
    • 单一职责原则: 每个工具应该只做一件事,并把它做好。例如,search_product_inventoryget_order_status 是两个独立的工具。
    • 操作与信息获取分离: 尽量将查询类工具和修改/操作类工具分开。
    • 考虑用户意图: 工具的设计应该与用户可能提出的高层级意图相对应。

4. 陷入循环或无法得出结论

  • 坑: Agent 可能会在 Thought-Action-Observation 循环中反复横跳,无法收敛到最终答案。这通常发生在 LLM 无法正确解析工具结果,或者工具返回的信息不足以让 LLM 做出下一步决策时。
  • 避坑指南:
    • 设置 max_iterationsAgentExecutor 中设置 max_iterations 参数(例如 max_iterations=10),防止无限循环。达到最大迭代次数后,Agent 会停止并返回一个错误或部分结果。
    • 清晰的工具输出: 确保工具的输出信息是清晰、简洁、无歧义的,能直接帮助 LLM 进行下一步判断。
    • 错误处理: 工具内部应有健壮的错误处理机制,即使外部 API 调用失败,也能返回有意义的错误信息给 LLM,而不是抛出异常或返回空。
    • Prompt 引导结束: 在 Prompt 中明确告诉 LLM 何时应该生成 Final Answer

5. 性能与成本考量

  • 坑: 每次 Thought 都是一次 LLM 调用,每次 Action 都会执行一个工具(可能也是一次外部 API 调用)。如果 Agent 迭代次数过多,会显著增加延迟和成本。
  • 避坑指南:
    • 优化 Prompt: 精简 Prompt,减少 LLM 不必要的思考。
    • 高效工具: 确保工具执行速度快,减少外部 API 的延迟。
    • 缓存: 对频繁查询且结果不常变动的工具,考虑加入缓存机制。
    • 选择合适的 LLM: 对于不需要极高智能的步骤,可以考虑使用更轻量、更便宜的模型。

Agent 的调教是一个迭代的过程,需要你不断地尝试、观察 verbose=True 的输出、调整 Prompt 和工具描述。把它当成你的学徒,多给它反馈,它会变得越来越聪明!

📝 本期小结

各位,今天我们完成了一次智能体世界的深度探索!我们不仅理解了 LangChain Agents 的核心原理——LLM 的“思考”与工具的“行动”相结合,实现了动态决策的能力,还亲手为我们的智能客服小助手打造了“手脚”,让它能够查询库存、获取订单状态。

Agent 的引入,将我们的智能客服从一个被动的问答机器,提升为一个能够主动解决问题、执行任务的“智能助理”。它不再局限于知识库中的死板信息,而是能够与外部世界交互,获取实时数据,甚至执行复杂操作。这是构建真正生产级 AI 应用的关键一步!

当然,我们也看到了 Agent 背后需要精细调教的一面。工具的描述、Prompt 的设计、粒度的把握,以及对性能和稳定性的考量,都是我们在实际项目中需要面对和克服的挑战。

下期预告: 尽管 Agent 已经很强大,但它目前还是个“金鱼脑”,不记事儿。用户和它多聊几句,它就忘了之前说过什么。在下一期,我们将深入探讨 LangChain 的内存管理 (Memory),教你如何让你的智能客服小助手拥有“长期记忆”,实现真正的多轮对话,让用户体验更上一层楼!敬请期待!