资讯中心

Agent技能库实战:从Function Calling到Skills封装与工作流编排

📅 2026/9/25 18:16:01
Agent技能库实战:从Function Calling到Skills封装与工作流编排
开头先打个招呼最近不少做AI应用的朋友都在问同一个项目agent-skills。我的理解里它不只是一个开源仓库的名字更是一套让Agent“真正做事”的方法论——把一个个可执行的能力封装成标准化模块让大模型在遇到具体任务时能够按需调用而不是每次都在提示词里串一堆工具函数。这篇文章我不打算照搬官方文档而是结合我自己把Skills库接入到实际业务系统中的经验讲讲这个项目的核心设计、手把手搭建流程以及那些文档里不会写、只有踩过坑才会知道的细节。如果你正在做Agent方向的产品或者打算用大模型处理真实工作流这篇内容应该能帮你在开始动手前把思路理清楚。1. 为什么Agent需要一套“技能系统”1.1 裸模型并不是Agent要先理解agent-skills的价值我们先得承认一个事实一个只有对话能力的语言模型和真正能解决问题的Agent中间隔着一条很宽的河。拿最简单的例子说你可以让GPT-4给你讲清楚Excel里VLOOKUP的用法它能把每一步说得明明白白。但如果你让它直接打开你电脑上某个表格、把B列的错误数据清洗干净、再生成一份新的汇总文件它会卡住——因为模型的输出边界只到“文本”为止它没有手不能真正操作文件、调用接口、读写数据库。所以行业里才有了Function Calling、Tool Use这一整套机制。它们的核心思路是让模型在推理过程中输出一个结构化的“调用请求”然后由业务系统去真正执行这个请求把结果再喂回给模型。这个思路是Agent的基石但它也带来了一个新问题工具越来越多之后怎么组织、怎么描述、怎么避免冲突这才是agent-skills这类项目真正要解决的。如果裸模型是一个刚毕业、有知识但没资源的年轻人那Function Calling就是给他配了一部电话让他有事可以摇人而agent-skills则是一张组织架构图告诉他什么人擅长什么事、什么场景该找什么人、处理完事情之后怎么汇报。1.2 没有Skills库的时候项目是怎么烂掉的你可能觉得“工具多了一点管理起来麻烦一点”只是个小问题不值得专门搞一套架构。我一开始也是这么想的直到在一个实际项目里系统里的工具函数增长到三四十个。当时的情况是这样的模型每处理一个任务我都要把全部工具的描述文档塞进提示词里不然它不知道有哪些能力可以用。token成本先不提关键是模型面对一长串工具列表时经常出现幻觉调用——明明用户问的是天气它却调了一个汇率转换工具。更麻烦的是工具之间有很多重复逻辑比如“联网搜索”这个能力Excel技能要用新闻聚合要用周报生成也要用每个Agent各写一份后来需求变了要统一加一个筛选条件我得全网搜代码去改。这个痛苦不是个例。任何一个Agent项目只要工具数量超过十几个必然出现三个问题描述信息互相干扰模型错误路由的概率飙升。工具能力不可复用同一个功能被不同模块重复实现。维护成本爆炸改一个公共逻辑要动七八处代码。而skills库的思路本质上是给Agent的能力做了一次“函数级重构”。它把每个可复用的能力封装成独立单元单元里既包含代码实现也包含模型需要的元信息——什么时候该用、参数是什么、依赖什么环境。模型只面对一份清晰的技能清单业务系统通过统一的注册中心来调度而不是在提示词里堆一坨又一坨的工具声明。结构上的收益用一张表就能看明白对比维度没有Skills库的Agent接入Skills库后的Agent提示词长度工具全部塞进上下文又长又乱只暴露匹配到的技能描述轻量路由准确率工具相互干扰经常选错每个技能有清晰描述边界明确代码复用同样的逻辑重复编写技能单元统一维护新增能力改提示词改调度逻辑新增一个Skill并注册即可出问题排查日志分散在各处按技能维度收敛日志定位快2. 拆解一个Skill的最小可用形态2.1 一个Skill单元里到底要塞什么东西很多人以为“agent-skills”就是把函数换个名字然后注册一下。实际上一个经得起生产环境考验的Skill至少要包含下面这五块内容缺了哪块都会在后期付出代价。第一块是元信息。包括技能的名称、版本号、作者、依赖的环境变量。别小看名称它直接决定模型能不能在正确的时候想起这个技能。我一般建议名称用动作对象的格式比如fetch_web_content、analyze_excel_file、send_email_notification尽量直白别起那种内部黑话式的代号模型不认识你的缩写。第二块是描述信息。这段是写给人看的也是写给模型看的但优先级不一样。对模型来说description是它在做工具路由时最重要的依据所以要把触发场景、典型用户意图、和它不能处理的情况都写清楚。比如“这个技能只在用户明确要求发送邮件时使用不要用于草稿或预览”这类负向约束非常有效能把误调用率降下来一大截。第三块是参数定义。技能需要什么输入、每个参数的类型和约束通常用JSON Schema来表示。这一块是给模型“填表”用的模板Schema写得越精模型的调用成功率越高。第四块是执行逻辑。也就是真正的代码实现。这一块没有太多花活但要注意执行环境隔离——技能如果依赖特定版本的三方库最好在依赖声明里写死而不是依赖全局环境。第五块是依赖声明。指明这个技能运行前需要安装哪些包、有哪些前置条件。有些框架还支持给技能打标签比如“只读”“写操作”“耗时任务”方便调度器做权限控制。一个典型Skill的目录结构大概是这样的skills/ ├── fetch_web_content/ │ ├── SKILL.md │ ├── requirements.txt │ └── api.py ├── analyze_excel_file/ │ ├── SKILL.md │ ├── requirements.txt │ └── processor.py ├── send_email_notification/ │ ├── SKILL.md │ ├── requirements.txt │ └── mailer.py其中SKILL.md是给模型和调度器看的“说明书”执行逻辑放在独立代码文件里。这样的好处是说明文件和实现分离后续调整描述措辞不需要动稳定运行的代码降低误改风险。2.2 模型到底怎么知道该调用哪个技能这是关键问题也最容易理解错。很多时候我们以为Agent能“智能地判断”该调用什么其实背后是一个很朴素的匹配过程。模型并不知道你的代码库里有哪些函数它只知道你在系统提示词里给了它什么。所以每一步调用本质上都是模型根据用户请求和技能描述生成一个JSON格式的函数调用指令然后由你的代码来执行。这个调用指令包括技能名和参数。agent-skills这类项目的核心工作就是帮你生成一份足够“低歧义”的技能描述清单并且设计一个优雅的调度器把模型输出和真实执行连接起来。我自己的项目里调度逻辑大致是这样一个循环接收用户消息和当前的对话上下文一起交给模型。模型判断当前需要哪个技能输出调用意图。调度器校验技能名是否存在、参数是否通过Schema校验。执行技能代码获取结构化结果。把执行结果作为观察值返回给模型进入下一步推理。这个过程听着简单但实际部署时容易在“描述”上栽跟头。比如你同时注册了get_stock_price和get_stock_history两个技能如果描述写得模棱两可模型很容易把“查询最近一个月的股价走势”路由到只返回实时价格的那个技能上最后返回结果对不上用户预期看起来就像“模型变笨了”。实际上模型的推理没有问题纯粹是你的技能描述没有把边界划清楚。2.3 和Function Calling、MCP之间是什么关系接触这个方向的新人常常问我到底该学agent-skills还是Function Calling还是MCP它们之间是不是竞争关系我通常会画一个三层模型来解释。最底层是模型提供的基础调用能力。不管叫Function Calling还是Tool Use本质上都是模型输出结构化指令让你能够对接外部系统。中间层是通信协议。MCP解决的是“技能怎么提供、客户端怎么发现、怎么安全地调用”的问题。它有点像USB-C接口规范了插头的形状各设备之间可以互相插拔。最上层才是Skills库。它解决的是业务语义层的封装——把“调用一个函数”进一步包装成“完成一项业务能力”。同样一个MCP服务器可以为多个不同的Skill提供底层能力支持而一个Skill背后也可能编排多个底层调用来完成一个复杂任务。理解这层关系之后你在做技术选型时就不会纠结“要不要抛弃Function Calling换MCP”之类的问题了。它们不在同一层也没必要互相替代。一个成熟项目通常是模型底层能力之上用MCP来做标准化接入在上层再建一个Skills层来管理业务能力。3. 手把手搭建自己的Skills库3.1 环境准备与基础框架安装前面讲了不少原理下面进入动手环节。我用一个轻量级方案来做演示技术栈是Python 3.10Agent框架用的是LangChainSkills层自己写一个简单的注册中心。这个选择适合绝大多数刚起步的团队——既不用重复造轮子又能看清底层逻辑。先建一个干净的环境。我习惯用pyenv管理Python版本避免系统环境和项目环境互相污染python -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install langchain langchain-openai openai pydantic接下来建立一个技能目录并且创建一个技能注册器。注册器是整个Skills库的中枢它负责扫描skills目录下的所有子目录、读取每个SKILL.md、解析元信息并维护一个“技能名到执行函数的映射表”。{ name: fetch_web_content, version: 1.0.0, description: 根据URL获取网页正文内容。当用户请求打开网页、读取文章、抓取网页信息时使用。此技能只执行GET请求不处理需要登录的页面。, parameters: { type: object, properties: { url: { type: string, description: 目标网页的完整URL地址 } }, required: [url] } }这段描述有意写得非常具体尤其加上了“不处理需要登录的页面”这个负向约束。这个约束看起来多余但其实能有效防止模型把它误用于需要登录态的抓取场景减少后续报错。3.2 编写一个真正可运行的Skill接着写执行逻辑。这个技能要做的事很朴素请求一个URL去粗取精提取正文文本。但为了避免无谓的失败我给它加了超时控制、UA头伪装和异常兜底import requests from bs4 import BeautifulSoup def fetch_web_content(url: str) - dict: headers { User-Agent: Mozilla/5.0 (compatible; AgentSkill/1.0) } try: resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) for tag in soup([script, style, nav, footer]): tag.decompose() content .join(soup.get_text().split()) return { status: success, url: url, content: content[:2000] } except Exception as exc: return { status: failed, url: url, error: str(exc) }这里有几个细节值得说。一是返回结果固定为结构化的dict并且统一挂上status字段。之所以这么做是为了让模型在下一步推理时能快速判断技能执行有没有成功而不是从错误堆栈里自己猜。二是正文内容做了截断限制在2000字符以内。很多新手容易忽略这一点把整篇十万字的网页全量塞给模型直接导致上下文爆炸、费用飙升。最后把这个函数注册到注册中心。注册方式是在SKILL.md同目录下放一个module.py然后注册器动态导入并且把执行函数绑定到元信息上def register_skill(skill_name: str, module_path: str, entry_point: str): import importlib module importlib.import_module(module_path) handler getattr(module, entry_point) SKILL_REGISTRY[skill_name] { handler: handler, schema: load_skill_metadata(skill_name) }3.3 把多个Skill组合成一个完整工作流单体技能有了接下来组合一个真实场景用户给一个文章链接请求输出一份简洁的中文摘要并发送到指定邮箱。这明显不是一个技能能搞定的。我做的是把任务拆成三个环节抓取网页、调用模型做摘要、发送邮件。前一个技能的输出直接作为下一个技能的输入。流程如下用户提交url和邮箱地址。调度器先调用fetch_web_content拿到正文。取出content字段和摘要要求一起传给模型让模型生成两到三句话的摘要。把摘要作为邮件正文调用send_email_notification发送。组合的核心在于技能间的数据传递。我推荐用中间状态去衔接而不是让每个技能直接依赖上一个技能的输出格式。具体说就是建一个上下文字典每个技能从里面取自己需要的键处理完再塞入新键。这样将来替换某个技能时只要保证新技能读写相同的键名其他部分完全不用改。state { url: https://example.com/article, email: userexample.com } fetch_result execute(fetch_web_content, state[url]) if fetch_result[status] ! success: raise RuntimeError(抓取失败) state[content] fetch_result[content] state[summary] summarize(state[content]) send_result execute(send_email_notification, state[email], state[summary])这比把技能A的返回值直接塞给技能B灵活得多。实际上到了复杂工作流层面技能库的架构价值才真正体现出来——单一技能的调试和测试都可以独立进行组合层只负责编排逻辑边界非常清晰。3.4 验收一个Skill的离线测试清单我见过不少项目技能写完调通一次就直接上结果到了线上各种翻车。原因很简单大部分Skill是“识别-调用”模式模型可能构造出各种你没料到的参数组合。所以我在每个技能上线前都跑一遍离线测试招数不多但管用。用例类型具体做法预期结果常规用例用Skill描述里定义的典型用户意图发起请求正确路由到目标技能返回成功边界参数URL缺协议头、邮箱格式非法、文件为空技能返回明确的错误信息而非崩溃混淆用例用相近技能的场景去触发当前技能如果描述写得准确应拒绝调用或路由到正确技能并发调用同一个技能同时被多个会话请求无共享变量冲突响应时间正常依赖缺失删除某个三方依赖再调用报错信息清晰直接指出缺哪个包这套清单跑下来通常能发现描述里的不少漏洞。比如我自己的邮件发送技能最初就对收件地址没有做格式校验测试时模型输出了一串莫名其妙的字符串导致发送接口报错。现在的逻辑是统一用pydantic对参数做类型验证非法输入直接拦截在技能外层from pydantic import BaseModel, EmailStr class EmailPayload(BaseModel): to_addr: EmailStr subject: str body: str4. 跑通Skills库后最容易踩的坑4.1 提示词太长模型频繁选错技能Skills库第一个好处是提示词变短但技能多了以后反而出现新的问题注册表里的技能描述加起来太长模型处理不过来。实测下来当暴露给模型的技能超过二十个并且描述冗长时路由准确率明显下降模型会突然开始“碰运气”式调用。解决方案不是把描述精简到极致而是分层暴露。具体做法是把技能分成两层常驻技能和按需加载技能。像“读取消息”“生成回复”这类高频技能始终在提示词里而像“读取PDF”“导出CSV”这类低频技能只保留一个极简的占位描述等模型明确表示需要时再动态注入完整描述和执行代码。这种方法在效果上最明显的一次优化是把一次工作流的路由准确率从78%拉到了95%以上。关键不是模型变强了而是我们不再用两万字去考验模型的注意力了。4.2 技能之间的隐式循环调用组合技能时另一个深坑是隐式循环。表面上你的调度逻辑是线性的A执行完走BB执行完走C。但如果某个技能的实现里又调用了Agent主循环就会造成A执行到一半Agent自己又发起新的识别去调技能BB又发起了识别再次调到A最后卡死在循环里。线上系统出现这个问题的表现是日志里同一个技能反复执行同一个调用token消耗异常飙升响应超时。针对这个坑我做了两件事。第一是全局限定单次任务的最大技能调用次数默认10次超过这个次数直接中断并把已执行步骤整理成日志返回。这个限制写在调度器里所有技能都生效相当于给失控的编排上了一道保险。第二是画依赖图的时候明确标注出哪些技能会触发Agent主循环。比如“代码生成”技能就属于危险技能因为模型在生成代码后往往会再调用代码解释器去测试这会重新进入主循环。现在我的做法是所有会引发二次调度的技能都在描述里标注“仅执行、不触发新请求”堵住循环源头。4.3 参数校验和错误恢复策略技能执行失败本身不可怕可怕的是失败之后没有恢复策略。默认情况下模型拿到一个异常堆栈往往不知所措甚至开始在错误信息里“编故事”一本正经地给出一个根本不存在的修复方案。我的做法是给每个技能的执行结果做一层标准化包装同时把常见异常翻译成模型能看懂的语言。比如“requests.exceptions.ConnectTimeout”会被翻译成“网页访问超时可能是网络环境异常或者目标站点拒绝对接”这样模型在下一步才知道该换URL还是该提示用户。标准化的返回结构我惯用三个字段status成功还是失败、result执行结果、hint给模型的下一步建议。hint字段看起来不起眼但它是整个错误恢复策略的关键——它让模型在失败时不是干瞪眼而是有明确的行动指令。{ status: failed, result: None, hint: 技能执行超时建议提示用户检查URL是否可访问或者稍后重试。 }除了错误信息翻译重试策略也很重要。像网络请求类技能我会在技能内部做两次重试使用指数退避间隔从1秒起步。超过次数才返回失败。这样做的原因很朴素模型调度一次技能的成本不低网络抖动一次就失败回到主循环既费token又费时间。5. 社区里有哪些成熟方向可以借力5.1 按业务场景归类成熟技能方向agent-skills这个生态里社区已经沉淀了大量开箱即用的技能方向。我按自己接触过的项目做了个分类每个方向都对应真实的业务需求不是纸面概念。方向典型技能示例常用场景数据搬运读取CSV、写入数据库、调用第三方API让Agent代替ETL的一部分手工环节文档处理解析PDF、提取表格、转换格式合同、报表、论文的结构化提取内容生产生成摘要、优化文案、翻译结合工作流做内容的半自动产出自动化测试跑用例、比对输出、生成测试报告让Agent做回归检查的辅助角色质量分析代码评审、日志分析、性能基线对比开发效率工具和线上问题排查个人助理日历操作、邮件收发、待办整理典型的办公自动化方向这些方向里我个人最推荐从数据搬运和文档处理开始切入。不是因为它们技术含量低而是因为它们目标明确、结果可用性高特别适合验证“Agent到底能不能稳定干完一个粗活”。等到跑通了一条链路再扩展到内容生产和自动化测试心理负担会小很多。5.2 选型还是自研的判断公式进入这个领域之后不少人会纠结是直接用社区的成熟Skill库还是自己写一套。我的判断标准其实很简单先算维护成本再算团队耦合度。如果团队已经有明确的Agent框架比如LangChain、LlamaIndex优先找和框架绑定的Skill仓库别自己重新写一遍接口层不然每次框架升级你都要跟着改。如果你对Agent框架没有强绑定想保持可迁移性那自研一套轻量的Skills层也就几天的成本不算重。自研的时候要控制一个度不要一开始就设计过度复杂的“技能编排引擎”。很多人一开始就奔着“流程可编排、可视化拖拽”去搞最后技能没写几个编排引擎倒是花了两个月。我从实践里得到的经验是先用手写代码的方式把三到五个真实工作流跑通等稳定了再回头看有没有必要做可视化编排。大部分项目到这一步会发现手写代码已经够用根本不需要引擎。注意Skill库的核心是“减少每个技能的认知负担”而不是构建一个宏大的平台。结尾项目做到后面我越来越觉得agent-skills这类体系最难的地方不是写代码而是定义能力的边界。每个Skill本质上就是在告诉模型“你有这个能力但只能在什么条件下用”。描述写得好模型指哪打哪描述写糊了模型就会在各种边缘场景里给你制造惊喜。我个人的体会是入手时别贪多先从三个流程最固定的技能做起把每个技能的描述打磨到连一个初级运营都能看明白的程度再慢慢扩展。等你真正用起来会发现这个库给你最大的回报不是代码复用率提升而是整个Agent的可调试性——出了问题你能快速定位是哪一项技能没做好而不是对着整段提示词和一堆工具函数发呆。最后分享一个小技巧每次模型路由出错不要急着改模型提示词把那个错误案例记下来倒推是哪个技能的描述存在歧义修正描述。坚持一个月你的Skills库会越用越顺手准确率也会在不知不觉里涨上去。

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案