1. 问题缘起当你的AI Agent突然“失忆”最近在折腾一个基于Hermes Agent框架的智能体项目目标是让它能根据用户指令自动调用一系列预定义的技能Skill来完成复杂任务。项目进展到一半一个诡异的现象出现了我明明在技能配置文件SKILL.md里清晰地定义了一个名为fetch_weather_data的技能并且在对话中明确要求Agent“请使用fetch_weather_data技能查询北京的天气”Agent的回复却变成了“我目前无法执行这个操作。” 或者更糟它直接忽略了我的指令开始一本正经地胡说八道用自然语言编造了一段根本不存在的“查询结果”。这不是个例。在社区里类似“SDD-skills执行遗漏”的讨论开始冒头。SDD在这里指的是“技能驱动开发”Skill-Driven Development一种构建AI Agent的主流范式。其核心思想是将Agent的能力模块化为一个个独立的、可复用的技能Skill通过一个中央调度器通常是Agent的核心逻辑来解析用户意图并调用相应的技能来完成任务。这听起来很美好但“执行遗漏”就像幽灵一样时不时地让整个系统“掉链子”。你可能会想是不是代码写错了或者技能描述不清排查一圈下来use_skill的调用逻辑看起来没问题SKILL.md的格式也符合规范。问题出在更深层的地方Agent的“大脑”——大语言模型LLM——在理解指令、规划行动链Action Chain和最终决定调用哪个技能时出现了解析偏差或逻辑短路。它“知道”有这个技能但在关键时刻却“选择”不使用或者错误地匹配了另一个技能。这种非代码层面的、源于模型推理过程的故障就是典型的“SDD-skills执行遗漏问题”。它直接导致智能体变得不可靠而这恰恰是AI应用落地中最忌讳的事情。2. 深入SDD架构技能调用的“理想”与“现实”要定位“执行遗漏”我们必须先搞清楚在SDD架构下一次完美的技能调用应该经历怎样的流程。以流行的Hermes Agent框架为例其核心工作流可以拆解为以下几个关键环节2.1 技能定义与注册一切的起点首先每个技能都需要被清晰地定义。这通常在SKILL.md或类似的技能清单文件中完成。一个标准的技能描述不仅仅是一个函数名它应该是一个完整的“能力说明书”。## fetch_weather_data **描述**: 根据提供的城市名称查询该城市的实时天气信息包括温度、湿度、天气状况和风力。 **函数签名**: fetch_weather_data(city_name: str) - dict **参数**: - city_name: 字符串类型需要查询天气的城市名称例如“北京”、“Shanghai”。 **返回**: 一个字典包含 temperature温度单位摄氏度、humidity湿度百分比、condition天气状况如“晴”、“多云”、“小雨”和 wind_speed风速单位米/秒等字段。 **示例调用**: fetch_weather_data(北京) 可能返回 {temperature: 22, humidity: 65, condition: 晴, wind_speed: 3}这个描述会被框架加载并注册到一个全局的技能库中。Agent的核心逻辑Orchestrator会知晓fetch_weather_data的存在、它能做什么、以及如何调用它。2.2 意图解析与技能匹配模型的第一道坎当用户输入“查询北京天气”时Agent并不是直接去调用函数。这个指令会先被送入大语言模型LLM进行意图解析。理想情况下LLM应该完成以下思考任务理解用户想要获取天气信息。参数提取目标城市是“北京”。技能映射在我的技能库中哪个技能能完成“获取天气信息”这个任务哦是fetch_weather_data。生成调用计划因此我需要执行use_skill(fetch_weather_data, city_name北京)。这个过程被称为“规划”Planning。LLM根据技能库的描述和当前对话上下文生成一个或多个行动步骤。这里就是第一个“遗漏”高发区描述模糊导致匹配失败如果技能描述写的是“获取气候数据”而用户说的是“查一下天气”LLM可能无法建立准确的关联。它可能认为这是两个不同的任务。上下文干扰如果之前的对话历史复杂LLM可能会被带偏优先采用更“省力”的文本生成方式来回应而不是严格遵循技能调用流程。模型本身的“偷懒”倾向一些研究发现LLM在有机会时会倾向于生成看似合理、但未经外部验证的文本内容即“幻觉”而不是执行需要精确参数和外部调用的技能。因为生成文本在它的训练数据中更常见计算路径更“顺畅”。2.3 技能调用与执行理想化的黑盒一旦LLM输出了正确的use_skill指令框架的调度器就会接手找到对应的技能函数传入参数并执行它。这一步通常是可靠和确定的只要代码没bug。执行结果比如天气数据的JSON会被返回给Agent。2.4 结果整合与回复生成模型的第二道坎拿到技能执行结果后Agent需要将其整合进对用户的回复中。这里LLM再次登场。它需要理解返回的数据结构并用自然、友好的语言组织成最终回复“北京今天天气晴朗气温22摄氏度湿度65%微风。”第二个“遗漏”风险点就在这里LLM可能“忘记”或“误解”技能返回的结果。例如它可能忽略部分数据或者错误地解读数据含义把“风速3m/s”说成“大风”。更隐蔽的是如果技能执行失败比如网络超时返回了错误LLM可能会选择隐瞒这个错误转而编造一个看似合理的成功结果这比直接报错更危险。所以“执行遗漏”并非单一环节的故障而是一个贯穿意图理解、规划决策和结果整合的链条问题。其根源在于我们试图用概率性的、擅长续写的LLM去驱动一个确定性的、需要精确逻辑的技能调用系统。两者的“思维模式”存在本质冲突。3. 实战排查定位“技能遗漏”的完整链路当你的Agent开始“装傻”或“胡说”时别急着怀疑人生。我们可以像调试传统软件一样对SDD技能调用链路进行逐层排查。以下是我在实践中总结的一套诊断流程你需要像一个侦探收集线索缩小范围。3.1 第一步确认技能是否被“看见”问题Agent是完全不知道这个技能还是知道但不用检查技能注册首先确保你的SKILL.md文件已被正确加载。在Agent初始化后打印或日志输出其技能列表。确认fetch_weather_data在列表中且描述完整。直接测试技能函数绕过Agent和LLM直接写一小段代码调用fetch_weather_data(北京)确保这个底层函数本身能正常工作并返回预期格式的数据。这能排除技能实现本身的Bug。使用“技能查询”指令许多Agent框架支持元指令例如直接问它“你现在有哪些可用的技能” 或者 “描述一下fetch_weather_data这个技能是做什么的” 观察LLM的回答。如果它回答不上来或描述错误说明技能描述没有成功嵌入到它的上下文中。3.2 第二步剖析LLM的“思考过程”这是最关键的一步。我们需要打开LLM决策的“黑箱”看看它在收到用户指令后到底生成了什么。启用详细日志在框架配置中打开Agent的“思考过程”Chain-of-Thought或“调试”日志。你需要看到LLM在接收到用户输入后其内部的推理文本。例如在类似LangChain或Hermes的框架中你可能会看到这样的日志[THOUGHT] 用户想查询北京天气。我拥有的技能包括fetch_weather_data查询天气、search_web搜索网页... fetch_weather_data 技能需要参数 city_name。用户提供了“北京”。所以我应该调用 fetch_weather_data。 [ACTION] use_skill(fetch_weather_data, city_name北京)如果日志中根本没有出现关于fetch_weather_data的思考或者思考后得出了“我无法处理此请求”的结论那么问题就出在意图解析和技能匹配阶段。分析思考链中的“蛛丝马迹”仔细阅读LLM的思考文本。是否存在以下问题误解意图它是否把“查询天气”理解成了“搜索天气新闻”参数混淆它是否认为“北京”不是一个有效的参数格式信心不足它是否因为技能描述不够清晰而对自己的判断产生了怀疑从而选择了更保守的“拒绝执行”策略存在更“简单”的路径它是否认为直接用自然语言编造一个天气回复比调用技能再整合结果更“高效”3.3 第三步检查技能调用与返回如果LLM输出了正确的use_skill指令但最终结果还是不对那么排查重点要向后移。检查调用参数确保框架正确地将city_name北京传递给了技能函数。有时参数名不匹配如技能期望city但LLM生成的是city_name会导致调用失败框架可能静默处理了错误。检查技能执行结果查看技能函数执行后的返回值。它是否是一个结构良好的字典还是抛出了异常框架是如何处理异常结果的是原样返回给LLM还是包装成了一个错误信息检查结果整合日志继续查看LLM在收到技能返回结果后的思考过程。日志可能是这样的[OBSERVATION] fetch_weather_data 返回{temperature: 22, humidity: 65, condition: 晴, wind_speed: 3} [THOUGHT] 我收到了天气数据。现在需要把这些数据组织成一句通顺的话告诉用户。如果[OBSERVATION]里是空的、错误的或者LLM的思考完全无视了这个观察结果那么问题就出在结果整合阶段。通过以上三步你基本上可以将问题定位到“意图解析”、“技能匹配”、“调用执行”或“结果整合”中的某一个或几个环节。定位之后才能对症下药。4. 根治方案从描述、提示到流程的优化策略找到问题根源只是第一步如何修复和预防才是重点。根据问题发生的环节我们可以采取一系列组合拳来加固SDD技能调用的可靠性。4.1 优化技能描述让LLM“一眼看懂”技能描述是LLM理解技能的唯一依据。模糊的描述是万恶之源。使用标准化模板为所有技能强制使用一个包含固定字段的模板如技能名称、功能描述、输入参数名称、类型、描述、示例、返回值类型、结构描述、调用示例。一致性大大降低了LLM的理解成本。描述多用同义词和场景不要在描述里只用一种说法。例如对于fetch_weather_data可以这样写“功能获取指定城市的实时气象信息。可用于回答‘今天天气怎么样’、‘上海气温多少’、‘明天北京会下雨吗’注仅限实时如需预报请使用其他技能。别名/相关词天气查询、气温、湿度、天气预报实时。” 这样能覆盖用户多种多样的提问方式。明确边界和限制清晰说明技能不能做什么。例如“本技能仅支持中国境内地级市及以上城市名称的拼音或中文查询不支持县区或模糊地名。” 这能防止LLM在参数不匹配时胡乱尝试或直接放弃。4.2 强化提示工程引导LLM“按规矩办事”系统的提示词Prompt是塑造LLM行为的关键。我们需要在系统指令中明确强调技能调用的纪律。强制“思考-行动”模式在系统提示词开头就定下基调“你是一个严谨的AI助手必须遵循以下流程1. 分析用户请求。2. 检查你的技能列表。3.如果存在能直接、准确完成任务的技能你必须优先使用该技能并生成use_skill(name, params...)指令。4. 等待技能执行结果。5. 根据结果生成最终回复。严禁在未使用技能的情况下直接编造技能执行结果。”提供反面案例在Few-Shot示例中不仅要提供正确调用的例子更要提供错误处理的例子。例如用户北京天气如何 助手错误示范北京今天大概20度左右挺舒服的。错误未调用技能凭空编造 助手正确示范我将为您查询北京天气。use_skill(fetch_weather_data, city_name北京)设定惩罚性条款在提示词中暗示不按流程调用技能会导致任务失败或严重后果从而利用LLM的“合规性”倾向。4.3 设计鲁棒的调用与容错流程在框架和代码层面我们需要为技能调用增加“安全网”。参数清洗与验证在技能函数内部入口处严格校验参数。如果city_name不是字符串或者为空立即返回结构化的错误信息{error: 参数 city_name 无效}而不是抛出异常让框架崩溃。LLM处理结构化的错误信息比处理异常堆栈更容易。技能调用结果标准化包装不要将技能函数的原始返回值直接丢给LLM。设计一个统一的响应格式例如{ status: success | error | partial_success, data: { ... }, // 成功时的数据 error_message: ... // 失败时的错误描述 }这样无论技能执行成功与否LLM收到的都是一个它熟悉且易于解析的结构。实现技能调用重试与降级对于网络请求类技能如天气查询调用失败时可以设计重试逻辑。如果重试仍失败可以降级到另一个备用技能如search_web去搜索天气并在回复中向用户说明“实时数据获取失败以下是搜索到的近期天气信息”。这比直接报错或幻觉更友好。4.4 实施监控与持续迭代SDD系统的可靠性不是一蹴而就的需要持续观察和优化。构建测试用例集为每个技能创建一组典型的、边界的和易混淆的用户查询进行自动化测试。定期运行这些测试监控技能调用成功率。收集生产环境日志分析真实用户交互中技能调用失败的案例。是哪些类型的提问容易导致遗漏是技能描述问题还是提示词问题A/B测试提示词尝试不同风格、不同强调程度的系统提示词通过小流量实验观察哪种提示词能最大程度地减少技能遗漏和幻觉。5. 进阶思考超越“调用”的技能架构设计当我们解决了基本的“遗漏”问题后可以更进一步思考如何设计更智能、更健壮的技能架构从根本上降低此类问题的发生概率。5.1 技能分层与路由机制不是所有用户请求都直接对应一个原子技能。我们可以引入技能路由层。原子技能如fetch_weather_data,calculate_tip,send_email。功能单一输入输出明确。复合技能/工作流由多个原子技能按顺序组成。例如plan_trip技能内部可能依次调用search_flights、search_hotels、fetch_weather_data目的地天气、calculate_currency汇率换算。在SKILL.md中复合技能也被定义为一个技能但其描述会说明它内部协调了多个子技能。路由器的价值一个轻量级的LLM或规则引擎可以作为路由器。它先分析用户请求的复杂程度。简单请求“北京天气”直接路由到原子技能。复杂请求“帮我规划一个去北京的三天行程要查好天气和机票”则路由到plan_trip这个复合技能。这避免了让主Agent的LLM去处理复杂的多步规划减轻了其负担也降低了它在多步推理中“跑偏”的风险。5.2 技能语义索引与向量检索当技能数量膨胀到几十上百个时仅靠名称和简短描述进行匹配会越来越吃力。我们可以为每个技能建立语义索引。做法将每个技能的名称、详细描述、示例查询、预期参数等内容拼接成一段文本然后使用文本嵌入模型如OpenAI的text-embedding-ada-002将其转换为向量Embedding存入向量数据库。调用时将用户查询也转换为向量然后在向量数据库中执行相似度搜索找出与当前查询语义最相关的Top N个技能再交给LLM做最终决策。这极大地提高了技能发现的准确率尤其是当用户使用非常规表述时。5.3 技能反馈与自我优化让系统具备从错误中学习的能力。技能效用反馈在每次技能调用后设计一个简单的用户反馈机制如“这个回答对你有帮助吗”。如果用户反馈负面且问题源于技能调用错误或遗漏可以将这个“查询-技能”配对作为一个负样本用于后续优化提示词或技能描述。技能描述自动优化定期分析技能调用日志。如果一个技能经常被某些特定类型的查询成功调用可以将这些成功查询的语句作为“正面示例”自动补充到该技能的描述中使其未来更容易被匹配到。5.4 拥抱更先进的Agent框架模式社区和学术界也在不断提出新的框架来应对这些挑战。例如ReActReasoning Acting范式明确将LLM的“思考”Reasoning和“行动”Acting步骤分离并循环进行每一步的思考都基于上一步行动的结果这迫使LLM更严谨地遵循外部工具的调用逻辑。而Toolformer或TALM这类方法则在模型预训练或微调阶段就让模型学习如何调用外部工具技能使工具调用成为模型的一种“本能”而非需要反复提醒的“纪律”。对于追求更高可靠性的项目考虑采用这些内建了更严谨交互机制的框架可能是比在基础框架上修修补补更根本的解决方案。解决“SDD-skills执行遗漏问题”是一个系统工程它横跨提示工程、软件架构、模型心理学和运维监控。没有银弹但通过层层剖析、针对性加固和架构优化我们可以显著提升AI Agent技能调用的确定性和可靠性让它从一个时常“灵光一现”或“突然失忆”的玩具变成一个真正值得信赖的数字化助手。这个过程本身就是对智能体“可控性”这一核心命题的深入实践。