一篇帮你从知道 Skill 这个词到能独立设计生产级 Skill的系统教学含 3 个完整实战案例。阅读提示适合谁看正在做或准备做 AI Agent 开发的工程师尤其是从传统后端 / 数据仓库转过来的同学看完能做什么能独立写一个完整的 Skill能说清 Skill 的注册、调度、执行全链路能避开 80% 的常见坑不适合谁只用 ChatGPT 聊天、不涉及 Agent 系统开发的用户先给结论Skill 本质上是一段被结构化描述的可复用能力它和普通函数的区别不在于代码本身而在于它对 LLM 是可发现、可理解、可调度的一个 Skill 的质量70% 取决于描述description和触发规则trigger而不是执行逻辑本身写 Skill 最常见的错误不是代码写错而是描述太模糊导致 LLM 不知道什么时候该用它你有没有遇到过这种情况你给 Agent 接了一个天气查询 API测试的时候手动调用没问题但用户说明天北京冷不冷Agent 就开始瞎猜根本不调你的接口。或者你写了一个计算工具用户说帮我算一下 15% 的折扣价Agent 自己心算了一个错的答案完全忽略了你的函数。问题不在模型笨而在你给模型的工具说明书写得太差了。很多人以为 Skill 就是包了一层的函数调用写个 name、写个 parameters 就完事了。但真正让 Skill 在 Agent 系统里可靠工作的是描述、触发规则、参数校验、返回格式、异常兜底这一整套设计。这一篇我会从最基础的定义开始一直讲到底层注册机制和调度引擎中间穿插 3 个可以直接跑的实战 Skill。看完之后你不只会写 Skill还能说清楚为什么这么写。01 先搞清楚Skill 到底是什么和插件 / 工具有什么区别很多文章把 Skill、Plugin、Tool 这三个词混着用导致读者越看越迷糊。先在这里把边界画清楚。Tool工具最底层的概念。一个 HTTP API、一个数据库查询函数、一个文件读写操作都是 Tool。它只关心输入什么、输出什么不关心什么时候该被调用。Plugin插件一组 Tool 的打包。比如一个飞书插件可能包含发消息、读文档、查日历等十几个 Tool。Plugin 解决的是怎么安装和管理不解决怎么被 LLM 理解。Skill技能在 Tool 之上加了一层对 LLM 的语义描述。它不只是一个可执行的函数还包含这个能力是干什么的description什么情况下应该触发它trigger rules需要从用户输入里提取哪些参数slot definitions执行出错了怎么办error handling用一句话总结Tool 是给机器看的接口Skill 是给 LLM 看的能力说明书。这个区别非常关键。一个写得好的 SkillLLM 能准确判断什么时候该调它、该传什么参数一个写得差的 Skill即使代码逻辑完全正确LLM 也可能在该用的时候不用、不该用的时候乱用。图 1Tool / Plugin / Skill 三层关系这一节的小结Skill 不是带描述的函数这么简单。它是连接 LLM 推理能力和外部可执行能力之间的桥梁核心价值在于让 LLM 能理解和选择什么时候使用哪个能力。02 Skill 的核心组成结构7 个缺一不可的模块一个完整的 Skill 由 7 个部分组成。少了任何一块在实际 Agent 系统里都会出问题。1. 名称nameSkill 的唯一标识符。要求全小写用下划线连接get_weather、calculate_discount动词开头一眼能看出它干什么避免泛化命名如do_something、process_data2. 描述description这是整个 Skill 里最重要的字段没有之一。LLM 靠这段文字决定什么时候调用你的 Skill。写好描述的关键原则说清楚能干什么查询指定城市未来 3 天的天气预报说清楚什么时候用当用户询问天气、温度、是否下雨、穿衣建议时触发说清楚不适用什么不支持历史天气查询不支持全球所有城市代码 1# 好的描述name: get_weatherdescription: | 查询指定城市的天气预报支持未来 1-7 天。 触发场景用户问天气、温度、降雨概率、穿衣建议。 不支持历史天气、空气质量、紫外线指数。# 差的描述LLM 无法准确判断何时调用name: get_weatherdescription: 获取天气信息3. 触发规则trigger rules描述是大致什么时候用触发规则是精确匹配条件。常见写法triggers: -intent:weather_query # 意图匹配 keywords:[天气,温度,下雨,穿什么] required_slots:[city] # 必须有城市参数才触发-intent:travel_planning keywords:[出差,旅游,带伞] optional_slots:[date_range]4. 入参槽位parameter slots定义 Skill 需要从用户输入里提取哪些信息。每个槽位包含parameters: city: type:string description:城市名称如北京、上海 required:true extraction_rules: -entity_type:location -aliases:[帝都,魔都,深圳]days: type:integer description:预报天数1-7 required:false default:3 constraints: min:1 max:75. 执行逻辑execution logic实际的业务代码。这一层和普通函数没什么区别关键是要做好参数校验和异常处理async def execute(city: str, days: int 3) - SkillResult: # 参数校验 ifnot city or len(city) 20: return SkillResult.error(城市名称不合法) if days 1or days 7: return SkillResult.error(预报天数必须在 1-7 之间) try: weather await weather_api.fetch(city, days) return SkillResult.success(format_weather(weather)) except CityNotFoundError: return SkillResult.error(f未找到城市{city}) except APIRateLimitError: return SkillResult.error(天气服务繁忙请稍后重试)6. 返回格式return format统一的返回结构让 Agent 框架能标准化处理所有 Skill 的结果dataclassclass SkillResult: status: str # success | error | partial data: dict # 结构化数据 message: str # 给 LLM 的自然语言摘要 metadata: dict # 耗时、来源、置信度等7. 权限控制permission control哪些场景下允许调用、哪些不允许permissions: rate_limit: 10/minute # 频率限制 auth_required: false # 是否需要用户授权 allowed_channels: [web, app] # 允许的调用渠道 data_retention: none # 是否缓存结果这一节的小结7 个模块各司其职。其中 description 和 trigger rules 决定了LLM 能不能准确找到它parameter slots 决定了找到之后能不能正确调用execution logic 和 error handling 决定了调用之后能不能可靠返回。03 手把手教你写第一个 Skill语法、规范与参数校验理论讲够了直接上手。我们来写一个商品价格查询 Skill这是 Agent 系统里最常见的查询类场景。Step 1定义 Skill 元信息name: query_product_priceversion: 1.0.0description: | 查询小米商城商品的实时价格。 当用户问多少钱、价格、便宜多少、有没有优惠时触发。 支持按商品名称、型号、SKU 查询。 不支持历史价格趋势、竞品比价。author: xitingtags: [ecommerce, price, xiaomi]Step 2定义参数槽位parameters: product_name: type:string description:商品名称或型号如小米14、RedmiNote14Pro required:true extraction_rules: -entity_type:product -fuzzy_match:true # 允许模糊匹配 validation: min_length:2 max_length:50platform: type:string description:查询平台 required:false default:xiaomi_mall enum:[xiaomi_mall,jd,taobao]Step 3编写执行逻辑from typing import Optionalfrom dataclasses import dataclassdataclassclass SkillResult: status: str data: dict message: strasyncdef execute(product_name: str, platform: str xiaomi_mall) - SkillResult: 查询商品价格 - 这是 Skill 的核心执行逻辑 # 1. 参数校验永远不要信任 LLM 提取的参数 ifnot product_name or len(product_name.strip()) 2: return SkillResult( statuserror, data{}, message商品名称太短请提供更具体的商品名称或型号。 ) # 2. 实体消歧同一个商品可能有多种叫法 resolved await resolve_product(product_name) ifnot resolved: return SkillResult( statuserror, data{}, messagef未找到名为「{product_name}」的商品请确认名称是否正确。 ) # 3. 调用外部 API try: price_info await price_api.query( skuresolved.sku, platformplatform ) except APITimeoutError: return SkillResult( statuserror, data{}, message价格服务响应超时请稍后重试。 ) # 4. 格式化返回给 LLM 看的自然语言摘要 给系统看的结构化数据 return SkillResult( statussuccess, data{ product_name: resolved.name, sku: resolved.sku, current_price: price_info.price, original_price: price_info.original_price, discount: price_info.discount, platform: platform, url: price_info.url }, messagef{resolved.name} 在{platform_name(platform)}的当前售价为 ¥{price_info.price} f原价 ¥{price_info.original_price}{price_info.discount}。 )Step 4编写参数校验规则参数校验是 Skill 稳定性的第一道防线。很多人的 Skill 在测试时没问题一上用户环境就崩80% 的原因是参数校验不够。# 校验模式先宽松匹配再严格校验def validate_params(raw_params: dict) - tuple[bool, dict, str]: 三步校验法 1. 类型检查 - 是不是期望的数据类型 2. 范围检查 - 值在不在合法范围内 3. 语义检查 - 值有没有实际意义 errors [] # 类型检查 product_name raw_params.get(product_name) if product_name andnot isinstance(product_name, str): errors.append(product_name 必须是字符串) # 范围检查 platform raw_params.get(platform, xiaomi_mall) valid_platforms [xiaomi_mall, jd, taobao] if platform notin valid_platforms: errors.append(fplatform 必须是 {valid_platforms} 之一) # 语义检查最容易被忽略 if product_name and len(product_name.strip()) 2: errors.append(商品名称至少需要 2 个字符) if errors: returnFalse, {}, .join(errors) returnTrue, {product_name: product_name.strip(), platform: platform}, 这一节的小结写 Skill 的执行逻辑不难难的是把各种异常情况都想到并处理好。参数校验、实体消歧、超时处理、错误消息——这些看起来是细节实际上决定了你的 Skill 在生产环境里能不能稳定工作。04 Skill 调用全链路从用户提问到结果返回中间到底发生了什么很多人写完 Skill 就以为完事了但 Skill 只是整个调用链路中的一个环节。理解全链路才能写出和系统其他部分配合得好的 Skill。图 3Skill 调用全链路流程完整的调用流程分 6 步第 1 步用户输入用户说了一句话“小米 14 Pro 现在多少钱”第 2 步意图识别Intent RecognitionAgent 框架把用户输入发给 LLMLLM 判断这句话的意图。这一步的核心是 prompt engineering你是一个意图分类器。根据用户输入判断应该调用哪个工具。可用工具1. query_product_price - 查询商品价格。当用户问多少钱、价格时使用。2. get_weather - 查询天气。当用户问天气、温度时使用。3. search_product - 搜索商品。当用户想找某个商品但没问价格时使用。用户输入小米 14 Pro 现在多少钱LLM 返回应该调用query_product_price提取参数product_name 小米 14 Pro。第 3 步槽位填充Slot Filling框架从 LLM 的输出中提取结构化参数。这一步常见的坑LLM 提取了product_name 小米14Pro没有空格但实际数据库里叫小米 14 ProLLM 漏提了可选参数platform需要用默认值补上LLM 多提了不存在的参数需要过滤掉第 4 步参数校验上一节讲的三步校验法在这里执行。校验不通过直接返回友好的错误消息不调用外部 API。第 5 步执行 Skill调用query_product_price.execute()实际发起 HTTP 请求查询价格。第 6 步结果返回与格式化Skill 返回结构化的SkillResultAgent 框架把message字段的内容拼进 LLM 的上下文LLM 生成最终的自然语言回复给用户。小米 14 Pro 在小米商城的当前售价为 ¥4499原价 ¥49999 折。需要我帮你看看有没有其他优惠券吗关键洞察整个链路里最容易出问题的不是执行逻辑而是第 2 步和第 3 步——意图识别和槽位填充。而这两步的准确性主要取决于你在 Skill 定义里写的description和triggers。这就是为什么我说一个 Skill 的质量70% 取决于描述。05 深入底层原理Skill 注册机制、调度引擎与上下文透传如果你想从会写 Skill进阶到能设计 Skill 系统这一节是核心。图 4Skill 注册与调度引擎架构5.1 Skill 注册机制Skill 写好了怎么让 Agent 框架知道它的存在常见的注册方式有三种静态注册在配置文件里声明所有 Skill框架启动时一次性加载。# agent_config.yamlskills:-name:query_product_price module:skills.price_query enabled:true-name:get_weather module:skills.weather enabled:true优点简单、可控。缺点新增 Skill 需要重启服务。动态注册Skill 在运行时主动向调度引擎注册自己。# Skill 启动时engine SkillEngine.get_instance()engine.register( namequery_product_price, description查询商品价格..., handlerexecute, triggers[多少钱, 价格, 优惠])优点热插拔不用重启。缺点需要处理注册冲突和去重。声明式注册Skill 通过装饰器或类继承自动注册框架扫描特定目录下的所有 Skill 定义。skill( namequery_product_price, description查询商品价格, triggers[多少钱, 价格])class PriceQuerySkill(BaseSkill): async def execute(self, product_name: str, **kwargs) - SkillResult: ...这是目前主流 Agent 框架LangChain、LlamaIndex、OpenCode 等最常用的方式。5.2 调度引擎怎么选择 Skill当用户发了一句话调度引擎需要从几十甚至几百个 Skill 里找到最合适的一个。这个过程通常是两阶段匹配第一阶段粗筛Fast Filtering用关键词匹配、意图分类器或向量检索从全量 Skill 里快速缩小候选范围。这一步要求快通常在 10ms 内完成。# 向量检索方式user_embedding embed(user_input)candidates skill_index.search(user_embedding, top_k5)第二阶段精排Precise Ranking把候选 Skill 的 description 拼进 LLM 的 system prompt让 LLM 做最终选择。这一步准但慢需要一次 LLM 调用。你是工具选择器。以下是候选工具1. query_product_price查询商品价格。当用户问多少钱、价格时使用。2. search_product搜索商品目录。当用户想找某个商品但没问价格时使用。3. get_discount_info查询优惠活动。当用户问有没有优惠、打折吗时使用。用户说小米 14 Pro 现在多少钱选择最合适的工具只返回编号5.3 上下文透传Skill 执行时不是在一个真空环境里。它需要访问的上下文包括对话历史用户之前说了什么可能影响参数提取用户信息用户 ID、会员等级、地区偏好会话状态之前调用过哪些 Skill、中间结果是什么系统配置超时时间、重试策略、降级方案这些上下文通过Context对象透传给 Skilldataclassclass SkillContext: user_id: str session_id: str conversation_history: list[Message] user_profile: dict system_config: dict intermediate_results: dict # 前面 Skill 的执行结果async def execute(params: dict, context: SkillContext) - SkillResult: # 可以通过 context 获取用户所在城市作为默认参数 default_city context.user_profile.get(city, 北京) ...这一节的小结注册机制决定了Skill 怎么被发现调度引擎决定了怎么选对 Skill上下文透传决定了Skill 能不能拿到它需要的信息。这三层共同构成了 Skill 系统的基础设施。06 高阶进阶多轮对话 Skill、嵌套 Skill、条件分支与异常兜底基础 Skill 是单轮的——用户问一句Skill 执行一次返回结果。但真实场景往往更复杂。图 5高阶 Skill 模式6.1 多轮对话 Skill用户说帮我查一下小米 14 的价格Skill 返回价格后用户接着问那 Pro 版呢“——这时候 Skill 需要理解上下文知道Pro 版指的是小米 14 Pro”。class PriceQuerySkill(BaseSkill): async def execute(self, params: dict, context: SkillContext) - SkillResult: product_name params.get(product_name) # 多轮对话如果用户没给完整商品名从历史里补全 if not product_name or len(product_name) 4: last_product self._extract_last_product(context.conversation_history) if last_product: product_name f{last_product} {product_name}.strip() # 继续执行查询...6.2 嵌套 Skill一个 Skill 的执行过程中需要调用另一个 Skill。比如下单 Skill内部需要调用查库存 Skill和计算价格 Skill。class PlaceOrderSkill(BaseSkill): asyncdef execute(self, params: dict, context: SkillContext) - SkillResult: # 嵌套调用先查库存 stock_result await self.engine.invoke_skill( check_stock, {product_id: params[product_id], quantity: params[quantity]}, context ) if stock_result.status error: return stock_result # 库存不足直接返回错误 # 嵌套调用计算价格 price_result await self.engine.invoke_skill( calculate_price, {product_id: params[product_id], coupon: params.get(coupon)}, context ) # 组合结果执行下单逻辑 ...嵌套 Skill 的关键设计原则嵌套深度不超过 3 层否则调试成本指数级上升每个嵌套调用都要有超时和降级方案子 Skill 的错误要能被父 Skill 捕获和处理6.3 条件分支 Skill同一个 Skill根据参数不同走不同的执行路径。async def execute(self, params: dict, context: SkillContext) - SkillResult: query_type params.get(query_type, current) if query_type current: # 查当前价格 returnawait self._query_current_price(params) elif query_type history: # 查历史价格需要调不同的 API returnawait self._query_price_history(params) elif query_type compare: # 多平台比价 returnawait self._compare_prices(params) else: return SkillResult.error(f不支持的查询类型{query_type})6.4 异常兜底与重试机制生产环境里外部 API 超时、网络抖动、参数异常都是家常便饭。一个好的 Skill 必须有完善的异常处理class ResilientSkill(BaseSkill): MAX_RETRIES 3 RETRY_DELAY 1.0# 秒 asyncdef execute_with_retry(self, params: dict, context: SkillContext) - SkillResult: last_error None for attempt in range(self.MAX_RETRIES): try: result await self.execute(params, context) if result.status ! error: return result last_error result.message except TimeoutError: last_error 请求超时 await asyncio.sleep(self.RETRY_DELAY * (attempt 1)) # 指数退避 except ConnectionError: last_error 网络连接失败 await asyncio.sleep(self.RETRY_DELAY * (attempt 1)) # 所有重试都失败走降级方案 returnawait self._fallback(params, context, last_error) asyncdef _fallback(self, params: dict, context: SkillContext, error: str) - SkillResult: 降级方案返回缓存数据或引导用户稍后重试 cached await self.cache.get(fprice:{params.get(product_name)}) if cached: return SkillResult( statuspartial, datacached, messagef数据可能不是最新的{cached[summary]} ) return SkillResult.error(f服务暂时不可用{error}请稍后重试。)重试策略的核心原则用指数退避1s, 2s, 4s不要用固定间隔设置最大重试次数通常 3 次最后一次失败后走降级方案不要无限重试只重试可恢复的错误超时、网络不重试参数错误07 实战从零完整手写 3 个 Skill实战 1查询类 Skill——飞书文档搜索from dataclasses import dataclass, fielddataclassclass SkillResult: status: str # success / error / partial data: dict field(default_factorydict) message: str classmethod def success(cls, data: dict, message: str ): return cls(statussuccess, datadata, messagemessage) classmethod def error(cls, message: str): return cls(statuserror, data{}, messagemessage)# Skill 定义SKILL_DEFINITION { name: search_feishu_doc, description: 搜索飞书云文档。当用户说找文档、搜一下、之前的文档时触发。 支持按关键词、作者、时间范围搜索。 不支持搜索聊天记录、搜索邮件。 , parameters: { keyword: { type: string, description: 搜索关键词, required: True }, owner: { type: string, description: 文档作者可选, required: False }, time_range: { type: string, description: 时间范围如最近一周、本月, required: False, default: 最近一周 } }}asyncdef execute(keyword: str, owner: str None, time_range: str 最近一周, contextNone) - SkillResult: 飞书文档搜索的执行逻辑 # 参数校验 ifnot keyword or len(keyword.strip()) 2: return SkillResult.error(搜索关键词至少需要 2 个字符。) # 时间范围解析 date_filter parse_time_range(time_range) ifnot date_filter: return SkillResult.error(f无法理解时间范围「{time_range}」请用最近一周、本月等表述。) # 解析作者如果有 owner_id None if owner: owner_id await resolve_user_id(owner) ifnot owner_id: return SkillResult.error(f未找到名为「{owner}」的用户。) # 调用飞书 API try: results await feishu_api.search_docs( keywordkeyword, owner_idowner_id, start_timedate_filter[start], end_timedate_filter[end], page_size10 ) except FeishuAPIError as e: return SkillResult.error(f飞书接口异常{e.message}) ifnot results: return SkillResult.success( data{documents: [], total: 0}, messagef未找到包含「{keyword}」的文档。 ) # 格式化结果 doc_list [ { title: doc.title, url: doc.url, owner: doc.owner_name, updated_at: doc.updated_at, snippet: doc.snippet[:100] } for doc in results ] summary f找到 {len(doc_list)} 篇文档\n for i, doc in enumerate(doc_list[:5], 1): summary f{i}. {doc[title]}{doc[owner]}{doc[updated_at]}\n return SkillResult.success( data{documents: doc_list, total: len(doc_list)}, messagesummary )实战 2计算类 Skill——折扣价格计算SKILL_DEFINITION { name: calculate_discount, description: 计算商品的折扣后价格。当用户说打折、优惠后多少、算一下折扣时触发。 支持百分比折扣、满减、多件折扣。 不支持跨商品组合优惠、会员积分抵扣。 , parameters: { original_price: { type: number, description: 原价, required: True, validation: {min: 0.01, max: 999999} }, discount_type: { type: string, description: 折扣类型, required: True, enum: [percentage, fixed_amount, buy_more] }, discount_value: { type: number, description: 折扣值百分比写 85 表示 85 折满减写减免金额多件写件数, required: True }, quantity: { type: integer, description: 购买数量多件折扣时必填, required: False, default: 1 } }}asyncdef execute(original_price: float, discount_type: str, discount_value: float, quantity: int 1, contextNone) - SkillResult: # 参数校验 if original_price 0: return SkillResult.error(原价必须大于 0。) if discount_type percentageand (discount_value 0or discount_value 100): return SkillResult.error(百分比折扣必须在 0-100 之间。) if discount_type buy_moreand quantity int(discount_value): return SkillResult.error(f多件折扣需要购买至少 {int(discount_value)} 件。) # 计算逻辑 if discount_type percentage: final_price original_price * (discount_value / 100) * quantity saving original_price * quantity - final_price detail f{discount_value / 10} 折 elif discount_type fixed_amount: if original_price discount_value: return SkillResult.error(满减金额不能超过原价。) final_price (original_price - discount_value) * quantity saving discount_value * quantity detail f满 ¥{original_price} 减 ¥{discount_value} elif discount_type buy_more: free_count quantity // int(discount_value) pay_count quantity - free_count final_price original_price * pay_count saving original_price * free_count detail f买 {int(discount_value)} 件减 1 件 else: return SkillResult.error(f不支持的折扣类型{discount_type}) return SkillResult.success( data{ original_price: original_price, quantity: quantity, discount_type: discount_type, discount_detail: detail, final_price: round(final_price, 2), saving: round(saving, 2) }, messagef原价 ¥{original_price} × {quantity} 件 ¥{original_price * quantity} f享受{detail}后实付 ¥{round(final_price, 2)}立省 ¥{round(saving, 2)}。 )实战 3工具调用类 Skill——数据库查询SKILL_DEFINITION { name: query_database, description: 查询业务数据库中的数据。当用户说查一下数据、跑个 SQL、看看有多少时触发。 只支持 SELECT 查询不支持 INSERT/UPDATE/DELETE。 自动限制返回行数为 1000 行。 , parameters: { sql: { type: string, description: SQL 查询语句只支持 SELECT, required: True }, database: { type: string, description: 目标数据库, required: False, default: analytics, enum: [analytics, user_db, order_db] } }}# SQL 安全校验关键FORBIDDEN_KEYWORDS [INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, CREATE, GRANT, REVOKE, EXEC, EXECUTE]asyncdef execute(sql: str, database: str analytics, contextNone) - SkillResult: 安全地执行 SQL 查询 # 1. SQL 注入防护第一优先级 sql_upper sql.upper().strip() for keyword in FORBIDDEN_KEYWORDS: if keyword in sql_upper: return SkillResult.error( f安全限制不允许执行 {keyword} 操作。本 Skill 只支持 SELECT 查询。 ) # 2. 必须以 SELECT 开头 ifnot sql_upper.startswith(SELECT): return SkillResult.error(SQL 必须以 SELECT 开头。) # 3. 自动添加 LIMIT防止查询过大拖垮数据库 ifLIMITnotin sql_upper: sql sql.rstrip(;) LIMIT 1000; # 4. 执行查询 try: conn await get_db_connection(database) result await conn.execute(sql) rows result.fetchall() columns result.keys() except SQLError as e: return SkillResult.error(fSQL 执行失败{e.message}) finally: await conn.close() # 5. 格式化结果 ifnot rows: return SkillResult.success( data{columns: [], rows: [], row_count: 0}, message查询结果为空。 ) data { columns: list(columns), rows: [dict(zip(columns, row)) for row in rows[:100]], # 前 100 行 row_count: len(rows), truncated: len(rows) 100 } summary f查询返回 {len(rows)} 行数据 if data[truncated]: summary 已截取前 100 行 summary 。 return SkillResult.success(datadata, messagesummary)实战小结三个 Skill 代表了三种典型模式查询类重点在参数校验和结果格式化计算类重点在业务逻辑分支和数值精度工具调用类重点在安全防护和资源管理不管哪种模式参数校验、异常处理、友好的错误消息都是必须的。08 常见报错排查、性能优化与面试核心考点8.1 常见报错与排查现象可能原因排查方法Skill 永远不被触发description 写得太模糊用 3 个不同的用户问法测试触发率触发了但参数全错参数描述和实际期望不匹配检查 LLM 提取的原始参数间歇性超时外部 API 不稳定加重试 降级方案返回格式乱了message 里混了 JSON 和自然语言统一返回格式结构化数据放 data多轮对话丢失上下文没有从 conversation_history 补全参数加上下文透传逻辑8.2 性能优化描述优化description 不是越长越好。太长的描述会占用 LLM 的上下文窗口反而降低匹配准确率。经验是 50-150 字。粗筛优化当 Skill 数量超过 50 个时不能让 LLM 逐个判断。用向量检索做粗筛把候选范围缩小到 5-10 个。缓存策略对于查询类 Skill同样的参数短时间内返回缓存结果避免重复调用外部 API。from functools import lru_cachefrom datetime import datetime, timedeltaclass SkillCache: def __init__(self, ttl_seconds: int 300): self._cache {} self._ttl ttl_seconds asyncdef get_or_execute(self, key: str, executor): now datetime.now() if key in self._cache: result, cached_at self._cache[key] if (now - cached_at).seconds self._ttl: return result result await executor() self._cache[key] (result, now) return result8.3 Skill 设计最佳实践单一职责一个 Skill 只做一件事。查询价格和计算折扣应该是两个 Skill。描述精确description 里要说清楚能做什么和不能做什么。错误友好错误消息要告诉用户下一步怎么做不要只说失败了。幂等设计同一个 Skill 同样的参数调用两次结果应该一样查询类天然幂等下单类需要特殊处理。可观测每次调用都要记录日志包括输入参数、执行耗时、返回状态。8.4 面试核心考点如果你在面试 Agent 相关岗位这些 Skill 相关的问题大概率会被问到Q1Skill 和普通函数有什么区别核心答Skill 在普通函数之上加了语义描述层description、triggers、slot definitions让 LLM 能理解什么时候该用这个函数。普通函数是给程序员看的接口Skill 是给 LLM 看的能力说明书。Q2怎么让 LLM 准确选择该调用哪个 Skill核心答两阶段匹配——先用关键词/向量检索粗筛候选再用 LLM 精排。关键是 description 要写得精确既要说能力也要说边界。Q3Skill 之间的依赖和冲突怎么处理核心答依赖用嵌套调用解决冲突用优先级机制解决。优先级可以基于 description 的语义相关度、Skill 的置信度评分、或显式的优先级配置。Q4怎么处理 Skill 执行失败核心答三层防线——参数校验防第一层、重试机制防第二层、降级方案防第三层。关键是不要让一个 Skill 的失败拖垮整个 Agent。Q5Skill 数量多了以后性能怎么优化核心答粗筛用向量检索快精排用 LLM准结果用缓存省。分层处理别让 LLM 看所有 Skill。决策帮助如果你现在是刚接触 Agent 开发先写 2-3 个单轮查询类 Skill把 description 和参数校验写扎实如果你已经在做多轮对话重点投入在上下文透传和槽位补全逻辑上如果你在设计Skill 系统优先把注册机制和调度引擎做对Skill 的代码反而是最简单的部分如果你只能先做一步先把 description 写好——这一个动作能解决 50% 的调用不准问题学AI大模型的正确顺序千万不要搞错了2026年AI风口已来各行各业的AI渗透肉眼可见超多公司要么转型做AI相关产品要么高薪挖AI技术人才机遇直接摆在眼前有往AI方向发展或者本身有后端编程基础的朋友直接冲AI大模型应用开发转岗超合适就算暂时不打算转岗了解大模型、RAG、Prompt、Agent这些热门概念能上手做简单项目也绝对是求职加分王给大家整理了超全最新的AI大模型应用开发学习清单和资料手把手帮你快速入门学习路线:✅大模型基础认知—大模型核心原理、发展历程、主流模型GPT、文心一言等特点解析✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑✅开发基础能力—Python进阶、API接口调用、大模型开发框架LangChain等实操✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经以上6大模块看似清晰好上手实则每个部分都有扎实的核心内容需要吃透我把大模型的学习全流程已经整理好了抓住AI时代风口轻松解锁职业新可能希望大家都能把握机遇实现薪资/职业跃迁这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】
AI Agent Skill 从入门到精通:手把手带你搞懂定义、结构、调用链路与底层原理
发布时间:2026/5/16 22:09:15
一篇帮你从知道 Skill 这个词到能独立设计生产级 Skill的系统教学含 3 个完整实战案例。阅读提示适合谁看正在做或准备做 AI Agent 开发的工程师尤其是从传统后端 / 数据仓库转过来的同学看完能做什么能独立写一个完整的 Skill能说清 Skill 的注册、调度、执行全链路能避开 80% 的常见坑不适合谁只用 ChatGPT 聊天、不涉及 Agent 系统开发的用户先给结论Skill 本质上是一段被结构化描述的可复用能力它和普通函数的区别不在于代码本身而在于它对 LLM 是可发现、可理解、可调度的一个 Skill 的质量70% 取决于描述description和触发规则trigger而不是执行逻辑本身写 Skill 最常见的错误不是代码写错而是描述太模糊导致 LLM 不知道什么时候该用它你有没有遇到过这种情况你给 Agent 接了一个天气查询 API测试的时候手动调用没问题但用户说明天北京冷不冷Agent 就开始瞎猜根本不调你的接口。或者你写了一个计算工具用户说帮我算一下 15% 的折扣价Agent 自己心算了一个错的答案完全忽略了你的函数。问题不在模型笨而在你给模型的工具说明书写得太差了。很多人以为 Skill 就是包了一层的函数调用写个 name、写个 parameters 就完事了。但真正让 Skill 在 Agent 系统里可靠工作的是描述、触发规则、参数校验、返回格式、异常兜底这一整套设计。这一篇我会从最基础的定义开始一直讲到底层注册机制和调度引擎中间穿插 3 个可以直接跑的实战 Skill。看完之后你不只会写 Skill还能说清楚为什么这么写。01 先搞清楚Skill 到底是什么和插件 / 工具有什么区别很多文章把 Skill、Plugin、Tool 这三个词混着用导致读者越看越迷糊。先在这里把边界画清楚。Tool工具最底层的概念。一个 HTTP API、一个数据库查询函数、一个文件读写操作都是 Tool。它只关心输入什么、输出什么不关心什么时候该被调用。Plugin插件一组 Tool 的打包。比如一个飞书插件可能包含发消息、读文档、查日历等十几个 Tool。Plugin 解决的是怎么安装和管理不解决怎么被 LLM 理解。Skill技能在 Tool 之上加了一层对 LLM 的语义描述。它不只是一个可执行的函数还包含这个能力是干什么的description什么情况下应该触发它trigger rules需要从用户输入里提取哪些参数slot definitions执行出错了怎么办error handling用一句话总结Tool 是给机器看的接口Skill 是给 LLM 看的能力说明书。这个区别非常关键。一个写得好的 SkillLLM 能准确判断什么时候该调它、该传什么参数一个写得差的 Skill即使代码逻辑完全正确LLM 也可能在该用的时候不用、不该用的时候乱用。图 1Tool / Plugin / Skill 三层关系这一节的小结Skill 不是带描述的函数这么简单。它是连接 LLM 推理能力和外部可执行能力之间的桥梁核心价值在于让 LLM 能理解和选择什么时候使用哪个能力。02 Skill 的核心组成结构7 个缺一不可的模块一个完整的 Skill 由 7 个部分组成。少了任何一块在实际 Agent 系统里都会出问题。1. 名称nameSkill 的唯一标识符。要求全小写用下划线连接get_weather、calculate_discount动词开头一眼能看出它干什么避免泛化命名如do_something、process_data2. 描述description这是整个 Skill 里最重要的字段没有之一。LLM 靠这段文字决定什么时候调用你的 Skill。写好描述的关键原则说清楚能干什么查询指定城市未来 3 天的天气预报说清楚什么时候用当用户询问天气、温度、是否下雨、穿衣建议时触发说清楚不适用什么不支持历史天气查询不支持全球所有城市代码 1# 好的描述name: get_weatherdescription: | 查询指定城市的天气预报支持未来 1-7 天。 触发场景用户问天气、温度、降雨概率、穿衣建议。 不支持历史天气、空气质量、紫外线指数。# 差的描述LLM 无法准确判断何时调用name: get_weatherdescription: 获取天气信息3. 触发规则trigger rules描述是大致什么时候用触发规则是精确匹配条件。常见写法triggers: -intent:weather_query # 意图匹配 keywords:[天气,温度,下雨,穿什么] required_slots:[city] # 必须有城市参数才触发-intent:travel_planning keywords:[出差,旅游,带伞] optional_slots:[date_range]4. 入参槽位parameter slots定义 Skill 需要从用户输入里提取哪些信息。每个槽位包含parameters: city: type:string description:城市名称如北京、上海 required:true extraction_rules: -entity_type:location -aliases:[帝都,魔都,深圳]days: type:integer description:预报天数1-7 required:false default:3 constraints: min:1 max:75. 执行逻辑execution logic实际的业务代码。这一层和普通函数没什么区别关键是要做好参数校验和异常处理async def execute(city: str, days: int 3) - SkillResult: # 参数校验 ifnot city or len(city) 20: return SkillResult.error(城市名称不合法) if days 1or days 7: return SkillResult.error(预报天数必须在 1-7 之间) try: weather await weather_api.fetch(city, days) return SkillResult.success(format_weather(weather)) except CityNotFoundError: return SkillResult.error(f未找到城市{city}) except APIRateLimitError: return SkillResult.error(天气服务繁忙请稍后重试)6. 返回格式return format统一的返回结构让 Agent 框架能标准化处理所有 Skill 的结果dataclassclass SkillResult: status: str # success | error | partial data: dict # 结构化数据 message: str # 给 LLM 的自然语言摘要 metadata: dict # 耗时、来源、置信度等7. 权限控制permission control哪些场景下允许调用、哪些不允许permissions: rate_limit: 10/minute # 频率限制 auth_required: false # 是否需要用户授权 allowed_channels: [web, app] # 允许的调用渠道 data_retention: none # 是否缓存结果这一节的小结7 个模块各司其职。其中 description 和 trigger rules 决定了LLM 能不能准确找到它parameter slots 决定了找到之后能不能正确调用execution logic 和 error handling 决定了调用之后能不能可靠返回。03 手把手教你写第一个 Skill语法、规范与参数校验理论讲够了直接上手。我们来写一个商品价格查询 Skill这是 Agent 系统里最常见的查询类场景。Step 1定义 Skill 元信息name: query_product_priceversion: 1.0.0description: | 查询小米商城商品的实时价格。 当用户问多少钱、价格、便宜多少、有没有优惠时触发。 支持按商品名称、型号、SKU 查询。 不支持历史价格趋势、竞品比价。author: xitingtags: [ecommerce, price, xiaomi]Step 2定义参数槽位parameters: product_name: type:string description:商品名称或型号如小米14、RedmiNote14Pro required:true extraction_rules: -entity_type:product -fuzzy_match:true # 允许模糊匹配 validation: min_length:2 max_length:50platform: type:string description:查询平台 required:false default:xiaomi_mall enum:[xiaomi_mall,jd,taobao]Step 3编写执行逻辑from typing import Optionalfrom dataclasses import dataclassdataclassclass SkillResult: status: str data: dict message: strasyncdef execute(product_name: str, platform: str xiaomi_mall) - SkillResult: 查询商品价格 - 这是 Skill 的核心执行逻辑 # 1. 参数校验永远不要信任 LLM 提取的参数 ifnot product_name or len(product_name.strip()) 2: return SkillResult( statuserror, data{}, message商品名称太短请提供更具体的商品名称或型号。 ) # 2. 实体消歧同一个商品可能有多种叫法 resolved await resolve_product(product_name) ifnot resolved: return SkillResult( statuserror, data{}, messagef未找到名为「{product_name}」的商品请确认名称是否正确。 ) # 3. 调用外部 API try: price_info await price_api.query( skuresolved.sku, platformplatform ) except APITimeoutError: return SkillResult( statuserror, data{}, message价格服务响应超时请稍后重试。 ) # 4. 格式化返回给 LLM 看的自然语言摘要 给系统看的结构化数据 return SkillResult( statussuccess, data{ product_name: resolved.name, sku: resolved.sku, current_price: price_info.price, original_price: price_info.original_price, discount: price_info.discount, platform: platform, url: price_info.url }, messagef{resolved.name} 在{platform_name(platform)}的当前售价为 ¥{price_info.price} f原价 ¥{price_info.original_price}{price_info.discount}。 )Step 4编写参数校验规则参数校验是 Skill 稳定性的第一道防线。很多人的 Skill 在测试时没问题一上用户环境就崩80% 的原因是参数校验不够。# 校验模式先宽松匹配再严格校验def validate_params(raw_params: dict) - tuple[bool, dict, str]: 三步校验法 1. 类型检查 - 是不是期望的数据类型 2. 范围检查 - 值在不在合法范围内 3. 语义检查 - 值有没有实际意义 errors [] # 类型检查 product_name raw_params.get(product_name) if product_name andnot isinstance(product_name, str): errors.append(product_name 必须是字符串) # 范围检查 platform raw_params.get(platform, xiaomi_mall) valid_platforms [xiaomi_mall, jd, taobao] if platform notin valid_platforms: errors.append(fplatform 必须是 {valid_platforms} 之一) # 语义检查最容易被忽略 if product_name and len(product_name.strip()) 2: errors.append(商品名称至少需要 2 个字符) if errors: returnFalse, {}, .join(errors) returnTrue, {product_name: product_name.strip(), platform: platform}, 这一节的小结写 Skill 的执行逻辑不难难的是把各种异常情况都想到并处理好。参数校验、实体消歧、超时处理、错误消息——这些看起来是细节实际上决定了你的 Skill 在生产环境里能不能稳定工作。04 Skill 调用全链路从用户提问到结果返回中间到底发生了什么很多人写完 Skill 就以为完事了但 Skill 只是整个调用链路中的一个环节。理解全链路才能写出和系统其他部分配合得好的 Skill。图 3Skill 调用全链路流程完整的调用流程分 6 步第 1 步用户输入用户说了一句话“小米 14 Pro 现在多少钱”第 2 步意图识别Intent RecognitionAgent 框架把用户输入发给 LLMLLM 判断这句话的意图。这一步的核心是 prompt engineering你是一个意图分类器。根据用户输入判断应该调用哪个工具。可用工具1. query_product_price - 查询商品价格。当用户问多少钱、价格时使用。2. get_weather - 查询天气。当用户问天气、温度时使用。3. search_product - 搜索商品。当用户想找某个商品但没问价格时使用。用户输入小米 14 Pro 现在多少钱LLM 返回应该调用query_product_price提取参数product_name 小米 14 Pro。第 3 步槽位填充Slot Filling框架从 LLM 的输出中提取结构化参数。这一步常见的坑LLM 提取了product_name 小米14Pro没有空格但实际数据库里叫小米 14 ProLLM 漏提了可选参数platform需要用默认值补上LLM 多提了不存在的参数需要过滤掉第 4 步参数校验上一节讲的三步校验法在这里执行。校验不通过直接返回友好的错误消息不调用外部 API。第 5 步执行 Skill调用query_product_price.execute()实际发起 HTTP 请求查询价格。第 6 步结果返回与格式化Skill 返回结构化的SkillResultAgent 框架把message字段的内容拼进 LLM 的上下文LLM 生成最终的自然语言回复给用户。小米 14 Pro 在小米商城的当前售价为 ¥4499原价 ¥49999 折。需要我帮你看看有没有其他优惠券吗关键洞察整个链路里最容易出问题的不是执行逻辑而是第 2 步和第 3 步——意图识别和槽位填充。而这两步的准确性主要取决于你在 Skill 定义里写的description和triggers。这就是为什么我说一个 Skill 的质量70% 取决于描述。05 深入底层原理Skill 注册机制、调度引擎与上下文透传如果你想从会写 Skill进阶到能设计 Skill 系统这一节是核心。图 4Skill 注册与调度引擎架构5.1 Skill 注册机制Skill 写好了怎么让 Agent 框架知道它的存在常见的注册方式有三种静态注册在配置文件里声明所有 Skill框架启动时一次性加载。# agent_config.yamlskills:-name:query_product_price module:skills.price_query enabled:true-name:get_weather module:skills.weather enabled:true优点简单、可控。缺点新增 Skill 需要重启服务。动态注册Skill 在运行时主动向调度引擎注册自己。# Skill 启动时engine SkillEngine.get_instance()engine.register( namequery_product_price, description查询商品价格..., handlerexecute, triggers[多少钱, 价格, 优惠])优点热插拔不用重启。缺点需要处理注册冲突和去重。声明式注册Skill 通过装饰器或类继承自动注册框架扫描特定目录下的所有 Skill 定义。skill( namequery_product_price, description查询商品价格, triggers[多少钱, 价格])class PriceQuerySkill(BaseSkill): async def execute(self, product_name: str, **kwargs) - SkillResult: ...这是目前主流 Agent 框架LangChain、LlamaIndex、OpenCode 等最常用的方式。5.2 调度引擎怎么选择 Skill当用户发了一句话调度引擎需要从几十甚至几百个 Skill 里找到最合适的一个。这个过程通常是两阶段匹配第一阶段粗筛Fast Filtering用关键词匹配、意图分类器或向量检索从全量 Skill 里快速缩小候选范围。这一步要求快通常在 10ms 内完成。# 向量检索方式user_embedding embed(user_input)candidates skill_index.search(user_embedding, top_k5)第二阶段精排Precise Ranking把候选 Skill 的 description 拼进 LLM 的 system prompt让 LLM 做最终选择。这一步准但慢需要一次 LLM 调用。你是工具选择器。以下是候选工具1. query_product_price查询商品价格。当用户问多少钱、价格时使用。2. search_product搜索商品目录。当用户想找某个商品但没问价格时使用。3. get_discount_info查询优惠活动。当用户问有没有优惠、打折吗时使用。用户说小米 14 Pro 现在多少钱选择最合适的工具只返回编号5.3 上下文透传Skill 执行时不是在一个真空环境里。它需要访问的上下文包括对话历史用户之前说了什么可能影响参数提取用户信息用户 ID、会员等级、地区偏好会话状态之前调用过哪些 Skill、中间结果是什么系统配置超时时间、重试策略、降级方案这些上下文通过Context对象透传给 Skilldataclassclass SkillContext: user_id: str session_id: str conversation_history: list[Message] user_profile: dict system_config: dict intermediate_results: dict # 前面 Skill 的执行结果async def execute(params: dict, context: SkillContext) - SkillResult: # 可以通过 context 获取用户所在城市作为默认参数 default_city context.user_profile.get(city, 北京) ...这一节的小结注册机制决定了Skill 怎么被发现调度引擎决定了怎么选对 Skill上下文透传决定了Skill 能不能拿到它需要的信息。这三层共同构成了 Skill 系统的基础设施。06 高阶进阶多轮对话 Skill、嵌套 Skill、条件分支与异常兜底基础 Skill 是单轮的——用户问一句Skill 执行一次返回结果。但真实场景往往更复杂。图 5高阶 Skill 模式6.1 多轮对话 Skill用户说帮我查一下小米 14 的价格Skill 返回价格后用户接着问那 Pro 版呢“——这时候 Skill 需要理解上下文知道Pro 版指的是小米 14 Pro”。class PriceQuerySkill(BaseSkill): async def execute(self, params: dict, context: SkillContext) - SkillResult: product_name params.get(product_name) # 多轮对话如果用户没给完整商品名从历史里补全 if not product_name or len(product_name) 4: last_product self._extract_last_product(context.conversation_history) if last_product: product_name f{last_product} {product_name}.strip() # 继续执行查询...6.2 嵌套 Skill一个 Skill 的执行过程中需要调用另一个 Skill。比如下单 Skill内部需要调用查库存 Skill和计算价格 Skill。class PlaceOrderSkill(BaseSkill): asyncdef execute(self, params: dict, context: SkillContext) - SkillResult: # 嵌套调用先查库存 stock_result await self.engine.invoke_skill( check_stock, {product_id: params[product_id], quantity: params[quantity]}, context ) if stock_result.status error: return stock_result # 库存不足直接返回错误 # 嵌套调用计算价格 price_result await self.engine.invoke_skill( calculate_price, {product_id: params[product_id], coupon: params.get(coupon)}, context ) # 组合结果执行下单逻辑 ...嵌套 Skill 的关键设计原则嵌套深度不超过 3 层否则调试成本指数级上升每个嵌套调用都要有超时和降级方案子 Skill 的错误要能被父 Skill 捕获和处理6.3 条件分支 Skill同一个 Skill根据参数不同走不同的执行路径。async def execute(self, params: dict, context: SkillContext) - SkillResult: query_type params.get(query_type, current) if query_type current: # 查当前价格 returnawait self._query_current_price(params) elif query_type history: # 查历史价格需要调不同的 API returnawait self._query_price_history(params) elif query_type compare: # 多平台比价 returnawait self._compare_prices(params) else: return SkillResult.error(f不支持的查询类型{query_type})6.4 异常兜底与重试机制生产环境里外部 API 超时、网络抖动、参数异常都是家常便饭。一个好的 Skill 必须有完善的异常处理class ResilientSkill(BaseSkill): MAX_RETRIES 3 RETRY_DELAY 1.0# 秒 asyncdef execute_with_retry(self, params: dict, context: SkillContext) - SkillResult: last_error None for attempt in range(self.MAX_RETRIES): try: result await self.execute(params, context) if result.status ! error: return result last_error result.message except TimeoutError: last_error 请求超时 await asyncio.sleep(self.RETRY_DELAY * (attempt 1)) # 指数退避 except ConnectionError: last_error 网络连接失败 await asyncio.sleep(self.RETRY_DELAY * (attempt 1)) # 所有重试都失败走降级方案 returnawait self._fallback(params, context, last_error) asyncdef _fallback(self, params: dict, context: SkillContext, error: str) - SkillResult: 降级方案返回缓存数据或引导用户稍后重试 cached await self.cache.get(fprice:{params.get(product_name)}) if cached: return SkillResult( statuspartial, datacached, messagef数据可能不是最新的{cached[summary]} ) return SkillResult.error(f服务暂时不可用{error}请稍后重试。)重试策略的核心原则用指数退避1s, 2s, 4s不要用固定间隔设置最大重试次数通常 3 次最后一次失败后走降级方案不要无限重试只重试可恢复的错误超时、网络不重试参数错误07 实战从零完整手写 3 个 Skill实战 1查询类 Skill——飞书文档搜索from dataclasses import dataclass, fielddataclassclass SkillResult: status: str # success / error / partial data: dict field(default_factorydict) message: str classmethod def success(cls, data: dict, message: str ): return cls(statussuccess, datadata, messagemessage) classmethod def error(cls, message: str): return cls(statuserror, data{}, messagemessage)# Skill 定义SKILL_DEFINITION { name: search_feishu_doc, description: 搜索飞书云文档。当用户说找文档、搜一下、之前的文档时触发。 支持按关键词、作者、时间范围搜索。 不支持搜索聊天记录、搜索邮件。 , parameters: { keyword: { type: string, description: 搜索关键词, required: True }, owner: { type: string, description: 文档作者可选, required: False }, time_range: { type: string, description: 时间范围如最近一周、本月, required: False, default: 最近一周 } }}asyncdef execute(keyword: str, owner: str None, time_range: str 最近一周, contextNone) - SkillResult: 飞书文档搜索的执行逻辑 # 参数校验 ifnot keyword or len(keyword.strip()) 2: return SkillResult.error(搜索关键词至少需要 2 个字符。) # 时间范围解析 date_filter parse_time_range(time_range) ifnot date_filter: return SkillResult.error(f无法理解时间范围「{time_range}」请用最近一周、本月等表述。) # 解析作者如果有 owner_id None if owner: owner_id await resolve_user_id(owner) ifnot owner_id: return SkillResult.error(f未找到名为「{owner}」的用户。) # 调用飞书 API try: results await feishu_api.search_docs( keywordkeyword, owner_idowner_id, start_timedate_filter[start], end_timedate_filter[end], page_size10 ) except FeishuAPIError as e: return SkillResult.error(f飞书接口异常{e.message}) ifnot results: return SkillResult.success( data{documents: [], total: 0}, messagef未找到包含「{keyword}」的文档。 ) # 格式化结果 doc_list [ { title: doc.title, url: doc.url, owner: doc.owner_name, updated_at: doc.updated_at, snippet: doc.snippet[:100] } for doc in results ] summary f找到 {len(doc_list)} 篇文档\n for i, doc in enumerate(doc_list[:5], 1): summary f{i}. {doc[title]}{doc[owner]}{doc[updated_at]}\n return SkillResult.success( data{documents: doc_list, total: len(doc_list)}, messagesummary )实战 2计算类 Skill——折扣价格计算SKILL_DEFINITION { name: calculate_discount, description: 计算商品的折扣后价格。当用户说打折、优惠后多少、算一下折扣时触发。 支持百分比折扣、满减、多件折扣。 不支持跨商品组合优惠、会员积分抵扣。 , parameters: { original_price: { type: number, description: 原价, required: True, validation: {min: 0.01, max: 999999} }, discount_type: { type: string, description: 折扣类型, required: True, enum: [percentage, fixed_amount, buy_more] }, discount_value: { type: number, description: 折扣值百分比写 85 表示 85 折满减写减免金额多件写件数, required: True }, quantity: { type: integer, description: 购买数量多件折扣时必填, required: False, default: 1 } }}asyncdef execute(original_price: float, discount_type: str, discount_value: float, quantity: int 1, contextNone) - SkillResult: # 参数校验 if original_price 0: return SkillResult.error(原价必须大于 0。) if discount_type percentageand (discount_value 0or discount_value 100): return SkillResult.error(百分比折扣必须在 0-100 之间。) if discount_type buy_moreand quantity int(discount_value): return SkillResult.error(f多件折扣需要购买至少 {int(discount_value)} 件。) # 计算逻辑 if discount_type percentage: final_price original_price * (discount_value / 100) * quantity saving original_price * quantity - final_price detail f{discount_value / 10} 折 elif discount_type fixed_amount: if original_price discount_value: return SkillResult.error(满减金额不能超过原价。) final_price (original_price - discount_value) * quantity saving discount_value * quantity detail f满 ¥{original_price} 减 ¥{discount_value} elif discount_type buy_more: free_count quantity // int(discount_value) pay_count quantity - free_count final_price original_price * pay_count saving original_price * free_count detail f买 {int(discount_value)} 件减 1 件 else: return SkillResult.error(f不支持的折扣类型{discount_type}) return SkillResult.success( data{ original_price: original_price, quantity: quantity, discount_type: discount_type, discount_detail: detail, final_price: round(final_price, 2), saving: round(saving, 2) }, messagef原价 ¥{original_price} × {quantity} 件 ¥{original_price * quantity} f享受{detail}后实付 ¥{round(final_price, 2)}立省 ¥{round(saving, 2)}。 )实战 3工具调用类 Skill——数据库查询SKILL_DEFINITION { name: query_database, description: 查询业务数据库中的数据。当用户说查一下数据、跑个 SQL、看看有多少时触发。 只支持 SELECT 查询不支持 INSERT/UPDATE/DELETE。 自动限制返回行数为 1000 行。 , parameters: { sql: { type: string, description: SQL 查询语句只支持 SELECT, required: True }, database: { type: string, description: 目标数据库, required: False, default: analytics, enum: [analytics, user_db, order_db] } }}# SQL 安全校验关键FORBIDDEN_KEYWORDS [INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, CREATE, GRANT, REVOKE, EXEC, EXECUTE]asyncdef execute(sql: str, database: str analytics, contextNone) - SkillResult: 安全地执行 SQL 查询 # 1. SQL 注入防护第一优先级 sql_upper sql.upper().strip() for keyword in FORBIDDEN_KEYWORDS: if keyword in sql_upper: return SkillResult.error( f安全限制不允许执行 {keyword} 操作。本 Skill 只支持 SELECT 查询。 ) # 2. 必须以 SELECT 开头 ifnot sql_upper.startswith(SELECT): return SkillResult.error(SQL 必须以 SELECT 开头。) # 3. 自动添加 LIMIT防止查询过大拖垮数据库 ifLIMITnotin sql_upper: sql sql.rstrip(;) LIMIT 1000; # 4. 执行查询 try: conn await get_db_connection(database) result await conn.execute(sql) rows result.fetchall() columns result.keys() except SQLError as e: return SkillResult.error(fSQL 执行失败{e.message}) finally: await conn.close() # 5. 格式化结果 ifnot rows: return SkillResult.success( data{columns: [], rows: [], row_count: 0}, message查询结果为空。 ) data { columns: list(columns), rows: [dict(zip(columns, row)) for row in rows[:100]], # 前 100 行 row_count: len(rows), truncated: len(rows) 100 } summary f查询返回 {len(rows)} 行数据 if data[truncated]: summary 已截取前 100 行 summary 。 return SkillResult.success(datadata, messagesummary)实战小结三个 Skill 代表了三种典型模式查询类重点在参数校验和结果格式化计算类重点在业务逻辑分支和数值精度工具调用类重点在安全防护和资源管理不管哪种模式参数校验、异常处理、友好的错误消息都是必须的。08 常见报错排查、性能优化与面试核心考点8.1 常见报错与排查现象可能原因排查方法Skill 永远不被触发description 写得太模糊用 3 个不同的用户问法测试触发率触发了但参数全错参数描述和实际期望不匹配检查 LLM 提取的原始参数间歇性超时外部 API 不稳定加重试 降级方案返回格式乱了message 里混了 JSON 和自然语言统一返回格式结构化数据放 data多轮对话丢失上下文没有从 conversation_history 补全参数加上下文透传逻辑8.2 性能优化描述优化description 不是越长越好。太长的描述会占用 LLM 的上下文窗口反而降低匹配准确率。经验是 50-150 字。粗筛优化当 Skill 数量超过 50 个时不能让 LLM 逐个判断。用向量检索做粗筛把候选范围缩小到 5-10 个。缓存策略对于查询类 Skill同样的参数短时间内返回缓存结果避免重复调用外部 API。from functools import lru_cachefrom datetime import datetime, timedeltaclass SkillCache: def __init__(self, ttl_seconds: int 300): self._cache {} self._ttl ttl_seconds asyncdef get_or_execute(self, key: str, executor): now datetime.now() if key in self._cache: result, cached_at self._cache[key] if (now - cached_at).seconds self._ttl: return result result await executor() self._cache[key] (result, now) return result8.3 Skill 设计最佳实践单一职责一个 Skill 只做一件事。查询价格和计算折扣应该是两个 Skill。描述精确description 里要说清楚能做什么和不能做什么。错误友好错误消息要告诉用户下一步怎么做不要只说失败了。幂等设计同一个 Skill 同样的参数调用两次结果应该一样查询类天然幂等下单类需要特殊处理。可观测每次调用都要记录日志包括输入参数、执行耗时、返回状态。8.4 面试核心考点如果你在面试 Agent 相关岗位这些 Skill 相关的问题大概率会被问到Q1Skill 和普通函数有什么区别核心答Skill 在普通函数之上加了语义描述层description、triggers、slot definitions让 LLM 能理解什么时候该用这个函数。普通函数是给程序员看的接口Skill 是给 LLM 看的能力说明书。Q2怎么让 LLM 准确选择该调用哪个 Skill核心答两阶段匹配——先用关键词/向量检索粗筛候选再用 LLM 精排。关键是 description 要写得精确既要说能力也要说边界。Q3Skill 之间的依赖和冲突怎么处理核心答依赖用嵌套调用解决冲突用优先级机制解决。优先级可以基于 description 的语义相关度、Skill 的置信度评分、或显式的优先级配置。Q4怎么处理 Skill 执行失败核心答三层防线——参数校验防第一层、重试机制防第二层、降级方案防第三层。关键是不要让一个 Skill 的失败拖垮整个 Agent。Q5Skill 数量多了以后性能怎么优化核心答粗筛用向量检索快精排用 LLM准结果用缓存省。分层处理别让 LLM 看所有 Skill。决策帮助如果你现在是刚接触 Agent 开发先写 2-3 个单轮查询类 Skill把 description 和参数校验写扎实如果你已经在做多轮对话重点投入在上下文透传和槽位补全逻辑上如果你在设计Skill 系统优先把注册机制和调度引擎做对Skill 的代码反而是最简单的部分如果你只能先做一步先把 description 写好——这一个动作能解决 50% 的调用不准问题学AI大模型的正确顺序千万不要搞错了2026年AI风口已来各行各业的AI渗透肉眼可见超多公司要么转型做AI相关产品要么高薪挖AI技术人才机遇直接摆在眼前有往AI方向发展或者本身有后端编程基础的朋友直接冲AI大模型应用开发转岗超合适就算暂时不打算转岗了解大模型、RAG、Prompt、Agent这些热门概念能上手做简单项目也绝对是求职加分王给大家整理了超全最新的AI大模型应用开发学习清单和资料手把手帮你快速入门学习路线:✅大模型基础认知—大模型核心原理、发展历程、主流模型GPT、文心一言等特点解析✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑✅开发基础能力—Python进阶、API接口调用、大模型开发框架LangChain等实操✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经以上6大模块看似清晰好上手实则每个部分都有扎实的核心内容需要吃透我把大模型的学习全流程已经整理好了抓住AI时代风口轻松解锁职业新可能希望大家都能把握机遇实现薪资/职业跃迁这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】