news 2026/10/28 15:31:13

大模型Agent技能系统:架构、编排与工程落地全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大模型Agent技能系统:架构、编排与工程落地全解析

最近我在重构一个基于大模型的自动化助手,顺手把agent-skills这套技能系统从头到尾梳理了一遍。这个项目名字看起来很抽象,其实核心并不复杂:当你给Agent添加的能力超过三五个时,每个新需求都要改主流程代码,测试、部署、回归全都跟着动,整个项目会变得非常难维护。而技能系统就是把Agent的能力拆成一个个独立、可插拔、可描述的单元,让模型在运行时自己发现、选择、调用,开发者只需要维护技能本身。这篇文章我会从技能系统的定位讲起,聊到注册表与Schema设计、一个完整技能怎么写、技能之间怎么编排,最后分享我在实际落地时踩过的坑。如果你正在做Agent应用开发,或者准备给现有Agent加技能却不知道从哪下手,这篇应该对你有用。

1. 先说清楚:agent-skills 到底在解决什么问题

1.1 从“工具调用”到“技能库”的进化

早期做Agent,最常见的做法是在代码里写死一系列函数,然后让模型按某种约定去调用。比如做天气助手,就写一个get_weather函数,做日程助手,就写一个create_event函数。这种方式在功能少的时候很直接,但随着功能膨胀,你会遇到一个尴尬的问题:每加一个能力,都要改Agent主逻辑、改提示词、改函数列表,测试和发布的成本越来越高。

后来大家开始用function calling,模型可以自主决定调用哪个函数、传什么参数,这确实解决了“模型能不能调用函数”的问题。但下一个问题立刻浮现:函数应该怎么组织?函数多了以后,模型面对几十个可选函数,经常选错。你没法只靠函数名让模型理解“什么时候该用这个”,你需要的是结构化的、模型可理解的描述。这正是技能(skill)要解决的事。

我在设计agent-skills时,把“技能”定义为一个完整的单元:不仅仅是一段可执行代码,而是代码加上描述文件、入参出参Schema、版本信息、错误处理逻辑、测试用例的组合体。这个单元被注册到统一的注册表里,Agent启动时加载,运行时按需发现、选择、调用。这样做的好处是,新增能力时不再动主流程,只是往技能库里丢一个包。

1.2 技能、工具、工作流的边界

很多人会把工具(Tool)、技能(Skill)、工作流(Workflow)混在一起,我用一个比较朴素的比喻来区分:

工具是一个函数,解决一个明确的、原子的动作,比如“发送HTTP请求”“读取文件”。技能则是工具的增强形态,它包含工具本身,还包含工具何时被使用的上下文、参数如何校验、错误如何恢复。而工作流是多个技能的编排结果,描述的是“先调用A,再根据A的结果决定是否调用B”。

我用一张表来对照它们的差别:

维度工具(Tool)技能(Skill)工作流(Workflow)
粒度原子动作完整能力单元多个技能的组合
模型理解依赖命名与注释依赖结构化描述与Schema依赖编排逻辑
复用方式代码层面复用包级别复用场景级别复用
典型内容一个函数描述+代码+校验+版本流程配置+条件分支

这个边界的意义在于,当你决定做技能系统时,你的设计重心要放在“技能”这一层。工具层你只需要保证可执行、可观测;工作流层你只需要保证编排可配置、可回滚;而真正需要花大量精力打磨的是技能层的描述质量、参数设计和错误处理。

1.3 什么场景下值得引入技能系统

我自己判断要不要上技能系统,就看三个条件是否满足:

第一个,Agent的能力数量已经超过三五个。说白了,一两百行代码就能写完的固定流程,搞技能系统就是过度设计。

第二个,同一个能力需要在多个场景或环境里复用。比如你的Agent既服务内部用户,也服务外部用户,或者需要在测试环境和生产环境跑不同配置,技能包的版本管理价值就体现出来了。

第三个,团队里不同角色都要给Agent加能力。如果只有你一个开发,那技能系统可以简化,但如果是产品、运营都要参与维护,标准化的技能包模式几乎是必须的。

还有一个容易被忽略的场景:当你的模型从GPT换到Claude,或者从Claude换到国产模型时,技能系统能帮你把能力层和模型层解耦。技能描述、参数校验、错误处理都是模型无关的,换模型时只需要调整Agent的提示词策略,不用重写所有能力代码。这一点在模型快速迭代的当下极其重要。

2. 技能系统的核心架构:注册表、描述文件与Schema设计

2.1 一个技能的完整生命周期

在agent-skills里,技能不是一段“挂在代码里被调用”的脚本,而是一个有着明确生命周期的对象。我把它拆成六个阶段:定义、注册、发现、调用、观测、迭代。

定义阶段,你要为技能创建一套文件,我通常要求一个技能目录里至少包含三样东西:一个描述文件(比如SKILL.md或SKILL.yaml)、一个实现文件(比如skill.py)、一个测试文件。描述文件负责告诉系统和模型“这个技能是干什么的”,实现文件负责真正干活,测试文件负责在技能升级时保证行为不倒退。

注册阶段,Agent启动时会扫描技能目录,把满足格式要求的技能包加载进内存,并在注册表里创建一条记录。注册表里保存的是技能名称、版本、描述、Schema、处理函数入口这些元数据,而不是技能内部实现。

发现阶段发生在模型需要决策的时刻。模型根据用户的请求,结合当前Agent的提示词,在注册表里检索可能的技能。这个检索通常包含两步:第一步是系统层面的粗筛(按关键词、命名空间过滤),第二步是让模型在候选集合里精读描述并做出选择。粗筛如果做了,模型的准确率会大幅提升,因为候选数量从几十个降到了三五个。

调用阶段就不用多说了,按Schema校验参数,执行处理函数,把结果按输出Schema归一化后返回给模型。

观测和迭代是很多团队最容易漏掉的两步。你必须在每次技能调用时记录输入、输出、耗时、是否出错、模型选择是否合理。这些痕迹是后续优化描述、调整Schema的关键素材。没有观测数据,你就是在盲调。

2.2 描述文件怎么写,模型才容易理解

我见过太多人把技能描述写成一行注释:“获取天气信息”。这个描述模型能理解个大概,但完全不够。模型选择技能靠的是描述,而不是代码逻辑,所以描述文件的核心任务是让模型在正确场景下想到这个技能,并在错误场景下不要选中它。

我通常会在描述文件里写清四件事:这个技能做什么、什么场景下用、什么场景下不要用、需要什么前置条件。以“获取天气信息”为例,一个合格的描述可能长这样:

name: weather_query version: "1.2.0" description: > 查询指定城市、指定日期的天气情况,包括温度、湿度、降水概率、风力等级。 当用户询问今日或未来几天的天气、是否需要带伞、适不适合出行时使用。 不要使用此技能回答历史天气数据,也不要用于查询空气质量、紫外线指数之外的其他环境指标。 input_schema: type: object properties: city: { type: string, description: "城市名,如北京、上海、广州" } date: { type: string, description: "日期,格式YYYY-MM-DD,默认今天" } required: [city]

注意描述里的“什么时候不要用”,这一点非常关键。模型在功能模糊时,往往会倾向于调用一个“看似相关”的技能,你提前把坑标出来,能大幅减少误调用。我实测下来,加上反向约束描述后,技能误调用的比例能降30%以上。

2.3 注册表与动态发现机制

注册表是技能系统的中枢。在agent-skills里,注册表我设计得非常薄,它不执行任何技能逻辑,只做三件事:保存技能元数据、提供按名称查找的接口、提供按描述语义检索的接口。

语义检索那部分,我会在上层做一层embedding匹配。比如用户的请求是“今天适合穿什么衣服”,系统先把这句话向量化,和所有技能的描述向量做相似度计算,挑出Top-K候选,再把这些候选的描述交给模型精读决策。这个小改动直接把模型从“在50个技能里大海捞针”变成“在3个技能里做选择”,效果立竿见影。

注册表的数据结构大概长这样:

@dataclass class SkillEntry: name: str version: str description: str input_schema: dict output_schema: dict handler: Callable enabled: bool = True tags: list[str] = field(default_factory=list)

动态发现机制我做了两层:启动时全量扫描加载,运行时可针对某个技能单独刷新。单独刷新这个能力在调试时特别有用,你改完技能代码,不需要重启整个Agent进程,只需调一个reload_skill("weather_query"),新逻辑就能生效。这个对迭代效率的提升非常明显,尤其在调试一些数据依赖比较多的技能时。

3. 从零写一个可复用技能:HTTP请求技能实战

3.1 技能目录与最小骨架

架构讲得再多,不如亲手写一个技能。下面我从最常用的“HTTP请求技能”拆起,这个技能几乎是所有Agent应用的地基,要查询外部接口、要抓取网页、要调用第三方服务,都绕不开它。

我的技能目录结构是这样:

skills/ http_request/ SKILL.yaml skill.py test_skill.py

SKILL.yaml是我们的描述与配置入口,里面定义了技能名称、描述、入参Schema、超时策略等:

name: http_request version: "1.3.0" description: > 发送HTTP请求并返回响应内容。支持GET、POST、PUT、DELETE方法。 当需要访问外部API、读取网页内容、提交表单数据时使用。 不要使用此技能访问内网地址或云元数据接口,这类请求会被安全策略拒绝。 input_schema: type: object properties: url: { type: string, description: "完整URL,必须包含协议头,如https://example.com/api" } method: { type: string, enum: [GET, POST, PUT, DELETE], default: GET } headers: { type: object, description: "请求头,可选" } body: { type: string, description: "请求体,可选,仅POST/PUT时生效" } timeout: { type: number, default: 10, minimum: 1, maximum: 30 } required: [url] output_schema: type: object properties: status_code: { type: integer } headers: { type: object } body: { type: string } truncated: { type: boolean, description: "响应体是否因超长被截断" }

实现文件skill.py的核心逻辑并不复杂,我保留了最关键的防御式处理:

import requests from urllib.parse import urlparse def handle_http_request(ctx): params = ctx.params url = params["url"] # 禁止访问内网与元数据接口,防止SSRF风险 parsed = urlparse(url) host = parsed.hostname if not host or host in ("localhost", "127.0.0.1", "169.254.169.254"): return {"status_code": -1, "body": "blocked host", "truncated": False} method = params.get("method", "GET") headers = params.get("headers", {}) timeout = min(params.get("timeout", 10), 30) max_size = 1024 * 1024 # 1MB response = requests.request(method, url, headers=headers, data=params.get("body"), timeout=timeout, allow_redirects=False) body = response.text truncated = len(body.encode("utf-8")) > max_size return { "status_code": response.status_code, "headers": dict(response.headers), "body": body[:max_size], "truncated": truncated, }

这段代码只做了一件核心的事情:发起请求并返回结构化输出,但里面的几个细节值得展开说。allow_redirects=False是一个教训,如果自动跟随重定向,模型拿到的最终结果和它看到的URL可能完全对不上,排查问题时会非常痛苦;限制响应体积则避免了超大响应直接撑爆模型上下文窗口。

3.2 参数设计与防御式处理

参数设计直接影响模型的调用成功率,我总结出一条经验:给模型越小的自由度,模型越不容易犯错。

比如http_request这个技能,我刻意没有提供auth参数,认证逻辑通过环境变量注入,而不是让模型自己拼。因为让模型处理密钥,几乎一定会出错,不是把密钥暴露到日志里,就是传错格式。参数越少,模型的选择空间越小,行为越可控。

防御式处理方面,除了上面代码里看到的禁内网、限超时、限大小,我还会在技能层面对返回内容做一层清洗:去掉不可打印的控制字符、规范化编码、统一换行符。这些不起眼的处理能避免很多后续问题,尤其是当模型拿到一段包含乱码的HTML时,它可能会一本正经地把乱码当成有效内容去理解。

还有一个很容易忽略的点:timeout的最大值限制。有人会问,为什么要限制在30秒?因为技能系统通常运行在一个共享的Agent进程里,任何一个技能长时间阻塞,都会拖住整个系统的响应。30秒是我实测过的一个折中值:大多数外部API在10秒内能返回,30秒足够覆盖慢接口,又不会让整个Agent卡死太久。

3.3 错误码、超时与重试

技能出错是常态,问题在于出错以后系统怎么响应。我在agent-skills里约定了一套错误码规范,每个技能返回的结构都包含status_code字段,业务上还用error_code补充细节:

返回码含义模型应如何处理
200成功继续后续流程
400请求参数错误检查入参格式,修正后重试
401/403认证失败提示用户需要授权,不要反复重试
404资源不存在告知用户,或者换个信息源
408/504超时可重试一次,仍然失败则放弃
-1安全拦截停止操作,不要尝试绕过

重试逻辑我放在了技能外层,而不是技能内部。原因很简单:技能内部重试会隐藏问题,让观测数据失真。外层重试我会配合幂等设计,比如GET请求天然幂等,重试安全;POST请求是否可重试,取决于接口是否支持幂等。在不知道接口语义的前提下,我倾向于不重试POST。

如果你是用OpenAI或其他大模型的function calling来组织Agent,那么当技能返回非200响应时,我建议把原始响应结构体直接丢回给模型,让模型自行判断下一步,而不是在技能内部替它决定。技能负责把事实传清楚,决策权留给模型。

4. 技能编排:单个技能只是零件,组合起来才是生产力

4.1 链式编排:让技能按顺序接力

单个技能能解决原子问题,但真实的用户请求往往是复合型的。比如“帮我看一下今天的新闻,然后总结成三条要点”,这就涉及“搜索/抓取新闻”和“生成摘要”两个环节。在agent-skills里,我通过编排配置把技能串成链式流程。

链式编排的核心思路是:上一个技能的输出,经过参数映射后变成下一个技能的输入。我在编排配置里显式声明这种映射关系,而不是让模型自己脑补:

workflow: news_summary steps: - skill: news_search output: { news_items: result.items } - skill: summarize input: source_text: ${steps.news_search.result.items} max_bullets: 3

这种显式的声明有几个好处:它让流程可观测、可调试;它减少了模型自由发挥的空间;它还让业务流程和模型能力解耦,即使未来换了模型,只要参数格式不变,工作流就不用动。

4.2 条件分支与并行调用

链式流程适合线性场景,但实际业务里常常有分支。比如“如果搜索结果超过5条,就做汇总;否则直接返回原文”。我处理的方案是在编排引擎里支持简单的条件判断:

- skill: search output: { results: result.items } - condition: ${len(results) > 5} then: - skill: summary input: { source_text: ${results} } else: - skill: direct_reply input: { content: ${results} }

条件分支的实现并不复杂,就是在一个状态机里对上游输出求值,然后选择对应分支。但需要注意限定条件语法的表达范围,我只允许数值比较、字符串包含、布尔判断这几类,不允许写复杂逻辑。越简单的东西越不容易失控。

并行调用是另一种常见的需求,比如“同时查三个城市的天气然后对比”。并行能显著缩短整体耗时,但也带来了两个麻烦:一是模型需要把任务拆分成多个独立子任务,二是最终要把并行结果合并成一致格式。我在实践中发现,并行调用更适合那些结果格式完全一致的技能,比如批量查天气、批量查快递、批量查股票。如果技能结果结构差异很大,合并阶段常常会崩溃,还不如串行来做。

4.3 编排收敛:防止Agent陷入技能狂欢

用技能编排一段时间后,你一定会遇到一类典型问题:Agent为了一个简单问题,不断调用技能,翻来覆去地搜索、验证、再搜索,就是不直接回答用户。我把这种现象称为“技能狂欢”。

究其原因,是模型对“什么时候该停止”缺乏内在判断。你给了它灵活的技能工具集,它反而变得犹豫不决,老想多验证一步。我在agent-skills里加了三个收敛机制:

第一个是步骤上限,一个Agent任务最多允许执行N步技能调用,默认我设成8,超过就强制停止并汇总当前结果。这个限制看起来粗鲁,但实际效果很好,能避免绝大多数失控场景。

第二个是阶段目标校验,在每个技能返回后,检查这一步输出是否让任务推进了。如果连续两次调用的输出和任务目标无关,就打断Agent,让它直接给出阶段性结论。

第三个是“够了就停”的策略注入,在系统提示词里明确告诉模型:只有显式说明信息不足时才需要继续调用,否则直接回答。这听起来像一句废话,但说和不说差异巨大,模型是提示词动物,你写得越明确,它就越会照做。

5. 工程落地中最容易踩的坑:安全、评估与版本管理

5.1 安全与权限边界,千万别等出事后才补

技能系统最大的工程隐患不在功能,在安全。因为技能本质上是给模型开放了一堆可执行能力,一旦策略没控制好,模型就可能被引导去执行危险操作。

我踩过的最大一个坑,是给一个数据查询Agent开放了一个“删除数据库记录”的技能。当时觉得模型不会随便调用删除操作,结果在一次测试中,用户提问“把上个月重复的测试数据清理一下”,模型真的调用了删除技能,幸好在测试库上,没造成实际损失。从那之后我立了一个规矩:危险操作必须在技能层加“操作确认闸门”,技能执行前必须向用户展示即将执行的语句,等待确认。

权限模型我按三个维度设计:网络(禁止内网访问)、文件系统(只允许白名单目录读写)、外部副作用(邮件发送、数据删除、支付操作必须人工确认)。这三个维度不一定覆盖所有场景,但方向上是对的:凡是模型能造成不可逆影响的技能,都必须经过人工授权。

5.2 技能质量怎么评估与回归

技能系统的另一个大坑是“看起来能用,但不知道好不好用”。如果你没有评估体系,就永远无法回答“升级技能后模型选择准确率是升了还是降了”这个问题。

我给技能库做了一套三指标的评估体系:工具选择准确率、执行成功率、结果相关性。工具选择准确率看模型在预设测试场景里是否正确选中目标技能;执行成功率看技能在合法输入下能不能正常返回;结果相关性则是对返回内容做人工抽样评分。

测试集我要求每个技能至少有10个golden case,覆盖典型场景、边界场景和易混淆场景。每次技能库改动后,全量跑一遍回归,比对三个指标的变化。这个流程看起来笨重,但它是技能库能持续演进的根基。没有回归测试,你每改一个技能描述,都可能悄悄劣化另一个技能的选择率,而且你自己根本察觉不到。

5.3 版本管理与灰度发布

我前面提到每个技能都带版本号,这不是摆设。技能库在多环境并存时,版本管理能救你的命。

我遇到的典型案例是:升级了http_request技能的Schema,把timeout字段从必填改成了可选,结果一个还在跑旧版本的业务流程直接崩溃,因为它按旧Schema传入的参数无法通过新校验。从那以后,我强制规定:修改Schema必须升主版本号,并且同一环境里不允许直接切换版本,必须走灰度。

灰度发布我的做法是,让Agent在启动时读取配置决定加载哪个技能版本,配置支持按流量比例切分。比如新版本先切5%的流量,观察一天的工具选择率和执行成功率,如果没有异常,再逐步提高到全量。这个流程让技能升级变成了一件不那么紧张的事,也避免了许多“改完就跑路”的线下问题。

版本管理的另一个细节是技能描述和实现的同步。很多人只改了代码,忘了更新描述文件,导致模型认知和实际行为脱节。我在CI里加了一个检查项:技能代码有变更时,强制要求描述文件里的version字段跟着变,否则直接拦截合并请求。这算不上什么高级手段,但非常管用。

我在维护agent-skills这么长时间后,最大的体会是:技能系统真正的复杂度不在技术,而在治理。写一个技能很容易,难的是让几十个技能在一个系统里互不干扰地稳定运行。注册表、Schema、描述文件、编排收敛、安全闸门、回归测试,每一层都是在给模型的行为加护栏。如果你也在做类似的Agent项目,我的建议是先小范围搭一套最小可用的技能框架,拿三五个真实技能跑通流程,再逐步完善治理能力。步子别迈太大,耐心一点,这套系统会在你迭代的过程中逐渐帮你省下大量时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 15:31:12

88E6320交换板原理图版本、电源树与I/O SEL检查指南

简介:围绕Marvell 88E6320/21交换芯片的硬件设计参考文档,面向网络设备硬件工程师、PCB设计人员及嵌入式开发者,服务于原理图评审、接口规划和板级调试等场景。文档为REV2.1版DB原理图,内容覆盖电源与稳压器选型、上电复位芯片、M…

作者头像 李华
网站建设 2026/10/28 15:30:47

MBIST实战:.atpg建模与March C+算法选型

简介:《Mentor_mbist学习总结》面向数字IC设计、DFT验证及可测性设计初学者与工程实践者,围绕存储器内建自测试的完整知识链路展开,帮助读者理解MBIST为何加入芯片、适用于哪些场景,以及其在测试效率、成本与故障覆盖之间的权衡。…

作者头像 李华
网站建设 2026/10/28 15:30:09

研发平台从支撑系统到智能决策中枢:数据底座与效能度量

去年年底一次内部评审,研发效能团队把大屏投上去,交付周期、缺陷密度、需求吞吐量一片绿,坐在后排的一位技术负责人问了一句:那下个季度我到底该先砍掉哪个项目、给哪个团队加人?会议室安静了十几秒,没人接…

作者头像 李华
网站建设 2026/10/28 15:29:39

Kubernetes核心架构与容器编排实践指南

1. 容器编排与 Kubernetes 概述容器技术已经成为现代应用部署的标准方式,但如何高效管理成百上千的容器实例却是个棘手问题。这就好比一个人管理自家后院的小菜园很容易,但要经营一个大型农场就需要系统的管理方法了。Kubernetes(简称K8s&…

作者头像 李华
网站建设 2026/10/28 15:27:52

2026年AI产品经理必备技能与知识体系解析

1. 为什么2026年会是AI产品经理的关键转折点2026年1月1日这个时间点并非随意选择。根据Gartner技术成熟度曲线预测,到2026年生成式AI将进入"生产力高原期",这意味着AI技术将完成从实验室到产业化的关键跨越。作为从业15年的AI产品老兵&#xf…

作者头像 李华
网站建设 2026/10/28 15:27:36

代码生成技术优化:从原理到实践

1. 代码生成技术的现状与挑战在当今软件开发领域,代码生成技术已经成为提升开发效率的重要手段。从企业级应用到嵌入式系统开发,代码生成工具正在改变着传统的编程方式。然而,随着应用场景的多样化,代码生成技术也面临着诸多挑战。…

作者头像 李华