Agent技能模块化:让大模型从提示词堆砌走向可控技能组合

发布时间:2026/9/17 8:41:36
Agent技能模块化:让大模型从提示词堆砌走向可控技能组合 1. agent-skills这个名字背后到底在解决什么问题先说结论agent-skills不是一个具体的开源仓库名也不是某个大厂发布的官方SDK而是一类项目的统称——它的核心思路是把大模型智能体Agent的能力从“靠提示词硬撑”升级为“靠可复用的技能模块来组合”。如果你最近在关注AI Agent的开发圈会发现一个很典型的现象很多人用GPT-4、Claude、通义千问这类大模型做Agent前期跑Demo特别快今天让它帮你查天气、订日历、发邮件第二天就能跑通。但一进入真实业务场景问题就全冒出来了——模型经常忘记上下文、工具调用一多就乱、同一个功能换个说法就听不懂、偶尔还会自己编一个不存在的函数名去调。你会发现问题根本不在模型的“智商”而在于你给Agent的“能力组织方式”太原始了。传统做法是把所有指令、工具描述、调用规则全塞进系统提示词里。提示词越长模型的注意力就越分散行为就越不可控。而agent-skills这类项目想做的事情是借鉴人类“掌握技能”的方式把一项能力封装成一个独立的、可命名、可调用、可组合的模块。比如“给指定联系人发邮件”是一个技能“从网页里抽取正文”是一个技能“把一段文本翻译成日语并保持语气”也是一个技能。Agent不再是每次从头读一遍所有规则而是按需加载对应的技能像搭积木一样完成复杂任务。这听起来有点像插件系统但又不完全是。插件解决的是“大模型能不能调用外部工具”的问题而agent-skills解决的是“大模型如何更稳定、更结构化地使用这些工具”的问题。后者比前者难得多。文章后面我会从原理、设计、实现、踩坑几个维度展开讲清楚这类项目到底该怎么做、怎么用、有哪些值得借鉴的细节。2. 为什么说技能模块比提示词堆砌更接近Agent的本质2.1 提示词堆砌的三个致命弱点如果你做过一段时间Agent开发一定经历过这样的阶段先写一个Base Prompt跑着跑着发现不够就往上加规则加了规则又发现和已有规则冲突再写优先级说明最后整个Prompt系统提示词有三千字历史记录里还塞着各种示例每次请求的token消耗大得惊人但效果反而更差了。这背后有三个结构性的问题。第一上下文稀释。大模型对上下文的注意力是有限的你塞进去的每一条规则都会占用注意力。当系统提示词里同时存在“你是客服助手”“回复要简短”“如果用户提到退款先查订单状态”“不要编造订单号”等二十条指令时模型在实际生成时很难精准把握哪条最重要。尤其是当用户输入本身就很长、工具返回结果也很大时模型很容易“忘掉”某些早期指令。第二行为不稳定。提示词是自然语言写的自然语言天然有歧义。同一个意思今天写的版本和明天调的版本模型理解出来的行为可能就不一样。哪怕你只是调整了句子的顺序模型的表现都可能产生明显波动。这种不确定性在做严肃业务时是致命的——你没法跟客户说“这个功能今天可能好用明天可能抽风”。第三难以测试和迭代。提示词堆砌的方式本质上把业务逻辑写在了“文本”里。你没法对它做单元测试没法单独验证某一个功能是否正常。整个系统是一个黑盒出问题之后只能靠调Prompt试来试去效率极低。2.2 技能模块化改变了什么agent-skills的思路是把“能力”作为一种一等公民来对待。每一个技能都有清晰的名字、输入输出定义、触发条件和适用边界。Agent在执行任务时先理解用户的意图再决定调用哪些技能、按什么顺序调用。这样做带来的直接好处有三个。一是可独立测试。每个技能都能单独跑通、单独验证输出格式是否正确不必等整个Agent链路通完才能测。二是可复用。同一个技能可以在不同Agent中使用。比如“从URL提取正文”这个技能既可以用在新闻摘要Agent里也可以用在舆情分析Agent里不需要重写。三是行为更可控。技能本身的实现是代码不是自然语言。只要代码逻辑正确技能的输出就是确定的。大模型只需要负责“选哪个技能、传什么参数”复杂的业务逻辑交给代码去执行而不是让模型“猜测”该怎么执行。打个比方。传统提示词方案像一个新手厨师照着菜谱做菜菜谱写得不详细他就发挥不稳定菜谱写得太详细他又看不过来。而agent-skills方案像把做菜拆成了“洗菜”“切菜”“焯水”“调味”几个标准动作每个动作都是肌肉记忆厨师只需要决定做哪几道菜、按什么顺序做具体动作不会出错。2.3 技能与工具、插件、Function Calling的关系这里需要理清几个容易混淆的概念否则后面看代码时会懵。概念核心作用典型实现工具Tool让模型能调用外部能力搜索API、计算器、数据库查询插件Plugin面向应用层的功能扩展包ChatGPT Plugin、Chrome扩展Function Calling让模型输出结构化的函数调用参数OpenAI的tools参数、Claude的tool use技能Skill封装“意图工具调用处理逻辑”的完整能力单元agent-skills、Semantic Kernel的Skill工具是“零件”Function Calling是“传动轴”技能是“一台能独立完成某个小任务的机器”。一台机器内部可能包含多个零件也可以对外暴露一个统一接口。Agent则是“车间主任”负责调度这些机器完成一条完整的生产线。从这个角度看agent-skills是对Function Calling的高层抽象。Function Calling只解决“模型决定调用哪个函数”但不解决“调用完之后如何处理结果、如何容错、如何把多个函数串起来”。Skill这层抽象把这些逻辑也收进来了。3. 手写一个技能模块从意图识别到代码实现的完整拆解这一节不讨论某个现成框架的特定API而是从零开始设计一个技能模块。我以“从一段文本里提取关键信息并结构化输出”这个通用场景为例把设计思路和核心代码讲透。理解这个例子之后你再去看市面上的框架基本一眼就能看懂。3.1 技能的接口设计让模型和代码都舒服一个技能模块本质上是一个函数但这个函数不能随便设计。它要同时满足两个使用方大模型负责决定何时调用、传什么参数和你的业务代码负责真正执行。我推荐用Pydantic或者类似的Schema库来定义输入输出。原因很简单它能自动生成JSON Schema而JSON Schema是当前所有主流大模型理解函数参数的通用语言。from pydantic import BaseModel, Field from typing import List, Optional class KeyInfoExtractInput(BaseModel): 从文本中提取关键信息的输入参数 text: str Field(description待提取的原文不要超过8000字) fields: List[str] Field( description需要提取的信息维度如[人名, 日期, 金额, 地址], min_length1 ) language: str Field( defaultzh, description输出语言zh或en ) class KeyInfoExtractOutput(BaseModel): 提取结果的结构化输出 extracted: dict Field(description提取出的键值对key为字段名value为对应值) confidence: float Field( description整体置信度0到1之间, ge0.0, le1.0 ) missing_fields: List[str] Field( description未能从文本中找到对应信息的字段列表 )这里有几个设计细节值得单独说。fields参数用一个数组而不是让模型自由发挥是为了约束模型的输出。如果不给这个约束模型会自己“发明”一堆字段你的下游代码就不好处理。让模型在给定维度里挑至少输出结构是可控的。missing_fields是很多人会忽略的字段。真实场景中文本里经常缺少你想要的某个信息。如果技能设计不包含“缺失”这个概念模型就会强行编造一个答案这在涉及金额、日期、人名时是绝对不能接受的。显式声明“哪些字段没找到”让后续业务逻辑能感知到信息的缺失比让模型硬猜要可靠得多。3.2 核心执行逻辑把“模型调用”封装成稳定行为有了输入输出定义接下来就是执行核心。这个技能内部要做的事情有三步构造提示词、调用大模型、解析并校验输出。class KeyInfoExtractSkill: def __init__(self, llm_client, model_name: str gpt-4o-mini): self.llm llm_client self.model_name model_name def execute(self, params: KeyInfoExtractInput) - KeyInfoExtractOutput: # 1. 构造提示词 prompt self._build_prompt(params) # 2. 调用大模型强制使用JSON输出 response self.llm.chat.completions.create( modelself.model_name, messages[{role: user, content: prompt}], response_format{type: json_object}, temperature0, ) # 3. 解析和校验 raw_json json.loads(response.choices[0].message.content) return self._validate(raw_json, params.fields) def _build_prompt(self, params: KeyInfoExtractInput) - str: field_desc 、.join(params.fields) return f请从以下文本中提取这些信息{field_desc}。 要求 1. 只提取文本中明确出现的信息不要推测。 2. 如果文本中没有某个字段的信息将该字段的值设为未找到。 3. 以JSON格式输出格式如下{{{params.fields[0]}: 值, confidence: 0.0, missing_fields: []}} 文本内容 --- {params.text} --- def _validate(self, raw: dict, fields: List[str]) - KeyInfoExtractOutput: extracted {} missing [] for f in fields: val raw.get(f, 未找到) if val 未找到 or val is None: missing.append(f) else: extracted[f] val confidence float(raw.get(confidence, 0.5)) return KeyInfoExtractOutput( extractedextracted, confidenceconfidence, missing_fieldsmissing )看到这里你可能觉得这不就是一个加了结构化输出的Prompt封装吗没错技能模块的第一层就是这么简单。但关键不在这段代码本身而在于它带来的改变——你的Agent不再需要每次都在提示词里写“如果文本中没有某某信息就输出未找到”这种长尾巴规则这个边界条件已经固化在技能内部了。而且这段代码有几个细节是实战里会踩坑的第一个是temperature0。信息抽取类任务要求确定性温度设高会让模型偶尔换个措辞、偶尔多输出几个字JSON解析就可能直接崩。固定为0是最稳妥的。第二个是response_format{type: json_object}。这是让模型输出结构化JSON的关键。没有这个参数时模型偶尔会在JSON外面包一层Markdown代码块或者解释性文字解析时你就得写一堆容错正则。加上这个参数之后绝大多数情况下能直接拿到干净JSON。第三个是校验逻辑要放在模型输出之后。模型是不可信的无论它输出了什么都要经过一层代码校验才能真正进入业务逻辑。上面的_validate函数就是这层防线——它把“模型说的”和“代码确认的”区分开了。3.3 技能注册中心让Agent知道“我会什么”单个技能只是孤岛真正的Agent需要知道自己有哪些技能可以用。这里就需要一个技能注册中心Skill Registry。class SkillRegistry: def __init__(self): self._skills {} def register(self, skill): 注册一个技能自动从Pydantic模型生成Schema name skill.__class__.__name__.lower() schema skill.input_schema.model_json_schema() self._skills[name] { skill: skill, schema: schema, description: schema.get(description, ), } return self def list_skills(self): 返回给大模型看的技能清单缩减版 return [ { name: name, description: info[description], parameters: info[schema], } for name, info in self._skills.items() ] def execute(self, skill_name: str, params: dict): skill self._skills[skill_name][skill] validated_params skill.input_schema(**params) return skill.execute(validated_params)这个注册中心的逻辑很直白但它解决了一个实际问题模型侧看到的技能描述和代码侧实际执行的技能是一致的不存在“文档说支持但实际上没实现”的情况。而且用Pydantic Schema自动生成模型可见的参数结构省去了手写JSON Schema的麻烦。手写的Schema和维护代码是两套东西时间久了必然出现不一致。自动生成则从根源上消灭了这个问题。4. 实战选型对比自己撸一个还是用现成框架4.1 四个主流方案的定位与取舍市面上已经有多个框架在做“技能”这件事每个的侧重点不太一样。我基于自己的使用体验和社区口碑列一个对比表。框架核心抽象适合场景学习成本备注Semantic KernelSkill / Function企业级业务集成.NET/Python生态中微软出品和Azure系产品结合紧密AutoGPT / AgentGPT插件式技能加载快速原型个人自动化低偏实验性生产环境慎用LangChainTool / Toolkits快速接入各种外部服务中抽象层多出问题时排查链路深CrewAITool / Task多Agent协作场景低更偏任务编排技能粒度较粗先说明没有绝对“最好”的框架关键看你的场景。如果是一个企业内部系统需要和Azure Active Directory、SharePoint、SQL Server这些微软生态深度集成Semantic Kernel会少走很多弯路。如果只是个人用脚本做自动化LangChain的Tool机制足够没必要引入重型框架。但如果你的核心诉求是“让Agent行为可控、技能可独立测试、可长期演化为业务资产”我更建议自己维护一个轻量的技能注册中心哪怕只有几百行代码。原因后面细说。4.2 为什么轻量自研有时比全盘框架更好框架的抽象层级越多你定位问题时的排查链路就越长。用LangChain举例子一个简单的工具调用链路要经过Agent → Chain → Tool → ToolKit → LLM多次转发跑到线上出了问题光搞清楚“参数是哪一层丢的”就能耗掉半天。而自己写一个SkillRegistry整条链路就三层Agent决定调用 → Registry路由 → Skill执行。出了问题打开日志立刻能看到是哪一层的事。第二个理由是框架的升级不受你控制。今天版本更新改了一个API签名你的整个Agent链路就全红了。自研模块只要接口稳定几乎不受上游影响。大模型跑的接口变动属于业务风险而框架变动属于技术债风险两种风险你至少要压住一种。第三个理由是技能本身是业务资产。用一个通用框架时你写出的技能代码往往和框架强耦合。将来想换框架等于重写一遍。而如果技能模块的接口设计得足够干净输入输出都是Pydantic模型它天然可以与任何框架解耦。你完全可以在自己的代码里定义好核心技能再用一个Adapter把它适配到LangChain或Semantic Kernel的Tool接口上。我的建议是核心业务技能一律自研外围集成用框架适配。这样既保住了技能的可控性和资产属性又能享受框架生态的便利。5. 高级玩法从单技能到技能编排让Agent真正“会干活”5.1 技能依赖与顺序编排单技能只是“手”多个技能怎么配合才是“脑”。我在一个信息处理类Agent里实际用过的流程是先调用“网页抓取”技能拿到正文再调用“关键信息抽取”技能提炼结构化数据接着调用“去重清洗”技能合并多条来源的信息最后调用“内容生成”技能按格式输出报告。这里有个容易被忽略的问题技能的调用顺序有时候不是固定的需要根据中间结果动态决定。比如网页抓取可能失败反爬、404、超时这时候后续的抽取步骤就没必要执行了。处理这个问题我的做法是引入一个“技能执行计划器”Skill Planner。它本质上也是一个大模型调用但输出不是最终答案而是一个执行计划数组。# 计划器的输出示例 { plan: [ {skill: web_fetch, params: {url: https://example.com/news}}, {skill: keyinfo_extract, params: {fields: [日期, 金额]}}, {skill: generate_report, params: {template: news_digest}} ], fallback: 如果第一步失败直接返回用户提示稍后重试 }这里的关键是计划器不要自己去执行计划而是把计划交给一个执行器Executor去跑。这样职责就分开了——计划器负责“怎么做”执行器负责“去做”。执行器可以处理异常、重试、超时甚至可以跳过计划中某一步继续往后走。5.2 技能的参数解析从自然语言到结构化参数的三种策略Agent决定调用某个技能之后还有一个难题用户的自然语言要转成技能的结构化参数。这个转换有三种做法。策略一让大模型直接根据用户输入生成JSON参数。这种最常用适合参数少、值明确的情况。比如用户说“把这篇新闻摘要发到邮箱”模型需要从对话历史里找到“这篇新闻”对应的URL或文本然后生成recipient参数。优点是灵活缺点是模型可能猜错。策略二先做意图分类再按不同意图走不同的参数提取流程。比如先判断用户是“想发邮件”还是“想存草稿”分别走两套Prompt。好处是参数提取的准确率更高坏处是流程更复杂不是所有场景都值得这么做。策略三部分参数从上下文自动补全不需要模型生成。比如当前用户的ID、当前时间、默认时区这些直接在技能执行时从上下文注入不让模型操心。这一步很多人会忽略但很影响体验。我见过一个Agent因为“忘记给模型提供当前日期”导致用户问“这周五的会议”时模型把日期算错了。5.3 用技能编排实现一个完整任务新闻简报Agent实战我拿一个真实例子把上面的概念穿起来。假设你要做一个“每日新闻简报Agent”输入是一个话题输出是一份包含摘要、来源、关键数据的Markdown简报。需要四个技能search_news(topic, time_range)调用搜索API返回新闻列表。web_fetch(url)抓取文章正文。summarize(text, max_words)用大模型生成摘要。render_markdown(items)把新闻摘要渲染成结构化Markdown。技能编排者在收到“帮我整理一下今天关于大模型Agent的新闻”这个请求时plan [ {skill: search_news, params: {topic: AI Agent, time_range: today}}, {skill: web_fetch, params: {urls_from: previous_result}}, {skill: summarize, params: {max_words: 120}}, {skill: render_markdown, params: {format: digest}} ]执行器依次执行每步的输出自动成为下一步的输入上下文。如果search_news返回空执行器就直接跳过后续步骤返回“今天没有该话题的相关新闻”。整个链路对用户来说就是一个自然语言请求但内部执行是确定性的、可监控的、可审计的。这就是agent-skills类项目的核心价值**把不可控的大模型行为转化成可控的技能调度行为。**模型负责“理解”和“决策”的部分变少了做对做错都有迹可循。6. 实操中避不开的坑来自一线开发的六条血泪经验6.1 技能描述写不好模型根本不会调用技能注册时你给模型看的不只是参数Schema更重要的是description字段。这一行描述决定了模型在意图匹配时能不能选中这个技能。我见过有人这么写技能描述发送电子邮件。结果模型在一个“帮我把这份合同发给客户”的请求里半天不调用这个技能反而自己编了一封邮件内容。原因很简单描述里没说清楚“需要收件人邮箱、需要正文内容、这是一个将内容发送出去的动作”。后来改成当用户要求发送、转发或回复电子邮件时使用。必须提供收件人地址email、主题subject、正文body。如果需要添加附件请先调用附件处理技能。效果立刻好了很多。技能描述的写法其实有规律可循说明触发场景、说明必填参数、说明边界条件。要像写API文档一样写技能描述而不是像写功能列表一样写。6.2 参数校验一定要做模型会给空值你以为模型会填好所有必填参数实测中不是。参数类型对不上、字段缺失、字符串里混入多余符号都是常见情况。Pydantic能在运行时校验参数但要特别注意Field(description...)里的描述因为模型依赖这个描述来生成参数值。描述越具体模型填错的可能性越低。6.3 中间结果过大上下文会被撑爆执行技能时如果返回了一大段网页正文再带着这个正文去做下一步Token消耗会迅速膨胀。我试过一个抓取技能的返回结果是15000个Token然后把这个结果塞给摘要技能结果单次请求Token就超了。解决方式是技能之间传递的不一定是原始内容可以是压缩后的摘要或指向存储位置的引用。比如抓取完网页后先做一次粗略的压缩——截断、去HTML标签、保留前2000字——再把压缩结果往下传。6.4 并发调用技能要加锁否则状态就乱了如果你的Agent同时处理多个用户请求技能注册中心往往是共享的。技能内部如果维护了可变状态比如缓存并发场景下就会出问题。我在实现时就踩过这个坑技能类里加了一个schema_cache字典结果两个用户同时调用时一个用户注册的技能覆盖了另一个用户的。后来所有状态都改成实例级私有变量、要么彻底无状态才解决了问题。**设计技能模块时尽量做到无状态式stateless。**所有输入通过参数传所有输出通过返回值给不要在技能内部保存跨请求的上下文。6.5 超时设置要分层模型调用超时、工具调用超时、整体超时技能内部如果调用了外部API一定要设置超时。而且要注意的是你不仅要在技能内部的HTTP请求上设超时还要给大模型的调用设超时。我之前遇到的一个线上事故是大模型API响应正常但外部搜索API卡了技能一直不返回Agent最终超时用户看到的是“机器人未响应”。排查之后发现是技能内部没有给第三方API设置超时。建议三个层级的超时技能执行的最长耗时比如30秒、每种外部调用的超时比如10秒、大模型单次生成的超时比如20秒。超时之后要执行降级逻辑——返回一个明确错误而不是默默重试到天荒地老。6.6 日志是技能的“后悔药”这个建议听起来很基础但实际做到的人不多。技能的执行日志里至少要包含输入参数、模型返回的原始JSON、校验后的输出、执行耗时、错误堆栈。有了这些你才能在Agent表现异常时快速定位是模型理解错了、技能参数传错了还是下游服务崩了。我甚至会在开发阶段把每个技能调用的模型原始输出存成JSON文件方便对比不同Prompt版本的效果。这一步看起来费事但一旦你开始调优就会庆幸自己留了这份数据。7. 演进方向从单Agent技能到多Agent技能共享聊到最后我想说说agent-skills的演进趋势因为这东西单Agent用和多个Agent组成的系统用复杂度是两个量级。当你只有两三个技能时一个Registry就够了。但当你积累了三四十个技能不同的Agent团队需要共享其中一部分技能、各自独享另一部分就需要技能仓库Skill Repo和权限管理了。就像一队厨师共享一个厨房菜刀可以共用但每个厨师的私房酱料是锁在自己柜子里的。未来的技能系统会向“可发现性”和“版本管理”两个方向演进。可发现性是指Agent能根据任务描述自动找到合适的技能而不是靠人工配置清单。版本管理是说技能本身要能平滑升级新版本的技能不能破坏旧Agent的行为。目前比较务实的做法是给每个技能加标签和元数据用语义搜索的方式做技能检索。比如“提取”“结构化”“信息”这些词组合在一起就可以检索到keyinfo_extract技能。这在技能数量少时看不出必要一旦技能库超过20个手动查找的效率就极低了。另外技能的组合模式也会沉淀成更高层的“工作流”Workflow。比如前面举例的“新闻抓取→抽取→摘要→渲染”链路完全可以固化为一个名为news_digest的复合技能。以后用户请求“生成一份今日AI新闻简报”Agent只需要调用这一个复合技能而不是重新编排四个步骤。这种“技能→复合技能→工作流”的逐步抽象是agent-skills项目从玩具走向生产环境的必经之路。我在自己维护的技能库中目前有四十多个基础技能、十来个复合技能。每次新接一个业务场景首先查复合技能表——如果不需要新技能就是纯配置化的事一两个小时内能落地。这就是模块化带来的复利效应。如果你打算动手做一个agent-skills项目我的建议是从最小的核心——一个技能接口、一个Registry、一个能跑的示例——开始不要一上来就想搭建一个宏大框架。第一版丑没关系先把“技能稳定可复用”这个理念跑通后面自然会长出符合你业务需要的结构。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询