资讯详情

Agent Skills实战指南:技能系统设计、实现与排查

📅 2026/10/8 21:21:23 | 华诺云谱 👁 阅读
Agent Skills实战指南:技能系统设计、实现与排查
Agent Skills 这个方向最近在 AI 工程圈子里热度蹿得很快。我自己的感觉是它恰恰卡在了“大模型会聊天”和“大模型能干活”之间的那道坎上。你光给模型一个提示词它顶多给你生成文本但你给它一套技能——也就是一组定义良好、可执行的动作——它就能去查数据库、调接口、操作文件甚至编排一组复杂任务。这篇文章我打算从一个实际做过 Agent 技能系统的人的角度把 Agent Skills 从概念拆到落地包括技能到底是什么、该怎么设计、怎么实现、怎么排查问题全流程走一遍。先说清楚这篇文章适合谁想给 LLM 应用加“动手能力”的开发者、正在做 AI Agent 平台选型的技术负责人、以及所有被“模型不会用工具”折磨过的人。不需要你有多深的强化学习背景只要你写过 Python、调过 API就能跟得上。1. Agent Skills 是什么以及它解决的到底是什么问题1.1 为什么现在大家都在折腾“技能”大语言模型本身是一个“文本生成器”它最擅长的是根据上下文预测下一个 token。但真实业务里没人只想听一段漂亮的回答——你要的是“帮我查一下这个客户的上三笔订单”“把这批数据按规则清洗后写入数仓”“定时巡检服务器并把异常项发到群里”。这些动作光靠模型生成文本是完不成的必须有一个机制让模型能调用外部能力。这个外部能力在过去有很多叫法插件Plugin、工具Tool、函数调用Function Calling、MCP 工具等等。Agent Skills 本质上也是这个思路但它的侧重点不一样。我理解下来Skills 更强调“把一个完整的、可复用的任务封装起来”。它不是一个原子性的“查询天气”接口而可能是“根据天气情况生成穿衣建议”这样包含了推理、调用、规则判断的完整动作。换句话说Tool 是手Skill 是包含了手的用法、判断逻辑和使用场景的完整技能。1.2 技能和普通工具的本质区别很多人会问既然都是让模型调用外部能力搞一个函数列表不就行了为什么还要单独强调 Skills我自己在实操中的体会是两者最核心的区别有四个维度粒度不同工具通常是单次操作技能则是“完成一个任务”所需的完整流程。工具回答“查一下股票价格”技能回答“评估当前持仓风险并给出调仓建议”。描述复杂度不同工具条目一般一两句话就够了技能的描述可能要写清楚适用条件、输入输出约束、内部流程、失败兜底逻辑。可编排性不同工具之间是平级关系技能之间往往存在依赖和调用关系。一个“生成周报”技能内部可能要调用“收集数据”“分析趋势”“格式化输出”三个子技能。可复用粒度不同工具是细粒度的零件技能是搭好的组件能直接被不同的 Agent 应用复用。这个区别直接决定了技能系统的设计复杂度。你完全可以把 Skills 理解为“给模型准备的一套标准化动作库”。动作库设计得好不好直接决定了 Agent 在真实任务中的可靠程度。2. 技能系统的整体设计思路拆解2.1 技能注册表模型怎么知道“我有什么技能”要让模型在合适的时机调用合适的技能第一步就是把技能信息以一种模型能理解的形式暴露出来。我常用的方案是维护一个技能注册表里面记录每个技能的 ID、名称、描述、输入参数 Schema、执行入口。关键在描述怎么写。模型不会执行代码它只看文本然后根据当前用户请求和对话历史决定要不要调用、以及怎么传参数。我见过太多失败的案例技能本身写得很漂亮但描述写得含糊结果模型压根不知道什么时候该用它。描述输出必须覆盖五个要素功能概述这个技能做什么。适用场景什么情况下应该调用它。输入说明参数怎么填格式是什么。输出说明执行后会返回什么。限制条件什么情况下不应该用它。2.2 技能触发机制两种路线怎么选技能触发有两种主流的实现路线各有取舍我实际测下来两者差异很大需要根据场景谨慎选择。第一种是模型自主决策触发。也就是把所有技能的描述和参数 Schema 塞进模型上下文让模型在生成过程中决定“现在要不要调用”。实现上通常借助 Function Calling 或 Tool Use 协议。这个方案的优点是灵活模型可以根据对话内容动态决定调用链缺点是费 token、有延迟而且模型可能会做出错误的调用选择。第二种是规则或流程引擎触发。也就是把技能的调用逻辑写在代码里由外部流程引擎根据用户输入的关键词、槽位或意图识别结果来触发技能。这个方案稳定可控但缺乏灵活性用户换个说法可能就触发不了。我个人的经验是别死磕某一种。生产环境更稳妥的做法是混合触发——先用一个轻量级意图分类器做粗筛把候选技能缩小到三五个再把这些候选技能的描述交给模型做精细选择。这样既控制了 token 开销又保留了模型的灵活性。2.3 技能编排单技能执行 vs. 多技能协同到多技能协同这一步才算真正进入 Agent 的核心地带。单个技能执行很简单就是“模型决定调用 代码执行 结果返回”。但真实任务往往要多个技能协作比如一个“处理客户投诉工单”的任务可能涉及“语义分析”“查询订单系统”“生成回复草稿”“提交工单系统”四个技能。编排有两种常见模式。顺序编排适合有明确先后依赖的任务一个技能的输出作为下一个技能的输入。另一种是动态编排让模型自己决定下一步调用哪个技能——这本质上就是一个简化版 ReAct 循环。我建议先把顺序编排做扎实把每一步的输入输出 Schema 定死再考虑让模型做动态决策。动态编排虽然上限高但下限也低模型有可能在中途跑偏工程上要做很多护栏。3. 实操从零搭一套 Agent 技能系统3.1 最小可用框架技能定义与注册管理器我先给你看一个最小可用的技能框架。我们用 Python 来写核心是三段代码技能基类、技能实现、注册管理器。# skill_base.py from abc import ABC, abstractmethod from typing import Any, Dict, Optional class BaseSkill(ABC): # 技能的唯一标识 skill_id: str # 技能名称 name: str # 给模型看的描述务必包含适用场景和限制条件 description: str # 输入参数的 JSON Schema input_schema: Dict[str, Any] {} # 技能版本方便迭代记录 version: str 0.1.0 abstractmethod async def execute(self, params: Dict[str, Any], context: Optional[Dict[str, Any]] None) - Dict[str, Any]: 执行技能的核心逻辑。params 是经过校验的入参context 是共享上下文对话历史、用户信息等。 pass# skill_registry.py from typing import Dict, Type, Optional from skill_base import BaseSkill class SkillRegistry: def __init__(self): self._skills: Dict[str, Type[BaseSkill]] {} def register(self, skill_cls: Type[BaseSkill]) - Type[BaseSkill]: if not skill_cls.skill_id: raise ValueError(fSkill {skill_cls.__name__} must define skill_id) self._skills[skill_cls.skill_id] skill_cls return skill_cls def get_skill(self, skill_id: str) - Optional[BaseSkill]: skill_cls self._skills.get(skill_id) if skill_cls is None: return None return skill_cls() def list_skills_metadata(self) - list[Dict]: 后端序列化为模型可读的 JSON 列表供 Function Calling 使用。 return [ { type: function, function: { name: skill_cls.skill_id, description: skill_cls.description, parameters: skill_cls.input_schema, }, } for skill_cls in self._skills.values() ] registry SkillRegistry()这里需要注意一个细节list_skills_metadata输出的格式要严格对齐你所用模型厂商的 Function Calling 格式。OpenAI 用的是tools数组加function类型Claude 在 Tool Use 协议里的格式略有差异。我的建议是把这个方法做成一个适配层不同模型走不同序列化逻辑别在业务代码里到处判断模型厂商。3.2 实战案例实现一个“天气查询 穿衣建议”复合技能单技能 demo 网上到处都是我直接上复合技能。这个案例我实际在项目里用过业务场景简化后就是这样用户问“今天北京穿什么合适”Agent 需要先调天气接口拿到温度、湿度、风力再根据一套规则生成穿衣建议。# weather_skill.py import httpx from skill_base import BaseSkill from skill_registry import registry registry.register class WeatherQuerySkill(BaseSkill): skill_id weather_query name 天气查询技能 description 根据城市名称查询实时天气数据返回温度、湿度、风力、天气状况。适用于所有需要了解当前或近期天气状况的场景。 version 1.0.0 input_schema { type: object, properties: { city: {type: string, description: 城市中文名例如 北京、上海}, date: {type: string, description: 日期格式 YYYY-MM-DD默认今天} }, required: [city] } async def execute(self, params, contextNone): city params.get(city) if date not in params or not params[date]: params[date] 2025-01-01 # 这里只做演示真实场景请替换成可用的天气服务商 API async with httpx.AsyncClient() as client: resp await client.get( fhttps://api.weather.example/v1/current, params{city: city}, timeout10.0 ) resp.raise_for_status() data resp.json() return { city: city, temperature: data[temp], humidity: data[humidity], wind_scale: data[wind_scale], condition: data[condition] }然后是穿衣建议技能。这里有个设计取舍我要说一下穿衣建议我不建议直接写进天气技能的代码里而是拆成独立技能。原因很简单业务规则变化很快今天用“温度区间判断”明天可能就要叠加“是否下雨”“紫外线强度”等因子。拆成独立技能后改规则不影响天气查询的稳定性而且别的场景——比如旅游攻略生成——也能单独复用穿衣建议这个技能。# clothing_advice_skill.py from skill_base import BaseSkill from skill_registry import registry registry.register class ClothingAdviceSkill(BaseSkill): skill_id clothing_advice name 穿衣建议技能 description 基于温度、湿度和风力给出穿衣建议。适用于用户询问穿什么衣服、如何搭配、是否需要带伞等场景。需要先获取天气数据再调用此技能。 input_schema { type: object, properties: { temperature: {type: number}, humidity: {type: number}, wind_scale: {type: number}, condition: {type: string} }, required: [temperature] } async def execute(self, params, contextNone): temp params[temperature] humidity params.get(humidity, 50) wind params.get(wind_scale, 2) condition params.get(condition, 晴) advice [] if temp 28: advice.append(短袖短裤或轻薄的连衣裙注意防晒) elif temp 20: advice.append(长袖T恤或薄衬衫早晚加一件薄外套) elif temp 10: advice.append(卫衣或毛衣搭配风衣或夹克) else: advice.append(棉服或羽绒服注意保暖) if humidity 80: advice.append(空气湿度大体感温度更低建议多穿一层) if wind 5: advice.append(风力较大建议穿防风外套) if 雨 in condition or 雪 in condition: advice.append(有降水记得带伞穿防水鞋子) return {advice: .join(advice)}3.3 把技能挂到 Agent 上模型调用链的实现技能定义好了接下来要让模型在对话中自动决定调用链。我这里用一个简化但完整的例子展示核心逻辑。不做复杂的状态机就用一个循环模型输出 → 如果有函数调用请求 → 执行对应技能 → 把结果回传给模型 → 模型继续生成。from skill_registry import registry from openai import AsyncOpenAI client AsyncOpenAI() SYSTEM_PROMPT 你是一个生活助手。你可以使用以下技能来帮助用户 - weather_query查询天气参数 city 必填 - clothing_advice生成穿衣建议参数需要 temperature 等天气数据 当用户提问涉及天气时先调用 weather_query 获取数据再把结果作为 clothing_advice 的输入最终给用户完整的穿衣建议。 async def run_agent(user_message: str): messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_message}, ] # 把所有技能的元数据发给模型 tools registry.list_skills_metadata() for _ in range(5): # 防止模型无限循环设置最大迭代次数 response await client.chat.completions.create( modelgpt-4o, messagesmessages, toolstools, tool_choiceauto ) msg response.choices[0].message messages.append(msg.model_dump(exclude_noneTrue)) if not msg.tool_calls: # 模型没有要求调用技能说明可以直接答复用户 return msg.content for tool_call in msg.tool_calls: func_name tool_call.function.name # 直接把 JSON 字符串解析成参数字典 try: import json func_args json.loads(tool_call.function.arguments) except json.JSONDecodeError: func_args {} skill registry.get_skill(func_name) if skill is None: result {error: fskill {func_name} not found} else: result await skill.execute(func_args, context{messages: messages}) messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse), }) return 抱歉处理超时请重试。这段代码是一个简化版但已经包含了一个 Agent 技能调用的核心骨架技能注册、元数据曝光、模型决策、执行、回传。你在生产环境里要加的东西当然更多——超时控制、重试、并发限制、审计日志、token 预算——但骨架就是这套。3.4 多技能协同编排让模型自己决定先调哪个上面的例子其实已经隐含着多技能协同天气查询在前、穿衣建议在后。但这只是依赖关系真正的编排还要处理更复杂的场景。我在实际项目中经常用到一个轻量级的编排器它的核心逻辑是让模型先输出一个“计划”然后我校验这个计划再按计划执行。# orchestrator.py from typing import Any, Dict class SkillOrchestrator: def __init__(self, registry, llm_client): self.registry registry self.llm llm_client async def plan(self, user_request: str, available_skill_ids: list[str]) - list[str]: 让模型输出一个技能调用序列。返回示例[weather_query, clothing_advice] prompt f 用户请求{user_request} 可用技能{, .join(available_skill_ids)} 请输出完成该任务所需的技能调用序列用英文逗号分隔不要输出其他内容。 response await self.llm.generate(prompt) skill_seq [s.strip() for s in response.split(,) if s.strip()] return skill_seq async def run_sequence(self, skill_seq: list[str], initial_params: Dict[str, Any]) - Dict[str, Any]: 按顺序执行技能前一个技能的 output 会合并到 context 供下一个技能使用。 context: Dict[str, Any] {} final_output: Dict[str, Any] {} for skill_id in skill_seq: skill self.registry.get_skill(skill_id) if not skill: raise ValueError(f未知技能{skill_id}) # 这里简化了参数来源策略优先用上一个技能的输出其次用用户原始参数 run_params {**initial_params, **context.get(last_output, {})} final_output await skill.execute(run_params, contextcontext) context[last_output] final_output return final_output这样做的优势在于把“做什么”和“怎么做”解耦了。模型只负责生成技能序列真正的执行逻辑完全由编程序控制。以我的观察这比让模型在每个步骤都做决策要稳定得多因为模型的决策跨度被压缩了出错的概率也大幅下降。4. 常见问题与排查技巧实录4.1 模型总是不调用技能这是所有人第一次接 Agent 技能系统时的崩溃瞬间。模型明明手里拿着工具列表就是宁可用嘴硬编一个答案。我排查这类问题的顺序基本是先检查技能描述里是否写清了“适用场景”很多模型不调用技能是因为它不知道当前用户提问适用这个技能。再看参数 Schema 是否足够清晰。如果一个技能是“查询订单”但参数要求传order_id模型拿不到就会放弃调用因为它觉得信息不足。实际解决方法是把必填参数标清楚或者把required只放真正必需的那个其余参数设计成可选并给默认值。还要检查是否给模型提供了过强的“硬编答案”空间。如果你的系统提示词里明确写了“根据已知信息回答”模型就会偏保守地放弃工具调用。需要显式鼓励使用工具比如在提示词里加一句“优先使用可用技能获取实时数据”。4.2 技能参数解析报错模型返回的 JSON 解析失败或者参数类型不对这是第二个高频问题。模型生成的arguments虽然是 JSON 字符串但偶尔会包含尾逗号、单引号或缺失字段。我验证过几种方案最简单有效的是在解析 JSON 前做一层清洗。import json import re def parse_json_arguments(raw: str) - dict: # 去掉首尾多余的空白 raw raw.strip() # 处理单引号 raw raw.replace(, ) # 去掉尾逗号 raw re.sub(r,\s*([}\]]), r\1, raw) try: return json.loads(raw) except json.JSONDecodeError: # 如果整体解析失败尝试抽取第一个大括号 match re.search(r\{.*\}, raw, re.DOTALL) if match: return json.loads(match.group()) raise ValueError(f无法解析函数参数: {raw})另外传入技能后还要做一层 Schema 校验。我建议用 Pydantic 或 JSONSchema 校验库别手写判断逻辑否则你会被各种边界情况折磨死。4.3 技能执行超时Agent 技能的调用链通常比较长模型一次决策 一次 HTTP 调用 再次生成加起来经常超过 10 秒。用户早就没耐心了。我的优化经验有三条给每个技能设置独立的超时时间不要用全局统一超时。天气接口通常 3 秒内能回来但“数据分析”技能可能要跑 30 秒统一设成 10 秒会导致简单任务等太久、复杂任务直接失败。把耗时长的技能改成“异步提交 结果轮询”模式。也就是技能执行后立刻返回一个 task_id用户或 Agent 稍后通过查询接口获取结果。这个模式对用户体验友好但要额外维护任务状态存储。如果必须在一次请求内完成考虑并行执行无依赖的技能。比如“查天气”和“查航班”互相没依赖可以并发发起。4.4 技能的安全性护栏技能系统本质上就是把模型的能力边界外扩到真实系统安全问题不容小视。我自己踩过几个坑整理成备忘技能调用必须有权限校验。不能因为模型“认为应该删掉”就真的允许删库。我的做法是技能层做 RBAC传入用户的身份信息技能执行前校验权限。技能入参要做白名单过滤尤其是涉及文件路径、URL、SQL 片段等参数的技能。模型生成的内容可能包含意外的特殊字符必须严格校验后再拼装。所有技能执行都要有审计日志。至少记录用户请求、模型生成的参数、技能执行结果、耗时。这既是排查问题的依据也是后续优化技能描述的素材。设置 token 和调用次数限流防止模型在循环里反复调用同一个技能导致费用失控或下游系统被打爆。5. 这套方案在真实业务中的表现观察最后补充一些我在线上系统里跑这套技能架构的观察。技能系统的效果很大程度上取决于技能拆分的粒度。拆得太粗技能就是个大杂烩模型根本不理解什么时候用拆得太细技能列表变长模型选择困难token 成本也飙升。我目前比较稳妥的经验是一个技能对应一个可独立验收的业务动作。比如“天气查询”是一个技能“穿衣建议”是另一个技能“生成周报”可以是一个技能但“分析销售数据”和“格式化周报模板”不应该塞在一个技能里。另外一个观察是技能描述需要持续迭代。模型对技能的理解误差往往靠真实用户反馈暴露。第一批用户用起来之后一定要定期翻看日志找出“模型该调用某技能但没调用”的 case去优化技能描述。这个过程琐碎但价值很高。我优化过的一个技能描述调用准确率从 63% 提到了 89%。如果你准备在自己的项目里引入 Agent Skills我个人建议不要一上来就铺大而全的技能平台而是先把两三个核心技能的完整链路跑通再逐步扩展。这套东西的上限很高但下限也取决于你的工程细节是否扎实多花点时间在参数 Schema 和描述文本上回报绝对值得。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑