代理就其特性而言,通过工具与外部系统交互。无论是查询数据库、调用网络API、执行代码,还是访问文件系统,这些交互都在代理核心逻辑的受控环境之外运行。因此,在生产级代理系统中,工具执行是故障的常见原因。API可能不可用,数据库可能遇到连接问题,输入可能无效,或者工具本身可能包含程序错误。构建能够妥善处理这些不可避免的错误并尝试恢复的代理,对于创建可靠的应用程序来说非常重要。如果缺少可靠的错误处理机制,代理在遇到单一工具故障时可能完全崩溃,过早放弃其任务,或者更糟的是,进入一个无效益的循环。本节介绍了在LangChain代理中检测、管理和从工具错误中恢复的策略。工具执行错误的来源故障可能源自代理与工具交互生命周期中的各个环节:网络与连接: 超时、DNS解析失败或无法访问支持工具的服务(例如,外部API)。外部服务错误: 工具成功联系到服务,但服务返回错误(例如,HTTP 4xx客户端错误,如401 Unauthorized(未授权)、429 Too Many Requests(请求过多),或HTTP 5xx服务器错误)。输入校验问题: 大型语言模型(LLM)为工具生成的参数不符合工具的预期模式或约束。这对于结构化工具尤为重要。输出处理故障: 工具成功执行,但返回了非预期格式的数据,导致代理的解析逻辑或大型语言模型无法正确理解。工具内部逻辑错误: 实现工具功能的自定义代码中的程序错误,在执行过程中导致未处理的异常。资源限制: 工具执行可能超出分配的时间、内存或其他资源限制。LangChain中错误的检测与上报LangChain提供了机制来捕获和报告源自工具的错误。当工具的执行方法(例如,_run或_arun)抛出异常时,AgentExecutor通常会捕获它。默认情况下,执行器会将异常(通常是ToolException)封装,并将其格式化为观察字符串。这个字符串随后会在下一个推理步骤中回传给大型语言模型,有效地告知代理其尝试的操作失败了。原始堆栈跟踪可能根据执行器的配置包含或概括。例如,如果一个旨在获取天气数据的工具因网络超时而失败,提供给大型语言模型的观察结果可能如下所示:Observation: 观察结果: 错误: 工具 'weather_api' 因以下错误而失败: 连接天气服务API时请求超时。 Thought: 思考: 天气API工具因超时而失败。我应该再试一次,或许使用更简短的查询,或者考虑是否有其他方式获取信息。如果再次失败,我可能需要通知用户目前无法获取天气数据。有效的错误检测也依赖于全面的日志记录和追踪。检查代理的执行轨迹,包括输入、思考、工具调用以及由此产生的错误或观察结果,对于调试代理失败的原因以及它是如何尝试处理这种情况来说非常重要。LangSmith之类的工具(第5章会介绍)对于在开发和生产环境中捕获和分析这些轨迹非常有用。代理内部的错误处理策略一旦检测到错误,代理就需要一个处理策略。主要机制是让大型语言模型根据观察结果中提供的错误消息,对失败情况进行推理。告知大型语言模型默认行为通常足以应对简单情况。大型语言模型接收错误消息并将其纳入其推理过程。根据其指令和错误上下文,它可能会决定:重试相同的工具: 如果错误暗示输入问题,或许带有修改后的参数。使用不同的工具: 如果存在替代方案以达成相同的子目标。请求澄清: 如果错误含糊不清或需要用户输入。修改其计划: 如果失败的步骤表明当前方法存在缺陷。报告失败: 如果它用尽了选项或确定任务无法完成。传递给大型语言模型的错误消息质量非常重要。过于冗长的堆栈跟踪会占用宝贵的上下文窗口空间,并可能让大型语言模型感到困惑。通常更偏好简洁、信息丰富的错误消息。您可以通过子类化代理或执行器,或通过封装工具来定制工具异常的格式。代理执行器错误处理参数AgentExecutor本身提供了参数来影响错误处理:handle_parsing_errors:此参数专门处理当代理无法解析大型语言模型输出时发生的错误(例如,大型语言模型的响应未正确格式化所需的工具名称或参数)。它不直接处理来自工具执行本身的错误。将其设置为True会向大型语言模型提供一个默认错误消息。您也可以提供自定义字符串或函数以获得更定制化的反馈,指导大型语言模型更正其输出格式。max_iterations:限制代理可以执行的步骤数量(大型语言模型调用 + 工具调用)。这可以防止无限循环,这种循环有时可能由失败的工具调用和无效重试循环触发。max_execution_time:为整个代理运行设置时间限制,防止代理无限期地卡住,可能由于重复的工具超时。实现自定义错误处理逻辑为了更精细的控制,您可以实现自定义错误处理,而不是仅仅依靠大型语言模型对错误字符串的反应。重试机制: 使用重试装饰器或函数封装您的工具执行逻辑。这对于暂时性问题(如网络中断或临时速率限制)特别有效。指数退避(在重试之间逐渐延长等待时间)是一种标准做法。import time import random from requests.exceptions import RequestException from langchain.tools import BaseTool def retry_with_backoff(retries=3, initial_delay=1, backoff_factor=2, jitter=0.1): def decorator(func): def wrapper(*args, **kwargs): delay = initial_delay for i in range(retries): try: return func(*args, **kwargs) except RequestException as e: # 示例:捕获特定的暂时性错误 if i == retries - 1: raise # 重新抛出最后一次异常 # 对延迟应用抖动 actual_delay = delay + random.uniform(-jitter * delay, jitter * delay) time.sleep(actual_delay) delay *= backoff_factor except Exception as e: # 如果需要,捕获其他潜在错误 # 以不同方式处理不可重试的错误或重新抛出 raise e return wrapper return decorator class MyApiTool(BaseTool): name = "my_api" description = "调用我的特殊API" @retry_with_backoff(retries=3, initial_delay=1) def _run(self, query: str) -> str: # 替换为使用'requests'或其他类似库的实际API调用逻辑 print(f"尝试API调用,查询内容: {query}") # 模拟潜在故障 if random.random() < 0.5: raise RequestException("模拟网络错误") return f"API调用成功,查询内容: {query}" async def _arun(self, query: str) -> str: # 实现异步版本,使用异步休眠(例如 asyncio.sleep) # 并在需要时进行适当的异步错误处理 raise NotImplementedError("异步版本尚未实现") # 在代理中使用时,需要创建一个实例: # tool = MyApiTool()备用工具: 设计代理的提示或逻辑,使其能识别特定错误类型并明确尝试替代工具。例如,如果主search_internal_docs工具失败,代理可能会被指示尝试更通用的web_search工具。优雅降级: 如果工具失败且没有替代方案,代理可以设计为提供部分响应或表明某个特定信息不可用,而不是导致整个任务失败。结构化错误报告: 除了字符串之外,让工具在失败时返回结构化错误对象。这需要在代理循环中进行自定义处理,但允许大型语言模型或自定义逻辑基于错误代码或类型更准确地响应。设计健壮的工具主动预防错误通常比被动处理更有效。在开发自定义工具时:添加输入校验: 在工具定义中(特别是对于StructuredTool)使用Pydantic等库,在尝试执行之前,校验大型语言模型提供的参数。如果校验失败,返回信息丰富的错误消息。处理预期异常: 在工具代码中,将外部调用(API、数据库查询)封装在try...except块中。捕获特定异常(例如,requests.exceptions.Timeout、sqlalchemy.exc.OperationalError),并返回清晰、可操作的错误消息,而不是让原始异常冒泡传递。提供清晰的错误消息: 确保工具返回的错误消息信息足够丰富,以便大型语言模型(或自定义逻辑)理解失败的性质。考虑幂等性: 如果工具修改外部状态,尽可能使其幂等(可以安全重试而不会产生意外副作用)。代理恢复流程示例包含错误处理的典型流程可能如下所示:digraph G { rankdir=LR; node [shape=box, style=rounded, fontname="sans-serif", color="#495057", fillcolor="#e9ecef", style=filled]; edge [fontname="sans-serif", color="#495057"]; Start [label="代理开始"]; Think [label="LLM思考/ 规划下一步"]; ChooseTool [label="LLM选择工具 及参数"]; ExecuteTool [label="执行工具"]; HandleError [label="处理错误"]; UpdateState [label="更新代理状态/ 草稿板"]; End [label="代理结束/ 最终答案"]; Fail [label="报告失败/ 警报"]; Start -> Think; Think -> ChooseTool; ChooseTool -> ExecuteTool; ExecuteTool -> UpdateState [label="成功"]; ExecuteTool -> HandleError [label="失败", color="#f03e3e", fontcolor="#f03e3e"]; UpdateState -> Think [label="需要更多步骤"]; UpdateState -> End [label="任务完成"]; HandleError -> ExecuteTool [label="重试工具", color="#fd7e14", fontcolor="#fd7e14"]; HandleError -> ChooseTool [label="尝试备用工具", color="#fab005", fontcolor="#fab005"]; HandleError -> UpdateState [label="告知LLM错误 (继续推理)", color="#1c7ed6", fontcolor="#1c7ed6"]; HandleError -> Fail [label="无法恢复", color="#d6336c", fontcolor="#d6336c"]; }代理执行流程图,包含潜在工具故障和错误处理分支。有效处理工具错误能将代理从脆弱的原型转变为更健壮的系统,使其能够应对交互中的不确定性。通过结合向大型语言模型提供信息丰富的错误反馈、策略性地使用执行器参数、自定义处理逻辑(如重试),以及设计工具,您可以在生产环境中大幅提升LangChain代理的可靠性和性能。