第 24 期 | LangServe 初探:一键部署你的 LangChain 应用
(申请发送: agentupdate)
🎯 本期学习目标
各位未来的 AI 大师们,欢迎来到《LangChain 全栈大师课》的第五站!今天我们要攻克的是 LangChain 中最激动人心、也最具魔力的概念之一——Agents (智能体)。学完本期,你将:
- 彻底理解 Agent 的核心工作机制: 从 LLM 的“大脑”到工具的“手脚”,掌握 Agent 如何进行思考、决策和行动。
- 熟练掌握自定义工具的创建与集成: 为你的智能客服小助手配备查询订单、修改资料等强大技能,让它不再是“纸上谈兵”。
- 构建一个能够动态决策、解决复杂问题的智能客服Agent: 让你的助手从固定脚本的“傻瓜”蜕变为能根据用户意图灵活应对的“智多星”。
- 识别并规避 Agent 设计中的常见陷阱: 避免智能体陷入“幻觉”、无效循环或错误工具调用,确保其稳定可靠地运行。
📖 原理解析
好了,伙计们,系好安全带,我们准备深入 LangChain 的智能体世界。
在之前的课程中,我们学习了如何构建 Chain,将 LLM 的能力串联起来。Chain 就像一条预设好的生产线,每一步做什么,输入输出是什么,都规划得清清楚楚。这对于完成明确、固定的任务非常有效。
但等等,现实世界是这样的吗?你的智能客服小助手,用户的问题总是那么规规矩矩、一成不变吗?
“我想查一下我上周买的那个蓝牙耳机的订单状态。” “我的收货地址填错了,能帮我改一下吗?” “这款新品鼠标什么时候有货?”
这些问题,哪个能用一个简单的 LLM 调用或一个固定 Chain 搞定?不行!它需要:
- 理解用户意图: 是要查订单,还是要改地址,还是要问库存?
- 获取外部信息: 订单状态、用户地址、商品库存,这些信息都在公司的数据库或第三方 API 里。
- 执行外部操作: 查询数据库、调用 API、更新记录。
这就是 Chain 的局限性——它没有“思考”和“行动”的能力。它只能按部就班。而 Agent,正是来解决这个问题的!
Agent 的核心思想:LLM 的“思考”与工具的“行动”
Agent 的核心理念是:让大型语言模型(LLM)成为一个“推理引擎”,它能够根据用户的输入和可用的“工具”来决定下一步该做什么。 简单来说,LLM 扮演了大脑的角色,而“工具”则扮演了手脚的角色。
Agent 的工作流程是一个动态的、迭代的**“思考-行动”循环 (Reasoning-Action Loop)**:
- 观察 (Observation): Agent 接收到用户的输入,或者上一步工具执行的结果。
- 思考 (Thought): LLM 根据当前观察到的信息,结合它被赋予的“人设”和“工具箱”,进行推理,决定下一步应该采取什么行动。它会思考:“我应该用哪个工具?工具的输入参数是什么?”
- 行动 (Action): LLM 决定好行动后,就会调用一个或多个工具,并提供相应的输入。
- 循环: 工具执行完毕,返回结果(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 -- "如果需要更多信息或用户后续追问" -- CReAct 流程图解析:
- 用户请求: Agent 接收到用户的原始问题。
- LLM (Thought): LLM 分析用户请求,结合其被赋予的系统提示和可用工具列表,进行推理。它会生成一个“思考”文本,表明它理解了什么,以及下一步打算做什么。
- Action (工具调用): 基于“思考”,LLM 决定调用哪个工具,并构造工具的输入参数。LangChain 的 Agent Executor 会解析这个 Action,并实际去执行对应的工具。
- Tool (工具执行): 注册好的工具被调用,执行其内部逻辑(例如:查询数据库、调用外部 API)。
- Observation (观察结果): 工具执行完毕,返回结果。这个结果将作为新的输入,再次传递给 LLM。
- LLM (Thought) & Action (循环): LLM 再次进行思考,根据新的 Observation 决定是继续调用其他工具,还是已经可以给出最终答案。这个循环会一直持续,直到 LLM 生成一个“Final Answer”的 Action。
- 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 的理论落地到我们的“智能客服知识库”项目。
设想一下,我们的智能客服小助手,除了能回答基于知识库的常见问题外,用户还希望它能处理以下事务:
- 查询商品库存: 用户询问某款商品是否有货。
- 查询订单状态: 用户提供订单号,查询物流信息。
这些都需要调用我们后端系统的 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("请尝试换一种方式提问,或稍后再试。")
代码解析:
- 工具定义 (
@tool装饰器): 我们定义了search_product_inventory和get_order_status两个函数,并用 LangChain 的@tool装饰器将其转换为 Agent 可识别的工具。- 关键点: 每个工具函数必须有清晰的
docstring(文档字符串)。这个docstring会被 LLM 用来理解工具的功能、输入参数以及何时使用它。这是 Agent 正确决策的基础!参数类型注解也很重要。 - 模拟外部系统: 在实际项目中,这些函数会调用你的数据库、微服务 API 等。这里我们用 Python 字典模拟了数据。
- 关键点: 每个工具函数必须有清晰的
- LLM 初始化: 我们使用
ChatOpenAI作为 LLM。推荐使用支持函数调用的模型(如gpt-3.5-turbo-1106或gpt-4),它们在工具调用方面表现更佳。 - Prompt 定义 (
ChatPromptTemplate):- 我们构建了一个
ChatPromptTemplate,它包含了系统指令 (system) 和用户输入 (human)。 MessagesPlaceholder("agent_scratchpad")是一个非常关键的占位符。Agent Executor 会将每次 Thought、Action 和 Observation 的历史记录填充到这里,从而形成完整的 ReAct 循环。MessagesPlaceholder("chat_history")预留给后续的内存管理,本期我们暂时留空。
- 我们构建了一个
- 创建 Agent (
create_react_agent): LangChain 提供了create_react_agent这个便捷函数,它会根据 ReAct 范式,将 LLM、工具和 Prompt 组装成一个 Agent 的“Runnable”。 - 创建 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_name比name更明确。
2. Prompt 工程不足,Agent “人设”崩塌
- 坑: 如果系统提示词 (System Prompt) 写得不好,Agent 可能不知道自己的角色、目标,或者在遇到工具无法解决的问题时表现得不知所措。它可能会“幻觉”出不存在的工具,或者在不该使用工具时乱用。
- 避坑指南:
- 明确角色与目标: 在 System Prompt 中清晰地定义 Agent 的身份(“你是一个智能客服助手”)、它的主要任务(“高效、准确地回答用户问题”),以及优先级(“优先使用工具获取信息”)。
- 指明处理未知情况: 告诉 Agent 在无法使用工具时应该如何应对,例如“如果问题无法通过工具解决,请尽量给出友好且有帮助的回复。”
- 引导工具使用: 可以通过示例或指令,轻微引导 LLM 在特定场景下考虑使用哪些工具。
3. 工具粒度不当
- 坑:
- 工具粒度过粗: 一个工具包含太多功能,导致 LLM 难以精准控制。例如,一个
handle_customer_request(request_type: str, details: str)工具,LLM 很难在details中正确地构造复杂请求。 - 工具粒度过细: 过多的细小工具会让 LLM 在决策时负担过重,增加思考的复杂度和成本。
- 工具粒度过粗: 一个工具包含太多功能,导致 LLM 难以精准控制。例如,一个
- 避坑指南:
- 单一职责原则: 每个工具应该只做一件事,并把它做好。例如,
search_product_inventory和get_order_status是两个独立的工具。 - 操作与信息获取分离: 尽量将查询类工具和修改/操作类工具分开。
- 考虑用户意图: 工具的设计应该与用户可能提出的高层级意图相对应。
- 单一职责原则: 每个工具应该只做一件事,并把它做好。例如,
4. 陷入循环或无法得出结论
- 坑: Agent 可能会在 Thought-Action-Observation 循环中反复横跳,无法收敛到最终答案。这通常发生在 LLM 无法正确解析工具结果,或者工具返回的信息不足以让 LLM 做出下一步决策时。
- 避坑指南:
- 设置
max_iterations: 在AgentExecutor中设置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),教你如何让你的智能客服小助手拥有“长期记忆”,实现真正的多轮对话,让用户体验更上一层楼!敬请期待!