LangChain 系列教程(七):Callbacks
介绍
Callbacks 是一个事件监听系统。它允许你在 LangChain 应用生命周期的特定节点(事件发生时)挂载自定义的函数,从而实现对内部状态的观察和干预。Callbacks 允许我们在LLM的各个阶段使用各种各样的“钩子”,从而达实现日志的记录、监控以及流式传输等功能。
为什么 Callbacks 如此重要?
日志与调试 (Logging & Debugging):精确记录每一个步骤的输入、输出和错误,是调试复杂 Agent 的基础。
监控与分析 (Monitoring & Analysis):可以实时计算 Token 消耗、API 调用延迟和费用,并将这些数据发送到监控平台(如Prometheus, Datadog)。
流式响应 (Streaming):实现像 ChatGPT 一样的打字机效果,将模型生成的词元(tokens)逐一实时地展示给用户,极大提升用户体验。
UI 交互:在前端界面上实时显示 Agent 的当前状态,例如“正在使用搜索工具...”、“正在分析数据...”。
自定义行为:在事件发生时执行自定义逻辑,例如,当工具调用失败时,自动触发重试或发送警报。
Callbacks 的核心:CallbackHandler
在 LangChain 中,所有的回调逻辑都通过继承 BaseCallbackHandler 类来实现。你只需在你自己的 Handler 类中,重写你关心的事件方法即可。
一些最常用的事件方法包括:
on_llm_start(serialized, prompts, **kwargs):当LLM开始调用时触发。
on_llm_new_token(token, **kwargs):当LLM生成一个新的词元时触发(用于流式输出)。
on_llm_end(response, **kwargs):当LLM调用完成时触发。
on_chain_start(serialized, inputs, **kwargs):当链开始运行时触发。
on_chain_end(outputs, **kwargs):当链结束运行时触发。
on_tool_start(serialized, input_str, **kwargs):当工具开始执行时触发。
on_tool_end(output, **kwargs):当工具执行结束时触发。
on_agent_action(action, **kwargs):当Agent决定一个行动时触发。
on_agent_finish(finish, **kwargs):当Agent完成任务时触发。
代码
创建自定义 Callback Handler
Python from langchain.callbacks.base import BaseCallbackHandler from langchain_core.agents import AgentAction, AgentFinish from langchain_core.outputs import LLMResult from typing import Any, Dict, List, Union
# 为了让输出更美观,定义一些颜色 class Colors: GREEN = "\033[92m" YELLOW = "\033[93m" BLUE = "\033[94m" RESET = "\033[0m"
# 继承 BaseCallbackHandler 来创建我们自己的处理器 class MyCustomCallbackHandler(BaseCallbackHandler): """一个自定义的回调处理器,用于打印 Agent 执行过程中的详细信息。""" def on_chain_start(self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs: Any) -> None: """ 当链条(Chain)开始运行时触发。 对于 AgentExecutor,这是整个执行流程的入口。 参数: - serialized: 包含链条的配置信息,如ID和名称。 - inputs: 传递给链条的初始输入数据,例如 {'input': '用户的问题'}。 """ print(f"{Colors.GREEN}[链条启动] 进入链条 '{serialized.get('name', 'N/A')}',输入为: {inputs}{Colors.RESET}")
def on_agent_action(self, action: AgentAction, **kwargs: Any) -> None: """ 当 Agent 决定执行一个动作(Action)时触发。这是 ReAct 框架中的核心事件。 参数: - action (AgentAction): Agent 的决策对象,包含以下关键信息: - tool (str): Agent 决定使用的工具名称。 - tool_input (str): Agent 准备传递给工具的输入。 - log (str): Agent 的“思考”过程,解释了它为什么选择这个工具和输入。 """ print(f"{Colors.YELLOW}[智能体行动] 思考: {action.log.strip()}{Colors.RESET}") print(f"{Colors.BLUE}\t动作: 使用工具 '{action.tool}',输入为: {action.tool_input}{Colors.RESET}")
def on_tool_start(self, serialized: Dict[str, Any], input_str: str, **kwargs: Any) -> None: """ 当工具(Tool)即将开始执行时触发。 参数: - serialized: 包含被调用工具的配置信息,主要是工具的名称。 - input_str: 传递给工具的、具体的输入字符串。 """ print(f"{Colors.GREEN}[工具启动] 正在使用工具 '{serialized.get('name', 'N/A')}',输入为: '{input_str}'{Colors.RESET}")
def on_tool_end(self, output: str, **kwargs: Any) -> None: """ 当工具执行结束时触发。 参数: - output (str): 工具执行后返回的输出字符串。这个输出将作为“观察结果”(Observation) 返回给 Agent, 用于下一轮的“思考”。 """ print(f"{Colors.GREEN}[工具结束] 工具返回结果 (观察): {output}{Colors.RESET}")
def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> None: """ 当 Agent 决定任务已经完成,并给出最终答案时触发。 参数: - finish (AgentFinish): Agent 的最终完成对象,包含以下信息: - return_values (dict): 一个包含最终输出的字典,通常键为 'output'。 - log (str): Agent 在得出最终答案前的最后一步思考,总结了整个过程。 """ print(f"{Colors.YELLOW}[智能体完成] 最终思考: {finish.log.strip()}{Colors.RESET}") print(f"{Colors.BLUE}\t最终答案: {finish.return_values['output']}{Colors.RESET}")
def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> None: """ 当链条(Chain)结束运行时触发。 参数: - outputs: 链条执行完毕后的最终输出字典。 """ print(f"{Colors.GREEN}[链条结束] 链条执行完毕。输出为: {outputs}{Colors.RESET}")
print("自定义 Callback Handler定义完成!") |
构建并运行带有 Callback 的 Agent
Python import os from dotenv import load_dotenv from typing import Any, Dict
from langchain_openai import ChatOpenAI from langchain import hub from langchain.agents import Tool, create_react_agent, AgentExecutor from langchain.chains import LLMMathChain from langchain_community.tools import DuckDuckGoSearchRun
# 从 .env 文件加载环境变量 (如 OPENAI_API_KEY) load_dotenv()
# 初始化 LLM,作为 Agent 的“大脑” # temperature=0 使得模型输出更具确定性,适合执行精确任务 llm = ChatOpenAI(temperature=0, model="gpt-4o")
print("环境准备完毕,LLM 已初始化。")
# --- 定义 Agent 可以使用的工具 --- # 每个工具都应有清晰的名称和描述,以便 Agent 理解其用途 search = DuckDuckGoSearchRun() llm_math_chain = LLMMathChain.from_llm(llm=llm, verbose=False) tools = [ Tool(name="Search", func=search.run, description="搜索引擎,用于查询未知信息。"), Tool(name="Calculator", func=llm_math_chain.run, description="计算器,用于执行数学运算。") ]
# 从 LangChain Hub 拉取标准的 ReAct 框架提示 prompt = hub.pull("hwchase17/react")
# 创建 Agent 的核心逻辑,它将 LLM、工具和提示组合在一起 agent = create_react_agent(llm, tools, prompt)
# 实例化我们自定义的回调处理器 my_corrected_handler = MyCustomCallbackHandler()
# 创建 Agent 执行器 (AgentExecutor),这是驱动 Agent 运行的引擎 agent_executor = AgentExecutor( agent=agent, # 要执行的 Agent 核心 tools=tools, # Agent 可以使用的工具列表 callbacks=[my_corrected_handler], # 挂载我们自定义的回调处理器,以实现详细日志记录 verbose=False, # 关闭 LangChain 默认的日志,避免与我们的自定义日志重复 handle_parsing_errors=True # 关键参数:当 Agent 输出格式错误时,将错误信息返回给 Agent 让其重试,而不是直接崩溃 )
print("Agent 及执行器已准备就D就绪。")
# 定义一个需要 Agent 进行多步推理和工具调用的问题 input_question = "目前世界排名第一的男子网球选手是谁?他赢得了多少个大满贯冠军?"
# 执行 Agent result = agent_executor.invoke({"input": input_question})
# 打印最终的答案 print("\n\n----------- 最终答案 -----------") print(result["output"]) |
结果
SQL 环境准备完毕,LLM 已初始化。 /opt/anaconda3/envs/langchain-mcp/lib/python3.11/site-packages/langsmith/client.py:272: LangSmithMissingAPIKeyWarning: API key must be provided when using hosted LangSmith API warnings.warn( Agent 及执行器已准备就D就绪。 [链条启动] 进入链条 'N/A',输入为: {'input': '目前世界排名第一的男子网球选手是谁?他赢得了多少个大满贯冠军?'} [智能体行动] 思考: 要回答这个问题,我需要查找当前世界排名第一的男子网球选手以及他赢得的大满贯冠军数量。 Action: Search Action Input: "current world number one male tennis player 2023" 动作: 使用工具 'Search',输入为: current world number one male tennis player 2023 [智能体行动] 思考: 根据搜索结果,目前世界排名第一的男子网球选手是Jannik Sinner。接下来,我需要查找他赢得的大满贯冠军数量。 Action: Search Action Input: "Jannik Sinner Grand Slam titles" 动作: 使用工具 'Search',输入为: Jannik Sinner Grand Slam titles [智能体完成] 最终思考: I now know the final answer.
Final Answer: 目前世界排名第一的男子网球选手是Jannik Sinner,他赢得了三个大满贯冠军。 最终答案: 目前世界排名第一的男子网球选手是Jannik Sinner,他赢得了三个大满贯冠军。 [链条结束] 链条执行完毕。输出为: {'output': '目前世界排名第一的男子网球选手是Jannik Sinner,他赢得了三个大满贯冠军。'}
----------- 最终答案 ----------- 目前世界排名第一的男子网球选手是Jannik Sinner,他赢得了三个大满贯冠军。 |