随着您使用 LangChain 的高级功能(如 LangChain 表达式语言 (LCEL)、异步操作和自定义组件)构建更复杂的应用程序,理解执行流程变得越来越重要。使 LangChain 强大的分层抽象有时会掩盖链或代理运行时发生的细节。因此,有效的调试对于修复错误以及真正理解和优化应用程序的行为都必不可少。本节提供专门用于追踪和分析 LangChain 执行流程的方法与工具。调试 LLM 应用程序的挑战调试使用 LangChain 构建的应用程序与传统软件相比,带来独特的挑战:抽象层: 链和代理涉及多个步骤,通常会调用 LLM、检索器、解析器和工具。在此序列中识别问题发生的位置需要对每个步骤有可见性。中间数据: 组件之间传递的数据(发送给 LLM 的提示、LLM 原始输出、解析结果、检索到的文档)通常是隐藏的。检查这些中间数据对诊断非常重要。LLM 的非确定性: 即使输入相同,LLM 的输出也可能不同(由于温度设置或模型更新),这使得复现更困难。调试通常侧重于确保 LLM 的输入及其输出的解析是正确的。复杂的控制流程: 尤其是代理,其执行路径根据 LLM 的决策是动态的,这使得简单的线性调试变得困难。LangChain 原生调试辅助LangChain 包含内置机制,以提高执行期间的可见性。全局详细设置获得更多信息的简单方法是启用全局详细设置:import langchain langchain.verbose = True # 现在,当您运行链或代理时,详细日志将打印到标准输出 # 例如,显示发送给 LLM 的准确提示、LLM 的原始响应等。尽管易于启用,verbose=True 可能会产生大量输出,特别是对于涉及多次 LLM 调用或工具使用的复杂链或代理。它通常对初步查看有用,但对于有针对性的调试可能会变得难以管理。全局调试设置更结构化的替代方案是全局调试设置:import langchain langchain.debug = True # 运行组件现在将打印更结构化的调试信息, # 通常包括时间信息和更清晰的步骤划分。langchain.debug = True 通常提供比 verbose=True 更清晰、可能带有颜色编码的输出,使其更容易追踪执行路径。然而,与 verbose 一样,它是一个全局设置,仍然可能生成过多的信息。LangSmith:可观测性平台对于严肃的开发和生产监控,LangSmith 是推荐工具。虽然第 5 章详细讲解 LangSmith 用于评估和监控,但其追踪能力在开发期间对调试不可或缺。要使用 LangSmith,您通常需要设置环境变量:export LANGCHAIN_TRACING_V2="true" export LANGCHAIN_API_KEY="..." # 可选:设置项目名称 # export LANGCHAIN_PROJECT="我的高级应用"配置完成后,LangChain 会自动记录执行追踪到 LangSmith。网页界面允许您:可视化运行: 查看您的链或代理的整个执行图。检查输入/输出: 单击任何节点(LLM 调用、工具执行、检索器查询)以查看其接收到的准确输入和产生的输出。分析延迟: 识别哪些步骤耗时最长。筛选和搜索: 快速找到基于输入、输出或错误的特定运行。LangSmith 将调试从解释原始日志转变为交互式查看结构化追踪,显著加快了查找根本原因的过程。使用回调获取自定义信息为了对执行事件进行细粒度控制和程序化访问,LangChain 的回调系统功能强大。回调允许您介入 LangChain 生命周期的各个阶段(例如,on_llm_start、on_chain_end、on_agent_action)。您可以通过继承 BaseCallbackHandler 并覆盖对应于您想要拦截的事件的方法来实施自定义回调处理器。from langchain.callbacks.base import BaseCallbackHandler from typing import Any, Dict, List, Union from langchain.schema import AgentAction, AgentFinish, LLMResult class MyCustomHandler(BaseCallbackHandler): def on_llm_start(self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any) -> None: print(f"LLM Start: Sending {len(prompts)} prompts.") # 将提示记录到自定义位置,可能仅当它们满足特定条件时 def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None: print("LLM End:") # 记录令牌使用情况或成本估算 if response.llm_output: print(f" Tokens Used: {response.llm_output.get('token_usage')}") def on_chain_start(self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs: Any) -> None: print(f"Chain Start ({serialized.get('id', ['Unknown'])[2]}): Inputs keys: {list(inputs.keys())}") def on_agent_action(self, action: AgentAction, **kwargs: Any) -> None: print(f"Agent Action: Tool={action.tool}, Input={action.tool_input}") # 在此处添加工具输入的验证逻辑 # --- 用法 --- # from langchain.chat_models import ChatOpenAI # from langchain.chains import LLMChain # from langchain.prompts import PromptTemplate # llm = ChatOpenAI(temperature=0) # 假设 OPENAI_API_KEY 已设置 # prompt = PromptTemplate.from_template("Tell me a joke about {topic}") # chain = LLMChain(llm=llm, prompt=prompt) # handler = MyCustomHandler() # result = chain.run(topic="debugging", callbacks=[handler]) # print(f"\n最终结果:\n{result}") 回调提供精准控制。您可以记录特定的数据片段、触发外部警报、执行自定义验证,或正好在您需要的时间和地点收集细粒度性能指标,而无需依赖全局设置或外部平台。LangChain 还提供诸如 StdOutCallbackHandler 之类的标准处理器,它模仿 verbose=True 的一些行为,但可以进行选择性应用。LCEL 结构可视化当使用 LangChain 表达式语言 (LCEL) 构建复杂链时,理解结构本身可以作为调试的一个步骤。LCEL 对象有方法来帮助可视化它们的组成:from langchain.prompts import ChatPromptTemplate from langchain.chat_models import ChatOpenAI from langchain.schema.output_parser import StrOutputParser # 定义一个简单的 LCEL 链 prompt = ChatPromptTemplate.from_template("Explain {topic} simply.") model = ChatOpenAI() # 假设 OPENAI_API_KEY 已设置 output_parser = StrOutputParser() chain = prompt | model | output_parser # 获取图形的可打印表示 # print(chain.get_graph().draw_ascii()) # 或者生成一个 Graphviz 图(需要安装 graphviz 库) try: graph_viz_object = chain.get_graph().draw_graphviz() # 您可以将其保存到文件或在适当的环境中(如 Jupyter)显示 # graph_viz_object.render("chain_structure", format="png", view=True) # 保存并打开图像 graph_dot_string = graph_viz_object.source print("Graphviz Definition:\n") # 显示 Graphviz DOT 字符串以供表示 print(f'```graphviz\ndigraph G {{\ncompound=true;\n\t"{chain.first.id}" [label="ChatPromptTemplate" shape=box style=rounded];\n\t"{chain.middle[0].id}" [label="ChatOpenAI" shape=box style=rounded];\n\t"{chain.last.id}" [label="StrOutputParser" shape=box style=rounded];\n\t"{chain.first.id}" -> "{chain.middle[0].id}" [ltail=lc_0 lhead=lc_1];\n\t"{chain.middle[0].id}" -> "{chain.last.id}" [ltail=lc_1 lhead=lc_2];\n}} \n```') except ImportError: print("Graphviz library not found. Install with 'pip install graphviz'.") print("而是显示 ASCII 表示:") print(chain.get_graph().draw_ascii()) digraph G { compound=true; "ChatPromptTemplate-5740" [label="ChatPromptTemplate" shape=box style=rounded color="#4263eb" fontcolor="#4263eb"]; "ChatOpenAI-1213" [label="ChatOpenAI" shape=box style=rounded color="#7048e8" fontcolor="#7048e8"]; "StrOutputParser-9358" [label="StrOutputParser" shape=box style=rounded color="#1098ad" fontcolor="#1098ad"]; "ChatPromptTemplate-5740" -> "ChatOpenAI-1213" [ltail=lc_0 lhead=lc_1 color="#868e96"]; "ChatOpenAI-1213" -> "StrOutputParser-9358" [ltail=lc_1 lhead=lc_2 color="#868e96"]; }简单 LCEL 链结构的可视化:提示 -> 模型 -> 解析器。可视化图有助于确认组件按预期连接,这在进行复杂 LCEL 组合的运行时调试之前特别有用。传统调试方法不要忘记标准 Python 调试技术。当处理自定义组件(封装为 RunnableLambda 的函数、类或自定义子类)时,使用 print 语句、日志记录或 Python 调试器(如 pdb 或您 IDE 的调试器)通常是检查自定义代码内部变量和控制流程最直接的方法。整合:调试策略从 LangSmith 开始: 对于任何非简单应用程序,尽早设置 LangSmith 追踪。它提供执行流程的最佳概览。复现问题: 尝试使用特定输入可靠地复现错误或意外行为。追踪执行: 使用 LangSmith 检查运行追踪。查看导致失败点或错误输出之前每个步骤的输入和输出。隔离组件: 如果特定组件(例如,某个提示、工具调用、解析器)似乎有问题,尝试使用从追踪中识别出的有问题输入隔离运行它。使用回调处理具体情况: 如果 LangSmith 没有提供关于特定内部事件或计算的足够细节,请实施自定义回调处理器以记录或检查该特定数据片段。调试自定义代码: 如果问题在于您集成的自定义函数或类,请在该代码内部使用标准 Python 调试工具(print、logging、pdb)。检查组件配置: 仔细检查每个组件的配置(例如,LLM 参数、提示模板变量、检索器设置)。掌握这些调试方法对于超越简单的原型非常重要。随着您构建日益复杂的链和代理,有效追踪、检查和诊断执行流程的能力对于创建可靠且高性能的生产应用程序必不可少。