尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Agent技能化实战:从单体烂摊子到标准化技能体系的设计与落地
做LLM应用开发这段时间绕不开一个词agent-skills。我们内部项目从最早的一堆if-else写死逻辑到后来全部改造成技能化调用中间踩的坑不少。这里记录一下我是怎么理解“给Agent装技能”的技能是什么、怎么设计、怎么落地以及哪些地方最容易翻车。想给智能体做工程化、又不想把代码写成意大利面的开发朋友可以重点看第二、三章直接照着搭一套最小可用的技能体系。1. 为什么需要给 Agent 装上“技能”先说结论技能化不是炫技是被单体Agent的烂摊子逼出来的。很多团队一开始做Agent都是把任务流程全塞给大模型一个巨大的system prompt里写满步骤和规则外部功能做成几个函数模型需要的时候调一下。表面上“智能体”有了实际上维护起来想哭。1.1 单体 Agent 的三大瓶颈第一是复用性极差。你给A场景写的步骤、工具和校验逻辑到了B场景几乎全得推翻重来。因为业务逻辑和提示词揉在一起场景一变提示词一变功能函数也跟着改。项目多了之后每个模块都是一坨“只能在这个场景下运行的活代码”换个项目就得复制粘贴再改一遍长期下来代码腐化速度非常快。第二是提示词膨胀。你可能见过那种5000字的system prompt所有规则、背景、工具说明、用户话术样例全在里面。问题是当前主流大模型对超长上下文的注意力分布并不均匀真正执行任务时最关键的工具说明反而可能被几个例子“挤”到次要位置。我见过一个项目模型每次都在错误场景调用同一个工具原因就是工具描述太长被其他指令淹没了。技能化之后每个技能只暴露自己那一小段描述短、职责明确模型选错概率明显下降。第三是可观测性差。单体Agent跑起来之后很难说清楚“哪一步错了”。是规则没匹配到还是工具返回了脏数据还是大模型理解偏了没有清晰的边界排查问题基本靠猜。技能化以后每个技能就是一条完整的调用链输入输出清楚、错误有明确分类、日志能单独追溯某个技能的耗时和失败原因问题定位成本直线下降。1.2 “技能”和“工具”到底有什么区别我刚开始也以为技能就是给模型多几个函数调用后来发现完全不是一回事。工具通常指单个可执行函数比如“获取天气数据”“计算两个日期差值”它是原子的、无状态的做完就返回结果。技能则是一个完整的“能力单元”它可能包含工具调用、前置条件检查、后置结果校验、失败重试、状态记录甚至还能调用其他技能。用个生活化的类比工具是一把螺丝刀技能是一个“能完成家具组装”的完整流程。螺丝刀只有“拧”这个动作但家具组装需要判断该用哪把型号、需要按什么顺序处理、遇到卡住要怎么处理、最后还要检查稳不稳。维度工具单一函数技能能力单元粒度一个函数/一个操作一组操作 校验 备用方案状态管理无状态可有内部记录与上下文传递失败处理抛出异常由外部兜底自带重试、降级、结构化报错组合性组合逻辑写在外部技能可显式调用其他技能提示词描述一两句话就能说清需完整说明触发条件与副作用所以“agent-skills”这套思路本质上是在大模型和具体业务代码之间加了一层标准化“技能层”。大模型不需要知道技能内部怎么实现只需要知道“什么场景选什么技能、传什么参数”剩下的由技能层保证执行质量。这个分层一旦做好模型换掉、技能增加、业务逻辑调整都能解耦。2. 技能体系怎么设计才能稳这一章说设计原则。技能体系好不好用不取决于底层模型多强而取决于技能拆得是否合理、描述是否清晰、调度是否可控。如果拆得不好轻则模型选错技能重则整个Agent在错误的路上越跑越远。2.1 技能拆解的三个层次我习惯把技能拆成三层每一层都有自己的职责边界L1 基础技能原子操作层。比如“读取网页正文”“执行数据库查询”“发送HTTP请求”“文件转码”。这一层不包含任何业务判断纯粹是通用能力的封装输入输出必须简单明确。L2 组合技能业务操作层。比如“根据URL归纳文章摘要”“定时抓取竞品信息并写入表格”。这一层会调用一到多个L1基础技能内部有顺序控制、简单分支判断但尽量不要contain复杂业务规则。L3 场景工作流面向最终用户的任务编排层。比如“生成一份周报”“完成一次客户意向初筛”。这一层是给大模型直接看到的入口描述里写清楚触发条件和执行边界。分层之后要管的规则很简单L3技能描述要口语化、容易被模型识别L2技能描述要说明它调用了哪些能力、输入参数是什么L1技能不直接暴露给模型而是藏在注册中心的内部索引里。这样提示词里放的都是L2和L3描述数量控制在合理范围内上下文压力小很多。2.2 技能描述与入参 Schema 的设计这是整个技能体系里最容易被低估的一环。很多团队技能建了一堆模型就是不用或者老用错八成是描述写得有问题。技能描述有个笨但有效的写法动词开头 目标对象 边界条件。比如差的描述网页内容处理功能用于提取有效信息。好的描述当用户给出一个网址且希望快速了解该页面核心观点时使用此技能。本技能会抓取网页正文并返回结构化摘要。仅适用于公开可访问的HTTP/HTTPS页面不适用于需要登录的文章。区别很明显。差的描述信息量太低模型无法判断该在什么场景触发好的描述直接定义了触发条件、能力范围和边界模型判断起来几乎不需要推理。入参Schema同样重要。以主流大模型的函数调用格式为例必填参数不要省枚举值要写全参数之间的依赖关系要在字段描述里点明。比如“当source_type填url时url字段必填当source_type填pdf时pdf_base64字段必填”这种互斥逻辑写清楚模型返回的参数基本不会乱。我见过太多团队参数schema写得模糊模型返回None或者传错字段最后还得靠后端做一堆兼容处理得不偿失。2.3 注册中心与调度策略技能注册中心是“agent-skills”体系的骨架职责就三个登记所有技能、生成模型可读的技能描述列表、根据模型决策分发到具体技能执行。注册中心的技术实现不难难在调度策略。业界最常见的是“模型自主选技能”——把技能列表拼进提示词让模型自己挑选并填参数。这种方法简单但不可控。还有一种“规则前置 模型兜底”——先做一次简单的关键词/分类匹配如果匹配度足够高就直接走该技能匹配不到再把候选技能列表交给模型让模型做最后决策。我强烈建议正式项目采用后者。一来省token二来把常见高频意图走规则路径准确率更高模型只处理边界情况。另外一个常见的坑技能多到一定程度不能一股脑全塞进提示词。L2和L3加起来超过20个之后模型选型准确率会明显下降。这时候要考虑对上屏技能做一次粗筛比如根据会话意图、用户身份、当前页面信息先排除明显无关的技能再让模型在剩余候选中做选择。2.4 组合技能的上下文传递组合技能最容易出的问题是状态丢失。比如L3“生成周报”先调用“获取项目进展”再调用“生成Markdown文档”中间如果没有一个贯穿始终的上下文对象第二个技能就不知道该拿什么数据去生成。实操中我用的是一个全局上下文容器本质就是一个可扩张的字典里面至少存三类数据第一类是用户初始输入和约束条件用户说“只要本周紧急事项”第二类是前序技能的产出比如“项目A进度80%风险无”第三类是临时变量比如解析后的结构化记录。L3启动时创建容器传给每一步子技能子技能执行完把结果回写容器下一步技能再从容器里取。这里有个细节子技能不应该直接读用户原始输入而应该读“经过了前序处理之后的规范化数据”。这样可以保证每个子技能拿到的参数是可控的。我见过很多组合技能写崩就是因为子技能依赖的是用户原话里的某个词用户换个说法就全乱套。3. 实操从零实现一个可用的 Skills 模块纸上谈兵没意思下面给一套可以直接复制的轻量实现。我尽量用简单结构不用重框架你拿到手就能跑。核心就三个类技能基类、注册器、调度器。3.1 基础数据结构与注册器技能基类我通常会定义一个命名元组或数据类字段包括name唯一标识、description暴露给模型看的描述、input_schemaJSON Schema格式的参数说明、executor实际执行函数、tags内部索引标签用于粗筛。from dataclasses import dataclass, field from typing import Callable, Any, Optional, Dict dataclass class Skill: name: str description: str input_schema: dict executor: Callable[[Dict[str, Any], Dict[str, Any]], Dict[str, Any]] tags: list[str] field(default_factorylist) timeout: int 30注册器管理技能的增删查改还要能生成“给模型看的技能列表”。注意这里生成的技能列表严格控制描述长度每个技能只保留description、参数名和必填项说明不要输出完整的大段文档。class SkillRegistry: def __init__(self): self._skills: Dict[str, Skill] {} self._tag_index: Dict[str, list[str]] {} def register(self, skill: Skill): if skill.name in self._skills: raise ValueError(fduplicated skill: {skill.name}) self._skills[skill.name] skill for tag in skill.tags: self._tag_index.setdefault(tag, []).append(skill.name) def get(self, name: str) - Skill: return self._skills[name] def list_prompt_block(self, candidate_names: Optional[list[str]] None): names candidate_names or list(self._skills.keys()) lines [] for n in names: s self._skills[n] params , .join(s.input_schema.get(properties, {}).keys()) lines.append(f- {s.name}({params}): {s.description}) return \n.join(lines) def filter_by_tags(self, tags: list[str]) - list[str]: names [] for tag in tags: names.extend(self._tag_index.get(tag, [])) return list(dict.fromkeys(names))实现细节上都值得说明注册时检查重名是为防止模型调度时撞车filter_by_tags是给调度器的粗筛用的list_prompt_block只输出参数名和描述控制prompt体积。3.2 技能内部执行器的分层每个技能的执行函数我习惯写成三段前置校验、核心逻辑、后置包装。三段分开写的好处是技能出问题能快速定位是哪一段的问题。def executor(params: Dict[str, Any], context: Dict[str, Any]) - Dict[str, Any]: # 前置参数校验 if url not in params: return {status: error, error_type: missing_param, message: 缺少url字段} # 核心执行任务 content fetch_web_page(urlparams[url], timeout10) # 后置结果结构化 return { status: success, data: { title: content.title, body_length: len(content.article_text), extract: content.article_text[:1000] } }很多真实项目把前置校验省掉了结果模型漏传参数时后端直接崩出一个500页面或者返回一段原始异常堆栈模型看着一堆英文trace无从下手。结构化错误返回才是跟大模型协作的正确姿势让模型知道“缺什么参数”它可以自己纠正后再调一次而不是直接放弃任务。超时控制也是必做的。外部请求可能hang住一个技能拖死整个会话。我给每个技能单独设timeout超时就返回结构化错误调度器再决定是否换一个技能或问用户。3.3 完整案例实现一个“URL正文抽取并归纳”组合技能这个案例非常典型既有基础技能又有组合技能能跑通整个链路。L1基础技能我先注册一个fetch_page负责抓网页并抽取正文。skill_fetch_page Skill( namefetch_page, description传入url抓取网页返回清理后的正文仅适用于公开页面, input_schema{ type: object, properties: { url: {type: string, description: 完整网页地址} }, required: [url] }, executorfetch_web_page_executor, tags[web, fetch], timeout15 ) registry.register(skill_fetch_page)再注册一个L2组合技能summarize_url它内部会调用fetch_page再让大模型或本地算法对文本做摘要。组合的时候L2相对L1多了一层步骤编排和输入输出的变换。def summarize_url_executor(params, context): # 先取到L1的执行结果 page_result registry.get(fetch_page).executor( {url: params[url]}, context ) if page_result[status] ! success: return page_result body page_result[data][extract] # 再交给本地摘要服务 summary summarize_client.generate(body, max_lengthparams.get(max_length, 200)) return { status: success, data: { summary: summary, source_url: params[url] } }组合技能的input_schema要定义成用户直接能理解的样子而不是暴露L1的参数细节。用户只传一个url和可选的max_length至于内部还调了什么技能不需要让模型关心。调度器这里我只写核心逻辑省略框架绑定相关的部分class SkillDispatcher: def __init__(self, registry: SkillRegistry): self.registry registry def dispatch(self, skill_name: str, params: dict, context: dict): skill self.registry.get(skill_name) try: result skill.executor(params, context) except Exception as exc: result { status: error, error_type: internal_error, message: f{skill_name}执行异常: {exc}, truncated_detail: str(exc)[:200] } return resultdispatch方法做一层统一异常捕获保证任何技能崩了都不会直接把异常抛给模型循环层。3.4 给技能装上记忆上下文注入与隐式状态我刚才提到过全局上下文容器实操里它就是每轮会话开始时建一个字典。用户说“看看这个页面讲什么顺便跟上一篇比较一下”如果技能之间不共享上下文第二个比较步骤根本无从下手。做法是给基础技能统一加一个可选参数context_ref。这不是给模型填的而是调度器注入的。def fetch_web_page_executor(params, contextNone): context context or {} # 把本次抓取的网页保存到上下文供后续技能使用 fetched crawl_page(params[url]) context.setdefault(fetched_pages, {})[params[url]] fetched return {status: success, data: ...}技能体系在隐式状态这块原则是显式参数走prompt隐式状态走context容器。模型只需要给“当前这一步”的参数跨步骤的数据由调度器和context来组织。这样做的最大好处是即使模型在多轮里说了“算了不比较了”只要新技能启动时从context读数据也不会因为之前步骤被跳过而报错。4. 常见问题与排查清单技能体系上线后真正考验的不是写出代码的那几天而是运营期的排查和调优。下面列的都是我实际遇到过的高频问题整理成一张速查表建议收藏。4.1 高频问题速查表症状原因解决措施模型总是不选某个技能描述太抽象模型识别不了触发条件描述里加具体场景和反例缩短短描述突出动词场景模型选择了技能但参数反复填错入参schema字段说明不清必填字段全部显式列出互斥关系在description里用if-then写清楚技能执行成功但结果不符合用户预期技能内部缺少后置校验增加结果规则校验例如“摘要长度低于20字则报错重跑”组合技能内部某一步失败整个任务中断子技能异常被吞或未被处理每个子技能结果单独检查失败时返回有语义的错误码技能列表太长模型出现幻觉调用20全量技能直接入prompt加粗筛/路由层让候选减少到5~10个同一技能在多用户并发时状态串了context容器被错误复用了每个会话/每个请求创建独立context禁止全局单例共享4.2 一次典型故障复盘说个我们具体遇到过的案例很有代表性。当时上了一个“数据库自然语言查询”技能用户输入“查一下最近一周收入超过5000的订单”。这个技能设计时描述写得太宽泛只写了“将自然语言转换为SQL并执行查询”没有写清楚转换后的SQL必须在哪类数据库上跑、截止到什么时候。结果模型在另一个更通用技能“文档问答”和这个SQL技能之间反复横跳偶尔还自己编一个SQL执行返回一堆无意义的数据。后来排查下来问题根源不在模型而在技能描述缺了边界条件。我们把描述改成“仅当用户明确提到订单、流水、交易数据且涉及数据库查询统计时调用此技能”并且加上反例“普通文档与网页内容查询请找其他技能”。改动之后调用准确率从不足60%提升到85%以上。所以每次模型表现不对优先怀疑技能描述而不是怀疑模型能力。很多情况下大模型完全有能力执行任务只是没搞懂“现在该不该用这个技能”。4.3 技能调优的个人心得最后分享几个我在真实项目中摸索出来的心得可能跟主流教程不一样但确实管用。第一个一定要建回归库。技能描述只要改动过就可能引发模型的选型行为变化。我们每周会把历史用户的真实请求抽200条做成回归测试集每次改完技能描述就跑一遍比较模型选择结果和期望选择结果。不跑不知道一跑吓一跳有时候一条无意的措辞调整能让某些冷门技能直接“失宠”。第二个技能要定期做减法。技能体系时间一长容易出现大量“僵尸技能”功能相似、描述重复、触发边界模糊。这种技能堆着不仅没用还拖累模型决策。我现在的习惯是每两周做一次技能盘点把近7天调用量为0、且可以被现有技能覆盖的先下架观察不急着删确认没有用户流失再移除。第三个单技能单测不能省。我见过太多团队只测整体流程不测单个技能。一个“网页摘要”技能在某个网页编码格式异常时崩掉整体流程覆盖不到直到生产环境被触发。给每个技能准备2~3条边角输入自动化跑一遍比什么花哨设计都实在。技能化的过程不是一次性工程它更像是在给Agent搭一套不断进化的工具箱。每加一个技能都要问自己描述清楚吗边界明确吗状态会不会串失败怎么兜底把这些问题养成习惯你的Agent体系会越用越顺手而不是越用越乱。
RELATED

相关推荐

浏览器插件ponytail:技能包机制与网页信息自动化整理

浏览器插件ponytail:技能包机制与网页信息自动化整理

ponytail 是我近一年在维护的一个浏览器插件,核心逻辑就一句话:把散落在网页里的关键信息“扎”成一束。以前我收集资料的方式特别原始,开一堆标签页,这边复制一句,那边截图一块,最后整理笔记的时候人已经麻…

📅 2026/10/14 7:55:17
企业环保评价报告全流程指南与实战技巧

企业环保评价报告全流程指南与实战技巧

1. 项目概述:环保评价报告的核心价值在工业生产领域,废气、废水、固废(简称"三废")的环境管理是企业合规运营的生命线。去年协助某电子制造企业完成扩建项目环评时,他们的总工说过一句让我印象深刻的话&…

📅 2026/10/14 7:55:17
FanControl 识别不到风扇:3 项快查 + 4 层定位,把转速找回来

FanControl 识别不到风扇:3 项快查 + 4 层定位,把转速找回来

FanControl 识别不到风扇:3 项快查 4 层定位,把转速找回来 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/Gi…

📅 2026/10/14 7:55:17
MORE NEWS

更多资讯

📰

Python自动化Excel数据清洗与汇总实战

1. 项目概述 每次月底盘点水果库存时,你是不是也对着几十张Excel表格发愁?从不同门店收集来的销售数据格式五花八门,手动复制粘贴不仅容易出错,还特别耗时。去年夏天我在连锁水果店做系统升级时就遇到过这个痛点 - 当时财务部的同…

📰

从“干完再发照片“到“每一步都能被确认“:异地交付的三段闭环,附 4 条可复制提示词

异地干活的真正成本:你做了 10 步,对方只看见最后 1 步 异地干活觉得累,第一反应通常是"累在来回跑、累在沟通"。 但真正让一个项目变难的,往往不是距离,是对方看不见你在干什么。 同一件活,你…

📰

罗伊氏粘液乳杆菌:肠道健康的微生物守护者

1. 罗伊氏粘液乳杆菌:从微生物学特性到实际应用罗伊氏粘液乳杆菌(Limosilactobacillus reuteri)这个看起来复杂的名字,其实是我们肠道里的"老朋友"。作为一名长期关注微生态健康的从业者,我见证了这个菌种从…

📰

值班大屏实战指南:轻量级工业可视化系统部署与信创适配

简介:这是一套专为值班监控场景设计的可视化大屏系统,面向运维、IT值守、数据中心及公共安全等需24小时实时信息展示的岗位人员,解决传统值班界面信息杂乱、更新滞后、缺乏设计感等问题。资源包共992个文件,体量9.7MB,…

📰

Web滚动性能优化与渲染管线深度解析

1. 高性能滚动与渲染优化的重要性现代Web应用对流畅滚动的需求越来越高。根据Google的研究,当页面帧率低于60fps时,用户就能明显感知到卡顿。而滚动性能直接影响到核心用户体验指标——首次输入延迟(FID)和累积布局偏移&#xff0…

📰

AB测试分层抽样实战:解决随机分组的样本偏差问题

1. 为什么AB测试不能只靠“随机分组”就万事大吉?我第一次在某电商公司的增长团队接手AB测试项目时,信心满满地写了二十行random.shuffle()np.array_split(),把10万用户按50:50切成了A组和B组,跑完一周数据后兴冲冲去汇报&#xf…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬