尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

DART框架:如何让AI智能体在工具调用失败时自动恢复任务语义

DART框架:如何让AI智能体在工具调用失败时自动恢复任务语义 1. 项目概述当工具调用“失序”时我们如何找回语义在构建基于大语言模型的工具调用Tool Calling或智能体Agent系统时我们常常会遇到一个令人头疼的场景模型在规划多步任务时可能会“跑偏”。比如你让一个数据分析Agent“先查询上周的销售数据然后生成一份可视化报告”。模型可能正确地调用了“查询销售数据”的工具但在收到数据后它可能突然“忘记”了后续任务或者被数据中的某个细节带偏转而开始评论数据质量而不是继续调用“生成报告”的工具。这种“一步错步步错”的语义断层是当前结构化工具Agent在实际落地中的主要瓶颈之一。DART一个研究框架或方法论并非特指Dart编程语言所关注的“Semantic Recoverability for Structured Tool Agents”直译过来就是“面向结构化工具Agent的语义可恢复性”。这个概念的核心就是为Agent系统赋予一种“自愈”能力当任务执行因模型幻觉、外部工具异常或环境干扰而偏离预定语义轨道时系统能够自动检测到这种偏离并尝试将任务流程拉回正轨确保最终目标的达成。这不仅仅是错误处理更是对任务高层语义的持续追踪与维护。简单来说DART要解决的不是“工具调用失败了怎么办”那是传统的异常处理而是“工具调用序列的语义一致性如何保证”。这对于需要多轮交互、依赖外部状态、执行链条较长的复杂Agent应用如自动化运维、智能客服、复杂工作流编排至关重要。没有语义可恢复性Agent就像一个记忆力只有7秒的金鱼无法完成任何需要持续专注的复杂任务。2. 核心思路拆解从“流水线”到“带导航的越野车”传统的结构化工具Agent工作流很像一条预设的流水线解析用户指令 - 规划工具调用序列 - 依次执行工具 - 汇总结果。这条流水线是脆弱的任何一个环节的意外都可能导致整条线停摆或产出废品。DART的思路则是将这条流水线升级为一辆“带有多传感器导航和路径重规划能力的越野车”。2.1 语义偏离的根源分析要设计恢复机制首先得知道“病根”在哪。根据我的实践经验语义偏离主要源于以下几类模型规划幻觉LLM在规划步骤时可能产生逻辑矛盾、循环依赖或无法实现的步骤。例如规划中要求工具A的输出作为工具B的输入但工具A的实际输出格式与工具B的预期输入完全不匹配。工具执行异常工具本身可能失败如API超时、权限错误或者返回了超出模型预期的结果如返回一个错误码而非数据。模型可能无法正确解析这种异常导致后续步骤基于错误上下文进行。环境状态漂移在多轮对话或长时任务中外部环境的状态可能发生了变化。例如Agent正在操作一个文件而用户同时手动删除了它。Agent基于旧状态规划的动作就会失效。上下文窗口污染与遗忘随着对话轮次和工具调用结果的不断追加关键的初始指令和中间规划可能被挤出模型的上下文窗口或被大量中间结果稀释导致模型“失忆”。DART的应对策略正是针对这些痛点进行系统性设计。2.2 DART的核心组件构想虽然DART可能是一个研究中的具体框架但其思想可以具象化为几个核心组件。一个具备语义可恢复性的Agent系统通常需要包含以下部分语义状态追踪器这是系统的“记忆中枢”。它不仅仅记录工具调用的历史谁在何时调用了什么输入输出是什么更重要的是它维护一个“高层任务语义状态”。这个状态可能包括最终目标是什么、当前完成了哪些子目标、剩余哪些子目标、各子目标间的依赖关系、当前遇到的主要障碍等。这个状态需要以一种结构化的、模型可理解的方式如JSON Schema或特定DSL进行维护和更新。偏离检测器这是系统的“传感器”。它持续监控执行流并与预期的语义状态进行比对。检测规则可以是规则型工具返回码非成功、输出格式不符合约定、执行时间超阈值。模型型用一个轻量级模型或提示词判断当前步骤的输出是否与任务目标相关或者是否出现了明显的主题漂移例如从“分析数据”突然跳到了“评论天气”。恢复策略引擎这是系统的“决策大脑”。一旦检测到偏离它需要决定如何恢复。策略可能是多层次的重试最简单的策略对失败的工具调用进行有限次重试。回滚与重规划撤销最近几步被认为“无效”或“偏离”的操作基于当前最新的环境状态和剩余任务目标让模型重新规划后续步骤。语义注入与提示修复将维护的“高层语义状态”作为强提示重新注入给模型提醒它核心任务是什么并要求它基于此继续或调整。目标分解与子任务重启将当前受阻的子任务进一步分解或者尝试另一种替代方案来实现同一子目标。人工干预请求在无法自动恢复时明确向用户请求澄清或帮助。健壮的工具封装与适配层这是系统的“执行保障”。对每个工具调用进行封装统一处理超时、异常并将工具返回结果进行标准化清洗和结构化尽可能减少“脏数据”对模型决策的干扰。注意这里的“恢复”不是追求完美还原到某个历史快照而是追求“语义等价”的目标达成。比如从A到B的路径被封恢复机制应该找到另一条从A到B的路而不是执着于清理路障。3. 实操要点构建一个具备DART思想的简易Agent系统理论需要落地。我们不依赖某个特定的“DART”框架而是用主流的LangChain和LangGraph来搭建一个体现上述思想的简易任务型Agent。我们假设一个场景“请从维基百科获取‘人工智能’的词条摘要然后将其翻译成法语最后总结法语摘要的核心观点。”3.1 环境准备与工具定义首先定义我们需要的工具。我们将使用LangChain的tool装饰器。# 环境安装pip install langchain langchain-community langgraph wikipedia from langchain.tools import tool from langchain_community.utilities import WikipediaAPIWrapper from langchain.chat_models import init_chat_model # 示例实际可能是ChatOpenAI, ChatOllama等 import json # 初始化LLM llm init_chat_model(modelgpt-4, temperature0) # 工具1维基百科查询 wiki WikipediaAPIWrapper() tool def search_wikipedia(query: str) - str: Searches Wikipedia and returns the summary of the most relevant page. try: return wiki.run(query) except Exception as e: return fError fetching Wikipedia data: {str(e)} # 工具2翻译这里用一个简单的LLM调用模拟实际应接入翻译API tool def translate_to_french(text: str) - str: Translates the given English text into French. prompt fTranslate the following English text to French. Return only the translation.\n\nText: {text} response llm.invoke(prompt) return response.content # 工具3总结 tool def summarize_text(text: str) - str: Summarizes the key points of the given text in one paragraph. prompt fSummarize the key points of the following text in one concise paragraph.\n\nText: {text} response llm.invoke(prompt) return response.content # 将工具包装成列表 tools [search_wikipedia, translate_to_french, summarize_text]3.2 实现语义状态追踪与偏离检测这是核心。我们将创建一个SemanticState类来追踪任务。class SemanticState: def __init__(self, ultimate_goal: str): self.ultimate_goal ultimate_goal # 最终目标 self.original_plan [] # 初始规划步骤 self.completed_steps [] # 已完成步骤含结果 self.current_step_index 0 # 当前计划步骤索引 self.current_subgoal # 当前子目标 self.deviation_detected False # 偏离标志 self.deviation_reason # 偏离原因 self.recovery_attempts 0 # 恢复尝试次数 self.MAX_RECOVERY_ATTEMPTS 3 def update_plan(self, plan: list): 接收来自LLM的初始规划 self.original_plan plan def mark_step_start(self, step_description: str): 开始执行一个步骤 self.current_subgoal step_description print(f[State] Starting subgoal: {step_description}) def mark_step_complete(self, tool_name: str, result: str): 记录一个步骤完成 self.completed_steps.append({ tool: tool_name, subgoal: self.current_subgoal, result_snippet: result[:100] ... if len(result) 100 else result }) self.current_step_index 1 print(f[State] Completed: {tool_name}. Progress: {len(self.completed_steps)}/{len(self.original_plan)}) def check_for_deviation(self, tool_name: str, tool_result: str) - bool: 简单的偏离检测逻辑 # 规则1工具执行返回了错误信息 if tool_result.startswith(Error): self.deviation_detected True self.deviation_reason fTool {tool_name} failed: {tool_result} return True # 规则2结果与当前子目标语义相关性极低简易版检查关键词 # 这里仅为示例实际应用可能需要更复杂的NLP判断或嵌入向量相似度计算 current_goal_lower self.current_subgoal.lower() result_lower tool_result.lower() # 假设一些关键词如果结果中完全不含任何相关词可能有问题 relevant_keywords [ai, artificial intelligence, intelligence, machine, learn] if artificial intelligence in current_goal_lower: if not any(kw in result_lower for kw in relevant_keywords): self.deviation_detected True self.deviation_reason fTool {tool_name} result seems irrelevant to subgoal {self.current_subgoal} return True # 规则3结果为空或异常短可能查询失败 if len(tool_result.strip()) 20: self.deviation_detected True self.deviation_reason fTool {tool_name} returned abnormally short or empty result. return True return False3.3 构建带恢复能力的Agent执行循环我们将使用LangGraph来构建一个带状态循环的图。图的节点包括规划、执行、检查、恢复。from langgraph.graph import StateGraph, END from typing import TypedDict, Annotated, List import operator # 定义图的全局状态结构 class AgentState(TypedDict): user_input: str semantic_state: SemanticState plan: List[str] current_tool_to_use: str last_tool_result: str final_answer: str needs_recovery: bool # 初始化图 graph_builder StateGraph(AgentState) # 节点1规划器 def planner_node(state: AgentState) - dict: 基于用户输入和当前状态生成或修正执行计划 user_input state[user_input] semantic_state state[semantic_state] # 如果是首次规划或者需要恢复重新规划 if not semantic_state.original_plan or state[needs_recovery]: prompt f You are a task planner. The users ultimate goal is: {user_input} You have access to these tools: {[t.name for t in tools]}. Please break down the goal into a sequential list of sub-tasks (steps). Return ONLY a JSON list of strings, each string is one sub-task description. Example: [Search Wikipedia for X, Translate the result to Y, Summarize the translation] response llm.invoke(prompt) try: new_plan json.loads(response.content) except: new_plan [Step1: response.content] # 降级处理 semantic_state.update_plan(new_plan) print(f[Planner] New plan generated: {new_plan}) else: new_plan semantic_state.original_plan return {plan: new_plan, needs_recovery: False} # 节点2路由与执行器 def executor_node(state: AgentState) - dict: 根据当前计划步骤选择并执行工具 semantic_state state[semantic_state] plan state[plan] # 检查是否所有步骤已完成 if semantic_state.current_step_index len(plan): return {final_answer: All steps completed successfully., current_tool_to_use: None} current_step_desc plan[semantic_state.current_step_index] semantic_state.mark_step_start(current_step_desc) # 让LLM根据步骤描述决定使用哪个工具及其参数 prompt f Current sub-task: {current_step_desc} Available tools: {[f{t.name}: {t.description} for t in tools]}. Determine the most appropriate tool name and its input argument for this sub-task. Return a JSON object with keys tool_name and tool_input. response llm.invoke(prompt) try: decision json.loads(response.content) tool_name decision[tool_name] tool_input decision[tool_input] except: # 如果LLM解析失败尝试简单匹配 tool_name, tool_input search_wikipedia, current_step_desc # 查找并执行工具 tool_to_call next((t for t in tools if t.name tool_name), None) if not tool_to_call: result fError: Tool {tool_name} not found. else: print(f[Executor] Calling tool: {tool_name} with input: {tool_input[:50]}...) result tool_to_call.invoke(tool_input) # 检查是否偏离 deviation semantic_state.check_for_deviation(tool_name, result) return { last_tool_result: result, current_tool_to_use: tool_name, needs_recovery: deviation } # 节点3恢复处理器 def recovery_node(state: AgentState) - dict: 处理偏离决定恢复策略 semantic_state state[semantic_state] semantic_state.recovery_attempts 1 reason semantic_state.deviation_reason print(f[Recovery] Deviation detected: {reason}. Attempt {semantic_state.recovery_attempts}/{semantic_state.MAX_RECOVERY_ATTEMPTS}) if semantic_state.recovery_attempts semantic_state.MAX_RECOVERY_ATTEMPTS: # 超过最大尝试次数请求人工干预或彻底失败 final_msg fFailed to recover after {semantic_state.MAX_RECOVERY_ATTEMPTS} attempts. Reason: {reason}. Manual intervention needed. return {final_answer: final_msg, needs_recovery: False} # 策略1简单重试当前步骤适用于临时性错误 # 策略2回退一步用不同的参数或方式重试这里我们实现策略2 if semantic_state.current_step_index 0: # 回退一步将上一步标记为未完成并清除其结果 print(f[Recovery] Rolling back one step.) semantic_state.current_step_index - 1 if semantic_state.completed_steps: semantic_state.completed_steps.pop() # 注意这里没有清除original_plan因为我们希望基于原目标重试。 # 重置偏离状态准备重试 semantic_state.deviation_detected False semantic_state.deviation_reason # 设置标志让下一个循环回到规划器规划器看到needs_recoveryFalse但步骤回退会继续执行 # 但我们需要让执行器再次尝试。这里我们通过返回一个指令让图循环回执行器。 # 更复杂的图可以有一个专门的“重试”节点。这里我们简化直接返回让主循环继续。 return {needs_recovery: False} # 节点4结果汇总器 def summarizer_node(state: AgentState) - dict: 所有步骤成功完成后生成最终答案 semantic_state state[semantic_state] final_result fTask {semantic_state.ultimate_goal} completed.\n final_result fExecuted {len(semantic_state.completed_steps)} steps:\n for i, step in enumerate(semantic_state.completed_steps): final_result f {i1}. [{step[tool]}] {step[subgoal]}\n # 这里可以整合最后一步的结果 if state[last_tool_result]: final_result f\nFinal output snippet:\n{state[last_tool_result][:200]}... return {final_answer: final_result} # 构建图 graph_builder.add_node(planner, planner_node) graph_builder.add_node(executor, executor_node) graph_builder.add_node(recovery, recovery_node) graph_builder.add_node(summarizer, summarizer_node) # 设置边和条件流转 graph_builder.set_entry_point(planner) graph_builder.add_edge(planner, executor) # 条件边执行后根据是否需要恢复决定下一步 def decide_after_execution(state: AgentState) - str: if state.get(final_answer): return summarizer elif state[needs_recovery]: return recovery else: # 继续执行下一个步骤或者如果步骤已完成则结束 semantic_state state[semantic_state] if semantic_state.current_step_index len(state[plan]): return summarizer else: return executor # 继续循环执行 graph_builder.add_conditional_edges( executor, decide_after_execution, { recovery: recovery, executor: executor, summarizer: summarizer } ) # 恢复后总是返回执行器重试 graph_builder.add_edge(recovery, executor) graph_builder.add_edge(summarizer, END) # 编译图 graph graph_builder.compile()3.4 运行与测试现在让我们运行这个具备基础语义恢复能力的Agent。# 初始化状态 initial_state AgentState( user_inputGet the summary of Artificial Intelligence from Wikipedia, translate it to French, and then summarize the French translation., semantic_stateSemanticState(ultimate_goalGet AI summary, translate, and summarize.), plan[], current_tool_to_use, last_tool_result, final_answer, needs_recoveryFalse ) # 运行图 final_state graph.invoke(initial_state, config{recursion_limit: 20}) print(\n *50) print(FINAL RESULT:) print(*50) print(final_state[final_answer])这个简易系统演示了DART思想的关键部分状态追踪、偏离检测基于规则和恢复策略回退重试。当维基百科查询失败或返回无关内容时check_for_deviation会检测到触发恢复节点回退步骤并重试。4. 深入解析高级恢复策略与工程化考量上面的例子是基础版。在实际生产系统中语义可恢复性需要更精细的设计。4.1 高级偏离检测技术基于嵌入向量的语义一致性检查将当前步骤的子目标描述和工具执行结果都转换为嵌入向量如OpenAI的text-embedding计算余弦相似度。低于某个阈值则判定为可能偏离。这比关键词匹配更鲁棒。预期输出格式验证为每个工具定义严格的输出JSON Schema。执行后不仅看成功与否还用JSON Schema验证输出结构。不符合即触发偏离。轨迹评分模型训练一个小型模型或者设计一个复杂的提示词给LLM对当前已执行的整个步骤序列进行评分判断其是否朝着最终目标有效推进。4.2 复杂恢复策略动态重规划恢复不仅仅是回退。recovery_node可以调用planner_node但将“当前状态”包括已成功完成的结果、当前错误作为输入要求LLM重新规划剩余的步骤而不是从头开始。这更智能。备选工具链在规划时为关键步骤准备备选工具或方案。例如如果“Google Search”工具失败恢复策略可以切换到“DuckDuckGo Search”。子目标重构如果某个子目标如“获取X数据”反复失败恢复引擎可以尝试将其分解为更小的子目标如“从网站A获取X”、“从API B获取X”或替换为一个语义相近但不同的子目标如“估算X数据”。检查点与快照对于长时间运行的任务定期保存“语义状态快照”和关键的外部状态如数据库事务ID、文件句柄。恢复时可以从最近的检查点重启而不是完全回滚节省资源。4.3 工程化实践中的注意事项恢复循环与无限递归必须设置恢复尝试次数的上限如我们代码中的MAX_RECOVERY_ATTEMPTS并定义终极失败策略如转人工、返回优雅错误信息防止系统陷入死循环。状态管理的开销维护详细的语义状态会带来额外的内存和序列化开销。需要权衡状态粒度和系统性能。对于超长任务可能需要将状态持久化到数据库中。恢复策略的副作用有些工具调用具有副作用如发送邮件、支付订单。回滚这类操作可能不现实或非常复杂。对于这类“非幂等”操作恢复策略必须格外小心通常采用“向前恢复”补偿操作而非“向后恢复”回滚。用户体验在恢复过程中尤其是需要较长时间或请求用户输入时应向用户提供透明的反馈例如“系统遇到一个意外正在尝试替代方案...”而不是长时间静默。5. 常见问题与排查实录在实际部署这类系统时我遇到过不少典型问题。问题1偏离检测过于敏感导致频繁误报恢复。现象系统经常把一些正常但略有不同的结果判定为偏离频繁触发重试效率低下。排查检查偏离检测的阈值如向量相似度阈值、关键词列表。可能是阈值设置太严格。解决引入“置信度”概念。低置信度偏离可以先记录日志或发出警告但不立即触发恢复而是继续执行一步观察。如果后续步骤顺利则忽略如果连续出现低置信度偏离再触发恢复。也可以使用A/B测试来调优阈值。问题2恢复后陷入“死循环”总是在同一个步骤失败。现象系统检测到偏离 - 回退重试 - 再次在同一位置失败 - 再次恢复循环往复。排查这通常意味着偏离的根本原因没有被解决。可能是工具本身不可用、输入参数有根本性错误、或子目标本身无法实现。解决在恢复策略中增加“多样性”。第一次重试可以原样重试第二次重试可以尝试微调输入参数第三次重试可以切换到备选工具或备选子目标。同时在恢复逻辑中增加对“重复性失败”的检测如果同一位置失败超过N次直接升级为终极失败避免无限循环。问题3语义状态膨胀影响LLM上下文和性能。现象任务步骤很多每一步的结果都保存在语义状态里导致传给LLM的上下文越来越长速度变慢成本增加甚至可能因为超出上下文窗口而丢失早期关键信息。排查审查SemanticState中保存的数据。是否每一步的原始结果都需要完整保存解决实施状态压缩与摘要。选择性保存只保存对后续规划有决定性影响的工具输出如关键数据、ID对于中间处理结果只保存其摘要或元数据。增量式上下文不要每次都把整个历史喂给LLM。在规划下一步时只提供最终目标 最近几步的上下文 高度摘要化的前期成果。将完整的执行历史保存在外部存储中仅在需要详细分析时才查询。使用长上下文模型如果成本允许优先选择上下文窗口更大的模型。问题4LLM在恢复时给出的重规划质量不高。现象偏离发生后让LLM重新规划但新规划可能更差或者忽略了之前已经成功完成的宝贵成果。排查检查给重规划LLM的提示词。是否清晰地传达了“当前状态”哪些已完成结果是什么和“失败原因”解决设计专门用于恢复规划的提示词模板。模板应强制包含以下部分原始任务 [用户最初的目标]已完成步骤 [列出已成功完成的步骤及其关键输出摘要]当前失败步骤与原因 [步骤描述] 失败因为 [偏离检测器提供的理由]可用工具 [工具列表]指令 请基于以上信息重新规划从当前状态开始如何完成剩余任务或绕过当前障碍以实现原始目标。请务必利用已完成步骤的成果。将DART的语义可恢复性思想融入你的工具Agent系统相当于为其安装了一个“自动驾驶故障应对系统”。它不能保证100%成功但能显著提高复杂任务执行的鲁棒性和成功率。从简单的规则检测到基于模型的智能重规划你可以根据应用的复杂度逐步升级这套机制。核心在于转变思维不要假设Agent的执行路径会一帆风顺而是要为其可能遇到的每一种“颠簸”设计好缓冲和纠偏方案。这会让你的智能体从实验室玩具变得更像是一个能在真实世界复杂环境中可靠工作的助手。
返回列表