1. 项目概述:这不是一个“技能库”,而是一套可插拔的智能体行为引擎
“agent-skills”这个名称乍看像某个开源工具包的代号,但实际在当前技术演进脉络里,它代表一种更底层、更务实的设计范式——把大模型驱动的智能体(Agent)所依赖的“能力”从推理链中解耦出来,封装成独立、可验证、可组合、可替换的运行时单元。我接触过不少团队,最初都以为这是个“预置函数集合”,结果在真实业务场景中跑了几周才发现:真正卡住进度的,从来不是模型本身,而是“让模型知道什么时候该调用什么能力、怎么调用、调用失败后如何降级”。这正是“agent-skills”要解决的核心问题。
它不等于API调用列表,也不等于Prompt模板合集。它的本质是定义了一套标准化的能力契约(Skill Contract):每个skill必须明确声明输入结构(Input Schema)、输出结构(Output Schema)、执行超时(Timeout)、重试策略(Retry Policy)、失败兜底逻辑(Fallback Handler),以及最关键的——能力边界说明(Boundary Statement)。比如一个“查天气”skill,不能只写“返回天气信息”,而必须注明:“仅支持中国地级市行政中心,不支持景区/街道级定位;温度单位默认摄氏,湿度为相对湿度百分比;若城市名存在歧义(如‘长安’),优先匹配陕西省西安市长安区”。这种颗粒度的约束,才是工程落地的起点。
适合谁参考?如果你正在做以下任何一件事,这篇内容就是为你写的:
- 正在搭建客服对话系统,发现用户一问“帮我查下昨天北京南站出发的G101次列车晚点没”,整个流程就卡在“解析意图→识别地点→识别车次→调铁路接口→解析返回→组织回答”这一长串链路上,且任意一环出错就崩;
- 在开发内部知识助手,但每次用户问“上季度华东区销售冠军是谁”,系统要么答非所问,要么直接抛异常;
- 或者你只是个技术负责人,在评估是否要把现有RAG系统升级为Agent架构,却苦于找不到可衡量、可测试、可运维的能力交付标准。
它不是银弹,但能让你少踩80%的“能力不可控”坑。接下来我会从设计逻辑、核心契约、实操实现、典型故障四个维度,把这套机制掰开揉碎讲透。
2. 设计思路拆解:为什么必须把“技能”从Agent主干中剥离?
2.1 传统Agent架构的三大隐性成本
很多团队一开始直接基于LangChain或LlamaIndex搭Agent,很快就会撞上三堵墙,而这三堵墙,全是因为“技能”和“决策流”混在一起导致的:
第一堵墙:调试黑洞
当用户问“帮我订明天上海到杭州的高铁票”,系统返回“未找到可用班次”,你根本不知道问题出在哪——是LLM没识别出“上海”和“杭州”是出发到达地?还是调用12306接口时参数拼错了?抑或是接口返回了HTML乱码但没做清洗?所有日志混在一条trace里,就像把十台机器的机油倒进同一个油桶,再想分清哪台漏油,基本靠猜。
第二堵墙:版本失控
某天运营提需求:“把天气查询结果里的‘多云’改成‘☁️ 多云’,加个emoji更亲切”。开发改完Prompt,测试通过就上线。结果三天后发现,财务报表生成模块也调用了同一个天气skill,导致Excel里出现无法解析的字符,整个月报流程中断。问题根源在于:没有能力隔离层,一次修改影响所有调用方。
第三堵墙:能力幻觉蔓延
LLM在规划阶段会自信满满地说“我将调用航班查询skill获取CA1501航班状态”,但实际该skill只支持国内航班,CA1501是国际航班,调用直接404。更糟的是,LLM不会说“我调用失败了”,而是自己编造一个“航班正常”的答案返回给用户。这就是典型的“能力幻觉”——模型对自身能力边界的认知,远不如代码里明确定义的schema可靠。
2.2 “agent-skills”的解法:用契约代替信任
“agent-skills”的核心思想,就是把上述混乱全部收束到一个“能力契约”里。这个契约不是文档,而是可执行、可验证、可拦截的代码契约。我们以一个真实的“企业微信消息发送skill”为例说明:
class WeComMessageSkill(Skill): name = "send_wecom_message" description = "向指定企业微信成员发送文本消息,支持@所有人" # 输入契约:强制校验 input_schema = { "type": "object", "properties": { "user_id": {"type": "string", "minLength": 1}, "content": {"type": "string", "maxLength": 2048}, "at_all": {"type": "boolean", "default": False} }, "required": ["user_id", "content"] } # 输出契约:定义成功/失败结构 output_schema = { "type": "object", "properties": { "status": {"enum": ["success", "failed"]}, "message_id": {"type": "string", "nullable": True}, "error_code": {"type": "string", "nullable": True} } } # 执行契约:超时+重试+兜底 timeout = 8.0 # 秒 max_retries = 2 fallback = lambda: {"status": "failed", "error_code": "FALLBACK_UNAVAILABLE"}看到这里你应该明白了:这个skill不是一段能“干活”的代码,而是一个带说明书、带质检线、带应急通道的微型工厂。LLM只需要按契约投递原料(input),工厂自动完成生产(execution)、质检(output validation)、返工(retry)或报修(fallback),最后交出符合标准的成品(output)。LLM不再需要“理解”怎么发消息,它只需要“知道”有这个能力可用。
2.3 为什么不用微服务?——轻量级契约的不可替代性
有人会问:既然要解耦,直接做成HTTP微服务不更彻底?确实可以,但代价巨大:
- 每次调用增加200ms网络延迟,而Agent决策链往往需要3~5次skill调用,端到端延迟直接翻倍;
- 需要单独维护服务发现、熔断、鉴权、日志聚合等整套基础设施,小团队根本玩不起;
- 最致命的是:微服务天然缺乏“能力边界描述”,你调用
/api/weather,它返回400错误,但错误体里不会告诉你“只支持地级市,不支持区县”。
“agent-skills”的精妙之处,在于它用进程内契约(In-process Contract)实现了微服务的治理能力,同时保留了本地调用的性能。它本质上是一种“面向能力的编程范式”(Capability-Oriented Programming),而非简单的函数封装。
3. 核心契约详解:五个不可妥协的契约要素
3.1 输入契约(Input Schema):不是校验,而是意图对齐
输入契约的终极目的,不是防止用户输错,而是确保LLM的规划意图与skill的实际能力完全对齐。很多团队只做基础类型校验(如user_id必须是字符串),这远远不够。
我们曾遇到一个真实案例:某HR系统接入“员工信息查询skill”,LLM规划时生成的input是:
{"employee_name": "张三", "department": "技术部"}但skill实际只支持按工号查询,employee_name字段被完全忽略,最终返回空结果。LLM却据此推断“该员工不存在”,误导HR。
正确做法是:在input_schema中用description字段强制声明语义约束:
"employee_name": { "type": "string", "description": "仅支持精确匹配,不支持模糊搜索;若传入中文名,必须为员工花名册登记的全名,不支持昵称或英文名" }更进一步,我们会在skill初始化时,对schema做静态分析,自动生成“能力说明书”Markdown文档,供LLM在规划前读取。例如:
本skill仅支持按工号查询员工信息。若用户提供姓名,请先调用'员工姓名转工号' skill获取工号,再调用本skill。
这才是真正的意图对齐——不是让LLM猜,而是给它一份操作手册。
3.2 输出契约(Output Schema):失败也是契约的一部分
90%的skill文档只描述“成功时返回什么”,却对失败场景闭口不谈。这导致LLM在收到{"error": "timeout"}时,无法判断是网络问题(可重试)、权限问题(需换账号)、还是参数错误(需修正input)。输出契约必须明确定义所有可能的失败形态。
我们采用三级错误分类法:
- Transient Errors(瞬时错误):如网络超时、服务暂时不可用。契约中声明
retryable: true,skill自动重试。 - Business Errors(业务错误):如“查无此人”、“日期超出范围”。契约中声明
retryable: false,但提供suggestion字段指导LLM下一步动作,例如:"suggestion": "请确认员工工号是否正确,或调用'员工工号查询助手' skill获取最新工号" - System Errors(系统错误):如JSON解析失败、内存溢出。这类错误不暴露给LLM,由skill框架捕获并触发fallback。
这种设计让LLM的决策逻辑变得极其清晰:收到retryable: true就重试;收到suggestion就按提示行动;收到系统错误就走全局降级流程。不再有“未知错误”这种黑洞。
3.3 执行契约(Execution Contract):超时与重试的科学设定
超时时间不是拍脑袋定的。我们有一套计算公式:
timeout = base_latency × (1 + p95_latency_variance) + network_jitter其中:
base_latency是skill在本地环境下的P50延迟(我们要求所有skill必须提供基准压测报告);p95_latency_variance是线上环境P95延迟相对于P50的波动系数(通常取0.8~1.2);network_jitter是跨服务调用的网络抖动预留(本地调用设为0.2秒,HTTP调用设为1.0秒)。
例如,某数据库查询skill本地P50=120ms,P95=380ms,则variance = (380-120)/120 ≈ 2.17,取保守值1.5,timeout = 120×(1+1.5)+0.2 = 320ms。实测下来,这个值能让99.2%的成功请求在超时前完成,同时避免因单次慢查询拖垮整个Agent链路。
重试策略同样讲究:绝不简单地“重试3次”。我们采用指数退避+去重键(deduplication key)组合:
- 第一次失败后等待100ms,第二次失败后等待300ms,第三次失败后等待900ms;
- 每次重试前,用
input_hash + timestamp_floor_to_second生成去重键,防止因网络重传导致同一请求被执行两次(比如扣款skill)。
3.4 降级契约(Fallback Contract):兜底不是摆设,而是预案
很多团队的fallback写的是return {"error": "服务繁忙"},这等于没写。真正的fallback必须满足三个条件:
- 可预测性:返回结构必须严格符合output_schema,不能临时加字段;
- 业务合理性:不能答非所问。比如“查天气”skill的fallback,绝不能返回“今天宜出行”,而应返回
{"status": "failed", "error_code": "FALLBACK_WEATHER_UNAVAILABLE", "suggestion": "可尝试查看手机天气APP"}; - 可观测性:每次触发fallback,必须记录完整上下文(input、原始错误、触发时间),用于后续分析是skill本身缺陷,还是LLM规划错误。
我们甚至为高频skill配置了分级fallback:
- 一级fallback:本地缓存(如天气skill缓存昨日数据);
- 二级fallback:调用简化版API(如只返回温度,不返回湿度);
- 三级fallback:纯静态文案(如“天气服务暂时不可用,建议稍后重试”)。
这样既保障用户体验,又为运维提供清晰的降级路径。
3.5 边界契约(Boundary Statement):用自然语言写清楚“不能做什么”
这是最容易被忽视,却最关键的一环。技术文档喜欢写“支持XX功能”,但LLM真正需要的是“不支持什么”。我们要求每个skill的boundary_statement必须包含三个要素:
地理/行政边界:
“仅支持中国大陆境内企业微信账号,不支持海外版WeCom(即WeCom International);港澳台地区账号需单独开通国际版权限。”
数据时效边界:
“航班状态数据延迟不超过15分钟,不保证实时性;起飞前2小时内的状态变更,可能无法及时同步。”
语义理解边界:
“不支持自然语言时间表达,如‘下周三’、‘大后天’;必须传入ISO 8601格式日期,如‘2024-06-15’。”
这些语句会被注入LLM的system prompt,成为其规划时的硬性约束。实践证明,明确写出“不能做什么”,比反复强调“能做什么”更能减少幻觉。
4. 实操实现:从零搭建一个可验证的skill运行时
4.1 技术选型逻辑:为什么选Python + Pydantic + asyncio?
我们对比过多种技术栈,最终锁定Python生态,原因很实在:
- Pydantic v2+ 的Schema校验能力无敌:支持JSON Schema导出、自动生成文档、运行时动态校验,一行代码搞定input/output验证;
- asyncio 原生支持异步I/O:skill多数是网络调用,async/await能轻松应对高并发;
- Python生态的成熟度:Requests、httpx、SQLAlchemy等库对异常处理、连接池、超时控制都有工业级实现,不用重复造轮子。
我们不推荐用Go或Rust重写——除非你的skill全是CPU密集型计算(如图像处理),否则网络I/O才是瓶颈,Python的async生态完全够用。
4.2 核心运行时骨架代码(精简版)
以下是经过生产验证的skill运行时最小可行骨架,已去除所有业务逻辑,只保留契约执行核心:
import asyncio import json import time from typing import Any, Dict, Optional, Callable, Awaitable from pydantic import BaseModel, ValidationError from pydantic.json_schema import model_json_schema class SkillResult(BaseModel): status: str # "success" or "failed" data: Optional[Dict[str, Any]] = None error_code: Optional[str] = None suggestion: Optional[str] = None execution_time_ms: float = 0.0 class SkillConfig(BaseModel): timeout: float = 5.0 max_retries: int = 2 fallback: Optional[Callable[[], Dict[str, Any]]] = None class Skill: name: str description: str input_schema: Dict[str, Any] output_schema: Dict[str, Any] config: SkillConfig async def execute(self, input_data: Dict[str, Any]) -> SkillResult: start_time = time.time() # Step 1: Input Validation try: validated_input = self._validate_input(input_data) except ValidationError as e: return SkillResult( status="failed", error_code="INPUT_VALIDATION_ERROR", suggestion=f"输入参数错误:{str(e)}", execution_time_ms=(time.time() - start_time) * 1000 ) # Step 2: Execute with Retry last_error = None for attempt in range(self.config.max_retries + 1): try: # Apply timeout result = await asyncio.wait_for( self._run_core(validated_input), timeout=self.config.timeout ) # Validate output validated_output = self._validate_output(result) return SkillResult( status="success", data=validated_output, execution_time_ms=(time.time() - start_time) * 1000 ) except asyncio.TimeoutError: last_error = "TIMEOUT" if attempt < self.config.max_retries: await asyncio.sleep(min(0.1 * (3 ** attempt), 2.0)) # exponential backoff continue except Exception as e: last_error = f"{type(e).__name__}: {str(e)}" if attempt < self.config.max_retries: await asyncio.sleep(min(0.1 * (3 ** attempt), 2.0)) continue # Step 3: Fallback if self.config.fallback: try: fallback_result = self.config.fallback() return SkillResult( status="failed", data=fallback_result, error_code="FALLBACK_TRIGGERED", execution_time_ms=(time.time() - start_time) * 1000 ) except Exception as e: pass return SkillResult( status="failed", error_code=f"EXECUTION_FAILED_AFTER_{self.config.max_retries}_RETRIES", suggestion=f"多次执行失败,最后错误:{last_error}", execution_time_ms=(time.time() - start_time) * 1000 ) def _validate_input(self, data: Dict[str, Any]) -> Dict[str, Any]: # Use Pydantic to validate against input_schema # Implementation omitted for brevity pass def _validate_output(self, data: Dict[str, Any]) -> Dict[str, Any]: # Use Pydantic to validate against output_schema pass async def _run_core(self, validated_input: Dict[str, Any]) -> Dict[str, Any]: # This is the actual business logic, to be implemented by subclass raise NotImplementedError这个骨架的价值在于:它把所有契约要素(输入校验、超时、重试、fallback、输出校验)全部封装在基类里,子类开发者只需专注写_run_core。我们要求所有skill必须继承此基类,确保契约一致性。
4.3 一个完整可运行的示例:企业微信消息发送skill
下面是一个真实部署过的skill,已脱敏处理,可直接运行:
import httpx from typing import Dict, Any, Optional from pydantic import BaseModel class WeComMessageInput(BaseModel): user_id: str content: str at_all: bool = False class WeComMessageOutput(BaseModel): status: str message_id: Optional[str] = None error_code: Optional[str] = None class WeComMessageSkill(Skill): name = "send_wecom_message" description = "向指定企业微信成员发送文本消息,支持@所有人" input_schema = WeComMessageInput.model_json_schema() output_schema = WeComMessageOutput.model_json_schema() config = SkillConfig( timeout=8.0, max_retries=1, fallback=lambda: {"status": "failed", "error_code": "FALLBACK_WECOM_UNAVAILABLE"} ) async def _run_core(self, validated_input: Dict[str, Any]) -> Dict[str, Any]: # Get access token from cache or refresh access_token = await self._get_access_token() # Build request url = f"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={access_token}" payload = { "touser": validated_input["user_id"], "msgtype": "text", "agentid": 100001, # your agent id "text": { "content": validated_input["content"] } } if validated_input["at_all"]: payload["text"]["mentioned_list"] = ["@all"] async with httpx.AsyncClient(timeout=5.0) as client: response = await client.post(url, json=payload) if response.status_code != 200: raise Exception(f"WeCom API returned {response.status_code}: {response.text}") resp_json = response.json() if resp_json.get("errcode") != 0: raise Exception(f"WeCom API error: {resp_json.get('errmsg')}") return { "status": "success", "message_id": resp_json.get("msgid") } async def _get_access_token(self) -> str: # Real implementation would cache token and refresh when expired # This is a stub return "fake_access_token_12345"关键细节说明:
WeComMessageInput和WeComMessageOutput使用Pydantic模型,自动获得JSON Schema、文档生成、运行时校验能力;_run_core中的httpx.AsyncClient显式设置timeout=5.0,与skill config的timeout=8.0形成两级防护(网络层5秒,整体执行8秒);- 错误处理分层清晰:HTTP状态码错误 → WeCom业务错误 → 全局异常,每层都有对应处理逻辑;
fallback是lambda,确保无状态、无副作用,避免fallback本身出问题。
4.4 可观测性埋点:没有监控的skill就是定时炸弹
我们为每个skill强制注入以下监控指标:
skill_execution_count{skill_name, status}:总执行次数,按success/failed打标;skill_execution_duration_seconds_bucket{skill_name, le}:执行耗时直方图,用于计算P95/P99;skill_fallback_count{skill_name, fallback_level}:各级fallback触发次数;skill_retry_count{skill_name, attempt}:各次重试触发次数。
这些指标全部通过OpenTelemetry SDK上报,与公司统一监控平台打通。当skill_execution_duration_seconds_bucket{skill_name="send_wecom_message", le="0.5"}的rate低于95%,告警立刻触发,SRE团队无需登录服务器,直接在Grafana看板上就能定位是网络抖动、token过期还是WeCom接口限流。
5. 常见问题与排查技巧实录:那些文档里不会写的坑
5.1 问题速查表:高频故障现象与根因定位
| 现象 | 可能根因 | 快速验证方法 | 解决方案 |
|---|---|---|---|
LLM反复调用同一skill但始终失败,日志显示INPUT_VALIDATION_ERROR | LLM生成的input字段名与skill契约不一致(如skill要user_id,LLM传userId) | 查看skill输入校验日志,提取原始input JSON,对比schema字段名 | 在LLM system prompt中加入字段名约束:“所有skill输入字段必须使用snake_case命名,如user_id、order_number” |
| skill执行耗时稳定在timeout值附近(如总是7.99秒),但实际API响应很快 | skill内部有阻塞式IO(如用requests而非httpx),asyncio事件循环被卡住 | 在_run_core开头加print("start", time.time()),结尾加print("end", time.time()),看是否真卡在业务逻辑 | 彻底替换为异步库,或用loop.run_in_executor包装阻塞调用 |
| fallback频繁触发,但fallback逻辑本身也报错 | fallback函数引用了未初始化的外部变量(如self.cache_client) | 在fallback函数内加try/except,打印完整traceback | fallback必须是纯函数,所有依赖通过闭包传入,禁止访问self |
| 同一input多次调用,有时成功有时失败(非幂等) | skill内部使用了全局变量或共享状态(如单例连接池未做线程/协程安全) | 用asyncio.create_task并发调用10次同一input,观察成功率 | 所有共享资源必须加锁,或改用连接池(如httpx.AsyncClient自带连接池) |
| LLM收到skill返回后,仍继续调用其他无关skill | skill output中status字段值不是严格字符串"success"或"failed"(如写了"Success") | 检查output_schema中status的enum定义,对比实际返回值 | 严格按schema enum值返回,禁止首字母大写等格式变化 |
5.2 实操心得:三个血泪教训
教训一:别信LLM的“自我宣称”,一定要做契约验证
我们曾上线一个“PDF文本提取skill”,LLM在规划时说“我将调用PDF提取skill获取合同正文”,结果skill返回{"status": "success", "text": ""}(空字符串)。LLM却据此生成“合同内容为空”,直接回复用户。后来发现,是PDF扫描件OCR失败,skill没做空内容检测。现在我们的强制规范是:所有返回status: "success"的skill,必须在output_schema中声明text字段的minLength: 1,并在_validate_output中强制校验。空内容必须走status: "failed"分支。
教训二:重试不是万能的,要区分“可重试”和“该重试”
某次支付skill因银行接口限流返回429,skill自动重试3次,结果用户银行卡被连续扣款3次。根因在于:429是服务端限流,重试只会加重问题;而真正的可重试错误是503(服务不可用)、504(网关超时)。现在我们的重试策略是:只对5xx中的503/504/522(Cloudflare错误)重试,其他错误一律不重试。这个规则写死在运行时框架里,子类无法覆盖。
教训三:文档即代码,契约变更必须触发LLM重新学习
有一次我们把“查天气skill”的输入字段从city_name改为location,只改了代码,忘了更新LLM的system prompt。结果LLM继续传city_name,skill持续报INPUT_VALIDATION_ERROR。现在我们的CI/CD流程强制规定:每次修改skill的input_schema或output_schema,必须自动生成新的prompt片段,并触发LLM微调任务。变更不是发布代码就结束,而是“代码+契约+LLM认知”三者同步更新。
5.3 性能压测实录:一个skill的真实承载力
我们对“企业微信消息发送skill”做了全链路压测,结果如下(环境:4核8G云服务器,WeCom接口QPS限制500):
| 并发数 | P50延迟(ms) | P95延迟(ms) | 成功率 | 备注 |
|---|---|---|---|---|
| 10 | 128 | 210 | 100% | 网络稳定,token缓存命中率100% |
| 100 | 142 | 380 | 100% | 连接池复用充分,无新建连接开销 |
| 500 | 215 | 1250 | 99.8% | 开始出现少量WeCom接口限流(429),skill自动降级到fallback |
| 1000 | 350 | 3200 | 92.1% | 限流加剧,fallback触发率上升,但无雪崩 |
关键结论:
- skill本身不是瓶颈,WeCom接口才是。这印证了“能力边界”的重要性——我们必须在契约中明确写入“QPS上限500”,让LLM在规划时主动做流量削峰;
- P95延迟突破1秒时,必须启动降级。我们在监控中设置了
skill_execution_duration_seconds_bucket{le="1.0"}的rate告警,低于95%即触发预案; - fallback的稳定性决定SLA。实测fallback平均耗时8ms,P99<15ms,确保即使主流程失败,用户也能在1秒内得到响应。
6. 能力治理:如何让skill体系可持续演进?
6.1 Skill Catalog:不是静态清单,而是活的契约注册中心
我们没有用Excel管理skill,而是构建了一个GitOps驱动的Skill Catalog:
- 每个skill是一个独立Git仓库,包含
skill.py、pyproject.toml、README.md(自动生成); README.md由CI流水线根据input_schema/output_schema自动生成,包含字段说明、示例、边界声明;- 所有skill的
name、description、boundary_statement被提取为YAML,提交到中央Catalog仓库; - LLM在规划前,会从Catalog拉取最新YAML,作为context注入system prompt。
这样做的好处是:契约变更自动同步,无需人工维护文档,LLM永远看到最新能力视图。当新同事加入,他不需要读文档,直接git clone任意skill仓库,make test就能跑通全部单元测试,make doc就能生成最新文档。
6.2 测试金字塔:从单元到混沌,缺一不可
我们为skill设计了四层测试:
- 单元测试(Unit Test):Mock所有外部依赖,验证
_run_core逻辑,覆盖率≥90%; - 契约测试(Contract Test):不Mock,真实调用skill基类的
execute方法,只验证input/output schema符合性; - 集成测试(Integration Test):对接真实下游服务(如WeCom沙箱环境),验证端到端流程;
- 混沌测试(Chaos Test):用Chaos Mesh注入网络延迟、DNS故障、HTTP 503错误,验证重试/fallback是否生效。
特别强调:契约测试是核心防线。它确保无论下游服务如何变化,只要skill返回符合schema的数据,上层Agent就不会崩溃。我们曾用契约测试提前发现一个bug:某skill在WeCom接口返回{"errcode": 0}时,错误地把errcode字段当成了status,导致返回{"status": 0},违反了status必须是字符串的契约。契约测试当场fail,阻止了这个bug上线。
6.3 演进路线:从单点skill到能力网络
当前阶段,我们聚焦单个skill的健壮性。下一阶段目标是构建能力网络(Capability Network):
- 能力编排(Orchestration):允许skill之间相互调用,如“查天气skill”内部调用“城市编码转换skill”;
- 能力路由(Routing):根据输入特征自动选择最优skill,如“查天气”时,国内城市走WeCom内置API,海外城市走第三方Weather API;
- 能力学习(Learning):记录LLM每次调用skill的input/output,用强化学习优化skill选择策略。
这条路很长,但每一步都建立在坚实的契约之上。没有可靠的skill,一切高级玩法都是空中楼阁。
我在实际项目中最大的体会是:不要追求“最聪明的LLM”,而要打造“最靠谱的skill”。当每个能力单元都像瑞士手表一样精准、可预测、可替换,整个Agent系统才能真正走出实验室,走进每天要处理上千个真实请求的生产环境。这背后没有玄学,只有对契约的敬畏、对边界的清醒、对细节的偏执。