新闻详情

新闻详情

首页 / 资讯中心 / 详情

从零搭建Agent Skills:技能设计、多技能协同与工程化落地

发布时间:2026/9/17 22:05:18来源:尧图网络
从零搭建Agent Skills:技能设计、多技能协同与工程化落地
前两年大家都在卷大模型本身的参数和基准分数今年风向明显变了圈子里聊得最多的变成了 Agent以及比 Agent 更下沉的一个词agent-skills。我自己的感觉是如果不把技能这套东西想清楚所谓 Agent 就是个空壳模型再强也就是个会聊天的接口。这篇文章把我从零开始搭建、调优、踩坑的全过程整理出来重点聊聊技能到底是什么、怎么设计才不烂尾、以及多技能协同时的那些坑。1. 先搞明白Agent Skills 到底在解决什么问题1.1 技能Skills与工具Tool的本质区别很多人刚开始会混淆 Skill 和 Tool包括我自己第一次接触 Agent 框架时也踩过这个认知坑。简单来说Tool 是单个、原子化的操作比如查询天气发送邮件调用某个 API而 Skill 是一个能完成某类子任务的能力单元它内部可能编排了多个 Tool还包含决策逻辑和异常处理。举个例子你就懂了。查天气是一个 Tool但根据用户的出行需求依次判断天气、交通、时间最终生成一份出行建议就是一个 Skill。后者不是简单调接口它包含了对输入信息的理解、对多个数据源的调度、以及输出格式的组织。用生活化的类比来说Tool 是一把螺丝刀Skill 是能把一个架子组装起来的完整手艺螺丝刀只是其中的一环。这个区别直接决定了你的 Agent 最终是像一个只会背菜单的服务员还是一个真正能处理复杂需求的人手。我在实际项目中见过不少团队上来就堆了几十个 Tool模型经常选错或者不知道该怎么组合原因就是缺少了 Skills 这一层抽象。1.2 为什么 Skills 比纯 Prompt 更接近会做事有些人会说我直接在 System Prompt 里写清楚你要先做 A 再做 B 再做 C这样不也算技能吗理论上能跑通但工程化之后完全不是一回事。第一Prompt 是概率性的模型可能这次照着做了下次就自由发挥。而 Skills 在代码层面锁定了流程模型只需要负责理解需求并在正确的时机调用技能具体的执行逻辑是确定的。第二Skills 可以内嵌校验、重试、回退等程序化逻辑这些是纯文本 Prompt 根本写不出来的。第三Skills 是可复用、可组合的单元写一次就能在多个项目里共享Prompt 只能靠复制粘贴。我自己在项目中最大的体感是把流程固化到技能层之后模型的行为稳定性有了质的提升。原来跑十个任务可能有两三个要人工修正现在基本百发百中因为技能的边界和输入输出都被定义死了模型可发挥的错误空间被压缩到很小。2. 设计 Agent Skills 的核心方法论2.1 确定技能的粒度多大算一个技能这是设计阶段最容易翻车的点。技能粒度太粗模型拿到一个模糊需求时会不知所措粒度太细Agent 需要做大量的路由选择不仅费 Token还容易选错。我的经验是一个技能应该满足三个条件有明确的触发场景、内部逻辑是连续的、输出对上层任务有明显贡献。举个例子我之前做一个文档处理 Agent初期把读取PDF解析表格抽取关键信息生成摘要全拆成了独立技能结果模型面对一份合同文件时要连续调用四五个技能才能完成需求中途任何一次调用意图偏移都会导致结果崩掉。后来我重构为一个合同文档理解技能内部串联解析、抽取、摘要对外只暴露一个输入和一个输出整个流程立刻顺畅了。还有一个实操参考如果你发现某个技能经常和另一个技能成对出现或者某个技能内部的决策分支过于复杂基本就是粒度需要调整的信号。一般来说一个交互式的 Agent 项目里技能数量控制在五到十五个之间是比较健康的区间。2.2 技能的输入输出契约设计技能的输入输出契约是整个设计中最需要较真的部分。别看这好像就是个函数签名实际上它直接决定了模型能不能正确调用你。我见过很多刚上手的人写技能描述时就一句话处理文件输入输出参数随便写结果模型不是传错参数就是理解错用途。我常用的方式是给每个技能定义三样东西角色定位、触发条件、输入输出 Schema。角色定位告诉模型这个技能是干嘛的、什么场景下用触发条件说明当用户提到哪些需求时需要优先考虑此技能输入输出 Schema 则用类型定义把每个参数的格式、取值范围、约束条件写明。例如一个网页正文提取技能输入 Schema 我会定义 url字符串必须以 http 开头、selector字符串可选CSS 选择器输出 Schema 则定义 title、content、publish_time 三个字段。这样模型在调用时就会按照这个契约来组织参数而不至于传进去一个乱七八糟的对象。注意输出 Schema 的设计很容易被忽视。很多项目只定义输入输出全靠模型自由发挥这样后续如果要把技能结果再接给其他技能或进入数据管道格式不统一就是灾难。建议每个技能的输出都尽量用严格的 JSON 格式定义。2.3 Skill 描述怎么写才让模型真的会调用技能描述是模型判断该不该调用这个技能和怎么调用的唯一依据它的质量直接决定你的 Agent 是聪明还是笨。描述写太短模型不知道你支持什么写太长模型在长上下文里容易抓不住重点。我总结了几个关键点。第一动词开头直接说明能力边界。比如提取网页正文内容去除广告和导航信息返回结构化文本比这是一个网页内容处理功能强得多。第二明确指出不适用的情况。这个很多人会忽略但负向描述能有效防止模型乱调用。比如本技能仅适用于静态 HTML 页面不适用需要登录或动态渲染的网页如遇到请提示用户。第三对容易混淆的技能做区分说明。如果你的 Agent 同时有文本摘要和关键词抽取两个技能那每个描述里最好都提一句当用户需要全文压缩概括时用本技能仅列举核心词时用关键词抽取技能。这种相互引用式的描述能显著降低选错概率。我前段时在做一个电商客服 Agent就是靠把每个技能的触发条件和不适用的边界写清楚才把技能误调率从百分之二十压到百分之三以内。描述这块值得花时间反复打磨。3. 从零实现一个可复用的 Agent Skill3.1 环境准备与整体结构设计代码实现我推荐用 Python不是因为别的语言不行而是生态成熟后续想接什么都能找到现成的库。我这里用一个轻量级的示例来演示不绑定任何特定的大模型框架核心思路和代码结构你可以直接迁移到自己的项目里。整个技能系统可以拆成三层技能定义层、注册调度层、执行逻辑层。技能定义层负责描述技能的角色、触发条件和输入输出契约注册调度层维护一个技能清单并在模型需要时提供决策依据执行逻辑层是真正干活的代码负责把技能的输入转换为最终结果。建议代码目录结构长这样agent-skills/ ├── skills/ │ ├── __init__.py # 技能注册入口 │ ├── registry.py # 技能注册表与调度器 │ ├── base.py # 技能基类定义 │ ├── web_extract.py # 网页提取技能 │ ├── text_process.py # 文本处理技能 │ └── data_analyze.py # 数据分析技能 ├── agent/ │ ├── __init__.py │ ├── router.py # 模型调用与技能路由 │ └── memory.py # 简单记忆存储 └── main.py # 主程序入口3.2 核心代码技能基类与注册机制先写一个技能基类把所有公共逻辑收拢进来这一步很关键。基类里定了技能的元数据格式、输入输出 Schema 的校验方式以及一个统一的执行入口。# skills/base.py from abc import ABC, abstractmethod from typing import Any, Dict, Optional import json import jsonschema class BaseSkill(ABC): 所有技能必须继承的基类。 # 必填元数据子类必须覆盖 name: str # 技能名称模型通过它识别 description: str # 技能描述模型判断触发场景用 input_schema: Dict[str, Any] {} # 输入参数 JSON Schema output_schema: Dict[str, Any] {} # 输出结果 JSON Schema def validate_input(self, params: Dict[str, Any]) - None: 校验输入参数是否符合契约。 try: jsonschema.validate(instanceparams, schemaself.input_schema) except jsonschema.ValidationError as e: raise ValueError(f技能 {self.name} 输入参数校验失败: {e.message}) def format_output(self, result: Any) - Dict[str, Any]: 标准化输出统一封装为带状态的 JSON。 if not isinstance(result, dict): result {data: result} return { skill: self.name, status: success, result: result } abstractmethod def execute(self, params: Dict[str, Any]) - Any: 子类实现真正的业务逻辑。 pass def run(self, params: Dict[str, Any]) - Dict[str, Any]: 外部统一调用入口。 self.validate_input(params) result self.execute(params) return self.format_output(result)注册表的设计也不复杂核心是提供一个全局的技能容器既能按名称索引也能在 Agent 决策时把所有技能的描述信息批量导出。# skills/registry.py from typing import Dict, List, Optional from .base import BaseSkill class SkillRegistry: 技能注册表维护所有可用技能并提供查询能力。 def __init__(self): self._skills: Dict[str, BaseSkill] {} def register(self, skill: BaseSkill) - None: if not skill.name: raise ValueError(技能必须要有 name) if skill.name in self._skills: raise KeyError(f技能 {skill.name} 已存在请勿重复注册) self._skills[skill.name] skill def get(self, name: str) - Optional[BaseSkill]: return self._skills.get(name) def list_skills(self) - List[BaseSkill]: return list(self._skills.values()) def skill_manifest(self) - str: 生成给模型看的技能清单文本。 lines [] for skill in self._skills.values(): lines.append( f- {skill.name}: {skill.description}\n f 输入参数: {json.dumps(skill.input_schema, ensure_asciiFalse)}\n f 输出格式: {json.dumps(skill.output_schema, ensure_asciiFalse)} ) return \n.join(lines) # 全局注册表 registry SkillRegistry()这样一个简单的注册系统就完成了。实际项目中你还可以加自动扫描 packages 目录的功能把技能按插件方式热加载但核心机制是一样的。3.3 实战示例实现一个网页提取技能下面我完整实现一个网页正文提取技能把整个流程走通。这个技能并不复杂但能说明一个真实的技能是怎么从定义到落地的。# skills/web_extract.py import re import requests from bs4 import BeautifulSoup from .base import BaseSkill from .registry import registry class WebExtractSkill(BaseSkill): name web_extract description ( 提取指定网页的正文内容自动去除导航、广告、页脚等干扰元素 返回页面标题和清洗后的纯文本。适用于静态可公开访问的 HTML 页面 不适用需要登录、验证码或 JavaScript 动态渲染的网页。 当用户需要了解某个链接的页面内容时使用本技能。 ) input_schema { type: object, properties: { url: { type: string, pattern: ^https?://, description: 待提取的网页链接 }, max_chars: { type: integer, minimum: 100, maximum: 20000, default: 5000, description: 返回正文的最大字符数 } }, required: [url] } output_schema { type: object, properties: { title: {type: string}, content: {type: string}, content_length: {type: integer} }, required: [title, content, content_length] } def execute(self, params): url params[url] max_chars params.get(max_chars, 5000) headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() # 防止编码识别错误 if resp.encoding and resp.encoding.lower() ! utf-8: resp.encoding resp.apparent_encoding soup BeautifulSoup(resp.text, html.parser) # 去除常见干扰节点 for tag in soup.find_all([script, style, nav, footer, aside]): tag.decompose() title soup.title.string.strip() if soup.title and soup.title.string else 无标题 content re.sub(r\s, , soup.get_text(separator )).strip() return { title: title, content: content[:max_chars], content_length: len(content[:max_chars]) } # 模块导入时自动注册 registry.register(WebExtractSkill())执行时只需要调用registry.get(web_extract).run(params)就行。基础校验、格式规范化、状态封装都在基类里完成了子类只管自己的业务逻辑。3.4 把技能接入 Agent 主流程技能写完不算完还得让 Agent 能在对话中主动调用它。我这里用一个基于函数调用模式的简化实现来说明整体逻辑。# agent/router.py import json from typing import List, Dict, Any from skills.registry import registry class AgentRouter: 负责理解用户输入调用模型判断应该执行哪个技能。 def __init__(self, llm_func, system_prompt: str): self.llm_func llm_func # 模型调用函数输入 prompt 输出文本 self.system_prompt system_prompt def decide_and_execute(self, user_message: str) - str: manifest registry.skill_manifest() prompt f{self.system_prompt} 当前可用的技能清单如下 {manifest} 请根据用户消息和技能清单判断是否需要调用技能。 如果要调用输出 JSON {{action: call_skill, skill: 技能名, params: {{...}} }} 如果不调用输出 JSON {{action: direct_reply, reply: 直接回复的内容}} 用户消息{user_message} output self.llm_func(prompt) decision json.loads(output) if decision[action] call_skill: skill registry.get(decision[skill]) if skill is None: return f抱歉技能 {decision[skill]} 不存在 try: result skill.run(decision[params]) # 这里可以把技能结果拼接提示词再做一轮生成也可以直接返回 return f技能执行完成结果如下\n{json.dumps(result, ensure_asciiFalse, indent2)} except Exception as e: return f技能执行出错{str(e)} else: return decision[reply]这个实现是教学级别的真实的项目里一般会把模型决策和结果后处理分开技能执行结果还会再喂回模型做一轮总结。但核心的调用链路就是这样的模型看到技能清单 - 决定调用哪个 - 按输入契约传参 - 技能执行 - 结果返回。实操建议模型输出的 JSON 不一定每次都能正确解析。我在实际项目里会先尝试json.loads失败时用正则抽取大括号片段再解析再不行就要求模型重新生成。这个兜底逻辑在上一线环境时一定要加。4. 多技能协同与编排实战4.1 技能间依赖与数据传递单个技能好做难的是多个技能怎么配合起来解决一个复杂需求。我常用的模式是把多个技能串成一个 Pipeline上一个技能的输出直接作为下一个技能的输入。比如做网页文章分析这个复合任务我可以定义三个技能web_extract负责抓取网页text_summarize负责生成摘要keyword_extract负责抽取关键词。Agent 拿到用户帮我分析这篇网页文章的需求后先调用提取技能拿到正文再把正文传给摘要和关键词技能最后把三个结果合并输出。这里有一个必须注意的点上一层的输出 Schema 要和下一层的输入 Schema 对齐。比如web_extract输出里有content字段那text_summarize的输入 Schema 就应该也定义content字段这样数据才能直接传递。如果字段名对不上就得在编排代码里做映射多写一层不说还容易出错。所以设计技能时最好提前规划好字段命名规范全局统一。4.2 路由策略让 Agent 在多技能间做正确决策技能多了之后核心矛盾就是路由选择。模型能不能在正确的时间调用正确的技能直接决定了体验。除了前面说的把技能描述写清楚之外我还会在决策提示词里加两条规则。第一条是如果用户需求涉及多个技能按先后顺序输出调用计划先执行第一步再继续。这条规则看似简单但能避免模型在一个回复里强行把所有技能结果都堆出来尤其在技能输出较多时非常有用。第二条是如果现有技能都不满足用户需求直接说明不能处理不要编造结果。很多模型在没有合适技能时倾向于胡编明确给出口令能显著减少幻觉。如果你用的是支持结构化输出的模型还可以把决策结果限定为枚举类型强制模型只能在已注册的技能名称里选。这种约束比任何 Prompt 都硬只要有条件建议都开。4.3 一个完整的多技能协作案例我拿自己做过的一个竞品信息汇总小项目举例。需求是给三个竞品官网链接自动提取每个网站的产品卖点、价格信息最后生成对比表格。当时实现的流程是web_extract技能逐个抓取三个网页的正文内容。info_parse技能接收正文用模型抽取特定的结构化信息产品名、价格、核心卖点。table_builder技能接收三份结构化信息渲染成 Markdown 表格。关键代码片段如下# 伪代码展示编排逻辑 def run_competitor_analysis(urls: List[str]) - str: extracted [] for url in urls: result registry.get(web_extract).run({url: url, max_chars: 8000}) extracted.append(result[result][content]) parsed_items [] for content in extracted: parsed registry.get(info_parse).run({content: content}) parsed_items.append(parsed[result]) table registry.get(table_builder).run({items: parsed_items}) return table[result][markdown_table]这里有个体感很强的经验web_extract抓取到的正文可能有大量噪声广告信息直接喂给info_parse会让模型抽取出一些无关内容。后来我在两个技能之间加了一个text_clean技能先做一轮内容清洗整体准确率提升了不少。多技能流水线的价值就在这里——每个技能专注干好一件事出了问题也容易定位。5. 常见问题与排查技巧实录5.1 模型总是不调用技能直接凭上下文胡答这个问题出现频率极高尤其是刚把技能系统搭起来的时候。排查思路我建议按照下面的顺序来第一检查技能清单有没有被正确注入到提示词里。很多人改了注册表之后忘了重启服务或者清单拼接逻辑出错导致模型根本看不到任何技能。第二检查技能描述的触发场景是否清晰。如果描述里都是处理数据分析文本这种模糊表达模型确实难以在具体问题面前联想到它。第三检查模型是否支持函数调用格式。如果你用的是老模型或者没有开启相关模式模型可能根本不理解你给的 JSON 清单这时需要降级为纯文本问答式决策。我常用的调试手段是做一个裸测把技能清单打进提示词然后随机给十个测试问句看模型每轮都会输出什么样的决策。如果十次里有五次输出了call_skill说明链路是通的剩下就是描述和路由策略需要优化。5.2 技能执行结果出错但不知道是调用问题还是代码问题技能的execute里既有对外部系统HTTP、数据库的调用又有内部的数据处理逻辑出错时很难第一眼定位。我的做法是在基类的外层加一层日志包装把参数、异常堆栈、耗时全部记录下来。# 在 BaseSkill.run 中增加日志记录 import time import logging logger logging.getLogger(skill) def run(self, params): start time.time() logger.info(f技能 {self.name} 被调用参数{json.dumps(params, ensure_asciiFalse)}) try: self.validate_input(params) result self.execute(params) output self.format_output(result) logger.info(f技能 {self.name} 执行成功耗时 {time.time() - start:.2f}s) return output except Exception as e: logger.error(f技能 {self.name} 执行失败耗时 {time.time() - start:.2f}s错误{e}, exc_infoTrue) raise有了日志之后排查就变成线性流程了先看参数对不对再看报错在基类还是子类最后看是不是外部服务的问题。绝大多数情况下问题都出在输入参数不符合预期上这时候回头去打磨输入 Schema 和模型传参的 Prompt 比改代码更有效。5.3 技能数量膨胀后上下文被撑爆技能清单随着项目变大越来越长全部塞进提示词的话光技能描述就能占好几千 Token既费钱又容易让模型注意力涣散。解决思路是分层路由先做粗粒度分类再做细粒度选择。例如你可以给技能打上标签先让模型判断用户需求属于网页处理文本处理数据分析图片处理中的哪一类再把对应分类下的技能清单注入到下一轮决策中。这样每轮提示词里最多只有三五个技能模型的选择压力大大降低。另一个好用的技巧是把技能描述压缩。长描述写在文档里提示词里只放一两句话的精简版。比如网页提取技能的精简描述是提取网页正文返回标题和清洗后文本不支持动态页面。等模型决定调用后再通过工具接口把完整描述和实现细节带回给模型这样可以兼顾上下文长度和可理解性。5.4 技能升级与兼容性问题技能是代码就会迭代。我在一次升级里把某个技能的输出 Schema 加了两个新字段结果下游的技能没同步更新直接解析报错。从那以后我给所有技能定了两条规矩。第一输出只做增量不随便删字段。哪怕新版本里某个字段已经没用了也先保留一个版本周期等下游全部迁完再下线。第二每个技能的注册信息里加一个version字段日志里记录技能版本排查问题时能快速判断是不是版本不一致导致的。项目大了之后这两条规矩能帮你省下大量明明没改代码但突然挂了的排查时间。独家技巧给技能加一个dry_run模式。在注册表里维护一份测试用例集每次技能更新后自动跑一遍全部用例校验输入输出是否符合 Schema。这相当于给技能做了回归测试能拦截大部分低级错误。别嫌麻烦这个投入产出比非常高。最后说点实在的做 Agent Skills 这一年多我最大的感受是这东西没有什么玄学本质就是一套工程化的能力边界定义系统。模型负责理解需求和做选择技能负责把选择变成确定性的执行结果。想让 Agent 好用功夫全在技能设计、描述打磨和异常处理这些笨功夫上。如果你正在从零搭自己的技能系统我的建议是先别追求大而全挑一个具体任务场景把三到五个技能做到极致跑通之后再横向扩展。另外一定要重视输入输出契约这是我见过所有 Agent 项目里最容易被忽略、也最影响稳定性的环节。最后再分享一个小技巧每次给技能写描述时想象你是在给一个理解力很强但完全不了解业务的新同事写交接文档写清楚边界、写清楚反面场景模型的表现会给你惊喜。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

2026年5大AI论文写作软件实测,这篇避坑攻略必看 2026/9/17 22:38:32

2026年5大AI论文写作软件实测,这篇避坑攻略必看

深夜改稿到词穷,查重率居高不下,AIGC检测总是亮红灯——这可能是当前学术工作者最真实的写照。随着各大高校和期刊对AI生成内容的检测愈发严格,传统的写作方式已难以满足效率与合规的双重要求。2026年的AI写作工具不仅需要智能,更…

阅读更多 →
Python处理.doc题库:格式转换、结构化抽取与FTS5检索 2026/9/17 22:38:32

Python处理.doc题库:格式转换、结构化抽取与FTS5检索

简介:这份资料是面向江西省高校教师岗前培训学员的《高等教育心理学》题库文档,围绕心理学概论与教育心理学两大部分梳理考点,适合备考教师岗前培训笔试、需要快速刷题巩固概念的学员使用。压缩包内共1个doc文件,约100KB&#xff…

阅读更多 →
LeetCode 799 Champagne Tower 香槟塔问题全解:从递归到空间优化的五重递进 2026/9/17 22:38:32

LeetCode 799 Champagne Tower 香槟塔问题全解:从递归到空间优化的五重递进

LeetCode 799 Champagne Tower 香槟塔问题全解:从递归到空间优化的五重递进 【免费下载链接】leetcode Leetcode solutions 项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode 本文以 LeetCode 799「Champagne Tower(香槟塔&…

阅读更多 →
免费打开 Visio 的 .vsdx 文件:drawio-desktop 零基础完整指南 2026/9/17 22:38:32

免费打开 Visio 的 .vsdx 文件:drawio-desktop 零基础完整指南

免费打开 Visio 的 .vsdx 文件:drawio-desktop 零基础完整指南 【免费下载链接】drawio-desktop Official electron build of draw.io 项目地址: https://gitcode.com/GitHub_Trending/dr/drawio-desktop 同事用 Visio 画好的流程图发到 Mac 上,你…

阅读更多 →
Baserow Ubuntu 服务器生产部署指南:Docker 一体化安装、Caddy 自动 HTTPS 与 1.8.2 旧版本迁移 2026/9/17 22:38:32

Baserow Ubuntu 服务器生产部署指南:Docker 一体化安装、Caddy 自动 HTTPS 与 1.8.2 旧版本迁移

Baserow Ubuntu 服务器生产部署指南:Docker 一体化安装、Caddy 自动 HTTPS 与 1.8.2 旧版本迁移 【免费下载链接】baserow Build databases, automations, apps & agents with AI — no code. Open source platform available on cloud and self-hosted. GDPR, …

阅读更多 →
Python自动化生成深度学习报告.docx 2026/9/17 22:35:29

Python自动化生成深度学习报告.docx

简介:本资源是一份系统梳理深度学习核心概念与技术脉络的综合性学习报告,面向人工智能初学者、高校学生及转行入门者,帮助建立对监督学习(DNN/CNN/RNN)、无监督学习(AE/GAN)、半监督与深度强化学…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞