Agent Skills技能体系设计:从工具调用到可复用技能系统

发布时间:2026/9/16 7:41:19
Agent Skills技能体系设计:从工具调用到可复用技能系统 先聊聊背景。我在做Agent相关项目时最头疼的不是选哪个大模型也不是Prompt怎么写而是Agent手里的“工具”总是乱成一锅粥。今天调一个函数明天塞一段API后天又发现同样的逻辑在另一个Agent里重复实现了一遍。直到我认真梳理了一遍“agent-skills”的思路把Agent的能力从“零散的工具调用”升级成了“可管理、可复用、可组合的技能系统”整个项目的稳定性和开发效率才算真正上来了。这篇文章我会完整拆解“agent-skills”这个主题它到底是什么和普通工具调用有什么区别核心的设计要点有哪些以及我实际落地时踩过的坑和最终的实现方案。内容偏向工程实践适合正在做Agent应用、或准备把Agent能力模块化的开发者参考。1. 方案设计为什么Agent需要“技能”而不是“工具”1.1 从工具调用到技能体系的演进逻辑当前大多数Agent框架都支持“函数调用”Function Calling说白了就是给模型声明一批函数模型根据用户需求选择合适的函数并生成参数。这个模式本身没问题但一旦你的Agent开始承担复杂任务几个痛点就会立刻暴露函数数量失控单个Agent超过20个函数后模型的选择准确率明显下降。我在实测中发现函数声明过多时模型经常把相似功能的函数搞混。逻辑重复严重两个Agent都要做“从网页抽取正文”我可能在Agent A里写了一个extract_web_content又在Agent B里复制了一份几乎一样的实现。后续如果改了抽取策略得去每个地方同步修改。长上下文被无效消耗每个函数都要把完整描述塞进系统提示词函数越多token占用越大留给真实任务上下文的空间就越小。跨步骤状态难以维护一个任务往往需要多轮工具调用我需要手动把上一步的结果传给下一步中间还得处理异常、重试、超时。“agent-skills”就是为解决这些问题出现的抽象层。它不是在“工具”概念上简单地换了个名字而是从根本上改变了组织方式把一组高度内聚、可独立完成一个子任务的能力封装成一个“技能单元”。每个技能单元内部可以包含一个主函数、若干个辅助函数、预定义的输入输出Schema、触发条件和错误处理策略。对外暴露的接口保持稳定对内实现可以自由迭代。如果用生活化的类比来理解普通工具调用就像你给一个实习生一张清单上面列着一堆零散的操作指令他每一步都在等你说“下一步做什么”而技能体系就像你给同一个实习生几套标准作业流程每套流程都包含从开始到结束的完整步骤、判断条件和异常处理你说一句“去把客户资料整理成报告”他自动从流程库里调出对应的作业手册来执行。本质是把“怎么做的细节”沉淀了下来让上层只需要关心“做什么”。1.2 技能体系的整体架构与核心模块围绕这个思路我在项目里把技能体系拆成了四个核心模块它们各司其职组合在一起才形成了一套完整的“agent-skills”基础设施技能注册中心Skill Registry负责统一登记所有的技能维护技能的唯一标识、版本号、依赖关系、元信息如描述、标签、作者。注册中心是技能体系的大脑Agent在启动时会从这里拉取可用的技能清单。技能执行引擎Skill Executor负责实际运行技能代码。它接收来自LLM的技能调用请求解析参数后执行对应的函数然后把执行结果格式化成统一的结构返回给模型。遇到异常时执行引擎会根据技能定义的retry策略进行重试或降级。技能描述层Skill Descriptor负责把技能的元信息翻译成LLM能理解的语义描述。这一步很关键同一个技能在面向不同任务、不同模型时描述的重点可能不同。比如面向OpenAI系模型时描述可以用JSON Schema风格面向开源模型时描述可能需要更口语化、附带更多示例。技能编排层Skill Orchestrator负责处理复杂任务的技能组合。单个技能只能完成原子操作但用户的需求往往是复合的。编排层根据任务流程图动态决定技能的调用顺序、分支选择和结果汇总方式。这四个模块不是一定要分开部署但逻辑上一定要清晰。我在早期把它们混在一起写后来发现每次加新技能都要动执行引擎的代码改动风险很大。把它们从职责上拆开之后加新技能就变成了纯粹的“注册”动作不需要碰核心代码。1.3 技能设计与“单一职责”和“高内聚低耦合”在设计具体的技能时我主要参考了软件工程里的两个经典原则单一职责原则和高内聚低耦合。这个决定不是拍脑袋做的而是建立在大量实测和经验之上的。单一职责原则一个技能只负责一个明确的任务。比如“fetch_webpage_content”只负责抓取网页并返回正文文本不要去想着同时完成“从正文中提取关键词”“判断文章情感倾向”这些事情。如果一个技能内部塞进了太多功能LLM在选择技能时会变得非常困惑它不知道这个技能到底能做什么、不能做什么容易造成误调用。而且功能一杂技能的错误处理逻辑就会变得无比复杂一旦某个子功能报错整个技能的执行结果都不可信。高内聚低耦合技能内部的数据处理、函数实现要高度内聚但技能之间、技能与Agent主流程之间的依赖要尽可能少。技能的输入输出尽量用基础类型字符串、数字、JSON对象避免直接传递复杂的自定义对象因为不同的技能可能运行在不同的运行时环境里面比如一个跑在本地Python环境一个通过HTTP调用远程服务复杂的对象传递会造成极强的环境依赖。我曾经设计过一个技能直接依赖另一个技能返回的类实例结果改成微服务部署时直接崩了后来改成传递JSON序列化的数据对象问题才解决。这两个原则直接决定了后续技能库的可扩展性。一个Agent往往有几十个技能需要管理如果技能之间互相纠缠、依赖关系混乱那规模一大就必然维护不下去。2. 核心实现技能注册、描述与执行引擎的细节拆解2.1 技能注册中心的数据结构与登记流程注册中心是整个技能体系的基础设施它的核心数据结构我设计成了类似下面这样用Python的dataclass来定义方便理解和扩展from dataclasses import dataclass, field from typing import Dict, Any, Callable, Optional, List import time dataclass class SkillMeta: name: str version: str description: str tags: List[str] field(default_factorylist) author: str params_schema: Dict[str, Any] field(default_factorydict) retry_policy: Dict[str, Any] field(default_factorydict) timeout_seconds: int 30 created_at: float field(default_factorytime.time) updated_at: float field(default_factorytime.time) dataclass class Skill: meta: SkillMeta handler: Callable[..., Any]关键字段的用途我逐一解释一下name技能的唯一标识Agent在调用时使用这个名字。命名规范我采用“动词_对象”的格式例如fetch_webpage_content、search_knowledge_base、send_email_notification。这种命令式的命名方式能让模型更直观地理解技能的动作含义。description技能的语义描述这一段会直接被塞进LLM的系统提示词里。这里的表述质量对调用效果影响极大不能只写“获取网页内容”要写清楚输入是什么、输出是什么、适用场景和边界条件。params_schema参数的JSON Schema描述。这里定义了技能接受哪些参数、每个参数的类型和必填性。LLM会参考这个Schema来生成入参因此必须要严谨。handler技能的实际执行函数接收一个字典形式的参数返回一个JSON可序列化的结果。注册流程本身很简单启动时扫描指定目录下的所有技能定义文件验证名称唯一性和版本号然后把技能实例放进一个全局的字典中。但有几个细节值得注意同名技能的处理策略我采用“版本优先双写保留”的策略。如果检测到同名的两个技能会优先注册版本号更高的那一个同时把旧版本保存在历史表中方便出问题时快速回滚。依赖校验有些技能依赖外部服务或SDK注册时要做一个导入自检。自检失败不代表技能不能注册但会被标记为“degraded”状态LLM在选择技能时执行引擎会自动过滤掉状态为“degraded”的技能避免误调用导致运行失败。注册信息的持久化除了在内存中保存我还会把技能元信息同步到SQLite或Redis中。好处是方便在Web后台查看技能列表、统计调用次数和失败率也方便做AB实验对比不同版本的技能效果。2.2 技能描述如何写给“模型”看而不只是给“人”看写技能描述是我觉得整个过程中最容易被低估的一个环节。很多初做Agent开发的同行会直接把函数docstring原封不动地搬过来然后惊讶地发现模型老是选错技能。其实技能描述的本质是写给大模型看的“使用说明书”需要考虑模型的选择逻辑。我给技能描述定了几条实操原则原则一说清楚输入尤其是边界情况。例如fetch_webpage_content的描述我一开始只写了“获取网页内容”。后来发现模型在遇到需要提取某篇文章正文摘要的任务时竟然也选了它而不是更精确地调用了另一个“extract_article_summary”技能。原因很简单模型并不清楚“获取网页内容”和“提取文章摘要”之间的边界。我后来把描述改为“获取指定URL网页的原始HTML正文内容仅返回文本内容不做任何摘要和归纳。输入为URL输出为字符串。若页面需要JS渲染请优先使用browser_render技能。”这样一改模型的选择准确率有肉眼可见的提升。原则二用“自动触发条件”给模型做引导。我会在描述里写一段“when_to_use”告诉模型什么场景下应该调我这个技能什么场景下不该调。比如搜索技能我会写“当用户询问的事实性信息可能超过模型知识截止日期、或是需要实时数据如新闻、股价、天气时使用本技能。当用户只是要求对已有文本进行改写或翻译时不要使用本技能。”这其实是把提示工程Prompt Engineering的思维方式融入了技能描述里。原则三控制描述长度突出高信息密度。一段描述建议控制在80到150个字之间太短说不清楚太长会浪费token。我把那些比较长的最佳实践和示例放在外部文档里通过“usage_example”字段引用。比如描述里只写“参数input_text为待翻译的文本source_lang和target_lang均为ISO 639-1代码”详细的示例代码放在技能的文档字符串里需要的时候再通过额外接口或者工具的元数据返回给模型。2.3 执行引擎的参数校验、上下文传递与超时重试执行引擎是技能真正跑起来的地方。它的核心职责可以概括为接收LLM生成的技能调用请求做参数校验执行对应的技能函数把结果以标准结构返回。我按照这个流程实现了引擎每个环节都有一些值得注意的细节。参数校验在把参数传给技能函数之前我会先根据params_schema做一次严格的校验。这一步非常重要因为大模型生成的参数虽然大多数时候是正确的但偶尔也会出现参数名拼写错误、类型不对、缺了必填字段的情况。如果不做校验错误信息会从技能函数内部抛出来往往非常晦涩而如果校验放在入口处我就能把错误信息格式化成模型能理解的文本。比如def validate_params(params: Dict[str, Any], schema: Dict[str, Any]) - List[str]: errors [] for req_field in schema.get(required, []): if req_field not in params or params[req_field] is None: errors.append(fMissing required parameter: {req_field}) for field, rules in schema.get(properties, {}).items(): if field in params: if type in rules: value params[field] if rules[type] string and not isinstance(value, str): errors.append(fParameter {field} should be a string, got {type(value).__name__}) if rules[type] integer and not isinstance(value, int): errors.append(fParameter {field} should be an integer, got {type(value).__name__}) return errors执行引擎在拿到校验错误后会把这些信息拼接到给LLM的function_call结果里让模型自行修正参数后再次调用。实测下来这种“让模型自己纠错”的策略比直接抛异常终止任务要有效得多任务成功率能提升不少。上下文传递技能运行往往需要访问某些共享上下文比如当前会话ID、用户ID、数据库连接池、日志记录器。我不会通过全局变量传递这些内容而是使用一个显式的context对象在执行时注入。这样做的好处是方便单测、也方便在不同任务间隔离数据避免线程安全问题。class SkillContext: def __init__(self, session_id: str, user_id: str, config: Dict[str, Any]): self.session_id session_id self.user_id user_id self.config config self.logger logging.getLogger(fskill.{session_id}) async def execute_skill(skill: Skill, params: Dict[str, Any], ctx: SkillContext) - Dict[str, Any]: errors validate_params(params, skill.meta.params_schema) if errors: return {status: invalid_params, errors: errors} try: result await asyncio.wait_for( skill.handler(contextctx, **params), timeoutskill.meta.timeout_seconds ) return {status: success, result: result} except asyncio.TimeoutError: return {status: timeout, error: fSkill {skill.meta.name} exceeded {skill.meta.timeout_seconds}s} except Exception as e: return {status: error, error: str(e)}超时和重试策略技能执行会有超时限制我在SkillMeta里给每个技能配置了自己的超时时间默认值是30秒。重试策略我放在retry_policy里可以灵活配置重试次数、重试间隔和最大重试时间。注意不是所有技能都适合无脑重试——对于纯粹的“读操作”比如查数据库、查API重试是安全的但对于“写操作”比如发邮件、扣款、建订单重试可能会导致重复执行。所以我在重试策略里加了一个idempotent字段只有标记为幂等的技能才会自动重试非幂等技能遇到错误直接把错误返回给上层由人工或编排层决定如何处理。2.4 技能编排层多个技能如何串成一条工作流单个技能解决单一问题但真实的用户请求通常是复杂的需要多个技能协同工作。举个例子“帮我查一下最近一周的销售数据并且和上月对比输出一份总结报告。”这个任务至少涉及三个技能查询销售数据、查询上月数据、生成文本报告。编排层的价值就在于用一套清晰的机制把多个技能串联起来。目前我用的是一种轻量级的编排方案定义工作流时用YAML或Python代码描述节点和边的逻辑。每个节点对应一个技能节点之间的数据流通过“参数映射”来传递。以下是一个简化的YAML示例workflow_name: sales_summary_report nodes: - id: fetch_current_week skill: query_sales_data params: start_date: {workflow.inputs.current_week_start} end_date: {workflow.inputs.current_week_end} - id: fetch_last_month skill: query_sales_data params: start_date: {workflow.inputs.last_month_start} end_date: {workflow.inputs.last_month_end} - id: generate_report skill: generate_text_report params: current_week_data: {nodes.fetch_current_week.output} last_month_data: {nodes.fetch_last_month.output} depends_on: - fetch_current_week - fetch_last_month这里的核心思路是每个节点的输出都被保存到工作流上下文中后续节点可以通过{nodes.xxx.output}这种模板语法来引用前面的结果。这样写的好处是可视化可调试每个节点的输入输出都记录在案出问题时可以回放整个流程看看是哪一步出了问题。并行执行容易实现像fetch_current_week和fetch_last_month这两个节点互不依赖编排引擎可以并行执行它们明显缩短总耗时。扩展性好要增加新的数据来源只需在工作流里插入一个新的节点不需要改动其他部分。不过也得提醒工作流越复杂编排层代码就会越难维护。所以我会刻意控制单个工作流的节点数量一般不超过8个节点。如果超过8个我会考虑把子流程抽出来做成“内嵌技能”——每个内嵌技能本身也是一个工作流但对外暴露成一个普通的技能。这种递归式的设计让整个系统既灵活又能保持清晰。3. 实操记录从零搭建一个可用的技能库3.1 环境准备与基础框架选择这一段我写一下实操过程。我用的基础环境是Python 3.11 FastAPI Redis逻辑上这套方案与具体框架无关但为了讲解方便我用FastAPI做服务端示例。技能库的核心代码我维护在一个子目录skills/下面结构大致是skills/ ├── __init__.py ├── registry.py # 技能注册中心 ├── executor.py # 技能执行引擎 ├── descriptor.py # 技能描述生成器 ├── orchestrator.py # 技能编排层 ├── defs/ # 具体的技能定义 │ ├── web_search.py │ ├── webpage_extract.py │ └── report_generate.py └── config.py # 配置文件包括Redis连接、模型参数等为了让技能的定义足够简单我给技能开发封装了一个装饰器register_skill。开发者只需要把自己写的函数挂上这个装饰器填好元信息技能就能自动被注册中心发现并加载。# defs/web_search.py from skills.registry import register_skill register_skill( nameweb_search, version1.0.0, description使用内置搜索引擎查询互联网信息返回前k条结果的标题、链接和摘要。 适用场景当用户的请求涉及实时信息、最新新闻、特定事件或超出模型知识范围的事实性问题时。, params_schema{ type: object, properties: { query: {type: string, description: 搜索关键词}, k: {type: integer, description: 返回结果条数, default: 5} }, required: [query] }, retry_policy{max_retries: 2, backoff_seconds: 1, idempotent: True} ) async def web_search(context, query: str, k: int 5) - dict: # 实际实现交给统一的搜索客户端这里不再展开 results context.search_client.search(query, top_kk) return [{title: r.title, link: r.url, snippet: r.snippet} for r in results]这样一个技能就定义完了。后续如果我要加一个新的搜索源不需要改动注册逻辑只需要替换内部的search_client实现让调用方无感知。3.2 技能描述的自动生成与动态注入实用中我发现如果技能描述完全靠手写很难保持统一格式。所以在实现中我写了一个descriptor.py把描述拆成结构化字段再自动拼接成LLM友好的一段文本。这样既能保证质量也能让描述信息在每次请求时按需生成。# descriptor.py def build_skill_description(meta: SkillMeta) - str: required_params , .join(meta.params_schema.get(required, [])) lines [ f技能: {meta.name}, f版本: {meta.version}, f描述: {meta.description}, f必填参数: {required_params}, 参数Schema:, json.dumps(meta.params_schema, ensure_asciiFalse, indent2) ] return \n.join(lines)在请求LLM时我不会把全部技能的完整描述都一次性塞进去而是先做一个粗粒度的“技能预筛选”。具体做法把技能的“名称一句话简介标签”拼成一个精简列表让LLM先用最小化token成本选出3到5个候选技能然后只把这几个候选的完整描述和参数Schema注入系统提示词。这个策略能显著减少token消耗实际效果也不错——模型的选择准确率没有因为步骤变多而下降原因是预筛选阶段的任务比较简单类似信息检索主模型仍然有完整信息来做最终决策。3.3 一个完整的技能调用流程演示下面我用一个具体的例子来串起整个流程。假设用户问“帮我搜索一下Agent Skills相关的最新博客文章简单总结一下每篇的核心观点。”第一步Agent收到问题后会把所有可用技能的“精简描述列表”发给模型做预筛选。模型可能会选中三个技能web_search搜索博客、webpage_extract如果搜索结果只有链接还需要提取正文、text_summarize总结观点。第二步把这三个技能的完整描述和参数Schema注入模型提示词。模型根据用户意图生成调用请求执行引擎发现第一个需要调用的技能是web_search参数为{query: Agent Skills blog post 2025, k: 5}。第三步执行引擎校验参数无误注入上下文设置超时和重试参数开始执行。我把执行结果包装成标准格式返回给模型{ status: success, result: [ {title: ..., link: ..., snippet: ...}, ... ] }第四步模型继续决策发现某些结果需要看完整正文才能总结于是调用webpage_extract传入对应的URL。执行引擎执行完后模型可能调用text_summarize对正文做摘要最后整理成一段自然语言回答。这个过程中的关键点在于模型每调用一个技能都需要等待结果返回后再继续推理相当于多轮对话。因此在实现大模型推理循环时要判断返回内容中是content还是tool_calls。我推荐把这套推理循环放到一个AgentSession类里让技能调用逻辑和会话管理解耦方便测试和复用。3.4 技能库的版本管理与灰度发布技能和普通业务代码一样也需要版本管理。刚开始我做技能开发时直接改函数后重新启动服务导致线上Agent的行为突然变化用户反馈“之前能用的功能怎么突然不行了”。这种情况经历过两三次后我引入了简单的版本管理机制每个技能都有version字段采用语义化版本号MAJOR.MINOR.PATCH。注册中心会同时保留当前版本和上一个稳定版本。线上Agent默认使用当前版本但可以在配置中心按“Agent ID范围”或“用户百分比”做灰度切换。这套机制实操起来并不复杂就是在SkillMeta里加一个is_current字段执行引擎根据灰度规则决定路由到哪个版本。如果你项目规模不大不需要引入完整的AB实验平台一个基于随机数的简单灰度开关就够了def route_skill_version(skill_name: str, user_id: str) - str: # 这里简化了逻辑仅用user_id哈希值取模做演示 versions skill_registry.get_versions(skill_name) if len(versions) 1: return versions[0].version threshold get_gray_percent(skill_name) # 50表示50%流量走新版本 if hash(f{user_id}:{skill_name}) % 100 threshold: return versions[0].version # 新版本 return versions[-1].version # 旧版本这种灰度方式特别适合“同一技能逻辑微调”的场景比如优化了排序算法、调整了超时阈值。如果是技能的大版本重写比如完全换了第三方服务商我建议直接切换上线不用灰度因为新旧版本的输入输出可能不兼容混合使用会让模型困惑。4. 常见问题与排查技巧实录4.1 模型总是选错技能该怎么定位和优化这是Agent开发里最常被问到的问题。模型明明看到web_search的描述写得清楚但遇到“查天气”还是调用了它。我总结下来原因通常有几类描述的语义区分度不够两个技能都在处理“获取内容”但一个面向网页一个面向知识库。它们的描述如果都写“获取相关内容”模型当然分不清。解决方案是对比所有技能的描述确保每个描述里都包含“最核心的差异点”。我习惯把所有技能的description打印出来自己站在模型的视角看一遍一眼扫过去如果分不清哪个是哪个就该改描述。技能粒度过大或过小技能太大一个技能干了几件事模型容易误判边界技能太小同名干扰太多选择成本高。一般发现模型频繁在A和B两个技能之间犹豫时就该考虑是不是该合并成一个技能或拆成更细粒度的技能。缺少负向引导模型是需要排除法的。如果描述里只写了“这个技能擅长什么”没写“不应该用什么场景下用”模型就不知道边界在哪里。我建议在描述末尾统一加一句“不要用于XXX场景”的负向示例。还有一个值得提醒的实干技巧如果模型选错技能我会在调试模式下记录每一次技能调用实际选择的理由模型会在tool_choice参数里给出简短的reasoning然后分析这些reasoning往往能很清晰地看出描述里的歧义点在哪里。4.2 技能执行超时、接口不稳定时的降级策略实际生产环境中第三方API的稳定性是不可控的。我在技能执行引擎里建立了一套分级降级机制Level 1单技能重试。对于幂等技能超时后自动重试1到2次间隔1秒。如果连续失败进入Level 2。Level 2备选技能降级。在技能注册中心里我给每个技能维护一个alternatives列表。主技能挂了自动调用备选技能比如web_search挂了可以降级到knowledge_base_search。注意备选技能必须有明确的替代关系否则降级后的结果会让Agent产生错误结论。Level 3返回可解释错误。如果所有备选都失败我不会让执行引擎抛异常而是把错误包装成结构化信息返回给LLM让它在最终回答中告诉用户“当前搜索服务暂时不可用”或建议用户稍后再试。这样用户体验会好很多不会看到一堆栈异常。这套降级机制最大价值在于不把底层API的不稳定传导到用户的最终体验层还让Agent有机会用其他手段绕开问题。4.3 长任务执行中的上下文丢失与记忆管理问题Agent在处理复杂任务时需要调用好多次技能每轮结果都会累积。但LLM上下文窗口有限特别是使用4K或8K的小模型时历史工具调用记录很容易就把窗口挤爆。我自己的处理方式是把上下文管理拆成两层短期记忆保留最近几轮对话和最新的技能调用结果这部分直接放在上下文里供模型推理用。长期记忆把过去的历史摘要通过一个summary_skill定期压缩。比如每完成5轮交互就用文本摘要技能把前面5轮的对话压缩成几个要点存入上下文更早的对话则持久化到Redis里。这个方案有点类似于LangChain里经典的ConversationSummaryBufferMemory思路唯一差别是我把摘要能力做成了一个普通技能这样Agent自身就能控制何时做压缩而不是套死在框架逻辑里。4.4 常见问题速查表下面这个表格是我在实际项目中整理的速查表遇到问题可以直接对照问题现象大概率原因排查思路处理建议模型经常调用错误技能描述语义重叠、负向引导缺失打印所有技能描述站在模型视角比对重写描述强调差异和“不适用”场景相同任务结果时好时坏LLM参数生成了不稳定的参数值检查技能参数Schema是否明确了枚举值或格式约束在Schema中增加enum、pattern、min/max等约束技能执行很快但结果不对前置校验太宽松坏数据进入函数在函数入口打好日志检查入参是否可信补充字段级校验对非预期值统一拦截多个Agent实例并发调用共享服务技能函数内部有状态、错误共享变量检查技能实现是否用了全局变量保存中间数据改成每次调用创建独立上下文技能升级后线上行为突变没有版本管理和灰度机制看注册中心当前版本的切换记录引入灰度发布保留回滚能力长任务在10多轮后开始胡说八道上下文被无关历史或工具结果占满监控token消耗检查上下文被哪些内容占据启用摘要压缩把早期细节移出主上下文表格里这几类问题系统刚上线时最容易中招的是第一类和第五类。尤其是第五类有了版本管理机制之后很多生产事故都能快速止血强烈建议优先把版本管理做进去。4.5 几个值得注意的细节技巧实际开发过程中我还积累了一些零散但特别有用的细节技巧这里一并分享技能的参数Schema一定要写description字段。很多框架允许你在JSON Schema的properties里附加描述这个描述对模型生成参数非常有帮助。例如定义了k参数为integer如果不写描述模型可能把5写成字符串“5”导致校验失败写了描述“返回结果条数为1到10的整数”后这类错误大幅减少。重试策略中合理使用指数退避。如果第三方API已经报出限流错误如HTTP 429立即重试只会加重限流。我用简单的指数退避从1秒开始每次翻倍最大到8秒。这个策略虽然简单但在稳定性和时效性之间找到了比较好的平衡。技能执行结果要做“结构化返回”。尽量不要让技能直接返回“成功”这样的文本而是返回结构化JSON包含状态码、数据、耗时、来源等。这能让编排层和模型更好地利用结果也能方便后续做调试和数据统计。给技能加上“来源标注”。比如web_search返回的结果里我会附带source字段标明是来自Bing还是内部库。这样LLM在生成回答时如果用户想追溯信息来源Agent才能准确给出引用来源这在知识密集场景下很重要。5. 一些沉淀下来的经验补充前面几部分基本把技术细节讲完了最后我聊聊项目沉淀下来的一些工程经验这些不是具体代码但比代码更影响整体效果。第一技能体系一定要“小步快跑”地搭不要一上来就设计几十个技能。早期阶段我会先接一个核心技能比如搜索跑通注册、执行、返回、模型调用全链路再加第二个技能。好处在于如果技能描述格式有问题、参数校验逻辑有缺陷在技能数量少时能轻松定位到原因等到技能库扩展到二三十个以后才去优化预筛选、灰度等机制。先重量再重质。第二要多看“模型实际执行记录”而不是只看自己的意图。刚开始我在写技能描述时总觉得描述得已经很清楚了可线上数据一拉出来发现模型的理解和我完全不一样。后来我养成了一个习惯每个技能上线前先用3到5个典型用户问题做离线验证把模型多次调用的记录整理出来比对。数据永远比感觉可靠。第三技能不是越多越好。受限于模型上下文长度也受限于技能选择准确率技能库需要控制规模。我现在的经验是单个Agent对外暴露的技能数量控制在15到25个之间、单个工作流的技能节点控制在8个以内整体效果最稳定。超出这个范围正确率会明显下降。如果你确实需要支持大量技能建议按领域拆成多个子Agent或者多个“专家模型”而不是强行塞进一个上下文里。最后技能体系的价值曲线是“先平后陡”的。前期的确要花不少时间搭框架、写描述、调参数短期内感觉不如直接把函数塞进系统提示词来得快但一旦技能库积累到一定规模复用带来的效率提升是巨大的——新Agent开发可能只需要从技能库里选几个出来编排根本不需要从零写代码。我现在的项目里Agent新功能的上线时间从几周缩小到了几天靠的就是这套沉淀下来的技能库。如果你正在做Agent开发我建议你也尽早把技能从“临时工具”升级到“可复用资产”去看待。它不要求你引入整套复杂的框架核心只在于想清楚技能如何被描述、如何被注册、如何被执行、如何被组合。想清楚这四件事就算自己手写一个几十行的注册中心也能在实战中发挥出远超过“函数堆砌”的效果。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询