AI智能体技能执行遗漏问题:从SDD原理到实战排查指南

📅 2026/8/10 3:12:20
AI智能体技能执行遗漏问题:从SDD原理到实战排查指南
1. 项目概述当你的AI智能体“忘了”执行技能最近在折腾AI智能体Agent开发的朋友估计都遇到过一种让人抓狂的情况你明明在技能清单SKILL.md里写得清清楚楚告诉Agent“你会这个、你会那个”结果在真实对话或任务执行中它就像选择性失忆一样完全“忘了”调用某个关键技能。这就是典型的“SDD-skills执行遗漏问题”。SDD在这里不是指固态硬盘而是“技能驱动开发”Skill-Driven Development的缩写。这是一种围绕“技能”Skills来构建和扩展AI智能体能力的开发范式。核心思想是你将Agent的每一项能力——比如调用一个API、执行一段代码、查询数据库、发送邮件——都封装成一个独立的、可复用的“技能”。然后通过一个类似SKILL.md的清单文件来声明和管理这些技能Agent在运行时根据任务描述和上下文动态地选择并调用合适的技能。听起来很美好对吧但现实是Agent经常“看”不到或“想”不起去用你精心准备的技能导致任务失败或结果不完整。这个问题之所以棘手是因为它处在系统设计、提示工程和模型理解能力的交叉地带。它不仅仅是代码bug更关乎你如何与一个大型语言模型LLM协同工作。今天我就结合自己踩过的坑和摸索出的经验把这个问题的来龙去脉、排查思路和解决方案掰开揉碎了讲清楚。无论你是在用Hermes Agent、Claude Code还是自研Agent框架这篇文章都能帮你大幅减少技能“失踪”的烦恼。2. 核心问题拆解为什么技能会被“遗漏”要解决问题首先得精准定位问题。技能执行遗漏表象是“没调用”但根因可能五花八门。我们可以从Agent执行技能的完整链路上来逐一排查这个链路通常包括技能声明 - 技能发现 - 技能匹配 - 技能调用。2.1 技能声明阶段你的SKILL.md写对了吗这是最基础也最容易出问题的一环。Agent框架如Hermes、Workbuddy等通常需要一个结构化的文档如SKILL.md来了解自己具备哪些能力。如果声明本身就有问题后续一切免谈。常见陷阱1格式不规范或位置错误不同的Agent框架对技能声明文件的格式、命名和存放路径有严格要求。比如有的要求必须是严格的Markdown且包含特定的元数据区块如用!-- SKILL ... --包裹有的则要求文件必须放在项目根目录或某个特定的skills/文件夹下。如果你放错了地方或者文件扩展名不对比如写成了SKILL.txtAgent的加载器可能根本找不到它。实操心得第一件事去翻看你所用框架的官方文档找到关于技能声明Skill Declaration或技能清单Skill Manifest的章节。一字一句地对照格式要求。一个常见的检查方法是在项目根目录运行框架提供的命令行工具如hermes list-skills或agent skills validate看看你的技能是否被成功加载和解析。常见陷阱2描述过于笼统或脱离场景技能描述不是写给自己看的API文档而是写给LLM看的“能力说明书”。如果你这样写!-- SKILL: query_database -- **描述**: 查询数据库。 **参数**: sql (字符串)这种描述对LLM来说信息量几乎为零。它不知道这个“数据库”是指用户数据库、日志数据库还是产品数据库也不知道应该在什么情况下使用。当用户说“帮我查一下上个月的订单”LLM可能无法将这个自然语言请求与你那个干巴巴的“query_database”技能关联起来。解决方案采用场景化、意图化的描述。!-- SKILL: query_order_database -- **描述**: 当用户需要查询历史订单信息例如按时间、订单号、用户ID或状态筛选订单时使用此技能。此技能连接的是公司的核心订单数据库。 **参数**: - sql (字符串): 必须是一个安全的SELECT查询语句用于从orders表中检索数据。严禁包含DELETE、UPDATE或DROP等操作。 **示例调用**: - 用户说“找出用户ID为12345的所有订单。” - 技能调用: query_order_database(sqlSELECT * FROM orders WHERE user_id 12345) - 用户说“显示上周已发货的订单。” - 技能调用: query_order_database(sqlSELECT * FROM orders WHERE status shipped AND order_date DATE_SUB(NOW(), INTERVAL 7 DAY))通过提供具体的场景、意图和调用示例你极大地降低了LLM的理解和匹配难度。2.2 技能发现与匹配阶段LLM的“注意力”在哪里即使技能声明完美无缺到了运行时LLM也可能“注意不到”或“认为不需要”某个技能。这涉及到提示工程Prompt Engineering的核心。问题根源上下文窗口与提示词设计Agent在决策时其“思考”基于你提供的系统提示词System Prompt和当前的对话历史。如果系统提示词没有有效地引导LLM去“查阅”技能清单或者技能清单的内容没有被巧妙地整合进上下文LLM就会依靠自己的内部知识来回答问题从而忽略外部技能。低效提示词示例你是一个助手。你可以使用一些工具。工具列表在附件的SKILL.md里。这种提示词过于模糊。“一些工具”是什么“附件”在哪里LLM很可能直接忽略。高效提示词设计要点明确指令清晰告诉LLM它的核心职责是分析用户请求并选择调用最合适的技能。结构化呈现技能不要只是说“见附件”。最好将最关键、最常用的技能描述直接内嵌在系统提示词的主要部分。对于大量技能可以采用摘要索引的方式。强制格式化输出要求LLM必须以特定的结构化格式如JSON、XML或特定的标记语言来输出它的“思考过程”和“技能调用决定”。这能约束LLM的输出使其更容易被后续的程序解析。改进后的提示词片段示例你是一个任务执行专家。你的核心能力来源于一系列可调用的技能函数。对于每个用户请求你必须遵循以下步骤 1. **分析意图**理解用户想要完成什么。 2. **技能匹配**检查你拥有的技能中哪一个或哪几个组合能最完美地满足用户需求。你的技能列表如下 - search_web(query): 当用户需要获取最新的、非私有的、通用知识或实时信息时使用。例如“今天天气如何”、“最新的AI新闻”。 - calculate_math(expression): 当用户需要进行精确的数学计算时使用。例如“123乘以456等于多少”、“计算圆的面积半径为5”。 - get_user_profile(user_id): 当用户请求涉及特定用户的个人信息在你的权限内时使用。例如“我的账户余额是多少”、“查看用户Alice的注册日期”。 ...更多技能... 完整技能文档详见skills {{SKILLS_CONTENT_PLACEHOLDER}} !-- 这里由框架自动注入SKILL.md的内容 -- /skills 3. **决策与调用**如果你决定调用技能你的响应必须是且只能是以下JSON格式 { thought: 你的推理过程解释为什么选择这个技能。, action: { name: 技能名称, args: { 参数1: 值1, ... } } } 如果你认为无需调用任何技能就能直接回答则用自然语言直接回复。通过这样的设计你将技能选择过程“制度化”显著提高了LLM调用技能的倾向性和准确性。2.3 技能调用与执行阶段框架的“桥梁”稳固吗假设LLM已经正确输出了技能调用指令如上面的JSON问题还可能出在框架执行层。问题1输出解析失败LLM的输出并不总是完美的JSON。它可能包含额外的解释文本、格式错误如缺少引号、尾随逗号或编码问题。如果你的框架里解析这块代码写得比较脆弱一个微小的格式偏差就可能导致整个调用被静默忽略或抛出未处理的异常从用户角度看就是技能没执行。解决方案增强解析的鲁棒性。不要只用简单的json.loads()。应该尝试从响应文本中提取JSON块使用正则表达式如rjson\n(.*?)\n或r\{.*\}配合json5这类更宽松的解析器。设置多层try-catch并提供有意义的错误日志便于调试。对于解析失败的情况可以设计一个fallback机制比如让LLM重新修正输出格式。问题2技能函数签名不匹配LLM输出的参数名和类型必须与你实际技能函数的参数定义完全匹配。例如技能函数定义为def send_email(to: str, subject: str, body: str)而LLM输出的是{to_address: userexample.com, subject: Hello, content: World}这会导致调用失败。解决方案严格定义与动态适配。在SKILL.md中参数描述要尽可能精确包括名称、类型、是否必填、示例。在框架层可以实现一个参数映射或适配层。当检测到参数名不匹配但含义相似时如tovsto_address尝试进行智能映射。或者更简单的办法是在调用前对参数进行一次验证和清洗将不匹配的调用视为无效并反馈给LLM。3. 系统性解决方案构建一个“健忘症”免疫的Agent理解了各个阶段的陷阱后我们可以从设计层面构建一个更健壮的系统。以下是我在实践中总结出的一个多层次解决方案。3.1 技能声明标准化创建你的SKILL.md模板建立一个团队内部统一的技能声明模板是保证质量的第一步。这个模板应该强制包含以下部分# 技能名称 (全局唯一动词开头如 fetch_weather) ## 功能描述 (Description) * **一句话概要**清晰说明这个技能是做什么的。 * **适用场景**列举2-3个典型的用户意图或问题用“当用户想要/询问...”的句式。 * **不适用场景**明确说明什么情况下不应该使用此技能防止误用。 ## 输入参数 (Parameters) 以表格形式列出包含参数名、类型、是否必需、描述、示例。 | 参数名 | 类型 | 必需 | 描述 | 示例值 | | :--- | :--- | :--- | :--- | :--- | | location | string | 是 | 城市名称或邮政编码 | 北京, 100080 | | unit | string | 否 | 温度单位celsius 或 fahrenheit默认为 celsius | celsius | ## 输出格式 (Output) 描述技能成功执行后的返回数据结构。如果是JSON给出示例。 json { location: 北京, temperature: 22, unit: celsius, condition: 晴朗, forecast: [晴, 多云, 小雨] }调用示例 (Examples)提供2-3个从用户自然语言请求到技能调用的完整转换示例。用户输入“上海今天多少度” - 调用fetch_weather(location上海)用户输入“用华氏度显示纽约的天气。” - 调用fetch_weather(locationNew York, unitfahrenheit)错误处理 (Error Handling)列出可能出现的错误及原因方便LLM和开发者理解。LocationNotFoundError: 提供的地点无法识别。NetworkError: 无法连接到天气数据源。权限与安全 (Security)说明此技能所需的权限等级如无需认证、用户级、管理员级以及涉及的数据敏感性。通过这个模板你能确保每个技能都有足够丰富的元数据既服务于LLM的理解也服务于开发者的维护。 ### 3.2 提示词工程优化动态上下文管理 系统提示词不能一成不变。随着技能数量的增长把所有技能描述都塞进主要提示词会耗尽上下文窗口拖慢速度并降低核心指令的注意力。我们需要动态管理。 **策略分层技能加载** 1. **核心技能常驻**将最常用、最通用的5-10个技能如calculate_math, search_web的描述精简后永久放在系统提示词中。 2. **技能摘要索引**在系统提示词中提供一个所有技能的摘要列表只包含技能名和一句话功能。例如 你拥有以下技能集 - fetch_weather: 查询指定城市的当前天气和预报。 - query_order_db: 从订单数据库查询信息。 - send_slack_msg: 向指定的Slack频道发送消息。 - ... (共25个技能) 3. **按需详细加载**当LLM在“思考”中初步判定可能需要某个技能时或者在用户查询非常明确指向某个领域时由框架动态地将该技能的**完整详细描述**从SKILL.md中提取出来插入到当前对话上下文的末尾或作为一个单独的工具描述块。这类似于“懒加载”保证了信息的深度和针对性。 许多先进的Agent框架如利用Claude的“工具使用”特性或OpenAI的“函数调用”已经内置了类似的机制。你需要做的是按照框架要求的方式通常是特定的JSON Schema格式来定义你的技能框架会自动处理动态呈现和匹配。 ### 3.3 实现一个反馈与学习循环 最理想的Agent应该能从“遗漏”中学习。我们可以设计一个简单的反馈机制。 1. **执行监控与日志**记录每一次用户请求、LLM的思考过程、技能调用决策无论是否调用以及最终的用户满意度可以通过简单的好/坏反馈按钮或在会话结束时询问“是否解决了您的问题”。 2. **遗漏检测**通过分析日志我们可以定义一些“遗漏”的启发式规则 * **规则1**用户请求中包含了明显的关键词如“天气”、“订单”、“计算”但LLM未调用相关技能且其自然语言回复未能完全满足需求用户给出了负面反馈或进行了追问。 * **规则2**LLM在思考过程中提到了某个技能如“我需要查询天气”但最终输出中却没有调用指令。 3. **数据收集与优化**将这些“疑似遗漏”的案例收集起来它们是最宝贵的优化素材。你可以 * **优化技能描述**检查是否因为描述不清导致LLM无法匹配。 * **创建Few-shot示例**将这些案例中“用户请求 - 正确技能调用”的配对作为少量示例Few-shot Examples添加到系统提示词中直接教LLM在类似场景下该如何做。 * **微调模型**如果案例积累到一定数量可以考虑用它们对底层的LLM进行微调Fine-tuning使其更倾向于在你定义的领域内调用工具。 这个循环将Agent的开发从一次性的“配置”变成了一个持续的“训练和优化”过程。 ## 4. 实战调试当技能遗漏发生时一步步排查 理论说再多不如实战。当你发现Agent又“犯傻”没调用技能时请打开你的调试工具按照以下清单一步步走。 ### 4.1 第一步检查技能加载日志 首先确认技能文件被正确读取。在Agent启动时查看框架的日志输出。你应该能看到类似这样的信息[INFO] Loading skills from /path/to/SKILL.md [INFO] Registered skill: fetch_weather [INFO] Registered skill: query_order_db ... [INFO] Total 15 skills loaded successfully.如果没有看到你的技能被注册或者报出格式错误那么问题就出在声明阶段。回头去仔细检查SKILL.md的语法、路径和框架兼容性。 ### 4.2 第二步审查原始提示词与上下文 你需要看到发送给LLM的**完整**提示词。大多数框架都提供日志级别设置将日志级别调到DEBUG或TRACE找到包含“System Prompt”或“Messages to LLM”的日志段落。 **重点检查** 1. 你的技能描述是否被正确地注入到了提示词中是完整的描述还是只有摘要 2. 技能描述的格式是否符合LLM的阅读习惯有没有因为格式混乱如Markdown渲染错误、特殊字符未转义导致LLM难以理解 3. 整个提示词是否过于冗长以至于你的技能列表被挤到了上下文窗口的末尾可能被模型截断了特别是对于有上下文长度限制的模型 ### 4.3 第三步分析LLM的“思考”过程 如果技能已加载提示词也没问题那就要看LLM的“脑子”里在想什么了。你需要捕获LLM的原始响应在它被框架解析和执行之前。 **在启用结构化输出如JSON的情况下查看LLM返回的完整文本。** 理想情况下它应该包含一个thought字段。这个字段是黄金诊断信息。 * **情况A**thought里根本没提你的目标技能。这说明LLM完全不认为这个技能与当前任务相关。问题出在**技能匹配**上你需要优化技能描述或提示词中的任务分析指引。 * **情况B**thought里提到了技能如“用户需要天气信息我应该调用fetch_weather”但最终的action字段是空的或者输出的是自然语言。这说明LLM**识别了技能但决定不调用**。可能的原因有 * 它认为自己掌握的知识足以回答比如它“知道”今天北京天气“大概”不错。 * 技能调用在它看来“成本”太高尽管对你来说只是一个API调用。 * 你的系统提示词中“强制调用”的指令不够强。你需要强化指令比如“**对于涉及实时数据、私有数据或复杂计算的问题你必须优先调用技能而不是依靠内部知识。**” * **情况C**thought和action都正确但技能调用失败了。这进入下一层排查。 ### 4.4 第四步追踪技能调用执行链 如果LLM输出了正确的调用指令但技能没生效问题就在框架的执行层。 1. **解析日志**查看框架是否打印了“Parsing LLM response”、“Executing skill: X”之类的日志。如果没有说明解析环节就出错了。 2. **参数验证**如果日志显示开始执行但很快失败查看错误信息。最常见的就是参数错误类型不对、缺少必需参数、参数值格式无效比如要求是日期字符串却传了个“昨天”。 3. **技能函数内部**如果框架成功调用了技能函数但函数内部抛出了异常如网络超时、数据库连接失败、权限不足而这个异常被框架全局捕获且没有反馈给用户也会造成“静默失败”。确保你的技能函数有完善的错误处理并将有意义的错误信息返回给Agent框架框架应能将这些信息作为上下文让LLM生成对用户的友好错误回复。 ### 4.5 第五步实施A/B测试与对比 对于难以定位的模糊问题A/B测试是最有效的方法。 * **测试描述**准备两个版本的SKILL.md一个用原描述一个用你优化后的、更场景化的描述。保持其他所有条件不变用同一组测试用例去询问Agent统计技能调用成功率。 * **测试提示词**准备两个版本的系统提示词一个用原版一个加入更强烈的技能调用指令和更结构化的输出要求。对比效果。 * **测试模型**如果条件允许尝试换一个不同的LLM例如从gpt-3.5-turbo换到gpt-4或claude-3-haiku。不同的模型在工具使用/函数调用上的能力和倾向性有差异。可能某个模型就是更“听话”更倾向于使用外部工具。 通过这种对照实验你可以将问题范围缩小到具体的环节。 ## 5. 高级技巧与未来展望 解决了基本的遗漏问题后我们可以追求更优雅、更智能的解决方案。 ### 5.1 技能路由与编排从“选择”到“规划” 对于复杂任务用户的一个请求可能需要多个技能按顺序或并行执行。例如“帮我总结上周销售额最高的三个产品的客户反馈并邮件发给经理”。这涉及到query_sales_db - query_feedback_db - analyze_sentiment - send_email 一系列技能。 简单的“单次匹配-调用”模式会失败。我们需要引入一个**技能编排层**。这个层可以是一个更高级的“规划Agent”Planner Agent它首先将复杂任务分解成子任务然后为每个子任务分配合适的技能并管理它们之间的数据流和执行顺序。或者也可以利用LLM本身的多步推理能力通过Chain-of-Thought思维链提示让它自己生成一个执行计划。这超越了防止遗漏进入了智能流程自动化的领域。 ### 5.2 技能向量化与语义检索 当技能数量膨胀到几百个时即使采用分层加载精准匹配也变得困难。我们可以借鉴搜索领域的经验将技能**向量化**。 1. 为每个技能生成一个嵌入向量Embedding。这个向量基于技能的名称、描述、适用场景、参数说明等文本信息。 2. 当用户请求到来时将用户请求也转化为向量。 3. 在向量数据库中进行相似度搜索找出与用户请求最相关的Top K个技能。 4. 只将这K个技能的详细描述动态加载到LLM的上下文中。 这种方法实现了真正意义上的“按需加载”极大地提高了大规模技能库下的匹配精度和效率。许多现代的Agent框架已经开始集成这类语义检索能力。 ### 5.3 建立技能效能评估体系 最后我们需要一个度量标准来衡量技能系统的健康度而不仅仅是解决“遗漏”。 * **技能调用率**每个技能被成功调用的频率。长期无人调用的技能可能需要优化描述或考虑下线。 * **技能成功率**技能调用后成功执行并返回预期结果的比率。低成功率可能意味着API不稳定、参数设计不合理或技能逻辑有bug。 * **用户任务完成率**在涉及技能调用的对话中最终用户问题得到解决的比率。这是终极指标。 * **平均技能链长度**完成一个复杂任务平均需要调用多少个技能。这反映了技能的原子性和可组合性。 通过监控这些指标你可以数据驱动地优化你的技能库和Agent系统让它不仅不忘事还能越来越聪明、越来越高效。SDD-skills执行遗漏问题从一个令人头疼的Bug最终会演变为你构建强大、可靠AI智能体过程中的一套核心方法论和最佳实践。