news 2026/10/21 19:23:51

大模型Agent技能体系:从Prompt堆工具到可注册可编排的插件化架构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大模型Agent技能体系:从Prompt堆工具到可注册可编排的插件化架构

先聊聊我为什么会对“agent-skills”这个方向投入这么多精力。过去半年,我一直在做基于大模型的智能体项目,最开始的版本非常朴素:把工具函数一股脑塞进System Prompt,再配上几段工具说明,让模型自己挑着用。能跑通,但维护起来极其痛苦——每个场景都要重写大段提示词,新增一个能力要全局改动,技能之间还会互相干扰。后来我发现,问题的根源在于我把“能力”和“调用”混在了一起,没有抽象出“技能”这一层。这才有了“agent-skills”这套体系:把Agent能做的事封装成独立、可注册、可编排的技能单元,让大模型通过统一的接口去发现和使用它们。

这篇文章我从设计思路、核心架构、实操落地到排查技巧完整拆解一遍。适合两类读者:一是正在做大模型Agent应用、但还在用“Prompt堆工具”方案的开发者,二是想把现有Agent改造成“技能插件化”架构的团队。内容不会堆概念,全部来自我实际跑通、反复迭代过的代码和踩坑记录。

1. 项目整体设计与思路拆解

1.1 先从“技能”这个词说起

在Agent语境里,“技能”(Skill)不是一个花哨的概念,它本质上是一次能力封装。人的技能是“知道怎么做某件事”的本领,Agent的技能则是“能执行某个操作”的代码单元。它把输入参数、执行逻辑、输出格式、依赖条件全部封装在一个标准接口后面,大模型不需要理解内部实现,只需要知道“这个技能做什么、需要什么参数、能返回什么结果”。

这个抽象的价值在于:它把Agent的“大脑”(推理决策)和“手脚”(执行能力)解耦了。大脑负责理解用户意图、制定计划、调用合适的技能;手脚负责具体执行、返回结果。没有这一层抽象,你只能把工具描述写进Prompt,让模型做“一次性的工具选择”,而有了技能层,模型做的是“基于技能清单的策略决策”,二者完全不在一个量级。

我见过很多团队在Agent雏形期就踩进一个误区:把工具函数直接作为函数注册给模型,确实单轮调用体验很好,但一旦任务复杂化——需要异步执行、需要技能组合编排、需要依赖前一个结果决定下一步——这种“裸函数式”设计立刻崩盘。技能层天然适合做这些事,因为技能之间可以组合、可以依赖、可以共享上下文。

1.2 为什么不能继续用“Prompt堆工具”

最直接的痛点有三个。

其一,Prompt长度失控。每个工具说明平均200到400字,挂5个工具就要多出近2000字。一旦技能数量超过10个,模型就开始“注意力涣散”,优先调用描述靠前或措辞生动的工具,而不是真正合适的那个。技能体系里,模型看到的是技能清单列表——名称、一句话简介、参数摘要——整体开销大幅压缩。

其二,工具逻辑分散。没有技能层时,工具的鉴权、参数校验、错误重试、上下文管理都写在调用点附近,四处复制粘贴。技能层强制把所有相关的横切逻辑收拢进一个执行体内,调用方只管“发起请求、拿结果”。

其三,无状态。很多Agent初版是无状态的一次性调用,但真实业务几乎都需要多轮、多步的上下文累积。技能框架天然能携带状态——比如一个“文件上传技能”需要在多个步骤间记住会话上传了哪些文件,技能状态对象可以在请求间存活。

从设计决策上说,我选择用“技能注册表 + 统一执行接口 + 上下文状态对象”这样的三层结构,而不是搞一个重型的Agent框架,原因只有一个:轻量、可嵌入。很多Agent项目其实只差一层技能抽象,不需要引入一整个规则引擎或编排平台。我的目标是让任何已有Agent代码库,通过引入一个skills目录和一两个核心类,就能获得技能化的能力。

1.3 方案选型:为什么是“注册制”而非“枚举制”

初期我试过“枚举制”——把所有技能写在一个大的if-else或switch里。性能倒是没问题,但每次加技能都要改主分支代码,违反开闭原则。后来切换到“注册制”,每个技能是一个独立模块,启动时扫描并注册进技能表。

注册制还有一个隐藏好处:技能可以按需加载。比如图像处理类技能只在用户对话中出现“图片”相关意图时才导入对应模块,内存占用和启动时间都更可控。技能模块可以独立测试,甚至可以做成热插拔——我现在的项目里,新技能发布不需要重启主服务,注册中心会自动发现新模块。

从运维角度看,注册制让技能的“可见性”变得可观测。我可以随时列出当前系统注册了多少技能、哪些被频繁调用、哪些从未被触发,这些数据对后续优化模型决策、精简技能列表非常有用。枚举制下,技能是否存在只有代码review时才知道。

2. 核心架构:技能系统的组成模块

2.1 技能定义Schema:让模型“看懂”技能

技能要让大模型准确调用,它的描述格式必须严格结构化。我最终确定的技能Schema包含这几个核心字段:

  • name:全局唯一标识,采用类似file.upload、db.query的命名风格,前缀区分领域,名称就是技能的身份。
  • description:一句话说明技能能力,重点写“能做什么、何时应该用”,避免写实现细节。模型靠这句话做语义匹配,描述越精准,误用率越低。
  • parameters:JSON Schema格式的参数定义,模型严格按照这个结构生成调用参数。参数描述必须注明类型、是否必填、取值范围。
  • executor:实际执行的入口,可以是函数引用、HTTP端点或一个子Agent。
  • runtime_hints:运行期提示,比如超时时间、并发限制、需要哪些环境变量。

其中“description”是最容易被低估的字段。我做过对比实验:把“计算两个日期的差值”改成“当用户询问XX事件到XX事件间隔多少天、或需要剩余天数倒计时时使用”,模型调用准确率从71%提升到了92%。描述里带触发场景,比泛泛描述效果强得多。

参数Schema方面,我强烈建议直接用JSON Schema而非自创格式。原因很实际:大模型在众多训练数据里见过大量JSON Schema,它理解这种结构的成本最低;同时校验库成熟,Python里jsonschema库可以直接复用。参数命名也有讲究,用file_path而不是path,用start_date而不是date_start,模型生成参数时的直觉准确性会更高。

2.2 技能注册与发现机制

技能注册的核心是一个注册表类,内部用一个字典维护技能名到技能对象的映射。启动流程不长,但顺序和细节决定了整个体系是否可靠。

第一步是技能扫描。我按目录约定扫描skills/下的所有模块,每个模块暴露一个register()函数,返回技能类实例。用pkgutil.iter_modules遍历目录,避免手写导入列表,这样新增技能文件后无需改动主入口。

第二步是依赖检查。很多技能依赖外部服务或配置项,注册时就要检查环境变量是否存在,缺依赖就标注为inactive而不是直接报错。这样系统不会因为某个技能缺配置就整个崩溃,问题技能会被隔离。

第三步是注册确认。所有技能注册完成后,生成一份技能清单摘要,记录数量、活跃数、名称列表。这份摘要就是后续注入给大模型“可调用技能列表”的依据。

2.3 技能编排与组合:从单技能到工作流

单技能能力有限,真正复杂的业务需要技能组合。我实现了一套简单的编排机制:技能的执行结果统一放入上下文状态对象,下一个技能可以通过上下文访问上一个技能的结果。比如“用户上传了一份财报,要求提取所有数字并做同比分析”,那么流程是file.parse解析文档,finance.extract提取关键指标,analysis.compare做对比分析。

编排时最关键的技巧是“结果摘要”。大模型上下文窗口有限,不能把前一个技能的完整输出直接灌给下一个技能。我的做法是定义了一个summarize()方法,每个技能负责把自己的结果压缩成一段摘要文本放进上下文。这样编排链条上传递的是高密度信息,而不是原始数据。

技能之间还可以声明依赖关系:比如finance.extract声明依赖file.parse的输出格式。编排器会检查依赖图,自动调整执行顺序,避免重复解析。依赖关系也防止了环形调用——我在注册时用拓扑排序检测环,一旦发现循环依赖直接标记配置错误。

3. 实操过程:从零实现一个技能框架

3.1 目录结构与初始化

我用一个最小的工程结构演示,这套结构能直接迁移到大多数项目中。

agent-skills/ ├── agent/ │ ├── __init__.py │ ├── core/ │ │ ├── __init__.py │ │ ├── registry.py # 技能注册表 │ │ ├── context.py # 上下文状态对象 │ │ ├── executor.py # 技能执行器 │ │ └── errors.py # 异常定义 │ ├── skills/ │ │ ├── __init__.py │ │ ├── file_parse.py │ │ ├── db_query.py │ │ └── calc_diff.py │ └── main.py # 入口示例 └── requirements.txt

注册表的实现非常简洁,核心逻辑是维护注册字典并提供查找、列表能力:

# agent/core/registry.py class SkillRegistry: def __init__(self): self._skills = {} # name -> Skill self._dependencies = {} # name -> set(names) def register(self, skill): name = skill.name if name in self._skills: raise DuplicateSkillError(f"skill {name} already registered") self._skills[name] = skill for dep in skill.dependencies: self._dependencies.setdefault(name, set()).add(dep) def resolve(self, name): if name not in self._skills: raise SkillNotFoundError(f"skill {name} not found") return self._skills[name] def active_skills(self): return [s for s in self._skills.values() if s.status == "active"] def to_catalog_prompt(self): return "\n".join( f"- {s.name}: {s.description} (params: {s.parameters})" for s in self.active_skills() )

to_catalog_prompt()就是把技能清单转成给模型看的文本,这一步让注册表和Prompt生成之间建立了天然联动。

3.2 上下文状态对象:让技能之间有“记忆”

技能执行过程中,输入参数、中间结果、最终输出都需要一个统一载体。我定义了一个SkillContext,它在一次Agent任务的生命周期中贯穿始终:

# agent/core/context.py @dataclass class StepResult: step_id: str skill_name: str status: str summary: str payload: Any class SkillContext: def __init__(self, initial_input: str): self.initial_input = initial_input self.steps: list[StepResult] = [] self._memory = {} def put(self, key: str, value: Any): self._memory[key] = value def get(self, key: str, default=None): return self._memory.get(key, default) def add_step(self, result: StepResult): self.steps.append(result) # 自动把摘要保存进memory,方便后续技能读取 self._memory[result.skill_name] = result.summary def full_history_text(self): return "\n".join( f"Step {r.step_id} [{r.skill_name}] {r.status}: {r.summary}" for r in self.steps )

注意add_step()里做了两个设计选择:一是保留完整payload,但同时总是生成summary;二是每一步的摘要自动写入内存,技能名就是key。这样后续技能既能通过ctx.get("file.parse")拿到摘要,也能在需要原始数据时访问ctx.steps[-1].payload。

3.3 技能执行器:统一调度入口

技能的调用不能是裸函数调用,必须经过执行器统一处理,这样横切逻辑才能集中。执行器负责:

  • 参数注入:把模型生成的JSON参数传给技能。
  • 前置校验:检查技能是否active,检查必填参数。
  • 超时控制:用asyncio.wait_for包裹执行,防止技能卡死拖垮Agent。
  • 结果规范化:把任意返回值统一包装成StepResult。
  • 异常隔离:技能抛出的异常被捕获后转为status="failed"并附错误摘要,而不是直接中断整个任务。
# agent/core/executor.py async def execute_skill(registry, context, skill_name, params: dict, timeout=30): skill = registry.resolve(skill_name) if skill.status != "active": raise SkillInactiveError(f"skill {skill_name} is inactive: {skill.inactive_reason}") # 参数校验 errors = validate_against_schema(params, skill.parameters) if errors: return StepResult( step_id=context.next_step_id(), skill_name=skill_name, status="param_error", summary=f"参数校验失败: {errors}", payload=None ) try: result = await asyncio.wait_for( skill.execute(params, context), timeout=timeout ) except asyncio.TimeoutError: return StepResult(... status="timeout", summary=f"skill {skill_name} timed out") except Exception as e: return StepResult(... status="failed", summary=f"{type(e).__name__}: {str(e)}") return StepResult( step_id=context.next_step_id(), skill_name=skill_name, status="success", summary=result.summary(), payload=result )

有了统一执行器,技能内部就可以“只做正事”,不用自己处理超时、校验、错误格式化。从我的经验看,这一步是最值得投入的,它把所有技能的鲁棒性问题收敛在一处解决。

3.4 一个标准技能长什么样

以“日期差计算”技能为例,完整的技能模块结构如下:

# agent/skills/calc_diff.py from datetime import datetime class CalcDiffSkill: name = "calc_diff" description = "计算两个日期之间的间隔天数,适用于用户询问XX日到XX日有多少天、剩余天数等场景" parameters = { "type": "object", "properties": { "start_date": {"type": "string", "description": "起始日期,格式YYYY-MM-DD"}, "end_date": {"type": "string", "description": "结束日期,格式YYYY-MM-DD"} }, "required": ["start_date", "end_date"] } dependencies = [] status = "active" async def execute(self, params, context): start = datetime.strptime(params["start_date"], "%Y-%m-%d") end = datetime.strptime(params["end_date"], "%Y-%m-%d") diff = (end - start).days class Result: def __init__(self, days): self.days = days def summary(self): return f"两个日期相差 {self.days} 天" return Result(diff) def register(): return CalcDiffSkill()

这是一个最小可执行技能。register()函数就是之前扫描机制依赖的入口。这里没有复杂的业务,但体现了一个技能该有的基本要素:标准接口、参数Schema、结构化结果。

3.5 模型决策循环:技能框架如何与LLM联动

技能框架本身不产生智能,智能来自大模型对技能清单的决策。我推荐的执行循环是“计划-执行-反馈-循环”,在一次任务中重复进行,直到满足终止条件。执行顺序如下:

  1. 把技能清单(registry.to_catalog_prompt())和用户输入一起发给模型。
  2. 让模型输出一个结构化的决策,格式为{"thought": "...", "skill": "skill_name", "params": {...}},或者输出{"answer": "最终答复用户"}表示任务完成。
  3. 如果是技能调用,执行器运行对应技能,把StepResult追加进上下文。
  4. 把“上一步执行结果”反馈给模型,允许它评估结果、决定继续调用还是给出最终答案。
  5. 设置最大循环次数,比如10轮,防止模型陷入反复调用同一技能的死循环。

这一循环的关键是“反馈必须包含状态”。如果技能失败,模型需要看到失败原因,才能决定是换参数重试、换技能还是直接结束。我会在Prompt里明确提示模型“技能执行可能失败,请根据失败原因调整策略”,这一步能极大降低模型在异常路径上乱撞的概率。

3.6 参数计算与错误重试策略

实操中经常出现模型生成的参数格式不对,比如把时间写成“2024-1-1”而不是“2024-01-01”。我总结的经验是:与其依赖模型永远生成标准格式,不如在技能内部做一层容错解析。具体做法是,技能入口处先做“宽松解析”,尝试多种时间格式和字符串清理,解析失败才返回参数错误。

重试策略也要谨慎设计。盲目让模型重试同一技能通常无效,更好的做法是:第一次失败后,把“可选的修正建议”注入下一轮Prompt。例如参数错误时,把校验错误信息原样返回,并加一句“请严格按参数Schema重新生成参数,特别是类型和格式”。另一种情况是技能返回status="timeout",此时应该建议模型尝试更小粒度的子任务,或者直接放弃该步并转向其他路径。

4. 常见问题与排查技巧实录

4.1 模型就是不肯调用技能

这是最常见、也最容易排查错方向的问题。模型明明看到了技能清单,却总是自己“脑补”答案,或返回一段泛泛而谈的文字而不是结构化调用。

我排查这个问题的经验有三步。

第一步,检查技能描述里的触发信号是否明确。如果技能描述只写“计算两个日期的差”,模型可能把它理解为“可选的辅助信息”而不是“必须调用的工具”。把描述改成“当用户询问日期差、剩余天数、倒计时时,必须调用此技能”,触发率立刻上升。重点在于“必须”和具体场景,不是泛泛的能力声明。

第二步,检查Prompt的决策指令是否用中文提示模型“输出JSON”。很多模型在收到开放式任务时更倾向直接回复而非调用工具,需要在指令里明确说明:如果你判断需要执行操作才能回答,请输出{"skill": "...", "params": {...}}的JSON;若能直接回答,才输出普通文本。

第三步,确认技能描述是否侵入上下文。我见过一个项目,因为技能描述写得太长,模型上下文被大量描述占满,导致模型“忘了”要调用工具。把描述压到一句话以内,长内容放runtime_hints而不是description字段。

4.2 技能调用对但参数总出错

参数错误大部分集中在三种情况:类型不匹配、日期格式不规范、枚举值超出范围。类型不匹配我通过两层校验来解决,第一层是前端Schema校验,错误立即返回;第二层是技能内部“宽松模式”,尽量从字符串中智能转换。前端校验避免脏数据进入执行逻辑,宽松模式则尽量给模型一个机会。

我的建议是:在参数Schema里把枚举型字段描述的非常具体。例如字段是mode,取值范围包括fast和accurate,描述里一定要写明“可选值仅限:fast、accurate”,而不是笼统地写“模式参数”。模型在生成参数时会倾向从描述中提取合法值,如果描述不够明确,它就可能生成quick或efficient这类自定义值。

另外,我强烈建议在开发阶段开启“参数错误日志记录”。每次模型生成的参数校验失败,都把原始参数、错误信息存下来。积累两三百条以后,会看到明显的模式——哪个技能最容易被某类参数坑到。针对模式修改Schema描述,参数错误率能稳定下降50%以上。

4.3 多个技能互相“打架”调用

当Agent同时拥有两个能力相近的技能时,模型可能会随机选择,或者反复尝试不同的技能直到一个成功。例如同时有search_db和search_web两个技能,用户问“帮我查一下产品库存”,模型有时调用数据库,有时调用网页搜索,结果天差地别。

这个问题要从根上解决:技能边界要清晰,描述要互斥。search_db的描述应注明“仅查询内部数据库中的结构化记录,适合库存、订单、用户资料”,search_web的描述应注明“仅查询互联网公开信息,适合实时资讯、公开资料”。模型看到两个技能的边界后,能做出正确的职责判断。

如果两个技能本质上是同一个领域的不同实现,更好的做法是合并成一个技能,用参数区分执行路径,而不是让模型在两个技能之间周旋。我踩过几次坑后总结了一个原则:同名不同实现的技能不要拆开注册,宁可内部用策略模式。

4.4 编排链路太长导致上下文爆炸

一个复杂任务可能产生15到20条步骤记录,每步的摘要和反馈都累积在上下文中,几轮之后模型就“记不清”前面的结果了。这个问题很隐蔽,任务本身没有跑错,输出质量却急剧下降。

我的解决方案是“滚动摘要窗口”。保留最近N步的完整记录,更早的步骤统一压缩成一条“任务进度摘要”。具体做法是,每隔几步就对之前的步骤做一次汇总,把关键结果提炼成一句长文本放进上下文。这个压缩动作本身可以用一个独立的context.summarize技能实现,让它用LLM对步骤历史做摘要。虽然增加了模型调用成本,但长任务稳定性提升非常明显。

另外,关于Agent技能项目的搭建,我再分享一个很多人忽略的技巧:给技能加“健康自检”能力。每个技能注册后,定期跑一次最小用例,确认其返回结果正常。大模型时代,依赖接口的工具经常因上游变化而悄悄失效,自检能在用户发现问题之前就暴露故障。我给框架写了一个health_check方法,每天早上自动跑一遍,配合告警推送,效果显著。这个机制在技能数量超过20个之后尤其有价值。

5. 从技能框架到技能生态:后续扩展路径

这套Agent技能框架跑通以后,我体会最深的一点是:技能化的核心价值不在于单个技能的效率,而在于能力的积累与复用。有了统一框架,每解决一个新需求就沉淀成一个新技能,技能库越来越大,系统能应对的任务范围也随之扩张。

扩展方向上,我建议优先考虑三件事。一是技能版本管理,技能的参数和逻辑总会演进,给技能加版本号,在注册表里记录兼容版本,可以让系统在技能升级时保持稳定。二是技能观测面板,把技能调用频率、成功率、平均耗时做实时的可视化统计,这不仅是运维工具,更是优化技能描述、调整编排策略的数据基础。三是技能的跨项目复用,把技能库设计成独立的Python包或服务,多个Agent项目共享同一套技能资产,避免重复开发。

我个人在实际迭代中最深的一个体会是:技能框架不是“做完就结束”的东西,它需要持续校准。模型的决策能力在变,技能的输入输出在变,使用场景也在变。每隔一段时间回顾技能注册表和调用日志,删掉长期闲置的技能、合并重叠的技能、优化高频技能的描述,这个维护节奏比写新代码更重要。

最后再分享一个我最近正在做的小扩展:把技能的选择从“单次决策”升级为“并行探索”。当任务目标不太明确时,允许Agent先并行调用两到三个相关技能获取信息,再综合分析后做出最终回答。这种方式在多源信息聚合场景下效果特别好,代价是增加了资源消耗,需要配合好并发控制和结果去重。技能的抽象让你可以低成本地试验这类新交互范式,这正是它最大的魅力所在。

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

JSP酒店管理系统实战:基于Servlet+JDBC的完整业务闭环

简介:这是一套基于JSP与Struts2框架开发的酒店管理Web系统源码,面向Java Web初学者及MVC架构实践者,聚焦Web开发核心能力训练,涵盖前后端交互、数据库操作与企业级分层设计。资源共138个文件,含40个Java业务类&#xf…

作者头像 李华
网站建设 2026/10/21 19:23:31

博客内容生成失败:输入信息严重缺失

我无法根据当前输入内容生成符合要求的博文。 原因如下: 项目标题为单个字母组合“rea”,无明确语义指向,无法判断所属领域(科技/生活/教育/创意等); 项目正文为空,无任何功能描述、技术背景…

作者头像 李华
网站建设 2026/10/21 19:21:16

rmallox勒索病毒解密恢复实战:密钥残留缺陷剖析

直接切入正题。这个项目标题看着就让人想起那段时间的焦头烂额,mallox变种勒索病毒,不少企业都栽在这上面。尤其是这个被戏称为“rmallox”的最新变种,已经不是早几年那种拿到加密文件就只会坐等赎金的雏形了。作为全程参与这次某房地产公司解…

作者头像 李华
网站建设 2026/10/21 19:20:15

基于Hadoop的健康饮食数据分析与可视化推荐系统设计

选毕业设计题目这件事,十个人里有九个会纠结。选纯大数据方向吧,担心环境搭建和硬件配置容易卡壳,最后变成和集群配置搏斗;选纯Web开发方向吧,又觉得缺亮点,评阅老师一眼就能看穿工作量。基于Hadoop的健康饮…

作者头像 李华
网站建设 2026/10/21 19:18:55

Android串口通信实战:USB转TTL稳定接入App开发指南

简介:本资源是一套基于Android Studio实现串口通信的完整开发实践项目,面向嵌入式开发、物联网应用及Android中级开发者,解决Android设备与硬件(如传感器、单片机)通过串行接口稳定交互的核心问题。压缩包含851个文件&…

作者头像 李华
网站建设 2026/10/21 19:18:20

低重力环境下智能采矿软件适配:从参数修正到模型置换

上个月我们刚完成了一轮针对月面低重力场景的软件适配测试。项目背景是一套面向无人采矿装备的智能控制软件——项目代号X,原本在常规地面矿区运行,这次要把它迁移到月球重力环境下做功能验证。这类工作最麻烦的点在于,采矿软件里几乎所有逻辑…

作者头像 李华