资讯动态

Agent技能体系设计:从函数调用到可复用技能编排的实战指南

发布时间:2026/10/14 9:33:35 来源:尧图企业网站定制
做AI应用一段时间后你会发现真正让大模型发挥价值的不只是“提示词写得好”而是它手上有没有一套趁手的“技能”。这也是我整理 agent-skills 这件事的初衷把Agent需要的能力拆成可复用、可组合、可评估的独立模块让模型不再是一个只会聊天的接口而是一个能查数据、能写代码、能操作业务系统的执行者。这篇文章会从设计思路、技能定义、落地实现到排查调优完整过一遍适合正在做智能客服、自动化办公流、知识库问答这类Agent应用的开发者参考。1. 项目整体设计与思路拆解1.1 为什么Agent不能只靠聊天能力大语言模型本质上是一个“文本生成器”它能根据输入产出回答但没法自己去实时数据库拉订单、没法调用外部接口发通知、也没法在你服务器上安全执行一段脚本。如果把Agent直接暴露给用户它只能信口开河给出看似合理其实并不可用的答案。基础的解决方式是给模型接“函数调用”能力。也就是为模型提供一组函数和参数说明模型根据用户意图决定调用哪个函数、传什么参数再把函数返回结果重新组织成自然语言回答。这个思路没错但真正做起来就会发现零散函数很难管理。三个工程师各写各的函数命名混乱参数格式不统一模型经常选错出了问题还不知道该看哪个日志。agent-skills 要解决的就是这个问题。它不是某个单一功能而是一套关于“技能”的组织规范每个技能都是一个可独立开发、独立测试、独立部署的能力单元并且自带一套让模型能“读懂”的说明书。系统运行时把这些技能统一注册到技能中心Agent需要时按需调用整个过程可观测、可控制、可审计。1.2 设计目标与整体架构在设计这套体系时我给自己定了四个目标技能必须可复用同一个“搜索知识库”技能在客服机器人、内部问答、文档助手等多场景里都能直接用不需要为每个场景重写一套。技能必须可测试每个技能可以在没有大模型参与的情况下单独调用输入固定输出可校验。技能必须可编排复杂任务不是让模型一步完成而是拆成多个原子技能用工作流串联。技能必须可观测每一次被调用、传了什么参数、返回了什么结果、耗时多久都能通过日志回溯。架构上agent-skills 采用“注册中心 执行器 技能库”的分层方式。技能库里的每个技能在启动时通过装饰器自动注册到注册中心注册中心维护一张“技能名——技能对象”的映射表同时把模型需要的技能描述列表技能名、描述、参数JSON Schema传给它。模型一旦决定调用某个技能执行器就会从注册中心取出对应的函数执行并把执行结果封装好交还给模型。这个流程和具体Agent框架无关可以适配任何一种底层模型或框架。1.3 核心设计原则与取舍技能设计不是写个函数就行背后有一套需要长期遵循的原则。第一原子性。每个技能只做一件事。例如“搜索文档”和“根据文档生成摘要”应该拆成两个技能而不是揉成一个“搜索并总结”。原子技能更容易复用也更容易测试。一个技能如果承担太多职责描述很难写准确模型也会犹豫到底该不该调用它。第二自描述。技能在注册时要写出完整的描述包括触发条件、输入参数、输出结果、边界情况。描述是写给模型看的“使用说明”必须保证模型在正确场景下能想起它在错误场景下不会被误用。第三无状态优先。技能执行时不应该依赖上一次调用的内部状态。如果确实需要保存用户偏好、历史记录这类状态应该通过专门的记忆技能来读写而不是让普通技能内部自持缓存。这样做的原因是当技能被编排、并发调用时状态容易互相污染排查起来也会很痛苦。第四权限内聚。每个技能在定义时就声明自己需要哪些权限而不是运行时动态去申请。比如代码执行技能只能在沙箱目录里写文件数据库查询技能只能使用只读账号。权限跟着技能走Agent的权限边界才容易做限制。下面用一个表格把原则和它们规避的问题放在一起方便照检查设计原则核心含义主要规避的问题原子性每个技能只做一件事技能职责不清复用困难自描述技能描述必须具体可读模型不调用或乱调用技能无状态优先技能不持有会话状态并发调用数据错乱权限内聚技能声明并限制权限高危操作越权执行可观测全调用链路有日志故障无法定位2. 技能分类与定义2.1 高频技能类型与典型场景从实际项目看Agent技能大致可以分为五类覆盖绝大多数业务场景。第一类是检索技能。包括数据库查询、知识库搜索、网页内容抓取、文档读取。典型场景是用户问“有没有上周的测试报告”Agent调用检索技能从文件系统或知识库中定位报告再决定如何回复。第二类是文本处理技能。包括摘要、翻译、格式转换、实体抽取。这类技能不依赖外部系统适合作为组合工作流的中间环节。例如用户上传一篇长文要求整理摘要Agent先读文本再调用摘要技能最后返回结构化结果。第三类是代码执行技能。在沙箱环境中运行SQL、Python、Shell命令适合做数据分析、批量文件操作、定时任务。这类技能风险最高必须配置权限和沙箱隔离。第四类是API交互技能。负责与外部业务系统对接创建工单、发送通知、查询物流状态、更新CRM记录。典型场景是用户说“帮我在项目管理系统里创建一个任务”Agent调用对应API技能完成操作。第五类是记忆技能。用于读写短期和长期信息例如保存用户偏好、记住上次沟通到哪一步、跨会话恢复上下文。记忆技能可以被其他技能组合比如用户前面透露过“我更喜欢表格形式”后续报告类技能可以提示模型调用记忆技能来调整输出格式。2.2 技能描述Schema怎么设计每个技能在上线前都要先写一份结构化描述也就是技能Schema。我习惯使用下面这个结构{ name: search_knowledge_base, description: 在内部知识库中搜索与给定关键词相关的文档。当用户询问产品使用、故障处理、政策规范时使用。返回文档标题、链接以及匹配片段。, parameters: { type: object, properties: { keywords: { type: array, items: {type: string}, description: 搜索关键词列表最多5个不要使用过于宽泛的词语。 } }, required: [keywords] }, output: { type: array, items: { title: string, url: string, snippet: string } }, permission: read-only }这里的重点在 description。它不应该只有一句“搜索知识库”而是要写清楚“什么情况下应该用、输入怎么传、返回什么”。我在早期写技能时经常偷懒描述写成 “A tool to search knowledge base.” 结果是模型面对真实提问时经常识别不出该调用它等到我改成带触发条件的描述命中率明显上来了。参数部分除了类型和字段说明最好加上约束和示例值。例如搜索关键词不能超过5个如果用户提问非常宽泛可以先让模型判断是否需要拆词。约束写不写直接决定模型传参质量。2.3 技能描述太宽泛会出什么问题有次项目里需要让Agent既能查天气又能给穿衣建议我图省事把两个技能集中成一个“weather_and_clothing”技能描述写得模棱两可“处理天气与穿搭相关的问题”。结果模型在用户只问“明天温度多少”时也调用了它这个技能内部却傻傻地等待用户提供穿衣场景最终报错。后来按原子性原则拆分成两个技能并且把描述写清楚weather_query仅当用户询问当前或未来天气、气温、降水时使用参数是地点和时间。clothing_advice仅当用户希望基于天气获得穿衣建议时使用参数是场合和地区。模型在决策时其实非常依赖技能名称和描述的字面匹配。你给它的选项越清晰它就能越准确地选中正确工具。本质上是把一部分“路由决策”从模型那里剥离出来提前在技能层面做了结构化约束。3. 从零构建一个技能模块3.1 最小工程结构怎么搭我建议每个技能都放在独立文件中目录结构类似下面这样agent_skills/ ├── registry.py ├── runner.py └── skills/ ├── __init__.py ├── search_docs.py ├── summary_text.py └── create_ticket.py把技能拆成独立文件的好处很多。第一开发时可以分开写不容易出现多人改同一个文件导致的冲突。第二测试时可以直接对技能文件做单元测试不用把整个Agent拉起来。第三部署时可以按需打包比如某些技能涉及敏感API只让指定服务加载。新人可能会觉得这有点小题大做直接在一个文件里定义几十个函数不更省事但技能数量一旦超过十个单文件会变得难以维护更重要的是自动化加载、动态注册这些能力没法复用。技能体系增长到一定程度结构规整带来的收益会非常明显。3.2 用Python定义技能对象在Python里我一般用一个Skill数据类来描述技能并用一个注册装饰器完成自动注册。这段代码的核心思路是让函数只关注业务逻辑注册和元数据通过装饰器统一处理。# registry.py from dataclasses import dataclass from typing import Callable, Any, Optional _SKILL_REGISTRY: dict[str, Skill] {} dataclass class Skill: name: str description: str parameters: dict func: Callable[..., Any] permission: str read-only def register_skill( name: str, description: str, parameters: dict, permission: str read-only, ): def decorator(func): _SKILL_REGISTRY[name] Skill( namename, descriptiondescription, parametersparameters, funcfunc, permissionpermission, ) return func return decorator def get_skill(name: str) - Optional[Skill]: return _SKILL_REGISTRY.get(name) def list_skills(): return list(_SKILL_REGISTRY.values())然后在技能文件中这样使用# skills/search_docs.py from registry import register_skill register_skill( namesearch_knowledge_base, description在内部知识库中搜索与给定关键词相关的文档。当用户询问产品使用、故障处理、政策规范时使用。, parameters{ type: object, properties: { keywords: { type: array, items: {type: string}, description: 搜索关键词列表最多5个。 } }, required: [keywords] }, permissionread-only, ) def search_knowledge_base(keywords: list[str]) - list[dict]: # 这里执行真实的搜索逻辑例如调内部搜索API或查向量数据库 results [] for keyword in keywords: results.append({ title: f关于{keyword}的文档, url: fhttps://internal.example/doc/{keyword}, snippet: f这篇文档介绍了{keyword}相关的内容。, }) return results这样写出来技能的业务逻辑和元数据放在一起谁负责维护这个技能一眼就能看清它暴露给模型的是什么。不用额外维护一份配置文件避免元数据与实际函数不一致的问题。3.3 把技能注册到Agent运行时注册完成之后关键是让Agent运行时能拿到完整的技能列表并且在模型发出工具调用请求时正确执行技能。构造工具描述列表的代码很简单def build_tool_schema(): tools [] for skill in list_skills(): tools.append({ name: skill.name, description: skill.description, parameters: skill.parameters, }) return tools真正核心的是执行循环。模型会收到用户消息和工具列表如果它决定调用某个技能返回内容里会带着技能名和参数。Agent运行时需要拦截这个调用去注册中心取出对应的函数传入参数然后执行def dispatch_tool_call(tool_name: str, tool_args: dict): skill get_skill(tool_name) if skill is None: return {error: fSkill {tool_name} not found} # 参数校验、权限检查可放在执行前 try: result skill.func(**tool_args) return {success: True, result: result} except Exception as exc: return {success: False, error: str(exc)}这里的思路不绑死任何具体框架。不管底层用的是哪一种Agent框架本质上都是“工具列表 调用循环”的范式。你可以把这套注册中心直接嵌入自己的Agent服务也可以让某个已有框架通过适配器来加载技能。实际项目中还要额外做几件事参数校验、超时控制、日志上报。参数校验我推荐在注册装饰器里根据Schema自动生成一个Pydantic模型这样调用前就能拦截类型错误而不是等函数内部发现了才抛异常。3.4 技能测试与调试的实操细节技能测试分成三个层次缺一不可。第一层是函数单元测试。直接调用技能函数检查正常输入下的返回结果是否符合Schema检查缺失必填参数时是否报错。这一层可以交给CI流程去跑。第二层是模型选择测试。真实环境中更要关注的是“模型在给定问题下能不能选中正确技能”。你需要准备一组用户问题让模型基于技能列表生成选择结果然后人工标注判断是否选对。选错的案例往往是描述不够清晰优化描述后重新跑。第三层是端到端联调。模拟真实用户会话看模型是否先调技能、再根据结果生成最终回复。这里建议加一条“探针日志”记录当前模型看到了哪些技能、哪些参数约束这样出了问题能立刻定位是模型选择错还是执行器执行错。4. 多技能编排与协作4.1 模型自主选择技能还是预设工作流单技能模型自主选择适合开放场景例如客服机器人用户的问题千奇百怪不可能提前预设路径。但复杂任务需要多个技能按顺序协作如果全部靠模型自由发挥结果不稳定。这时就要引入工作流预设。两者可以共存。基本策略是有稳定业务路径的任务用预设工作流没有固定路径的任务采用模型自主选择。例如“用户想创建任务工单”是一个稳定路径先查用户身份、再创建工单、最后发通知。你可以把这三个技能串成固定流程。而“用户问为什么登录失败”则开放很多可能需要查日志、查文档、查账号状态让模型临场决定。用表格对比一下两种模式的特点编排方式适用场景优点缺点模型自主选择开放式问答、客服灵活能覆盖未知场景不稳定依赖模型能力预设工作流固定业务流程结果可控可调试需要提前梳理路径混合模式大部分生产场景兼顾灵活和稳定设计复杂度高4.2 实现一个“资料整理工作流”我来演示一个混合编排的简单实现。假设用户输入一个主题Agent需要搜索资料、生成Markdown格式长文、并保存到个人笔记。把它拆成三个技能search_docs、generate_markdown、save_note。三个技能分别幂等或半幂等然后用一个非常轻量的Pipeline执行器串联def run_pipeline(start_args, steps): current_input start_args for step in steps: skill get_skill(step[skill]) if skill is None: raise ValueError(fmissing skill: {step[skill]}) current_input skill.func(**current_input) return current_input pipeline [ {skill: search_docs, args_from: first}, {skill: generate_markdown, args_from: prev_output}, {skill: save_note, args_from: prev_output}, ]这种流水线式的编排非常适合“输入稳定、步骤固定”的任务。每一步的输入输出都有Schema约束中间出了问题可以精准定位。比让模型一次性调用多个工具要稳定得多因为模型不需要在一轮里同时维护多个状态。对于更复杂的非串行流程比如“先并行搜索多个数据源再合并结果”可以引入简单的DAG调度但大多数场景用串行流水线就够了。我的经验是不要一开始就把编排器设计成通用工作流引擎先用最简单的数组加循环等流程复杂到现有模式撑不住时再重构。4.3 技能冲突场景如何排序技能越来越多时会发现两个技能描述高度相似。比如“查询订单状态”和“查询物流轨迹”用户一句“我的货到哪了”可能同时激活两个技能。这时候需要处理冲突和优先级。我会在技能Schema里增加一个可选字段trigger_examples列出两三个典型触发语句。模型看到具体示例时会比抽象描述更容易判断边界。同时在执行层可以做一个简单的候选评分器对用户问题做关键词抽取计算和技能描述的相似度如果分数最高的技能和模型选择的技能不一致就记录一次告警后续用来优化描述。另一种做法是在技能描述里显式声明“如果你想了解商品配送节点请使用另一个技能”。这种交叉指引能让模型更快厘清边界。我试过在多个团队项目里用这个方法技能误用率下降非常明显。5. 常见问题与排查技巧实录5.1 模型死活不调用技能怎么办这个问题几乎每个项目都会遇到。最常见的根因是技能描述与用户问题的字面距离太远。你描述里写的是“搜索知识库”用户问的是“帮我看下产品说明书里怎么设置静音”模型可能觉得这不需要技能直接凭训练记忆回答了。排查步骤看日志里模型到底有没有收到技能列表。有时候是技能列表构建失败导致模型根本没有选项。看用户问题是否命中了技能描述中的关键词。如果没命中尝试在描述中加入更多同义触发词。看技能数量是否太多。直接在模型上下文里塞20个技能一些老模型可能会忽略部分技能。数量多时应该先按场景分组减少单轮可见技能数量。优化描述时不要只加一句要给出具体触发语句。比如# 差 在内部知识库中搜索文档 # 好 当用户询问产品使用、故障处理、操作手册时使用。例如“怎么设置静音”“邮件退信怎么处理”“保修多久”。搜索关键词应从用户问题中抽取最多5个。这样模型更容易建立“问题——技能”的连接。5.2 技能参数传得驴唇不对马嘴模型生成的参数经常会出现类型不对、缺字段、格式错误的情况。比如搜索技能要求 keywords 是数组模型却传了一个字符串日期参数要求YYYY-MM-DD模型传了2024年5月1号。这种情况靠两层防护。第一层是在参数Schema里尽量写清楚格式和示例加入枚举和正则约束第二层是在执行器内做宽容解析不要直接把参数丢给业务函数。例如日期参数就写一个宽松解析函数from datetime import datetime import re def parse_date(value): if isinstance(value, datetime): return value if isinstance(value, str): m re.match(r(\d{4})年(\d{1,2})月(\d{1,2})日, value) if m: return datetime(int(m.group(1)), int(m.group(2)), int(m.group(3))) m re.match(r(\d{4})-(\d{1,2})-(\d{1,2}), value) if m: return datetime(int(m.group(1)), int(m.group(2)), int(m.group(3))) raise ValueError(f无法解析日期: {value})宽容解析不是让你忽略错误而是把模型常见的错误表达归一成标准格式减少无谓的失败调用。如果真的解析不了就返回一个清晰的错误信息让模型修改参数后重新调用。5.3 技能执行超时和重试要怎么设计外部API调用的超时不能随便拍脑袋。我常用的方法是先记录技能在过去一段时间的历史执行耗时取中位数和P95值超时时间设置为中位数的3~4倍但不要低于2秒也不要超过业务容忍上限。比如某个技能历史耗时中位数是1.2秒P99是3.5秒那超时设置到5秒左右大概率合理。重试要谨慎。对于幂等的查询技能可以重试对于非幂等的创建类技能不能盲目重试否则可能产生重复数据。我建议给技能Schema增加一个字段retryable由技能开发者显式声明是否允许重试。重试策略用指数退避第一次等1秒第二次等2秒最多重试2次。熔断机制也很重要。某个技能连续失败超过5次自动暂停该技能一段时间避免Agent反复卡在同一个失败调用上。这个开关要在运行时可配置运维人员发现外部系统故障后可以手动恢复。5.4 权限与安全管控要点技能执行涉及代码、文件系统、外部API安全漏洞会直接被模型利用。常见风险和控制手段我列了一个速查表风险点表现最小化控制手段代码执行模型生成恶意或错误代码沙箱隔离限制网络禁止连接外网文件读取模型读取任意服务器文件路径白名单禁止绝对路径穿越API凭证技能代码泄露密钥凭据加密存储运行时注入日志脱敏高危操作误删数据、创建违规资源写操作默认需二次确认提示注入外部文本诱导技能执行异常技能内部不执行来自内容的外部指令安全不是做完功能后补的而是每个技能在定义权限时必须声明清楚。我为每个技能都指定了最小权限模型查询类只有只读权限写操作类必须单独标记并走审批链路。这样做虽然增加了配置工作量但能在模型误操作时避免真正的事故。6. 技能评估与持续优化6.1 评估指标体系怎么建技能体系上线后需要持续评估。我一般关注四个指标技能调用准确率在评测集上模型选中的技能与标注结果一致的比例。这个指标衡量技能描述和模型理解是否匹配。技能执行成功率技能函数实际执行成功的比例。失败可能是参数错误、依赖服务异常或业务逻辑bug。无效调用率模型调用技能但返回结果对最终回复没有帮助的比例。经常发生在“问天气也会带上穿衣建议”这种误用场景。端到端任务完成率用户需求最终被正确满足的比例。这是业务最关心的指标。如果技能准确率低优先优化描述如果执行成功率低优先检查参数解析和依赖稳定性如果无效调用率高考虑给技能加更严格的触发边界。6.2 构建评测任务集做离线回放评测集不用很大但要有代表性。我从历史用户会话里挑出50条问题覆盖每个技能至少5个触发场景并且故意加入几个容易混淆的边界问题。然后把评测集离线跑一遍让模型基于当前技能列表选工具自动比较选择结果和标注结果。简单实现思路是写一个回放脚本test_cases [ {query: 怎么设置静音, expected: [search_knowledge_base]}, {query: 帮我查下这两个订单哪个先到, expected: [query_order, query_logistics]}, ] for case in test_cases: tools build_tool_schema() selected model_choose_tool(case[query], tools) is_match set(selected) set(case[expected]) log(case[query], selected, is_match)评测集要随业务变化持续更新。每出现一次线上误用案例就把这条案例加入评测集防止后续改动又把相同问题带回来。6.3 日志复盘与技能版本管理每次技能调用都要记录完整上下文用户原始问题、模型选择结果、传入参数、执行返回、耗时、错误信息。这些日志是优化技能最宝贵的素材。我每个星期会做一次复盘把误用样本挑出来分类为“描述问题”“参数问题”“编排问题”再逐条优化。技能同样要做版本管理。我给每个技能加了一个version字段当description、参数Schema或执行逻辑发生变化时递增版本。特别是描述变了必须发新版本不能在同一个技能上直接改动线上描述否则无法对比新旧版本的准确率差异。兼容规则参照语义化版本字段增加小版本字段变更或删除大版本。在实际运营中一个技能从上线到稳定通常会经历三轮描述调整。第一轮是刚上线描述写得太窄很多真实问题漏触发第二轮是变宽又把边界问题误触发第三轮通过添加反向约束和示例终于稳定下来。所以不要怕技能描述频繁改怕的是改完不看效果。最后分享一个个人经验技能体系做得好的团队往往不是那些写了最多技能的团队而是最会做减法的团队。很多项目初期会放上二十几个“看起来有用”的技能结果模型常常挑错工具后来把技能列表从三十个裁到十二个准确率反而上来了。如果你也要走这条路克制可能比努力更重要。先把最核心的十个技能做扎实再去扩展长尾能力这套 agent-skills 的思路才能真正发挥价值。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑