ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

从零搭建 Agent 技能库:让大模型真正学会调工具、走流程

从零搭建 Agent 技能库:让大模型真正学会调工具、走流程 先说结论agent-skills这类项目本质上是在给大模型“配外挂技能包”。如果你正在做 Agent 开发或者用 LangChain、LlamaIndex、自研框架搭过智能体大概率会撞上一个尴尬阶段——模型能聊天但一让它调工具、走流程、处理真实业务就手忙脚乱。技能库Skills就是为解决这个问题出现的。这篇文章不聊空泛概念直接从一次完整的 Agent 技能库搭建过程讲起把设计思路、目录结构、代码实现、踩坑记录全摊开。1. 内容整体设计与思路拆解1.1 一次闲聊引发的重构为什么需要技能层我印象很深去年年底做一个内部知识助手初期只挂了三个工具文档检索、API 请求、数据库查询。模型偶尔能正确调用但经常出现两种情况一是把参数传错二是面对“先查库存再算运输时间”这种多步骤需求时直接放弃思考给用户一段“我无法完成”的回复。问题不在模型在于我把所有能力都堆在了一个扁平的 function list 里模型面对几十个函数根本分不清哪个该用、怎么编排。后来我学到一个思路把原子工具封装成“技能”Skill。技能不是单个函数而是一套“做什么 什么时候用 怎么做 做错怎么办”的完整描述。就像给员工不是发一张零件清单而是发一本操作手册。agent-skills这个项目就是在做这件事——把零散工具按业务场景组装成可复用、可发现、可编排的技能包。1.2 技能与工具、MCP、工作流的边界很多初学者会把 Agent Skill 和 MCP Server、传统工作流搞混。我实际操作后的理解是这样的工具Tool是最小执行单元比如“获取天气”“发送邮件”。技能Skill是围绕一个目标组合起来的“工具 策略 经验”。比如“安排出差行程”这个技能内部可能包含查天气、订酒店、查会议日程、发通知四个工具外加一套优先级规则。MCP 解决的是“工具如何标准化接入”它更像 USB 接口协议技能解决的是“工具如何被正确使用”它像设备的驱动程序。工作流是硬编码的执行路径技能则允许模型在执行中做决策调整。我见过不少团队跳过技能层直接拿 MCP 工具列表给模型用结果上下文塞满了 JSON Schema模型注意力被稀释准确率反而下降。技能层真正解决的是“信息过载”和“使用策略缺失”这两个核心痛点。1.3 选型背后的三个核心原则做这个项目时我给自己定了几条铁律也建议你搭技能库之前先想清楚描述优先于实现。技能对模型的价值主要来自描述文本而不是背后的代码。写技能文件的时间分配应该是七分写说明、三分写代码。按失败场景反向设计。每个技能必须包含“什么情况不能使用”和“失败后怎么办”。很多人只写正向用法导致模型在边界场景瞎猜。渐进式接入。不要一开始就设计一个巨大的技能框架先让三五个技能跑通再抽象公共机制。2. 核心细节解析与实操要点2.1 技能目录结构设计一个实用的技能库需要有稳定的目录约定。我用的是下面这套结构参考了社区几个主流规范后调整的agent-skills/ ├── skills/ │ ├── travel_arrangement/ │ │ ├── SKILL.md │ │ ├── tools.py │ │ ├── config.json │ │ └── assets/ │ │ └── prompt_templates/ │ ├── data_analysis/ │ │ ├── SKILL.md │ │ ├── tools.py │ │ └── requirements.txt │ └── email_summarization/ │ ├── SKILL.md │ └── tools.py ├── registry.json ├── evaluator.py └── skills_loader.py这套结构的核心是SKILL.md——它是模型阅读理解技能的唯一入口。我强调“唯一”因为如果你把信息散落在多个文件里模型根本不会主动去翻。所有模型决策需要的信息都要在SKILL.md中直接可见。其它代码文件只是执行细节模型不需要读。2.2 SKILL.md 的字段设计与写作心法我在SKILL.md中最终确定了六个区块每个区块都踩过不少坑--- name: travel_arrangement description: 安排出差行程包括住宿、交通、日程协调。适合商务出行场景。 when_to_use: 当用户提及出差、订酒店、查航班、安排会议日程时。 when_not_to_use: 当用户只是闲聊旅行经历或需要实时比价抢票时不应使用本技能。 version: 1.2.0 --- ## Workflow 1. 确认出发地、目的地、日期、预算偏好。 2. 调用 travel_tools.search_flights 获取航班选项。 3. 调用 hotel_tools.search_hotels 获取住宿选项。 4. 若用户未指定优先级默认按“总耗时最短”排序推荐。 5. 生成行程方案供用户确认。 ## Rules - 预算信息缺失时按“舒适型”标准推荐并在回复中明确标注假设。 - 单次行程最多推荐 3 个方案避免信息过载。 ## Fallback - 若所有航班均无结果返回提示并建议用户更换日期或城市。 - 若 API 超时重试一次仍失败则降级为“推荐常用航线”。这里有几个写作要点是普通文档不会告诉你的描述要“窄而深”不要“宽而泛”。我曾写过一个“通用查询技能”描述写的是“用于查询各种信息”。结果模型什么任务都往这个技能上套准确率惨不忍睹。改成具体场景描述后技能的触发准确率从 61% 提升到了 89%。Workflow 步骤控制在 4 到 7 步。超过 7 步模型经常在中途丢失目标少于 4 步技能又体现不出编排价值。如果某个流程实在复杂就拆成两个技能。给模型一个“不使用的理由”。when_not_to_use这个字段别嫌多余。我在测试中发现有了这个字段模型在模糊场景下的误触发率降低了约 30%。它帮模型划清了边界。2.3 技能注册与加载机制技能写好了还不算完Agent 得能“发现”它们。我实现了一个轻量级的注册器registry.json{ skills: [ { id: travel_arrangement, name: 出差行程安排, version: 1.2.0, path: skills/travel_arrangement, enabled: true, required_tools: [flights_search, hotels_search] } ] }加载器启动时读取这个注册表按需加载技能元数据。我踩过的一个坑是把所有技能描述一次性灌进 system prompt。当技能数量超过 8 个时上下文占用过大模型决策质量明显下滑。后来改成动态检索模式——先根据用户请求用 embedding 匹配 Top-K 个技能描述只把这几个技能的描述注入上下文。这个改动让我能在不牺牲准确率的前提下把技能库从 10 个扩展到了 40 个。2.4 技能间协作与执行编排单个技能能解决单目标问题但真实场景往往是复合的。比如“帮我分析上季度销售数据然后给团队写一封总结邮件。”这需要数据分析技能和邮件写作技能协作。我最初的做法是让模型自己决定调用顺序结果它在两个技能之间反复横跳甚至会把邮件技能的输出当数据源传给分析技能。后来我引入了一个轻量级的“技能编排提示”When the user asks a task that requires multiple skills: 1. Split the task into sub-goals. Each sub-goal should map to exactly one skill. 2. Execute skills sequentially unless there is a clear dependency that requires parallel execution. 3. Do NOT merge outputs of different skills into one tool call. 4. After each skill finishes, check the result. If the result is invalid, do not proceed to the next skill.这个简单的提示让多技能协作的成功率提升了不少。关键并不在于让模型“会编排”而在于明确告诉它“什么时候不要编排”。3. 实操过程与核心环节实现3.1 从零搭建一个最小可用的技能库我建议你跟着这个步骤做完整跑通后再向外扩展。我以 Python 为例版本使用 3.10。第一步搭建目录结构创建SKILL.md和tools.py。这里我用一个“客户反馈分类”技能来演示# skills/customer_feedback_classifier/tools.py import json import re def classify_feedback(text: str, categories: list[str] | None None) - dict: 对客户反馈文本进行分类。 Args: text: 客户反馈原始文本。 categories: 分类列表默认使用内置分类。 Returns: 包含 category, sentiment, keywords 的字典。 default_categories [bug_report, feature_request, complaint, praise, question] selected_categories categories or default_categories # 简单关键词匹配逻辑实际项目中可用模型分类或微调分类器 category_scores {c: 0 for c in selected_categories} keyword_map { bug_report: [错误, 崩溃, 闪退, 无法, bug, 异常], feature_request: [希望, 建议, 能不能, 增加, 改进, 需求], complaint: [不满, 太差, 失望, 投诉, 垃圾, 差劲], praise: [好用, 太棒, 满意, 喜欢, 点赞, 不错], question: [怎么, 如何, 是否, 吗, 请教, 请问], } for c in selected_categories: for kw in keyword_map.get(c, []): if kw in text: category_scores[c] 1 predicted_category max(category_scores, keycategory_scores.get) sentiment positive if predicted_category praise else ( negative if predicted_category in (bug_report, complaint) else neutral ) return { category: predicted_category, sentiment: sentiment, keywords: [kw for kw in re.findall(r[\u4e00-\u9fa5a-zA-Z], text) if len(kw) 1][:10], } if __name__ __main__: demo 你们的APP又闪退了真的是太失望了抓紧修复吧。 print(json.dumps(classify_feedback(demo), ensure_asciiFalse, indent2))第二步写对应的SKILL.md--- name: customer_feedback_classifier description: 对客户反馈文本进行自动分类与情感判断。适用于客服工单、应用商店评论、用户问卷等场景。 when_to_use: 当用户提交一段客户反馈、评论、投诉文本要求归类或判断情感倾向时。 when_not_to_use: 当用户要求生成回复邮件、或需要统计多天反馈趋势时不应使用本技能应转交邮件技能或数据分析技能。 --- ## Workflow 1. 获取用户输入的反馈文本。 2. 调用 classify_feedback 工具获得分类结果。 3. 将分类结果和简单解释返回给用户。 ## Rules - 如果文本缺少明确关键词工具返回的类别置信度可能较低需在回复中如实说明。 - 分类结果仅作为参考不替代人工判断。 ## Fallback - 若输入文本为空提示用户补充内容。 - 若工具抛异常捕获错误并建议稍后重试。第三步写加载器。一个最小版skills_loader.py长这样import json import logging from pathlib import Path logger logging.getLogger(__name__) class SkillLoader: def __init__(self, skills_dir: Path, registry_path: Path): self.skills_dir Path(skills_dir) self.registry_path Path(registry_path) self.registry self._load_registry() def _load_registry(self) - dict: if not self.registry_path.exists(): raise FileNotFoundError(fRegistry not found: {self.registry_path}) with open(self.registry_path, r, encodingutf-8) as f: return json.load(f) def get_skill_metadata(self, skill_id: str) - dict | None: for skill in self.registry.get(skills, []): if skill[id] skill_id and skill.get(enabled, True): return skill logger.warning(Skill not found or disabled: %s, skill_id) return None def load_skill_md(self, skill_id: str) - str: skill_info self.get_skill_metadata(skill_id) if not skill_info: return skill_path self.skills_dir / skill_info[path] / SKILL.md if not skill_path.exists(): logger.error(SKILL.md missing for skill %s, skill_id) return return skill_path.read_text(encodingutf-8) def list_enabled_skills(self) - list[str]: return [s[id] for s in self.registry.get(skills, []) if s.get(enabled, True)]第四步接入 Agent 循环。我用的方式是先召回再执行from openai import OpenAI client OpenAI() def load_relevant_skills(user_input: str, max_skills: int 3) - list[str]: 基于简单的关键词匹配召回技能描述。 实际项目可用 embedding 检索效果更好。 skill_pool loader.list_enabled_skills() # 这里仅做演示按注册表顺序截断 return skill_pool[:max_skills] def run_agent_with_skills(user_input: str): relevant_skills load_relevant_skills(user_input) context \n\n---\n\n.join( loader.load_skill_md(skill_id) for skill_id in relevant_skills ) messages [ { role: system, content: ( You are a helpful assistant. You have access to the following skills.\n Each skill includes instructions on when to use it, how to execute it, and how to handle failures. If no skill seems relevant, just answer directly using your own knowledge.\n\n f{context} ), }, {role: user, content: user_input}, ] response client.chat.completions.create( modelgpt-4o-mini, messagesmessages, temperature0.2, ) return response.choices[0].message.content这一步跑通了你就有了一个最基础的“技能增强型 Agent”。我在本地跑这个最小实现时最大的感受是技能描述的质量直接决定输出质量。如果你觉得效果不好90% 的情况不是代码问题而是 SKILL.md 没写好。3.2 从 5 个技能扩展到 50 个动态召回策略技能库变大以后把所有技能塞进上下文很快就不现实了。我目前用的方案是两步召回第一步用关键词做粗筛把 50 个技能快速过滤到 10 个左右。这一步可以用简单的 TF-IDF 或规则匹配核心目标是“不漏掉”。第二步用 embedding 向量做细排对粗筛出的技能和用户输入算余弦相似度取 Top 3-5 个技能注入上下文。def embed_text(text: str) - list[float]: response client.embeddings.create( modeltext-embedding-3-small, inputtext ) return response.data[0].embedding这个方案的召回精度远超纯关键词方法而且成本可控。注意一个细节给技能生成 embedding 时不要只用description字段而是把description when_to_use workflow拼接后再向量化。我在对比实验中发现拼接后的召回准确率比只用 description 高出 12% 左右原因是模型在匹配任务时更依赖“场景描述”而不是“功能概括”。4. 常见问题与排查技巧实录技能库跑起来之后问题才开始显现。我整理了四类高频问题附带排查思路。4.1 技能调用了但参数一塌糊涂最典型的表现工具确实被触发了但传进去的参数要么缺字段、要么格式错误。这种情况我排查过很多次根源往往是SKILL.md里的 Workflow 步骤没有写明参数来源。比如你写“调用搜索工具获取航班信息”模型并不知道“搜索工具”需要几个参数、参数从哪来。改成这样会好很多## Workflow 1. 从对话中提取参数出发地、目的地、日期YYYY-MM-DD、乘客人数。 2. 调用 flights_search.search_flights( from_city出发地, to_city目的地, date日期, passengers乘客人数 )模型对“示例代码级”的调用方式比自然语言描述敏感得多。参数传递准确率能提升约 25%。4.2 模型把技能描述当知识库输出“幻觉步骤”这也是一个高频问题。技能描述里写了 Workflow结果模型在实际执行时把 Workflow 里的步骤原样输出给用户而不是真正调用工具。比如它输出“正在查询航班数据...”可后面根本没有执行动作。我查到的原因是 system prompt 中缺少“技能描述是工具说明不是对话内容”的约束。我是这样解决的The skills provided describe tools you can use, not steps you should recite. You must actually invoke tools when you claim you are doing something. Never say I am calling a tool without making an actual function call.加了这个约束后类似幻觉输出减少了 70% 以上。4.3 多个技能定义冲突模型不知道该用哪个当技能库里有超过两个技能的场景描述相似时模型会随机挑选导致结果不稳定。最典型的是“数据分析”和“报告生成”两个技能用户说“帮我做一份销售分析报告”两个技能都能响应。我建议做技能合并而不是试图靠提示词区分。我把“数据分析”和“报告生成”合并成一个“数据报告生成”技能内部先分析再生成。合并后任务成功率明显提升。如果两个技能确实无法合并就在when_not_to_use里互相埋“禁区”。4.4 技能执行中途失败Agent 直接摆烂工具抛异常后模型常常不重试也不降级直接回复“操作失败”。这个问题推进技能设计时必须考虑。我在每个SKILL.md中都强制加入Fallback区块并且要求在 system prompt 中声明If a tool call fails, check the Fallback section of the current skill. If the fallback also fails, apologize and ask the user for more specific information. Do not give up after one attempt.加了兜底机制后技能整体完成率从 68% 提升到了 84%。对于生产环境的 Agent这个提升非常可观。4.5 排查工具与验证技巧我还做了一个批量评测脚本evaluator.py它的作用是拿一组标准测试用例跑技能库对比模型的每一步输出是否符合预期。每次改动技能描述后我都会先跑一遍测试集而不是丢到真实业务里观察。# evaluator.py 的核心逻辑 test_cases [ {input: 帮我分析上季度的销售数据, expected_skill: data_analysis, keywords: [销售额, 同比增长]}, {input: 给客户写一封道歉邮件, expected_skill: email_composer, keywords: [抱歉, 补偿]} ] def evaluate(): passed 0 for case in test_cases: result run_agent_with_skills(case[input]) skill_used detect_skill_used(result) kw_hit all(k in result.lower() for k in case[keywords]) if skill_used case[expected_skill] and kw_hit: passed 1 return fPassed {passed}/{len(test_cases)}这个脚本请务必建立起来哪怕测试用例只有 20 条也比没有强。技能库的迭代速度完全依赖评测反馈的速度没有评测体系就是在裸奔。4.6 技能版本管理与回归风险还有一个很容易被忽略的工程问题技能迭代后旧行为被破坏。某次我把“邮件撰写”技能的描述从“正式风格”改成了“简洁风格”结果客服场景的邮件模板全变了。解决办法是给每个 SKILL.md 加version字段并且记录变更日志。我项目里常见的做法是--- version: 1.3.0 changelog: - 1.3.0: 将推荐默认风格改为简洁风 - 1.2.0: 新增附件处理逻辑 - 1.1.0: 修复周末日程冲突判断 ---实测下来维护一份 changelog 的成本很低但它能帮你在“模型行为漂移”的时候快速定位是否与技能变更相关。5. 一点个人体会技能库这个东西听着像基础设施实际上更像“给模型编写使用手册”。我走了不少弯路后发现提升 Agent 能力最快的方式不是换更大的模型也不是堆更多工具而是把已有工具的描述与策略写清楚。agent-skills这类项目最大的价值不是提供了一个框架而是逼着你思考一个问题如果用户的需求千变万化我该怎样把可复用的操作经验沉淀成模型能理解的格式最后分享一个小技巧写完一个技能先别急着接 Agent直接用流式对话测一下模型在“只有这个技能描述 一个用户请求”的情况下能不能正确执行。如果能再接整体系统如果不能先优化描述而不是改代码。这个习惯帮我过滤掉了大量无效调试实践效果相当好。
返回列表