news 2026/10/7 17:26:27

Agent技能体系实战:构建可落地的agent-skills框架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent技能体系实战:构建可落地的agent-skills框架

这两年做 AI Agent 相关项目,我踩得最深、也最绕不开的一个问题就是:怎么让 LLM 智能体真正学会“用工具干活”。如果你也搞过基于 LLM 的 Agent 开发,大概率会对agent-skills这个词不陌生——它正在被越来越多的团队当成“给智能体写技能”的体系化方案来讨论。说白了,它就是一套让 LLM 知道“什么场景该调什么工具、参数怎么给、失败了怎么处理”的完整机制,而不是简单地把几个函数扔给模型。

简单说,agent-skills 要解决两件大事:一是 LLM 本身只会生成文本,天生不懂怎么稳定地调用外部 API;二是当技能数量越来越多时,代码结构、上下文管理、权限控制会迅速失控,最后变成“demo 能跑、生产难上”。这篇文章我会从技能定义规范、注册加载机制、调度引擎设计、实战复现到问题排查,完整拆解一套可落地的 Agent 技能体系,适合正在做 Agent 应用、想从“能跑”过渡到“架构能用”的开发者参考。

1. agent-skills 的核心:为什么智能体需要一套“技能体系”

1.1 一个始终绕不开的痛点:LLM 与工具之间隔着鸿沟

先说个很基础但经常被忽略的事实:LLM 本身是一个文本生成模型,你问它问题,它给你输出文字;但如果你想让 Agent 去查订单、发邮件、改数据库,它不能直接操作外部系统。行业内最常见的做法,是借助各家模型服务商的 function calling / tool calling 能力,把一批工具以 JSON Schema 的格式塞给模型,让模型在回复中夹带一个结构化的“调用意图”,然后由后端代码去真正执行。

这个思路没问题,可一旦工具数量超过一定阈值,问题就来了。我见过不少项目,初期只有三五个函数时跑得顺顺当当,等业务一扩张,技能列表膨胀到二十几个,模型开始频繁“选择困难”:明明该查订单却调了退款的接口,参数张冠李戴,甚至把两个毫不相关的工具串起来执行。最头疼的是代码层面,每个工具函数都有自己的参数校验、错误处理、权限检查逻辑,散落得到处都是,维护成本直接翻倍。

所以 agent-skills 本质上是把“工具函数”升级成“技能(skill)”。一个技能不再是孤立的函数实现,而是由元数据、输入输出约定、前置条件、依赖策略、错误恢复逻辑共同组成的一个独立单元。LLM 只负责“决策”,框架负责“执行”,两边通过一套统一协议对话,才不会越扯越乱。

1.2 从一个实际业务出发:技能库到底解决什么问题

拿一个真实场景来讲:假设你在做一个电商客服 Agent,它至少需要这几个能力——查订单、处理退换货、计算运费、发工单、查库存。如果按老办法,你得在 system prompt 里写一大段“你可以使用以下工具”,然后把五个函数的 Schema 全贴进去。一开始好像也够用,但用户一旦问出这种问题:

“我上周买的手机订单到哪了?如果还没发货,能帮我申请退款吗?”

这个请求就同时涉及两个技能:查订单状态、申请退款。模型要先看懂第一句话要调 query_order,第二句话要调 refund_apply,而且退款前还得先确认订单状态。这件事让模型裸做,虽然能跑通,但很容易出错——比如它根据“退款”就把整个订单信息填进了退款申请,连校验都没做。

换成 agent-skills 的思路,你需要做的是:把 query_order 和 refund_apply 都封装成标准技能,各自带着清晰的描述和参数约束;调度引擎负责接收用户消息,先定位到“订单查询”和“售后申请”两个候选技能,再按顺序编排执行。模型只做选择题,参数校验、依赖判断和异常兜底全部由技能层接管。这样整个系统的稳定性和可维护性,和“把所有逻辑都压在 prompt 里”完全不是一个级别。

2. 技能定义规范与统一接口:给每个技能立规矩

2.1 一份可落地的技能 Schema 设计

技能规范是整个 agent-skills 的地基。我建议每个技能都用一份统一的 JSON Schema 来声明,字段不需要多,但每个字段都得有明确用途。我目前常用的模板长这样:

{ "skill_name": "query_order", "description": "根据订单号查询订单状态、金额、物流信息", "input_schema": { "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,例如 20250120123456,必填" }, "with_logistics": { "type": "boolean", "description": "是否同时返回物流轨迹,默认 false", "default": false } }, "required": ["order_id"] }, "state_requirements": ["user_authenticated"], "timeout_ms": 3000, "retry": { "max_attempts": 2, "backoff_ms": 300 } }

逐个字段讲一下设计的理由。skill_name是技能的唯一 ID,命名建议用“动词_对象”的格式,比如 query_order、apply_refund、create_ticket,这样模型在阅读候选列表时能快速理解语义。description是写给模型看的,不是写给代码看的——它一定要写清楚“这个技能在什么场景下用”,甚至可以加一句“绝对不要在什么场景下用”,等下我会展开讲。input_schema是模型生成参数时的约束边界,里面每个字段的 description 也都很讲究,必须包含“示例值”,因为模型对具体例子的理解能力远远强于抽象描述。

state_requirements是前置状态校验,比如查订单前必须先确认用户已完成登录授权,不然技能执行到一半才会发现没权限,白白浪费一次调用。timeout_ms是给技能执行的硬性上限,防止某个下游 API 卡住,把整个 Agent 主循环拖死。retry则是针对瞬时错误的自动重试配置,和超时配合使用,基本能覆盖大部分网络抖动场景。

2.2 函数实现与技能元数据分离:一套代码,多个模型通用

定义好 Schema 之后,还要解决一个重要问题:同一个技能库,怎么做到在不同 LLM 之间复用?今天你可能用 OpenAI 的 GPT 系列,明天可能换成 Claude,或者某个开源模型,总不能每换一个模型就重写一套技能逻辑。

我的做法是把技能拆成两层:元数据层和执行层。元数据层就是上面那份 JSON Schema,它只负责告诉模型“有什么技能、参数长什么样”;执行层是一段普通的 Python 函数,只认输入字典、返回输出字典,完全不关心这个参数是怎么被模型生成的。两者之间用一个统一接口对接:

# 统一技能接口:输入 dict,输出 dict def query_order_executor(params: dict) -> dict: order_id = params["order_id"] with_logistics = params.get("with_logistics", False) # 这里调用真实订单服务... return { "status": "ok", "data": { "order_id": order_id, "status": "SHIPPED", "amount": 3999.00, "logistics": [...] } }

这个设计有什么好处?第一,底层模型随便换,技能执行层的代码一行都不用动,只要模型供应商支持 function calling,就能把元数据单独抽出来喂给它。第二,测试变得极其简单,你可以脱离 LLM,直接用构造好的参数调执行层,做纯函数级别的单元测试。第三,未来做技能市场、技能共享也有基础——别人只要按这个协议写一个执行函数,再配一份 schema,就能被你的 Agent 框架识别。

结合我自己的经验,这个分离的架构看起来多了一层抽象,但带来的维护收益是实打实的。早期我把函数实现和提示词里的工具描述混在一起写,结果每次改业务逻辑都要重新调 prompt,极其痛苦。现在改技能函数不影响提示词,改描述词不会碰业务代码,两边彻底解耦。

2.3 参数校验与错误码规范:别让报错飘进 system prompt

技能的返回值也必须定规矩。我见过最糟糕的写法,是把 Python 堆栈异常直接往回抛,模型看到一堆Traceback (most recent call last),完全不知道怎么回应给用户。正确的做法是,每个技能无论成功还是失败,都返回一个统一结构的字典:

成功时:

{ "status": "ok", "data": { ... } }

失败时:

{ "status": "error", "error_code": "ORDER_NOT_FOUND", "message": "订单号 20250120123456 不存在,请提醒用户核对订单号" }

这里面有个关键细节:error_code是给框架看的,用来做逻辑判断和告警;message是给模型看的,而且必须包含“可恢复的动作提示”。比如“订单号不存在,建议用户核对后重试”就比“订单不存在”有用得多,因为模型拿到前者,会自动组织出“您核实一下订单号是不是 20250120123456,我再帮您查”这类回复;拿到后者,可能就只能干巴巴地说“查不到”。

参数校验要内置到技能执行器的入口处,用 Python 的jsonschema库做一遍严格校验。校验失败时返回一个固定错误码,比如INVALID_PARAMETER,并明确指出是哪个字段不合法,而不是把异常继续往上抛。这才叫“技能层的自我修养”。

3. 技能注册与动态加载:像插件一样组装能力

3.1 注册器设计:从一个项目里的技能目录说起

当技能数量达到十几个之后,最忌讳的做法是手动维护一张全量技能清单。每次新增技能都要改注册表、改前端展示、改模型提示词,漏一步就是线上事故。更好的方式是采用“约定优于配置”的自动注册机制。

我的目录结构一般是这样的:

agent_project/ core/ # 框架层:注册器、调度器、上下文管理 skills/ # 业务技能层,每个技能一个文件 __init__.py order_skills.py refund_skills.py logistics_skills.py runtime/ main.py # Agent 主循环

技能文件里用装饰器标注,比如:

# order_skills.py from core.skill_registry import skill @skill def query_order(params: dict) -> dict: """查询订单状态""" ... @skill def list_user_orders(params: dict) -> dict: """列出用户最近订单""" ...

注册器启动时扫描skills/目录下所有带@skill装饰器的函数,自动收集它们的函数名和 docstring,组装为技能元数据,并校验 Schema 是否合法。这样新增技能只需要加一个文件、写一个装饰器,注册、校验、生效全部自动完成,省掉的脑力劳动相当可观。

有人问我为什么不用全量 JSON 配置文件来声明技能。我的回答是:配置文件可以和代码分离得很好,但一旦技能数量多了,配置散落在外部文件里,反而容易出现“代码改了配置忘改”的错位问题。把技能声明和执行逻辑放在同一个文件里,用装饰器绑定,至少能保证改技能的人不会只看代码、不更新描述。

3.2 多技能依赖与禁用:按业务场景打包技能集

技能注册完还要解决“选择性加载”的问题。一个全能 Agent 并不需要在所有场景下加载所有技能。比如一个客服机器人分售前、售中、售后多条线,售前接待根本不需要“申请退款”技能;你硬塞给它,模型反而可能在用户抱怨贵的时候错误触发退款操作。

我引入了“技能包(skill pack)”的概念。每个技能可以声明自己属于哪个包,调度引擎按业务场景决定加载哪几个包。比如:

  • 售前包:商品详情查询、库存查询、优惠券计算
  • 售中包:订单查询、修改地址、催发货
  • 售后包:退款申请、退货登记、工单创建

加载时只把当前业务场景对应的技能 Schema 暴露给模型,不相关的技能一律不可见。这个设计既控制了上下文长度,又可以降低模型误选率,还能配合权限做到“售前 Agent 根本调不了退款接口”。如果技能之间有依赖关系——比如“计算违约金”必须先查“订单详情”——也可以在技能声明里加一个depends_on字段,由调度层在编排阶段解析,而不是让模型自己去猜。

3.3 沙箱与超时保护:让技能失败不砸整个 Agent

技能执行的安全性和稳定性,是生产环境和实验 demo 的分水岭。我见过不少 Agent 挂了,不是模型出错,而是技能函数里一个网络请求没有设置超时,直接阻塞了整个主循环。所以我的框架里,所有技能都会被一个统一的执行沙箱包裹:

  • 每个技能调用都强制走超时控制,Pythonasyncio里用asyncio.wait_for,同步代码用线程池 + Future 超时;
  • 外部网络请求只允许访问白名单域名,防止模型被诱导去请求内网或本地端口;
  • 高风险技能(比如执行命令、写支付单)运行在一次性子进程里,用完即销毁,避免把宿主进程搞挂;
  • 技能执行失败只向模型返回结构化错误,不暴露底层堆栈和敏感环境信息。

把这些安全手段类比成管理一个实习生:你给他明确的任务书(Schema)、限定工作区域(沙箱)、要求定时汇报(超时机制)、出问题迅速召回(结构化错误返回)。如果为了图省事,让他拿着公司内网权限到处跑,那只要一次误操作就是一场事故。

4. 调度引擎的工作原理:模型做选择,框架做执行

4.1 两种主流调度方式:全自动工具调用 vs 意图+技能路由

技能就绪之后,调度引擎就是大脑。目前主流有两种做法。

第一种是全量工具调用,也就是把技能库里所有技能的 Schema 一股脑塞给模型,让模型在生成时直接选择并返回工具调用参数。这种做法的好处是实现简单,模型原生支持,适合技能数量不超过 5 个的小场景;坏处是技能一多,上下文会被撑爆,而且模型很容易在十几个相似技能面前犯迷糊。

第二种是意图识别 + 技能路由,这也是 agent-skills 更推荐的做法:框架先把技能目录压缩成一张极简的“技能卡片列表”,只包含技能名和一句话描述,发给模型;模型先定位出最相关的 2~3 个技能,框架再从技能库里取出这几个技能的完整 Schema,进行精准注入;最后模型只在这几个候选里做参数填充和最终调用。

为什么第二种更稳?因为模型不需要一次性处理二十几份详细 Schema,它先做粗筛,再做细决策,每一步的负担都小很多。而且这给了系统一个中间层——“路由层”,你可以在路由结果里做权限校验、AB 策略、敏感技能拦截。如果某个用户没有退款权限,路由阶段就直接把 refund_* 技能从候选里摘掉,根本不会进到模型那一层。

我个人建议一开始做 Agent 就用第二种方式搭建框架,哪怕技能很少也无妨。因为从 5 个技能扩张到 50 个技能是必然的,一个支持路由的架构可以平滑承载增长,而纯靠全量工具调用的方案到后期只能推倒重来。

4.2 路由层 Rerank 与组合编排:多个技能如何协同完成一件事

路由层也不能只按模型的一次输出拍板。我通常会给路由层加一个“强规则优先”机制:有些技能和某些关键词有非常稳定的对应关系,比如用户消息里出现“退款”“退货”,路由层直接给出refund_apply作为硬候选;然后再结合模型对完整意图的判断进行 Rerank,把硬命中结果和模型候选做一次去重和加权。这样做的好处很明显,模型偶尔抽风不要紧,规则兜底能把最核心的路径拉回来。

技能编排则更复杂一些。还拿“退款”举例,正常流程是:查订单详情 → 判断订单状态是否符合退款条件 → 计算可退金额 → 提交退款申请。这是一条技能链,不是单次调用。

我的框架支持用一份简单的“技能编排配置”描述这种链式关系:

{ "name": "refund_flow", "steps": [ { "skill": "query_order", "required": true, "next": "validate_refund_eligibility" }, { "skill": "validate_refund_eligibility", "required": true, "next": "calc_refund_amount" }, { "skill": "calc_refund_amount", "required": true, "next": "apply_refund" }, { "skill": "apply_refund", "required": true } ] }

调度引擎按顺序执行这些步骤,前一步的输出会作为上下文传给后一步进行参数补全。重要的一点是:不要让模型一次性生成整条链的所有参数。每到一个新步骤,框架会把前几步执行结果的关键字段收集成一个“状态摘要”,和该技能的参数 Schema 一起交给模型,让模型只填空。这样即使某一步失败,也能准确定位到是哪个环节出了问题,而不是让模型从一团乱麻里猜。

4.3 上下文压缩与记忆保留:控制在上下文窗口内的技能提示

技能体系还有一个常被忽略的收益——上下文压缩能力。如果不做技能路由,20 个技能的完整 Schema 加起来,可能直接烧掉上下文窗口的三分之一,留给真实对话的余量就少了。而做了路由 + 技能卡片后,基础指令可能只占 800 token 以内,完整技能 Schema 只在被选中后才注入,这就能把更多的上下文预算留给多轮对话和业务理解。

实际操作中,我会对每轮对话维护一个“历史摘要 + 最近轮次原文”的双层结构:超过 6 轮之前的内容压缩成摘要,最近 3 轮保留完整原文。当调度引擎需要给模型补充上下文时,优先提供技能执行结果组成的“事实卡片”,而不是把所有历史消息一股脑塞回去。这个策略在长对话场景下非常有效,尤其适合客服、销售助理这类需要持续多轮交互的业务,能在不扩容模型的前提下延长有效会话轮次。

5. 实操演练:从零搭一个低配版 agent-skills

5.1 搭建目录与核心代码骨架

理论讲完,上点能直接跑的东西。我带你手写一个极简版 agent-skills 框架,麻雀虽小五脏俱全,主要包含三个部分:技能装饰器、注册器、Agent 主循环。完整目录结构如下:

mini_skills/ core/ __init__.py registry.py runner.py skills/ __init__.py order_skills.py agent.py test_agent.py

先看注册器core/registry.py:

from __future__ import annotations import inspect from typing import Callable, Dict class SkillRegistry: def __init__(self): self._skills: Dict[str, Callable] = {} self._metadata: Dict[str, dict] = {} def register(self, func: Callable): skill_name = func.__name__ self._skills[skill_name] = func self._metadata[skill_name] = { "skill_name": skill_name, "description": func.__doc__ or "暂无描述", } return func def get_skill(self, name: str): return self._skills[name] def catalog(self): return [ {"skill_name": name, "description": m["description"]} for name, m in self._metadata.items() ] registry = SkillRegistry() def skill(func): return registry.register(func)

再定义一个最简技能执行器core/runner.py:

import time from concurrent.futures import ThreadPoolExecutor, TimeoutError def run_with_timeout(func, params: dict, timeout_ms: int = 3000): executor = ThreadPoolExecutor(max_workers=1) future = executor.submit(func, params) try: return future.result(timeout=timeout_ms / 1000) except TimeoutError: return {"status": "error", "error_code": "TIMEOUT", "message": "技能执行超时,请稍后重试"} finally: executor.shutdown(wait=False)

这个执行器只是框架的最小骨架,但已经覆盖了统一入参、统一超时这两个核心约束。

5.2 5 分钟接入两个业务技能

在skills/order_skills.py里注册两个技能,演示标准写法和 docstring 的重要性:

from core.registry import skill @skill def query_order(params: dict) -> dict: """根据订单号查询订单状态、金额、物流信息。当用户问订单到哪了、发货没有、商品状态时使用。""" order_id = params.get("order_id", "") if order_id == "20250120123456": return {"status": "ok", "data": { "order_id": order_id, "status": "SHIPPED", "logistics": "已到达【上海市浦东新区】配送站,预计今天送达" }} return {"status": "error", "error_code": "ORDER_NOT_FOUND", "message": f"订单 {order_id} 不存在,请提醒用户核对订单号"} @skill def calc_shipping_fee(params: dict) -> dict: """根据商品重量和收货地址计算运费。当用户询问运费多少钱、配送费多少时使用。""" weight_kg = params.get("weight_kg", 0) city = params.get("city", "") base_fee = 8 if city in ("北京", "上海", "广州", "深圳") else 12 total = base_fee + max(0, weight_kg - 1) * 2 return {"status": "ok", "data": {"shipping_fee": total}}

注意两个技能的行为:找不到订单时,message 里带了明确的“用户侧动作建议”;运费计算不依赖外部服务,纯本地得出,方便演示。

5.3 跑通一条完整链路:从用户提问到技能执行结果回填

接下来是 Agent 主循环agent.py,我在这里用一个简化版的模型选择逻辑来模拟 LLM 的能力,而不是真的调用大模型 API——毕竟重点看框架结构:

from core.registry import registry from core.runner import run_with_timeout def model_choose_skill(user_input: str, catalog: list) -> str: # 极简意图匹配,实际项目里这里应该是 LLM 调用 if "运" in user_input or "运费" in user_input or "快递" in user_input: return "calc_shipping_fee" if "订单" in user_input or "到哪" in user_input or "发货" in user_input: return "query_order" return None def extract_params(user_input: str, skill_name: str) -> dict: if skill_name == "query_order": return {"order_id": "20250120123456"} if skill_name == "calc_shipping_fee": return {"weight_kg": 2.5, "city": "上海"} return {} def main(): user_input = "我在京东买了一个手机,订单号20250120123456,现在到哪了?运费多少钱?" catalog = registry.catalog() skill_name = model_choose_skill(user_input, catalog) if not skill_name: print("Agent:抱歉,我没有找到对应技能") return params = extract_params(user_input, skill_name) fn = registry.get_skill(skill_name) result = run_with_timeout(fn, params, timeout_ms=3000) if result["status"] == "ok": print("Agent:技能执行成功,结果是:", result["data"]) else: print("Agent:技能执行失败,原因是:", result["message"]) if __name__ == "__main__": main()

这段代码跑起来会输出类似“Agent:技能执行成功,结果是:{...}”这种格式。看起来很简单,但它已经形成了“技能目录 → 选择技能 → 参数提取 → 统一执行 → 结果规范返回”的完整闭环。真实项目中,你需要把model_choose_skill替换成真正的 LLM 决策层,把extract_params改成由模型按 input_schema 生成参数,再把执行结果回填给模型组织自然语言回复。整个架构不需要变。

这里比较容易踩的坑是:直接把技能执行结果盲目传给用户。正确做法是把结果 data 作为“上下文事实”交给模型,让它用自然语言重新组织一遍,还要结合对话历史补充语气和细节。比如“订单已到达上海配送站,预计今天送达”,这句话应该是模型生成的,而不是技能库硬编码返回的。

有一个细节值得强调:技能本身不要承担“组织自然语言”的职责。技能只负责面向业务流程输出结构化事实,语言表达是 LLM 的强项。一旦你把话术写死在技能里,那这个技能就只能服务这一种话术风格,换个“活泼客服”的人格就废了。始终保持技能层“纯业务、纯事实、纯结构化”的边界,才能让上层有足够的自由度去适配不同对话风格。

6. 常见问题与排查技巧实录

6.1 模型就是不调用技能怎么办

这是新手最常见的问题。症状是用户问“订单查一下”,模型却回“好的,我来帮您查询”,然后就没有下文了——它压根没触发 function call。原因一般有三个:

  • 技能描述里没有说明“何时调用”,模型意识不到这个场景应该走工具;
  • 参数 Schema 过复杂,必填字段超过四五个,模型一算成本高,干脆放弃;
  • 没有给示例,模型不知道调用之后会返回什么。

我的解决方案是在技能描述里加“正面触发词 + 负面抑制词”。比如 query_order 的描述改成:“当用户询问任意订单状态、物流进程、发货/到货时间时使用;仅在纯闲聊、不含任何订单语境时忽略。” 另外给每个技能配一个极简的 few-shot 示例,包含一次完整的 JSON 调用示例。实测下来,模型工具调用率会有明显提升。

另外,参数 Schema 能做得多简单就做多简单。必填参数尽量控制在 2 个以内,其他字段全部给默认值。模型也是“懒”的,它偏好生成更简单的调用;如果你把一个技能设计成必填 6 个参数,模型大概率会绕开它。

6.2 技能调用参数张冠李戴怎么排查

参数填错的问题是第二大高频故障。典型症状:模型把用户名字填进 order_id 字段,或者把商品金额填进数量字段。排查时我一般先看技能描述里的字段示例够不够明确。比如order_id的描述如果只写“订单号”,模型不知道订单号长什么样;改成“订单号,例如 20250120123456,数字长度为 14 位”之后,准确率会立刻提升。

如果描述已经够清楚还是填错,那就在执行层加一道规则校验。比如order_id字段进入执行器后,先做一个正则匹配,不满足格式的直接返回INVALID_PARAMETER错误,并给出参考格式。注意这种规则校验不是要和 LLM 对抗,而是给它一个“及时反馈”的机会——模型在下一轮看到错误码后,通常会自己修正参数再请求一次。

6.3 超时、并发与资源泄漏的经验

技能执行偶尔卡死、内存不断往上涨,这是生产环境最隐蔽的问题。通常有这几个来源:某个 API 客户端没有设置超时;每次技能调用都新建 HTTP Client,用完不关闭;数据库连接池耗尽导致请求排队。框架层能兜底的就是我前面提到的统一超时机制,但根本解决还要靠技能实现者自己注意资源释放。

我习惯在技能装饰器里增加一个finally钩子,统一释放可能持有的资源。另外要求所有外部 API 客户端必须使用连接池,禁止在技能内手动 new client。还有一个用得上的技巧:给技能执行加一个“重试熔断”机制,连续失败 3 次后,该技能自动熔断 30 秒,防止一个下游故障把所有 Agent 请求全部拖垮。

6.4 一个成熟技能的自我修养:版本、灰度与可观测性

技能一旦投入生产,就不能只靠“本地跑得好”来保证了。我给团队立了几条规矩:每个技能必须带version字段,发版记录到 changelog;新技能先小流量灰度,比如只让 10% 的请求路由到新技能,对比错误率和耗时,再逐步放量。每个技能的执行日志必须记录:入参、出参、耗时、触发它的模型决策轨迹。这样出现问题时,可以回溯到底是模型选错了技能,还是技能本身有 bug。

我还会按周维度统计所有技能的调用率和失败率。调用率为 0 的技能不一定是没用的,大概率是路由层有问题,模型看不到它。失败率偏高的技能会优先进入 review 队列。这些可观测性数据,比模型本身的精度指标更能反映整个 Agent 系统的健康程度。

6.5 安全边界:不能把执行裸奔交给模型

最后聊一个容易被忽视但非常重要的点:技能执行的安全边界。模型本质上是一个概率系统,你没法 100% 保证它不会在某个奇怪上下文里,给某个高风险技能填出危险参数。所以在技能注册阶段,就要给每个技能打上权限标签:read-only、write、admin。调度引擎执行前必须检查当前会话身份是否具有权限。敏感操作(创建订单、发起付款、删除数据)一律要求用户二次确认,这个确认动作也做成一个技能,由 Agent 在操作前主动触发。

此外,外部网络请求要强制走白名单代理,输入给技能的所有参数都要做基础清洗——尤其是会拼接到 SQL、命令行中的字段。这一点没有捷径,必须从框架层面默认全 deny,按需放行。宁可配置繁琐一点,也别把裸奔的执行权交到模型手里。

最后分享三个我长期受益的小习惯。第一,所有技能名都用动词开头,query_order、apply_refund、send_email,一看就懂是干什么的,对模型和开发者都友好。第二,每个月清理一次超过 30 天未被调用的技能——它们不是没用,而是大概率路由层出了问题,留在库里只会增加模型的决策负担。第三,在技能描述里明确写一句“不可用于某某场景”,比如查运费技能注明“不用于计算退款金额”,这能显著减少模型跨域误用。agent-skills 不是一次性工程,它是需要持续迭代、持续观测的技能资产。体系搭建起来之后,后续每新增一个技能,都是在往这套方法论里添加资产,整体的 Agent 能力就会越来越厚,而不是越来越乱。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 17:25:37

AI大模型应用开发:从胶水层到可信交付的工程化路线图

1. 这不是“速成神话”,而是一份真实可落地的AI大模型应用开发路线图 你点开这个标题,第一反应可能是:又一个营销话术?七天从小白到大神?少走99%弯路?学完即就业?——我干这行十多年&#xff0c…

作者头像 李华
网站建设 2026/10/7 17:23:48

JCache规范解析:从JSR-107到Spring Boot集成实践

前几天有个读者私信我,说他面试某厂 Java 高级岗时被问了一道“基础篇”的题:JCache(JSR-107)在 Java EE 或 Spring Boot 环境中如何集成和启用。他当场愣了一下——平时用的都是 Redis、Caffeine,没正经研究过 javax.…

作者头像 李华
网站建设 2026/10/7 17:23:46

claude-mem:基于MCP的Claude跨会话记忆增强实践

Claude的上下文窗口堆得再大,它还是记不住你上周让它整理的那份客户名单。这事儿我憋了很久了,直到看到claude-mem这个开源项目,才觉得终于有人把“记忆”这件事当正经需求做了。它不是给Claude硬塞一个超长提示词,而是接了一套独…

作者头像 李华
网站建设 2026/10/7 17:22:51

低代码流程编排实战:Mendix Workflow五大高级功能深度解析

先丢个场景给你:你花了两周搭好一个审批流程,上线第二天业务部门打电话过来说,某个节点的审批人其实应该是部门主管而不是发起人;又过了两天,财务说希望流程跑到某个环节时,能自动把关键数据同步给ERP。你打…

作者头像 李华
网站建设 2026/10/7 17:22:48

PHP程序员庖丁解牛:老项目维护、调试排错与AI时代生存之道

这个标题看着像句玩笑话,但做PHP这行超过十年的人,应该能读出一点真实的沉重感。昨天还在加班改接口的同事,今天可能已经离职、转行,或者只是把项目交接给你,留下一堆没有文档的代码。而你现在敲下的每一行&#xff0c…

作者头像 李华
网站建设 2026/10/7 17:22:25

text-to-cad实战:用自然语言生成CAD模型,附CadQuery工作流

前阵子有个做非标自动化的朋友发我一段需求,原话是:“我要一个外壳,能装下一个805030的电机,壁厚3毫米,四个角要能过M4螺丝,出线口在侧面。”他问我能不能直接让AI把这句话变成能拿去加工的3D模型。这个需求…

作者头像 李华