☰
Agent技能化:构建可复用的智能体技能库
2026/9/26 14:31:24 网站建设 项目流程

1. 为什么我盯上了“技能化”这条路

先说说这个 agent-skills 项目是怎么来的。做 AI Agent 应用开发做了两年多,我最大的感受是:大部分团队做智能体,做着做着就变成了“给大模型套壳”,核心逻辑就是写一大段 system prompt 塞进去,然后接两三个 API,跑通一个 demo 就算完事。但一旦进入真实业务场景,你会发现这种“一次性提示词”的打法立刻见底——业务方今天要一个日报生成助手,明天要一个合同审查工具,后天要一个舆情分析机器人,每一个需求都从零开始写提示词、调参数、排错误。代码仓库里堆满了prompt_utils.py、agent_v2.py、agent_final_v3.py,维护成本直线飙升。

当时我手里同时压着三个项目:一个是给运营部门做的活动效果分析助手,一个是给客服团队做的工单分类与话术推荐工具,还有一个是给研发团队做的代码评审助手。这三个项目看起来八竿子打不着,但抽出来看,它们有超过 40% 的基础能力是重叠的——都要做意图识别,都要做信息抽取,都要做结果格式化输出,都要有兜底回复逻辑。区别只在于业务规则和知识范围。于是我开始认真思考一个问题:能不能把这些“能力”沉淀成一套可以被任意 Agent 复用和组合的技能库?

这就是 agent-skills 这个项目的起点。它要做的事情很简单——把零散的提示词工程、工具调用逻辑、参数校验规则、错误处理策略,封装成一个个标准化的“技能模块”,让 Agent 按需加载、灵活组合,而不是每次从一张白纸开始。

我后来在复盘文档里写过一句话:“提示词是能力,技能是产品”。提示词解决的是“模型能不能做”,技能解决的是“业务怎么用”。这个项目本质上就是在两者之间搭一座桥。如果你现在的 Agent 开发也遇到了“每次都要重写提示词”“能力无法跨项目复用”“工具调用老是出错”这类问题,那这篇文章里的思路和实践,应该能给你一些直接能上手的参考。


2. 整体设计思路:给 Agent 装一套“可插拔的技能模组”

agent-skills 设计的第一个原则,是搞清楚“技能”和“工具”到底有什么区别。很多人会把这两者混为一谈,实际差别非常大。

我见过不少团队的 Agent 设计,基本上长这样:定义几个函数,比如search_database()、send_email()、get_weather(),然后把函数说明塞进 tools 参数里,模型需要的时候自己选。这种做法的好处是简单直接,但弊端很快会显现:工具只是能力的“接口层”,它不包含“什么时候该用”“用的时候先做什么后做什么”“结果出来之后如何处理”这些业务逻辑。换句话说,工具知道怎么执行,但不知道为什么要执行。

技能则不同。技能 = 工具的调用规则 + 业务场景判断 + 步骤流程 + 参数规范 + 结果处理策略。它是一个完整的“能力单元”,可以独立使用,也可以互相组合。

举个例子,我在 agent-skills 里定义了一个report_generation技能,它内部并不直接调用某个单一工具,而是编排了三个步骤:先调用data_fetcher获取结构化数据,再调用metric_calculator计算关键指标,最后调用template_renderer生成 Markdown 报告。在传统工具模式下,你需要让模型一步步自己决定“该先调谁后调谁”,错了就重来;而在技能模式下,这整条链路被打包成一个整体,模型只需要判断“用户需要报表吗?需要,那这把report_generation技能推上去就行”。

2.1 技能分组:把相似能力收拢到同一屋顶下

第一个设计决策是给技能做分组。我参考了插件系统里常见的“体系分层”思想,没有任何犹豫地把技能分成了三层:basic(基础技能)、business(业务技能)、meta(元技能)。

基础技能是一般化、跨领域通用的能力,比如text_summarize(文本摘要)、data_extract(结构化抽取)、format_convert(格式转换)、web_search(联网检索)。这些技能与具体业务无关,任何 Agent 都可以挂载。业务技能则绑定特定领域,比如customer_ticket_classify(客服工单分类)、contract_risk_scan(合同风险点扫描)、code_review_violation(代码违规检查)。元技能是用于管理其他技能本身的技能,比如skill_router(技能路由)、skill_fallback(技能兜底)、skill_merge(技能结果融合)。

这个分层的收益非常直接。新项目启动时,团队不用从零讨论“这个 Agent 需要什么能力”,而是先看基础技能里有哪些直接能挂,再看业务技能里有没有同领域的沉淀,最后只针对业务盲区开发新技能。我在内部做过一个粗略统计,接入 agent-skills 之后,三个并行项目的重复开发量下降了差不多 60%,主要省下的就是“基础能力重写”这部分。

2.2 注册制管理:技能不是堆在仓库里,而是靠配置暴露

第二个设计决策是技能注册机制。我没有把技能做成“硬编码类”,而是选择了“注册制 + 配置文件”的模式。

每个技能由一个目录承载,目录里包含三样东西:skill.yaml(技能元信息)、prompt.md(技能执行提示词)、examples/(示例样本)。skill.yaml长这样:

name: contract_risk_scan version: 1.2.0 group: business description: 对合同文本进行风险条款扫描,输出风险等级和具体条款编号。 triggers: - 合同审查 - 合同风险 - 帮我看看这份合同 - 条款有没有坑 steps: - load_document - clause_segment - risk_predict - report_generate params: doc_type: ["pdf", "docx", "txt"] output_format: ["markdown", "json"] risk_levels: ["high", "medium", "low"] fallback: general_qa

模型在运行时会先读取所有已注册的skill.yaml,将其中的name、description、triggers预置到上下文中,形成一个“技能清单”。当用户输入到来时,模型基于这个清单判断该激活哪个技能。这个机制的核心在于:技能暴露给模型的是“元信息”,而非完整逻辑。真正详细的执行步骤和提示词存在prompt.md里,等技能被选中时才注入上下文。

这样做的原因很简单,上下文窗口再大也是稀缺资源。如果每个技能都把自己几千字的完整提示词塞进 system prompt,上下文能爆得飞快。注册制把上下文占用压缩到了“最少描述”,用的时候再把详细内容放进来,这是整个项目性能表现稳定最重要的一个架构决策。

2.3 为什么不用纯 Function Calling,而要自己干一套

聊到这里有人会问:直接用 OpenAI/Anthropic 的 Function Calling,把每一步都定义成函数,不是也能实现类似效果吗?非要自己造轮子吗?

我的回答是:Function Calling 适合“工具”场景,不适合“技能”场景。 Function Calling 本身不解决业务步骤编排的问题,它只是帮你做函数选择的接口。即便你把合同审查拆成五个函数,模型还是要自己决定先调哪个后调哪个,中间夹杂着大量失败重试和调用顺序混乱。而技能是一个已经编排好的“流程单元”,它把步骤、判断、异常处理都固化了,模型做选择题的难度就从“选哪个函数”简化成了“选哪个技能包”。

这不是说 Function Calling 没用——实际上我底层还是用 Function Calling 去触发技能,只是技能内部的步骤编排逻辑不再依赖模型临场决策,而是由技能自己的执行器(executor)去逐步驱动。这套混合架构在后续项目里反复验证过:精度更高、调用次数平均下降了 35% 左右,用户体验提升非常明显。


3. 技能定义的核心细节与实操要点

聊完了宏观设计,我来拆解一个技能模块内部该怎么写。这是 agent-skills 项目里最花功夫的部分,也是决定最终效果上限的地方。很多人做智能体犯的最大错误,就是把技能脚本当成“给模型看的说明书”,写完 description 就结束了。实际操作下来,至少需要从五个维度来打磨。

3.1 技能描述:让模型在“模糊意图”下也能匹配对

技能描述的第一原则是从用户视角描述,而非从功能视角描述。什么叫用户视角?用户不会说“请帮我调用一个文本摘要工具”,他会说“给我概括一下这篇东西的核心观点”。所以text_summarize这个技能的 description 我写的是:

对用户提供的长文本进行信息压缩与要点提炼,保留关键数据、结论与逻辑主线,去除冗余描述和背景铺垫。适用于用户提到“总结”“概括”“太长不看”“看重点”等表达的场景。

注意这里我没有写任何技术术语,全是口语化的场景描述。为什么?因为模型对自然语言触发的理解远远强于对功能标签的理解,贴合用户表达习惯的描述能让意图匹配的准确率明显提升。

triggers字段则可以理解为“关键词触发器”,但不是简单字符串匹配,而是给模型看的“语义路标”。我一般会列 4 到 8 个最常见的高频触发表达,同时会主动说明“不要把 triggers 当唯一判断标准,语义相似就激活”。

3.2 技能步骤:定义“执行路径”,而不是“任务清单”

我的prompt.md里会包含一个步骤执行区,但写法非常讲究。拿code_review_violation这个技能举例,第一個版本我写的是:

1. 读取代码 2. 检查语法 3. 检查规范 4. 输出结果

后来发现模型压根不买账,给的输出非常空泛。问题出在“步骤描述抽象度太高”,模型不知道该关注什么。后来我把步骤改成了决策式引导:

1. 读取用户提供的源代码文件,判断语言类型(Python/Java/Go/JS 等)。 2. 先做静态扫描,检查是否存在语法错误、命名不规范、重复代码块。 3. 再审查业务逻辑,重点排查空指针隐患、未处理的异常路径、不安全的输入输出。 4. 对发现的每个问题给出:文件位置、问题类型、严重级别、修改建议。 5. 全部检查完毕后,按“严重问题 > 一般问题 > 建议优化”的顺序输出报告。

核心变化在于每个步骤都加上了“判断维度”和“产出要求”。模型看到的不再是一个泛泛的任务,而是一条可执行的路径。这个技巧后来被我用到了所有技能里,效果立竿见影。

3.3 参数规范:宁可前置过滤,不要后置补救

一个很容易被忽视的点是技能参数的校验。模型调用技能时给的参数经常格式漂浮,比如有的传fileName,有的传文件名,有的直接不传。如果技能执行器不做参数归一化,下游函数必然报错。

我的做法是在每个技能里定义一个参数预处理规则,执行器在拿到模型输出后先做一轮清洗和校验,包括:

  • 字段名映射:兼容英文、中文、下划线、驼峰等多种写法
  • 类型强制转换:"123"转成123,"yes"转成true等
  • 必填校验:必填字段缺失时,不直接报错,而是通过追问补全

这个设计让我避免了一整类“模型乱传参数”的问题。后来我又在规则里加了一条:如果参数缺失且无法补齐,技能可以降级为“询问模式”,而不是硬着头皮用错误参数跑结果。

3.4 示例注入:给模型一张“标准答卷”

每个技能目录下的examples/文件夹,放的是这个技能的输入输出示例。示例文件通常是两三组结构化的案例,比如:

{ "input": "帮我总结一下这份交通事故责任认定书的要点。", "activated_skill": "text_summarize", "output": "已提炼责任认定书核心信息:事故时间、地点、涉及方、责任归属、赔偿建议。" }

示例的真正价值不在“给模型背答案”,而在约束输出格式。有一次我在report_generation技能里加了一个示例,展示 Markdown 表格格式的报告长什么样,之后模型生成的报告格式稳定性立刻大幅提升。

3.5 技能命名:简单直接,不搞抽象

最后讲一个很不起眼但很关键的细节——命名规范。技能名我要求用小写字母加下划线,避免大小写混用,因为模型对大小写敏感,命名不一致会导致技能检索失败。同时名称要直观,data_extract比information_extraction_helper要好得多,命名越短,模型记忆和复用的成本就越低。这条建议虽然简单,但确实是我踩了坑之后总结出来的。


4. 实操过程:从零搭建一套可运行的技能调度链路

如果你看到这里,说明你已经接受“技能化”这个思路了。那我们就动手落地,我带你完整过一遍 agent-skills 的搭建流程。这套流程不需要特别高级的硬件,一个能跑大模型 API 的服务器就够了,整个链路的核心逻辑都可以用 Python 实现。

4.1 目录结构设计:一个技能一个家

我的技能仓库根目录是这样组织的:

agent-skills/ ├── skills/ │ ├── basic/ │ │ ├── text_summarize/ │ │ │ ├── skill.yaml │ │ │ ├── prompt.md │ │ │ └── examples/ │ │ └── data_extract/ │ ├── business/ │ │ ├── contract_risk_scan/ │ │ └── customer_ticket_classify/ │ └── meta/ │ ├── skill_router/ │ └── skill_fallback/ ├── executor/ │ ├── registry.py │ ├── loader.py │ ├── router.py │ └── runner.py ├── config/ │ └── default.yaml └── main.py

executor目录是整个运行时的核心,每个文件职责单一。registry.py负责扫描全部技能目录并建立索引;loader.py负责按需加载指定技能的prompt.md;router.py负责意图匹配和技能选择;runner.py负责执行技能内的固定步骤链。四个模块各干各的,互不依赖,后期维护成本很低。

4.2 技能注册与加载:遍历目录,生成技能清单

注册逻辑比较直白,核心就是遍历skills/目录下所有skill.yaml,解析后放入内存字典。代码大概长这样:

import yaml from pathlib import Path class SkillRegistry: def __init__(self, skills_root: str = "skills"): self.skills_root = Path(skills_root) self.skills = {} def scan_all(self): for yaml_file in self.skills_root.rglob("skill.yaml"): skill_dir = yaml_file.parent with open(yaml_file, "r", encoding="utf-8") as f: meta = yaml.safe_load(f) # 自动补全关键路径字段 meta["prompt_path"] = str(skill_dir / "prompt.md") meta["examples_path"] = str(skill_dir / "examples") self.skills[meta["name"]] = meta return self.skills def generate_manifest(self) -> str: """生成一段供大模型读取的『技能清单』文本。""" lines = [] for name, meta in self.skills.items(): lines.append( f"- {name}({meta['group']}): {meta['description']} " f"触发场景: {', '.join(meta['triggers'][:4])}" ) return "\n".join(lines)

generate_manifest()输出的这段文本,最终会被拼到 system prompt 里。它是模型选择技能的唯一依据,所以描述要简洁、信息密,一眼能看出每个技能是干什么的。

4.3 意图路由:让模型先选技能,再执行内容

技能路由我使用的是轻量级方案——让模型先针对用户输入输出一个技能选择决策,而不是直接去生成最终回答。

我给路由环节设计了一个单独的小提示词模板:

ROUTING_PROMPT = """你是智能体技能路由模块。你的任务是根据用户输入,从技能清单中选出最匹配的一个技能。 要求: 1. 如果存在明确匹配,输出技能名,格式如: SKILL: contract_risk_scan 2. 如果没有匹配,输出 SKILL: none 3. 一次只选一个技能,不要输出解释,不要输出多余内容。 可用技能清单: {manifest} 用户输入:{user_input} """

为什么不直接生成回答而要单独做一次路由?因为我把“决策”和“生成”拆开了。先压缩成一个选择问题,模型确定选了哪个技能后,再把该技能的完整 prompt 注入第二次请求。这个做法的好处是前一轮的上下文非常干净,不会因为塞入大量无关技能说明而干扰模型的判断。

4.4 技能执行:注入详细提示词,按步骤跑链路

路由环节确定技能后,runner.py开始干活。它的职责是读取技能的prompt.md,将其注入到一个新的会话上下文里,同时准备技能需要的输入参数、外部工具句柄,最后调用模型生成输出。

class SkillRunner: def __init__(self, llm_client, registry: SkillRegistry): self.llm = llm_client self.registry = registry def run(self, skill_name: str, user_input: str, params: dict = None): meta = self.registry.skills.get(skill_name) if not meta: raise SkillNotFoundError(skill_name) with open(meta["prompt_path"], "r", encoding="utf-8") as f: skill_prompt = f.read() messages = [ {"role": "system", "content": skill_prompt}, {"role": "user", "content": user_input}, ] # 这里可以插入外部工具注入、历史对话回填等扩展逻辑 response = self.llm.chat(messages, tools=meta.get("tools", [])) return response

实际生产环境中,runner内部还会处理多轮对话、工具结果回填、上下文裁剪这些事情。核心思路就是:系统提示词按技能动态变化,同时保留用户意图的连续语义。

4.5 一个最小可运行主流程

把上面几个模块拼起来,主流程极其简洁:

from executor.registry import SkillRegistry from executor.router import SkillRouter from executor.runner import SkillRunner registry = SkillRegistry("skills") registry.scan_all() router = SkillRouter(llm_client, registry) runner = SkillRunner(llm_client, registry) def handle_message(user_input: str): skill_name = router.route(user_input) if skill_name == "none": return "当前没有匹配的技能,请更换描述方式再试。" return runner.run(skill_name, user_input)

先注册、后路由、再执行,三步走,一个能复用技能库的最小智能体就跑起来了。这套代码我后来抽成了模板,新项目接入 agent-skills 时,只需要配置 LLM client 和技能目录,其余全部复用。


5. 技能运行中的常见问题与排查技巧实录

任何系统跑起来都会遇到问题,agent-skills 也不例外。我在这套机制上踩过不少坑,其中有些属于设计缺陷,有些属于模型特性导致的“不可抗力”。我把最典型的四类问题整理出来,附带排查思路和最终解法,你可以直接拿去做参考。

5.1 技能冲突:多个技能都匹配,模型到底选哪个

这是接业务需求后暴露的第一类问题。比如用户说“帮我分析一下这份数据”,数据分析技能觉得该激活,报表生成技能觉得自己也能干,最后模型随机选了一个,效果完全看运气。

我的解法是给skill.yaml加一个priority字段,在 manifest 里按照优先级从高到低排序。同时调整描述写法,让每个技能的适用边界更清晰,别都写“适用于用户需要分析数据的场景”,而是写清楚“适用于用户提供了结构化数据文件(Excel/CSV/JSON)且需要做统计分析的场景”。边界清楚了,模型的选择自然就准了。另外,我在路由阶段增加了“重问机制”——当模型对多个技能的置信度都低于阈值时,反问用户一句“你是想生成报表,还是做数据洞察?”把选择权交还给用户,而不是让模型盲目猜。

5.2 上下文被撑爆:技能越多,Token 消耗越高

技能数量上了 20 个以后,manifest 本身会占用不少上下文空间,再加历史对话和工具返回结果,很快就逼近上下文窗口上限。这个问题在长会话场景下尤其致命。

我的处理策略是给 manifest 做“动态裁剪”:只把高优先级的 10 个技能完整展示,其余技能只保留name和一句话简介。同时把所有技能的详细 prompt 改为按需加载,避免一次性全量注入。另外还做了查询缓存——同一个用户在同一个技能下的多轮交互,技能 prompt 只注入一次,后续轮次复用同一份上下文。

5.3 技能内部步骤断裂:第一句正常,第二句开始胡言乱语

有一次contract_risk_scan技能跑出了非常离奇的结果——它读取了文档、切分了条款,但到了“风险预测”这一步竟然自己编造了一份“标准合同文本来对照”。排查后发现,原因是技能执行链中的某一步超出了模型自身的知识边界,让它产生了幻觉。

这个问题靠提示词已经解决不了,我在两个层面做了修复:第一,给技能步骤加上“依赖外部工具”的标记,如果某一步需要真实数据支撑那就强制调用检索接口,模型没有权限自主生成;第二,给prompt.md增加了“禁止行为”段落,明确列出模型不能做的事,比如“不得编造条款内容”“不得对未提供的合同文本进行假设”。这一步是整个项目里对结果质量提升最大的一次修改,从此技能输出的可控性上了一个台阶。

5.4 跨场景迁移失效:换了一个业务,技能就不灵了

最后一个坑属于“成长的代价”。我把text_summarize技能从运营场景迁移到研发场景时,发现它输出的摘要风格完全不对——运营要的是数据和结论导向,研发要的是变更点和影响范围导向。迁移之后生成的内容两头不靠。

研究之后我把方案改成了“技能模板 + 领域参数”的模式。text_summarize里增加一个summary_focus参数,默认值是“通用”,在运营场景里配置为focus_on_business_metrics,在研发场景里配置为focus_on_tech_changes。技能逻辑不变,参数变了,输出风格随即跟着变。这套思路后来演化成了我所有技能设计的标准操作:能通过参数适配的,就不要新开一个技能。


6. 进阶玩法:技能编排、降级兜底与自动生成

当你把基础技能跑通之后,一定会产生一个更进一步的念头:能不能让 Agent 自己编排技能,解决一个之前从没定义过的复合问题?这个方向我在 agent-skills 的后续迭代里尝试了一部分,有些成果,有些还在改进。

6.1 技能编排:让几个技能按顺序协同完成复合任务

最直接的编排方式是“链式调用”。用户说“帮我分析这个网页的内容,然后结合我的历史行为数据,生成一份今日运营简报”——这里至少涉及三个技能:web_page_reader(读取网页内容)、data_analyst(分析行为数据)、report_generation(生成日报)。

我的做法是定义一个pipeline类型的技能,它内部把其他技能当作“步骤”来调用。Pipeline 的prompt.md只描述整体流程,而每个具体步骤仍然由对应的技能执行器来跑。pipeline的好处是模块边界保持清晰,单个技能仍然可以独立复用。

6.2 技能降级:主技能挂了,备选方案自动顶上

Agent 总会遇到主技能走不通的情况,比如合同审查时文档格式不支持,或者联网搜索服务临时不可用。我在路由层增加了一个降级策略表,每个技能可以配置fallback技能。主技能失败时,降级到备用技能,保证用户的诉求至少有个回应。

举个例子,web_search失败时降级到knowledge_base_query,虽然不能拿到最新实时信息,但至少能从内部知识库给用户一个基于历史资料的参考回答。这条策略让 agent-skills 的可用性有了显著提升,线上兜底回复占比从原来的 12% 降到不足 3%。

6.3 技能自动生成:离“AGI”最近的一次尝试

最后一个进阶方向有点实验性质——我尝试让模型基于一段业务需求描述,自动生成一个新的skill.yaml和prompt.md草案。原理不复杂:把“技能开发指南”作为系统提示词,让模型先分析业务场景,再起草技能定义,最后由人审核修正。

做出来的效果还不够完美,但已经能生成 60% 可用的初稿。负责新技能开发的同事反馈说,以前一个技能从需求对齐到写提示词、调整参数,至少需要半天;有了自动生成辅助,半天能跑通两三个技能的初版。这个方向我还在持续改进,目前重点关注的是“自动生成的提示词与手写提示词之间依然存在质量差距”这个问题。


7. 从项目实践里沉淀的三条体会

项目做到这个阶段,有些话我觉得值得单独拿出来说一下,算是我个人视角的经验沉淀。

第一,技能化重的东西是“抽象层次”,不是“功能数量”。很多团队一上来就追求技能数量多,做了几百个技能,最后发现维护成本爆炸。少而精、覆盖面广、边界清晰的技能库,效果远好于一堆细碎的小技能。

第二,技能描述的投资回报率极高。你在skill.yaml的description和triggers上多花的每一分钟,都会在后续无数次调用中持续回报。与其频繁调试模型参数,不如先把每个技能的描述打磨到“读者一看就知道该不该选它”的程度。

第三,Agent 开发的壁垒不在模型有多强,而在流程有多稳。同样一个模型,有人拿它做出来的东西三天两头出错,有人拿来就能稳稳支撑业务。差别不在 prompt 长短,而在有没有把执行路径、异常处理、降级策略这些东西都固化下来。agent-skills 给我的最大启发是:AI 应用的竞争力,正在从“调用模型的能力”转向“定义流程的能力”。

如果你也在做 Agent 类应用,不妨从今天开始,试着把项目里最常用的几个能力抽成标准的技能模块,哪怕不用全自动路由,先做一套手动触发的技能库,我也相信你会在三个月后明显感受到沉淀带来的复利。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询