做AI Agent开发这一年多,我越来越确定一件事:模型本身的差距在缩小,真正拉开产品体验差距的,是Agent外围那层技能系统,也就是常说的 agent-skills。它跟传统的插件封装、API对接完全是两码事,核心在于让模型自己判断“什么时候该调用哪个能力、参数怎么填、结果如何使用”,形成一个可闭环的自主执行链路。我最近把整套技能库从零重新搭了一遍,这个过程里踩了不少坑,也沉淀出一套相对稳妥的做法。这篇文章就把技能库的设计思路、实现细节、踩坑经历完整拆开,正在做Agent产品落地、被工具调用不稳定折磨、或者刚接触技能封装的朋友,应该都能在里面找到自己需要的东西。
1. 项目概述与核心需求解析
1.1 agent-skills 到底在解决什么问题
先聊清楚概念。裸的LLM本质上只是一个“会说话的模型”,它能跟你聊天、写代码、做推理,但没法真正去查天气、查数据库、发邮件、操作文件系统。想要让Agent做事,就得给它接上“手和脚”。早期大家普遍的做法是把所有能力塞进一个巨大的System Prompt里,要求模型以特定JSON格式输出命令,然后自己在代码里解析执行。这个方案在demo阶段看着还行,一旦任务复杂起来就崩:模型输出格式稍微一飘,解析逻辑就报错;参数填错也没人把关;能力一多,prompt越改越长,模型根本记不住边界在哪里。
技能系统就是针对这些痛点来的。它的核心是把每一项能力定义成一个结构化的“技能包”,包含名字、描述、入参Schema、执行逻辑、返回规范这几部分,然后通过模型原生支持的Function Calling机制,让模型在对话过程中自主选择技能并填充参数。整个链条是:用户提问 → 模型分析 → 根据技能描述决定调用哪个技能 → 生成结构化参数 → 你的代码执行技能 → 把结果喂回模型 → 模型继续推理或给出最终回答。这是一条有反馈、可纠错的闭环,而不是一次性生成的死命令。
所以这个项目要解决的,本质上是一个工程问题:怎么把模型“会想”和程序“会做”这两件事,用一种稳定、可扩展、可维护的方式接起来。我见过不少人一上来就追求技能数量,几十个技能堆进去,结果模型连该用哪个都分不清。真正该先想清楚的是:技能如何定义、如何注册、如何让模型精准命中、执行结果如何结构化地回流。这套地基打好了,后面加技能只是往仓库里多放一个目录的事情;地基打不好,每加一个技能都是在给系统埋新的雷。
1.2 为什么说技能设计直接决定Agent质量
我审计过不少团队做坏的Agent项目,最后问题几乎都出在同一个地方:技能定义写得含糊。同样一个模型,技能描述写得讲究与写得随意,工具调用的准确率可以相差40个百分点,这个数字在真实项目里一点不夸张。
用生活里的例子类比,技能描述就是给新同事的“岗位说明书”。有新同事入职,你给他一本四十页的规章制度,不如给他十张卡片,每张写清楚“你负责什么、什么情况下需要你出面、输入什么、输出什么、遇到异常怎么处理”。模型面对几十个技能也是如此。技能的描述字段是模型判断“要不要调用它”的唯一依据,写不好,模型就会张冠李戴。比如“date_diff”这个技能,描述里只写“计算两个日期之间的差异”,模型遇到“上周到今天有几天”这种问题就可能会犹豫,但如果你写清楚“适用于计算两个日期字符串之间的天数差,支持自然语言日期表达,如‘上周五’、‘三天前’”,模型就能很确定地用上它。
技能设计还直接影响三个硬指标:任务完成率、Token消耗、失败恢复能力。任务完成率好理解,技能选错、参数填错都会直接失败;Token消耗上,技能描述写得好,模型一次就能选对,不需要反复试错,一次工具调用省下几百个Token,放大到整个系统就是真金白银;失败恢复能力则体现在错误返回的设计上,返回信息写得足够结构化、足够“可读”,模型就能在下一轮自动修正继续执行,而不是直接甩给你一句“抱歉我做不到”。
2. 方案选型与整体设计思路
2.1 三种主流实现方式对比
实现技能系统的路线大致有三条,我分别试过,说下实际体感。
第一种是直接用模型厂商的原生Function Calling。OpenAI、Anthropic、国产几家大模型都提供这个能力,你在API请求里把工具列表传进去,模型就会在需要时返回一个工具调用指令,包含函数名和参数JSON。这套方案的好处是链路短、兼容性最好,模型原生理解这个协议,基本不会在“要不要调用工具”这个层面犯傻。坏处是完全依赖厂商协议,换模型厂商要改代码,而且它对参数的校验是完全放权的,模型填了错误参数你只能自己在执行端拦截。
第二种是借框架的Tool机制,比如LangChain的@tool装饰器、CrewAI的Tool类、AutoGen的函数封装等。框架帮你做了很多脏活,比如格式转换、错误重试、与Agent循环的集成。如果你团队里全是新手、或者想快速出MVP,这个路线能省不少时间。但用久了会发现问题:框架抽象了一层又一层,出问题了排查链路特别长;而且框架会限制你自定义行为,比如我后面要做“技能协商”“技能编排”,在框架里就很别扭。
第三种是自己实现注册表加统一加载器,也就是我这个项目最终选择的路线。本质上是自己写一个极简的“技能管理中间层”:每个技能一个目录,目录里有元信息文件(YAML)和执行逻辑文件(Python),由一个注册表统一管理,再写一个适配层把技能定义翻译成各家模型能识别的Tool格式。这样一来,底层协议可以随时切换,但技能本身的定义方式始终不变。代价是这部分代码必须自己维护,没有现成的轮子可抄。
我最终选第三条路线,核心理由有三个:一是可控性,出问题我能沿着自己写的代码一路查到根因;二是技能定义与模型协议解耦,以后模型厂商怎么变,技能资产不会打水漂;三是方便做动态技能管理,比如按用户权限过滤技能、按成本路由技术栈,这些在框架里很难优雅地实现。
2.2 技能库的目录结构与注册机制
一个好的技能库,从文件结构上就要“一眼看懂”。我最终沉淀下来的目录结构是这样的:
agent-skills/ ├── skills/ │ ├── date_utils/ │ │ ├── skill.yaml │ │ └── run.py │ ├── sql_query/ │ │ ├── skill.yaml │ │ └── run.py │ ├── web_search/ │ │ ├── skill.yaml │ │ └── run.py │ └── email_send/ │ ├── skill.yaml │ └── run.py ├── registry.py ├── loader.py └── agent/ └── loop.py每个技能一个独立目录,skill.yaml存元信息,run.py存执行逻辑。这套结构看起来朴素,但有两个好处:第一,新增技能不需要改任何全局配置,往skills目录下丢一个新文件夹,代码里会自动扫描注册;第二,技能是独立可交付的单元,可以单独测试、单独加版本号,甚至可以用Git Submodule分享给别的项目。
注册机制这块我走了几轮弯路。最初是写死一个字典登记所有技能,加一个技能要改两处代码;后来改成自动扫描目录,启动时遍历skills下的所有子目录,解析YAML、加载Python模块,统一放进一个SkillRegistry对象。扫描注册这种方案对新增技能零负担,换来的复杂度是必须处理加载异常。比如某个技能目录缺文件、YAML格式错了、Python模块导入失败,都不能影响其他技能正常加载。我在这一步的把关是:加载失败的技能单独打日志并在注册表里标为unavailable,Agent侧根本看不到它,等修好了再热加载,线上不至于被一个坏技能拖死。
2.3 四个必须守住的底层设计原则
第一是单一职责。一个技能只做一件事,并且把事情说清楚。“查询订单信息”是一个技能,“根据订单信息发送催单邮件”就应该拆成两个技能。原因很简单:技能是给模型做选择题的选项,选项边界越清晰,模型选得越准;混在一起相当于给选项加了一堆模棱两可的描述,模型一定会选错。第二是描述即接口。代码里的函数签名是给程序员看的,技能描述是给模型看的,模型只能通过描述理解功能,所以描述的质量直接等于接口质量。第三是Schema严格化。入参必须用JSON Schema描述,并且把enum、minimum、pattern这类约束尽量用足,模型填参数时能被约束逼到合法区间内。第四是结果可诊断。所有技能统一返回结构,带上success标志、错误信息和耗时,这样不仅模型能读懂,人排查问题也能一眼看到根因。
这四个原则里,我花最多时间调整的是第二条。你写技能时很容易用人脑的“理解”去脑补模型的“理解”,比如你觉得“查库存”这个词够直白,但模型可能把它和“查订单”“查价格”搞混。正确的做法是站在模型的角度思考:如果我是模型,我手上有这二十个技能,用户问我这个问题,我凭什么选中你?这个视角一转,很多描述问题就暴露了。
3. 技能定义与实现细节拆解
3.1 技能描述怎么写,模型才真正读懂
技能描述是整套系统里最容易被低估的环节。我见过太多人把“description”字段当成注释随便写两句,结果模型调用准确率上不去,第一反应是换大模型,很少怀疑是描述的问题。
一套有效的描述,我建议至少覆盖四件事:功能边界、适用场景、参数语义、常见误用提醒。来看一个实际对比。差的描述长这样:“计算两个日期相差的天数,参数为date1和date2。”好的描述则要写成这样:“计算两个日期之间的天数差。当用户询问‘从某日到某日有多少天’、‘距离某日期还剩几天’时使用。date1为起始日期,date2为结束日期,均支持ISO格式(2025-06-01)或常见中文表达(如‘昨天’、‘上周五’)。注意:本技能只处理日期差计算,不做日期格式化,格式化请使用format_date技能。”后面半段其实已经把“什么时候别用我”也写清楚了,这种“反向边界”能让模型在技能选择上少犯很多错。
另外一个技巧是给描述里的关键词做“同义词覆盖”。模型的语义理解虽然强,但不同的提问习惯会带来匹配差异。比如日期计算这个技能,描述里就有必要拆出十几组说法:“相隔几天”“间隔多少天”“哪个日期早”“倒计时多少天”。把这些自然语言变体都融进描述,等价于提高了这个技能在模型眼里的“曝光度”。实测下来,这个动作能把技能命中率拉高十几个点。代价是描述变长、Token变多,收益和成本之间要自己权衡,我一般控制在150个汉字以内。
3.2 入参Schema设计的四个要点
入参Schema是技能系统的“保险丝”。模型生成参数的过程本质上是“在约束下的填空”,你给的约束越紧,它瞎填的余地越小。我总结了几个实践要点。
参数数量宁少勿多。一个技能超过五个入参,模型出错的概率会指数上升。真要那么多参数,想办法合并或拆技能。其次,能用枚举就用枚举。比如“排序方式”这个参数,定义成enum: ["asc", "desc"]比让模型自由填字符串安全得多。第三,每个参数都写description,告诉模型这个参数的含义和取值范围。比如“limit”这个参数,写“返回的最大条数,默认10,最大100”,模型填超界的概率就小很多。第四,required字段要谨慎设置。非必要参数尽量设成可选,模型漏填了系统还能用默认值兜底;一旦设成必填,模型一旦漏填,整个调用就失败了,还得走重试链路。
这里还有一个反直觉的经验:给参数默认值,不只是给用户方便,更是给模型减负。能由系统自动推断的值,就不要交给模型填写。比如“时区”参数,与其让模型去猜用户在哪,不如默认取服务器时区,模型根本不用看见这个参数。你仔细想一想,每个交给模型的参数,都是它犯错误的一个潜在入口,能不暴露就不暴露。
3.3 返回值与错误约定的统一规范
技能执行完要把结果交还给模型,这个“交还”的动作比很多人想的更讲究。模型不是人,它不会“看到”一段代码日志,它只能读取返回的文本内容并基于此继续推理。所以返回值格式必须统一,并且包含足够明确的语义。我用的统一结构是这样的:
{ "success": true, "data": { "days": 7, "diff": "2025-06-01 到 2025-06-08" }, "error": null, "meta": { "duration_ms": 12, "skill_version": "1.2.0" } }data里的内容尽量用结构化字段而不是一段话,这样模型能直接引用数值。error字段平时是null,出错时给出可读的错误描述。重点说一下错误描述怎么写——它不能是纯技术日志,而要写成“模型能接住并自我修正”的提示语。例如,不要写“ValueError: invalid date format”,而要写“date1参数解析失败,期望格式为YYYY-MM-DD或中文日期,例如‘2025-06-01’或‘昨天’,请检查后重试”。模型看到后就会在下一轮自己修正参数再调一次,这就把一次失败变成了可恢复的交互。
为了尽量少出错,我还在执行端做了一道“兜底校验”。正式执行技能函数之前,先用JSON Schema校验模型传进来的参数,校验不通过就当场返回错误提示,而不是把错误参数丢进函数里跑。这两个环节分开的好处是:职能清晰,Schema校验管“参数对不对”,函数执行管“事情办没办成”,排查问题时能快速定位故障层级。
4. 实操:从零搭建一套技能库
4.1 环境与基础框架准备
下面进入可复现的实操部分。我的主要开发环境是Python 3.10以上,模型侧用OpenAI兼容的Function Calling接口。如果你用的是其他厂商,协议虽有差异,但下面的设计思路完全一样,只要在适配层改一下格式映射即可。
依赖的库很少,核心就一个:
pip install openai pyyaml我建议在一开始就建好下面三个基础文件,它们是整个技能库的地基:
loader.py:扫描skills目录、解析YAML、动态导入执行函数。registry.py:维护技能注册表,提供增删查和“转成模型工具格式”的接口。agent/loop.py:Agent主循环,负责多轮调用、把技能结果回填给模型。
不要把Agent循环和技能库耦合在一起。技能库只负责“有什么技能、如何执行”,Agent循环只负责“如何决定调哪个、如何继续推理”。这两个模块耦合了,后面扩展任何一个都会牵连另一个,拆开的成本很低,收益很大。
4.2 第一个技能:日期计算
我习惯用日期计算作为第一个技能练手,原因很实际:它逻辑足够简单、但边界足够多,时区、闰年、各种日期表达方式都能测出问题,而且它几乎是任何Agent日常都会用到的能力。先建立技能目录:
mkdir -p agent-skills/skills/date_utils然后写skill.yaml:
name: date_utils version: "1.0.0" description: > 处理日期相关的计算和转换。当用户询问日期差、倒计时、 某日期是星期几、或需要将中文日期表达转换为标准日期时使用。 支持'昨天'、'上周五'、'三天前'等自然语言日期。注意: 本技能不处理时间格式化展示,那属于format_date技能。 parameters: type: object properties: op: type: string enum: [date_diff, date_weekday, date_parse] description: "要执行的操作类型" date1: type: string description: "起始日期,支持ISO格式或中文日期表达" date2: type: string description: "结束日期,date_diff操作时必填,其余情况可省略" required: [op, date1]skill.yaml里定义的parameters就是JSON Schema,后面会原样映射到模型工具的parameters字段。它既是给模型看的参数说明,也是我执行前做校验的依据,一份定义两个用途。
再写run.py:
from datetime import datetime, date, timedelta import re CN_MAP = {"昨天": -1, "前天": -2, "今天": 0, "明天": 1, "后天": 2} def _parse_date(raw: str) -> date: raw = raw.strip() if raw in CN_MAP: return date.today() + timedelta(days=CN_MAP[raw]) m = re.match(r"(\d{4})-(\d{2})-(\d{2})", raw) if m: return date(int(m[1]), int(m[2]), int(m[3])) match = re.search(r"(\d+)\s*天前", raw) if match: return date.today() - timedelta(days=int(match[1])) raise ValueError(f"无法解析日期: {raw}") def run(params: dict) -> dict: op = params["op"] d1 = _parse_date(params["date1"]) d2 = _parse_date(params["date2"]) if params.get("date2") else date.today() if op == "date_diff": result = {"days": (d2 - d1).days} return {"success": True, "data": result} if op == "date_weekday": return {"success": True, "data": {"weekday": d1.strftime("%A")}} if op == "date_parse": return {"success": True, "data": {"iso_date": d1.isoformat()}} return {"success": False, "error": f"未知操作: {op}"}这个run函数就是技能的“执行器”,入参是模型填好的params字典,出参严格遵循前面说的统一返回结构。代码本身不复杂,但注意我故意把日期解析函数做成可失败并抛出带提示的异常,这样在校验和错误处理环节,能演示一套完整的失败恢复路径。
4.3 注册表与自动加载机制
写loader.py实现自动扫描加载:
import importlib.util, pathlib, yaml, sys def load_skill(skill_dir: pathlib.Path): yaml_path = skill_dir / "skill.yaml" run_path = skill_dir / "run.py" if not yaml_path.exists() or not run_path.exists(): raise FileNotFoundError(f"技能目录缺文件: {skill_dir}") meta = yaml.safe_load(yaml_path.read_text(encoding="utf-8")) spec = importlib.util.spec_from_file_location( f"skill_{meta['name']}", run_path) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return {"meta": meta, "run": mod.run} def scan_all(skills_root: str = "skills"): skills = {} for skill_dir in pathlib.Path(skills_root).iterdir(): if not skill_dir.is_dir(): continue try: skill = load_skill(skill_dir) skills[skill["meta"]["name"]] = skill except Exception as e: print(f"[loader] 技能加载失败 {skill_dir.name}: {e}") return skillsloader.py里最重要的设计是错误隔离。某个技能加载失败,只影响它自己,其他技能正常注册。这在技能数量超过十个之后非常关键,否则一个语法错误能拖垮整个Agent启动。
然后写registry.py,负责把技能转成模型能理解的工具格式,并提供Schema校验:
import json from jsonschema import validate, ValidationError class SkillRegistry: def __init__(self, skills: dict): self.skills = skills def list_tools(self): tools = [] for name, skill in self.skills.items(): meta = skill["meta"] tools.append({ "type": "function", "function": { "name": name, "description": meta["description"], "parameters": meta["parameters"], } }) return tools def invoke(self, name: str, params: dict): if name not in self.skills: return {"success": False, "error": f"未知技能: {name}"} try: validate(instance=params, schema=self.skills[name]["meta"]["parameters"]) except ValidationError as e: return {"success": False, "error": f"参数校验失败: {e.message},请检查参数后重新调用"} try: return self.skills[name]["run"](params) except Exception as e: return {"success": False, "error": f"执行异常: {str(e)}"}invoke这个方法是整个技能库的“安全关口”。它先校验参数,再执行函数,两层夹击。上面这段代码里用到jsonschema库,属于额外依赖,但这一层校验非常值得。
4.4 跑通Agent调用闭环
技能库本身已经就绪,接下来把它接到Agent主循环里。核心逻辑不复杂:给模型传工具列表,模型若决定调用工具就返回一个工具调用请求,我们执行registry.invoke,再把执行结果作为tool消息回填,继续让模型推理。一个简化可跑的版本如下:
import json from openai import OpenAI client = OpenAI() registry = SkillRegistry(scan_all("skills")) def run_agent(user_msg: str) -> str: messages = [{"role": "user", "content": user_msg}] system = {"role": "system", "content": "你是一个能处理日常事务的助手,需要工具时主动调用。"} messages.insert(0, system) for _ in range(5): resp = client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=registry.list_tools(), tool_choice="auto", ) msg = resp.choices[0].message if msg.tool_calls: messages.append(msg) for tc in msg.tool_calls: args = json.loads(tc.function.arguments or "{}") result = registry.invoke(tc.function.name, args) messages.append({ "role": "tool", "tool_call_id": tc.id, "content": json.dumps(result, ensure_ascii=False), }) else: return msg.content return "达到最大轮数仍未完成"这里有个关键点:模型可以一次性请求多个工具调用,所以tool_calls是一个列表,循环里面必须逐个执行并把结果按tool_call_id回填。tool_call_id是模型与工具消息的关联凭证,漏了它模型会分不清哪个结果对应哪个调用。
跑一个例子验证链路:“今天是2025年5月10日,那上周五到今天间隔了多少天?”模型应当先尝试解析“上周五”,如果底层解析没覆盖到这个表达,会校验失败并返回错误描述,模型再修正表达重试。这就是前面说“错误信息要可恢复”的价值——模型有了一次自我修正的机会,整体任务完成率会明显提升。
4.5 技能质量如何评估与回归
技能库不是写完就完事,它跟代码一样需要回归测试。我的做法是给每个技能配一组“验收用例”,每个用例是一个问题加上预期技能名和预期关键结果,然后用脚本批量跑Agent循环,统计三个指标:技能命中准确率、参数合法率、任务完成率。
cases = [ {"question": "今天是2025年5月10日,上周五到今天间隔多少天", "expect_skill": "date_utils", "expect_data": {"days": 4}}, {"question": "2025-06-01是星期几", "expect_skill": "date_utils", "expect_data": {"weekday": "Sunday"}}, {"question": "今天过期的订单有多少", "expect_skill": "sql_query"}, ]这个评估脚本很值得尽早写,因为技能的每次描述改动都可能引起连锁反应。比如你改了date_utils的描述,可能让它更容易覆盖原本属于sql_query的场景,这种“技能争抢”问题全靠回归用例暴露。我习惯把用例集固化到仓库里,每次改完技能跑一遍,跟CI一样当门槛。
5. 常见问题与排查技巧实录
5.1 高频问题速查表
技能库运行过程中,我遇到的高频问题基本可以归纳成下面几类,直接给表:
| 现象 | 根本原因 | 解决方案 |
|---|---|---|
| 模型就是不调用技能 | 描述与问题语义匹配度不够,工具列表里该技能被其他技能“抢占” | 重写描述,补充同义词和反向边界;精简同域技能数量 |
| 参数乱填、必填漏填 | Schema约束不够,required设置过多 | 缩减参数数量、多用enum、必填只留最核心字段 |
| 同一个技能反复被调用 | 执行结果没达到模型预期,或返回值无法支撑下一步推理 | 检查返回结构,把关键数值字段化;错误信息要给出下一步建议 |
| 技能执行报错频繁 | 参数校验缺失,或执行函数未做输入防御 | 在invoke层加Schema校验,执行函数内对异常兜底 |
| 加载技能时一个报错拖垮全部 | 扫描加载没有做错误隔离 | 参考loader.py,每个技能单独try-catch并标记不可用 |
| 换模型后工具偶尔不识别 | 各家协议细节有差异,函数名或参数字段格式不同 | 在注册表层做协议适配映射,技能定义层保持统一 |
这张表看着简单,但每一条都是真实线上事故换来的。尤其第一条“技能争抢”,等你技能库超过十五个技能,一定会遇到。两个技能描述相似,模型随机翻牌,结果完全不可控,只能靠描述边界和用例回归来治。
5.2 排查三板斧:日志、单技能回放、降级对比
遇到线上问题,我有一套固定的排查顺序,分享出来供参考。
第一步是查全链路日志。我会把每次Agent循环的完整消息序列记录下来,包括模型每次返回的工具调用请求、参数原文、技能执行结果。很多问题一看日志就明白了,比如“模型选了A技能,但用户问的其实是B技能的领域”,这种要按技能争抢处理。
第二步是单技能回放。把出问题的用户问题固定住,只让模型看到一个技能,看它能不能正确调用。如果单技能下模型表现正常、多技能下就出错,那一定是技能之间相互干扰;如果单技能下模型也乱来,那就是这个技能定义本身有问题。这一步能快速缩小问题范围。
第三步是降级对比。换一个小参数或者不同系列的模型跑同一批用例,对比技能命中率。有时不是你的技能写得差,而是当前模型的工具调用能力确实弱。老板问起来,有这组对比数据,你也能有理有据地说服他换模型,而不是自己瞎调技能。
5.3 几条独家心得
最后说几条其它文章里不太会讲、但我实际用下来特别有帮助的经验。
技能版本要标在返回结构里。我在meta字段里加skill_version,刚开始只是顺手,后来发现价值很大:线上模型缓存了旧格式的技能返回,数据比对对不上时,一看版本号就知道是不是缓存问题。
给技能打“成本与风险”标签。有些技能调用一次很贵(比如联网搜索、大模型二次生成),有些技能有写操作不可轻易执行。我会给这类技能在描述里加一句“仅当用户明确要求时使用”,并且可以在调用层加权限控制。模型是没法理解成本的,但你可以在描述里把使用门槛写清楚,把决策权交给模型的同时加上一层护栏。
定期做技能“体检”。技能描述不是写完就一劳永逸的。模型厂商更新模型版本后,同一批技能表现会发生漂移;用户使用习惯变了,也会让技能命中率下降。我现在保持一个习惯:每周跑一遍验收用例集,指标低于阈值的技能单独拉出来重写描述。这套“体检”机制看着笨,但它是整个技能库长期稳定最有效的一道保险。
在这个项目里我还有一个体会很深的点:技能库的真正价值不在于“封装了多少个工具”,而在于它把Agent的决策过程变得可观察、可控制、可改进。每次模型选错技能、填错参数,背后都对应一个可以修正的设计漏洞。把每一个漏洞补上,你的Agent就会肉眼可见地变聪明。这也是为什么我一直建议团队在早期就把技能库当独立产品来做,而不是作为Agent项目的一个边角料。后续如果再扩展,我会在技能协商和动态技能编排两个方向上继续深入,把多个技能串成复合流程,同时把技能使用的反馈数据回流到描述优化里,让整个系统自己越用越准。但那已经是下一个项目的故事了。