news 2026/10/21 9:40:25

Agent-Skills:智能体能力治理的核心范式与工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent-Skills:智能体能力治理的核心范式与工程实践

1. 项目概述:一个被严重低估的“技能容器”概念

“agent-skills”这个词组乍看像技术黑话,但拆开来看——agent 是智能体,skills 是技能——合起来就是“智能体所具备的能力集合”。它不是某个具体工具、框架或开源库的名字,而是一个正在快速成型的架构范式。过去三年里,我参与过多个跨团队协作的自动化系统搭建,从某高校实验室的科研流程辅助系统,到某公司内部的跨平台工单调度中台,再到面向中小企业的低代码任务编排平台,所有项目在中期复盘时都绕不开一个问题:“这个 agent 到底能做什么?它的能力边界在哪里?”——答案最终都收敛到“skills”的定义、注册、调用与组合逻辑上。

这个词之所以突然出现在热搜词列表里,并非因为某家大厂发布了新模型,而是因为大量一线开发者开始集体意识到:光有大语言模型(LLM)推理能力远远不够,真正决定 agent 落地效果的,是它背后那套可管理、可测试、可灰度发布的技能治理体系。就像操作系统不等于 CPU,agent 也不等于 prompt 工程;skills 就是那个让 agent 从“会聊天”走向“能办事”的操作系统内核。它解决的是真实业务场景中最痛的三个断层:一是 LLM 输出和实际执行动作之间的语义鸿沟,二是多工具调用时的权限、参数、错误处理混乱,三是不同业务线之间技能复用率极低导致的重复造轮子。

适合谁来读这篇?如果你正卡在这些节点上,这篇文章就是为你写的:你已经能用 LangChain 或 LlamaIndex 搭出一个基础 agent,但每次加个新功能就要重写整个 chain;你发现同一个“查天气”技能,在销售侧要返回带销售话术的摘要,在运维侧却要输出结构化 JSON 给监控系统;或者你刚被老板问:“我们做了三个月的 agent 项目,到底沉淀下来哪些可复用的能力资产?”——那么,“agent-skills”就不是术语,而是你下个月 OKR 的核心指标。它不教你怎么调 API,而是告诉你怎么把 API 调用变成一种可审计、可版本化、可被自然语言精准触发的“数字劳动力”。

2. 核心设计思路:为什么必须把 skills 单独拎出来建模?

2.1 技能不是函数,而是带上下文契约的服务单元

很多新手一上来就写def send_email(to, subject, body),然后把它塞进 agent 的 tools 列表里,以为这就是一个 skill。实测下来,这种做法在第 3 个业务需求进来时就会崩。问题出在认知偏差:把 skills 当成纯技术函数,忽略了它在 agent 架构中的语义角色。

真正的 skills 必须满足三项契约约束:

  • 意图对齐契约:skill 的名称、描述、输入参数命名,必须与人类自然语言指令高度匹配。比如用户说“把这份周报发给张经理并抄送李总监”,skill 名不能叫email_sender_v2,而应是send_report_email;其 description 字段不能写“调用 SMTP 发送邮件”,而要写“将指定文档内容以正式汇报格式发送至指定收件人,并自动添加抄送”。我试过用 GPT-4 生成 skill 描述,结果 70% 的描述里藏着“调用”“接口”“API”这类技术词,导致 LLM 在 planning 阶段根本无法准确识别该调用哪个 skill。后来我们强制规定:所有 description 必须通过“五岁小孩测试”——让非技术人员听一遍就能判断这技能干啥。

  • 输入净化契约:skills 必须自带参数校验与语义归一化能力。举个典型反例:某团队的search_knowledge_baseskill 接收一个query字符串,但用户实际输入可能是“上季度华东区销售额”,也可能是“Q2 East China revenue”,甚至“去年夏天卖得最好的产品”。如果 skill 层不做标准化,下游知识库检索必然失准。我们后来在每个 skill 入口加了一层轻量级语义解析器(基于 spaCy + 小样本微调的 BERT 分类器),把原始 query 映射到预设的 12 个标准查询模板上,再填充参数。这步看似多此一举,但实测将模糊查询的召回准确率从 58% 提升到 89%。

  • 失败自愈契约:skills 必须声明自己的失败模式及降级策略。比如generate_chartskill 在数据缺失时,不能直接抛ValueError让整个 agent 流程中断,而应返回结构化错误码(如{"error_code": "NO_DATA_FOUND", "suggestion": "请先运行 fetch_sales_data"}),并附带推荐的补救 skill。我们在某次金融报表生成项目中,因未定义此契约,导致 agent 在遇到空数据时反复重试生成图表,最终触发风控限流。后来我们给所有 skills 强制增加fallback_skill和retry_policy两个元字段,前者指向备用方案(如改用文字描述替代图表),后者定义重试次数与间隔(如指数退避:1s, 2s, 4s)。这套机制上线后,agent 任务整体成功率从 63% 稳定在 92% 以上。

提示:不要试图用一个万能 skill 解决所有问题。我们曾设计过一个universal_action_executor,想让它根据用户指令动态选择执行路径。结果它成了系统最不稳定的模块——LLM 对指令的理解偏差会被放大十倍。经验教训:skills 越细粒度、职责越单一,agent 的可控性越强。一个 skill 只做一件事,且这件事必须能被一句话说清。

2.2 Skills 的生命周期管理:从手写函数到可发布资产

当项目规模超过 5 个 skills 后,手写 Python 函数的方式会迅速失控。我们经历过一个典型场景:市场部需要新增“生成社交媒体海报文案”skill,开发同学写了代码、测了 demo、合并进主干,但三天后销售部反馈:海报文案里缺了最新促销政策编号。查日志发现,政策编号来源的 API 地址已在上周被运维同学更新,但没人通知 skill 维护者——因为这个 skill 的依赖关系从未被显式声明。

这暴露了本质问题:skills 不是散装代码,而是可独立演进的软件资产。我们为此构建了最小可行的 skills 生命周期管理体系,包含四个强制环节:

  1. 注册中心(Registry):所有 skills 必须通过 YAML 文件注册,而非硬编码。文件包含 name、description、input_schema(JSON Schema 格式)、output_schema、required_env_vars(如 API_KEY)、dependencies(如依赖fetch_product_catalogskill)、version(遵循语义化版本 2.0)。例如send_report_email.yaml的 dependencies 字段明确写着["format_report_for_email"],这样当format_report_for_email升级时,注册中心能自动标记所有依赖它的 skills 为“待验证”。

  2. 沙箱执行环境(Sandbox):每个 skill 运行在隔离的 Docker 容器中,仅挂载声明的环境变量与只读配置文件。我们禁止任何 skill 直接读取本地文件或全局变量。这样做看似麻烦,但换来的是确定性——同一份 skill 代码,在开发机、测试环境、生产环境的行为完全一致。某次线上事故排查中,我们发现一个 skill 在测试环境正常,生产环境失败,根源是开发机上有个同名但版本不同的 Python 包被意外 import。沙箱机制让这类问题在 CI 阶段就被拦截。

  3. 契约测试(Contract Testing):每次 skill 提交 PR,CI 流水线会自动运行三类测试:① schema 验证(输入输出是否符合声明);② 边界值测试(空输入、超长输入、非法字符等);③ 语义一致性测试(用 5 条典型用户指令测试 skill 名称与 description 是否真能触发它)。我们用 GPT-4 生成测试用例,人工审核后固化为 baseline。这套测试跑完平均耗时 42 秒,但它拦下了 37% 的低级错误提交。

  4. 灰度发布通道(Canary Release):新 version 的 skill 不会立即全量生效。我们设置了一个traffic_ratio字段,默认为 0,运维同学通过配置中心将其逐步调高(1%, 10%, 50%, 100%)。同时,系统自动采集每个流量分片的 success_rate、latency、error_code 分布。当 error_code 中AUTH_FAILED突增时,系统会自动将该 skill 的流量比降至 0 并告警。这套机制让我们在一次支付网关升级中,提前 17 分钟发现新 skill 的证书验证逻辑缺陷,避免了订单损失。

这套体系看起来重,但算笔账很值:项目初期投入 3 人日搭建,后续每新增 1 个 skill,平均节省 2.3 小时的联调与故障排查时间。更重要的是,它让 skills 从“代码片段”变成了“可交易的数字资产”——市场部可以向 IT 部申请开通generate_social_media_postskill 的使用权限,IT 部只需在配置中心勾选即可,无需安排开发排期。

3. 核心实现细节:如何构建一个生产级 skills 框架

3.1 技能注册与发现:YAML 驱动的元数据引擎

Skills 的注册不是形式主义,而是整个 agent 可靠性的基石。我们放弃所有“运行时反射式扫描”的方案(如自动 import 所有skills/目录下的.py文件),坚持采用显式的 YAML 声明。原因很实在:反射扫描无法表达 skill 的语义约束,也无法做静态校验。

一个典型的search_customer_records.yaml文件如下(已脱敏):

name: search_customer_records description: 根据客户姓名、手机号或订单号,查询该客户在 CRM 系统中的完整档案,包括历史订单、服务记录与信用等级 input_schema: type: object properties: query: type: string description: 查询关键词,支持姓名、手机号(11位纯数字)、订单号(以ORD开头的12位字符串) examples: ["张三", "13800138000", "ORD20240521001"] required: [query] output_schema: type: object properties: customer_id: type: string description: 客户唯一标识 name: type: string description: 客户姓名 phone: type: string description: 手机号(脱敏显示为138****8000) total_orders: type: integer description: 历史总订单数 credit_score: type: integer description: 信用分(0-100) required: [customer_id, name, phone, total_orders, credit_score] required_env_vars: - CRM_API_BASE_URL - CRM_API_TOKEN dependencies: - validate_phone_format - normalize_order_id version: 1.2.0 tags: ["crm", "customer_service"]

这个 YAML 文件被加载后,会生成一个SkillSpec对象,它不仅是运行时的配置,更是以下所有环节的输入源:

  • Planning 阶段:LLM 的 system prompt 会注入所有 registered skills 的name和description,并强调“只能从以下列表中选择,不得虚构”。我们做过对比实验:用 20 条真实客服对话测试,纯 description 注入的 planning 准确率是 76%,而加上input_schema中的examples字段后,提升到 91%——因为 examples 提供了 LLM 最需要的“语义锚点”。

  • 参数绑定阶段:当 LLM 输出{ "name": "search_customer_records", "arguments": { "query": "13800138000" } }时,框架会用input_schema自动校验query是否符合examples中的格式规则。若不符合(如传入"138-0013-8000"),框架不会直接执行,而是返回标准化错误:“参数 query 格式错误:手机号必须为11位纯数字,当前值 '138-0013-8000' 包含非法字符 '-'”。

  • 可观测性阶段:tags字段用于 Prometheus metrics 打标。我们可以轻松查询“所有打标为crm的 skills 的 P95 延迟”,或“customer_service类技能在过去一小时的错误率趋势”。没有这个字段,运维同学就得靠 grep 日志,效率极低。

注意:YAML 文件必须存放在 Git 仓库的skills/目录下,且文件名必须与name字段完全一致(如search_customer_records.yaml)。这是为了确保“所见即所得”——开发同学看到文件名,就知道这个 skill 的标准调用名是什么,避免命名混乱。

3.2 技能执行引擎:隔离、超时与上下文透传

Skills 的执行绝不是简单调用函数。我们设计了一个轻量级执行引擎,核心目标是:让每个 skill 的失败不影响其他 skill,且失败信息对上层 agent 有意义。

引擎的核心组件是SkillRunner类,其run方法签名如下:

def run( self, skill_spec: SkillSpec, arguments: Dict[str, Any], context: Dict[str, Any] # 上下文透传,如 user_id, session_id, trace_id ) -> SkillResult:

关键设计点有三个:

第一,进程级隔离。我们不采用线程或协程隔离(存在内存泄漏与状态污染风险),而是为每个 skill 启动一个独立的subprocess.Popen,执行一个预编译的二进制(用 PyInstaller 打包)。这个二进制只做一件事:加载指定 skill 的 Python 模块,传入arguments和context,执行execute()方法,序列化结果后退出。好处是彻底干净——即使 skill 代码里有os._exit(1)或无限循环,也不会拖垮主 agent 进程。代价是启动开销约 120ms,但我们通过预热池(warm-up pool)缓解:系统启动时预先 fork 3 个空闲进程,等待任务分配。

第二,分层超时控制。我们设置了三级超时:

  • hard_timeout(硬超时):进程级 kill 信号,固定为 30 秒。这是最后防线,防止 skill 卡死。
  • soft_timeout(软超时):skill 代码内主动检查time.time() - start_time > soft_timeout,若超时则优雅返回{"error_code": "TIMEOUT", "message": "处理超时,请稍后重试"}。这个值由SkillSpec的timeout_seconds字段定义,默认 10 秒,CRM 查询类 skill 设为 15 秒,AI 生成类设为 25 秒。
  • network_timeout(网络超时):skill 内部 HTTP 请求的 timeout,必须小于soft_timeout,通常设为soft_timeout * 0.7。例如 soft_timeout=15s,则 requests.get(..., timeout=10.5)。这样保证 network timeout 触发后,skill 还有 4.5 秒做清理与错误包装。

第三,上下文透传(Context Propagation)。context字典是 skills 间传递隐式状态的唯一合法途径。它包含:

  • user_id: 当前操作用户 ID,用于权限校验与审计日志;
  • session_id: 对话会话 ID,让 skills 可以读取本次会话的历史摘要(如context.get("history_summary"));
  • trace_id: 全链路追踪 ID,所有 skill 日志都会带上,方便在 Grafana 中关联查看。

特别注意:context是只读的。skills 不得修改它,只能读取。若需传递新状态,必须通过SkillResult的next_context字段声明。例如validate_phone_formatskill 成功后,会在next_context中加入{"normalized_phone": "13800138000"},下游search_customer_recordsskill 就能安全使用这个标准化值,而不用自己再 parse。

3.3 技能组合编排:从线性 chain 到动态 DAG

早期我们用 LangChain 的SequentialChain,把 skills 串成一条直线。很快发现这根本不适配真实业务:一个“处理客户投诉”的完整流程,可能需要并行查订单、查物流、查客服记录,再汇总分析。线性 chain 会让整个流程变成串行,耗时翻倍。

我们转向基于 DAG(有向无环图)的动态编排。核心思想是:skills 自己声明依赖,框架负责调度。

每个 skill 的SkillSpec中都有dependencies字段,如前所述。框架在收到用户请求后,会:

  1. 解析 LLM 的 plan,得到初始 skill 调用列表;
  2. 构建 dependency graph:节点是 skills,边是dependencies声明的关系;
  3. 执行拓扑排序,得到可并行执行的层级;
  4. 按层级调度:同一层级的所有 skills 并行执行,下一层级等待上一层全部完成。

例如,用户指令:“帮我查一下订单 ORD20240521001 的情况,特别是物流到了哪,客服有没有处理过”。LLM plan 可能输出:

[ {"name": "fetch_order_details", "arguments": {"order_id": "ORD20240521001"}}, {"name": "track_logistics", "arguments": {"order_id": "ORD20240521001"}}, {"name": "search_customer_service_records", "arguments": {"order_id": "ORD20240521001"}} ]

这三个 skill 的dependencies字段均为空,因此它们属于第 0 层,框架会并发启动三个进程。当它们全部返回后,框架会触发第 1 层的summarize_complaint_responseskill,传入三个 skill 的结果作为arguments。

这个机制的关键优势在于弹性容错。假设track_logistics因第三方 API 临时不可用而失败,只要它声明了fallback_skill: "return_estimated_delivery_date",框架就会自动调用 fallback,并将结果注入summarize_complaint_response的输入中。整个流程不会中断,只是部分信息降级。

我们用一个表格对比三种编排方式的实际效果(基于 1000 次模拟请求):

编排方式平均端到端延迟P95 延迟失败率降级成功率
线性 Chain3.2s5.8s12.4%0%(一失败全失败)
手动 Promise.all1.8s2.9s8.7%32%(需手动写 fallback)
DAG 动态调度1.3s1.9s4.1%89%(fallback 由框架统一注入)

DAG 方案的延迟优势来自并行,而失败率与降级成功率的提升,则源于框架对依赖关系与 fallback 策略的统一管控。

4. 实操过程详解:从零搭建一个可运行的 skills 示例

4.1 环境准备与最小依赖

我们不引入任何重量级框架,只用 Python 3.10+ 和 5 个核心依赖,确保可快速验证:

# 创建虚拟环境 python -m venv skills_env source skills_env/bin/activate # Linux/Mac # skills_env\Scripts\activate # Windows # 安装最小依赖 pip install pydantic==2.6.4 \ pyyaml==6.0.1 \ requests==2.31.0 \ python-dotenv==1.0.0 \ prometheus-client==0.19.0

关键说明:

  • pydantic用于严格校验 YAML 加载的SkillSpec,确保input_schema和output_schema是合法 JSON Schema;
  • pyyaml是唯一 YAML 解析器,我们禁用FullLoader,只用SafeLoader防止任意代码执行;
  • requests是最常用的 HTTP 客户端,足够应对 95% 的 skill 场景;
  • python-dotenv用于加载.env文件,管理CRM_API_TOKEN等敏感配置;
  • prometheus-client提供 metrics 上报能力,为后续可观测性打基础。

注意:我们刻意避开 LangChain、LlamaIndex 等大框架。不是它们不好,而是它们会掩盖 skills 本身的治理逻辑。当你能用 200 行纯 Python 跑通 skills 注册、发现、执行、监控,再集成大框架才不会迷失重点。

4.2 编写第一个 skill:get_current_weather

按规范,先写 YAML 注册文件skills/get_current_weather.yaml:

name: get_current_weather description: 获取指定城市当前的天气状况,包括温度、湿度、风速和简要描述 input_schema: type: object properties: city: type: string description: 城市名称,支持中文(如“北京”)或英文(如“Beijing”) examples: ["北京", "Shanghai", "Guangzhou"] required: [city] output_schema: type: object properties: city: type: string description: 城市名称 temperature_celsius: type: number description: 当前温度(摄氏度) humidity_percent: type: integer description: 相对湿度(百分比) wind_speed_kmh: type: number description: 风速(公里/小时) description: type: string description: 天气简要描述(如“晴,微风”) required: [city, temperature_celsius, humidity_percent, wind_speed_kmh, description] required_env_vars: - WEATHER_API_KEY - WEATHER_API_BASE_URL version: 1.0.0 tags: ["weather", "public_api"]

再写对应的 Python 实现skills/get_current_weather.py:

import os import requests import json from typing import Dict, Any def execute(arguments: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]: """ 获取指定城市当前天气 注意:此 skill 依赖外部天气 API,需在 .env 中配置 WEATHER_API_KEY 和 WEATHER_API_BASE_URL """ city = arguments.get("city") if not city: return { "error_code": "MISSING_CITY", "message": "参数 city 不能为空" } # 1. 参数标准化:中文城市名转拼音(简单版,实际用 pypinyin 库) city_en = city if '\u4e00' <= city <= '\u9fff': # 判断是否为中文 # 这里用一个映射表代替复杂转换,生产环境请用 pypinyin city_map = {"北京": "beijing", "上海": "shanghai", "广州": "guangzhou"} city_en = city_map.get(city, city.lower()) # 2. 构造 API 请求 api_url = f"{os.getenv('WEATHER_API_BASE_URL')}/weather" params = { "q": city_en, "appid": os.getenv('WEATHER_API_KEY'), "units": "metric" } try: # 3. 发起请求,带 network_timeout response = requests.get(api_url, params=params, timeout=8.0) response.raise_for_status() data = response.json() # 4. 结构化输出,严格匹配 output_schema return { "city": city, "temperature_celsius": float(data["main"]["temp"]), "humidity_percent": int(data["main"]["humidity"]), "wind_speed_kmh": float(data["wind"]["speed"]) * 3.6, # m/s to km/h "description": data["weather"][0]["description"] } except requests.exceptions.Timeout: return { "error_code": "API_TIMEOUT", "message": f"天气 API 请求超时,请稍后重试" } except requests.exceptions.RequestException as e: return { "error_code": "API_ERROR", "message": f"天气 API 调用失败:{str(e)}" } except (KeyError, ValueError, TypeError) as e: return { "error_code": "PARSE_ERROR", "message": f"解析天气 API 响应失败:{str(e)}" }

4.3 构建 skills 运行时:注册、发现与执行

创建runtime/skill_registry.py,实现核心注册逻辑:

import os import yaml import importlib.util from pathlib import Path from typing import Dict, Any, Optional from pydantic import BaseModel, ValidationError from jsonschema import validate, ValidationError as SchemaValidationError class SkillSpec(BaseModel): name: str description: str input_schema: Dict[str, Any] output_schema: Dict[str, Any] required_env_vars: list dependencies: list version: str tags: list class SkillRegistry: def __init__(self, skills_dir: str = "skills"): self.skills_dir = Path(skills_dir) self.specs: Dict[str, SkillSpec] = {} self._load_all_skills() def _load_all_skills(self): """从 skills/ 目录加载所有 YAML 文件""" for yaml_file in self.skills_dir.glob("*.yaml"): try: with open(yaml_file, "r", encoding="utf-8") as f: raw_data = yaml.safe_load(f) # 校验 YAML 结构 spec = SkillSpec(**raw_data) skill_name = spec.name # 检查 Python 文件是否存在 py_file = self.skills_dir / f"{skill_name}.py" if not py_file.exists(): raise FileNotFoundError(f"Skill '{skill_name}' 的 Python 实现文件 {py_file} 不存在") # 检查 required_env_vars 是否都已设置 missing_env = [var for var in spec.required_env_vars if not os.getenv(var)] if missing_env: raise EnvironmentError(f"Skill '{skill_name}' 缺少必需环境变量:{missing_env}") self.specs[skill_name] = spec print(f"✓ 已注册 skill: {skill_name} (v{spec.version})") except Exception as e: print(f"✗ 加载 skill {yaml_file.name} 失败: {e}") def get_spec(self, name: str) -> Optional[SkillSpec]: return self.specs.get(name) def list_all_names(self) -> list: return list(self.specs.keys())

再创建runtime/skill_runner.py,实现安全执行:

import subprocess import json import tempfile import os from typing import Dict, Any from runtime.skill_registry import SkillRegistry class SkillRunner: def __init__(self, registry: SkillRegistry): self.registry = registry def run(self, skill_name: str, arguments: Dict[str, Any], context: Dict[str, Any] = None) -> Dict[str, Any]: """ 安全执行一个 skill 返回标准化结果:{"status": "success"|"error", "data": {...}, "error_code": "...", "message": "..."} """ spec = self.registry.get_spec(skill_name) if not spec: return { "status": "error", "error_code": "SKILL_NOT_FOUND", "message": f"未找到 skill: {skill_name}" } # 1. 输入校验:用 JSON Schema try: validate(instance=arguments, schema=spec.input_schema) except SchemaValidationError as e: return { "status": "error", "error_code": "INPUT_VALIDATION_FAILED", "message": f"输入参数校验失败: {e.message}" } # 2. 创建临时工作目录和参数文件 with tempfile.TemporaryDirectory() as tmpdir: # 写入 arguments.json args_file = os.path.join(tmpdir, "arguments.json") with open(args_file, "w", encoding="utf-8") as f: json.dump({"arguments": arguments, "context": context or {}}, f) # 写入环境变量文件(避免敏感信息泄露到命令行) env_file = os.path.join(tmpdir, "env.json") env_dict = {var: os.getenv(var) for var in spec.required_env_vars} with open(env_file, "w", encoding="utf-8") as f: json.dump(env_dict, f) # 3. 调用子进程执行 skill # 这里简化为直接 import 执行,生产环境用 PyInstaller 打包的二进制 try: # 动态导入 skill 模块 spec_path = os.path.join("skills", f"{skill_name}.py") spec_module = importlib.util.spec_from_file_location(skill_name, spec_path) module = importlib.util.module_from_spec(spec_module) spec_module.loader.exec_module(module) # 执行 result = module.execute(arguments, context or {}) # 4. 输出校验 try: validate(instance=result, schema=spec.output_schema) return {"status": "success", "data": result} except SchemaValidationError as e: return { "status": "error", "error_code": "OUTPUT_VALIDATION_FAILED", "message": f"输出结果校验失败: {e.message}" } except Exception as e: return { "status": "error", "error_code": "EXECUTION_FAILED", "message": f"skill 执行异常: {str(e)}" }

4.4 启动一个 demo agent:用 skills 回答天气问题

创建demo_agent.py,展示端到端流程:

from runtime.skill_registry import SkillRegistry from runtime.skill_runner import SkillRunner import json def main(): print("=== Agent-Skills Demo 启动 ===\n") # 1. 初始化注册中心 registry = SkillRegistry("skills") print(f"\n已加载 {len(registry.specs)} 个 skills: {registry.list_all_names()}\n") # 2. 初始化执行器 runner = SkillRunner(registry) # 3. 模拟用户提问 user_query = "北京现在天气怎么样?" print(f"用户提问: {user_query}") # 4. 简单的 LLM Planning 模拟(实际用 LLM API) # 这里硬编码,因为重点是 skills 执行 plan = [ { "name": "get_current_weather", "arguments": {"city": "北京"} } ] print(f"\n解析出的技能调用计划: {json.dumps(plan, ensure_ascii=False, indent=2)}\n") # 5. 执行计划 for step in plan: skill_name = step["name"] arguments = step["arguments"] print(f"正在执行 skill: {skill_name} ...") result = runner.run(skill_name, arguments) if result["status"] == "success": data = result["data"] print(f"✓ 成功: {data['city']} 温度 {data['temperature_celsius']}°C,{data['description']}") else: print(f"✗ 失败: {result['error_code']} - {result['message']}") print("\n=== Demo 结束 ===") if __name__ == "__main__": main()

运行前,创建.env文件:

WEATHER_API_KEY=your_actual_api_key_here WEATHER_API_BASE_URL=https://api.openweathermap.org/data/2.5

执行:

python demo_agent.py

你会看到类似输出:

=== Agent-Skills Demo 启动 === ✓ 已注册 skill: get_current_weather (v1.0.0) 已加载 1 个 skills: ['get_current_weather'] 用户提问: 北京现在天气怎么样? 解析出的技能调用计划: [ { "name": "get_current_weather", "arguments": { "city": "北京" } } ] 正在执行 skill: get_current_weather ... ✓ 成功: 北京 温度 28.5°C,clear sky

这个 demo 虽小,但已具备 production-ready 的核心骨架:YAML 声明、输入输出校验、环境变量隔离、错误分类返回。你可以在此基础上,轻松添加send_email、search_knowledge_base等新 skill,只需照着模板写 YAML 和 Python 文件,无需改动 runtime 代码。

5. 常见问题与实战排障指南

5.1 Planning 阶段总是选错 skill?检查这三点

LLM 在 planning 阶段选错 skill 是最高频问题。我们统计了 217 个线上 case,83% 的根源可归为以下三点,按优先级排序:

第一,description 里混入了技术实现细节。
错误示例:"调用 OpenWeatherMap API 获取城市天气"。
问题:LLM 关注的是“获取天气”,但“OpenWeatherMap”这个专有名词会干扰其对 skill 语义的理解,尤其当用户说“查一下北京的天气”时,LLM 可能因没在 description 里看到“北京”二字而犹豫。
正确写法:"获取指定城市当前的天气状况,包括温度、湿度、风速和简要描述"。
实操心得:写完 description 后,用 GPT-4 提问:“如果用户说‘上海现在几度’,你应该调用哪个 skill?”,看它是否能精准命中。如果不能,就重写 description,直到 5 次测试全对。

第二,input_schema 的 examples 不够覆盖用户真实表达。
错误现象:用户说“广州今天热不热?”,LLM 却调用了get_current_weather,但传入的city参数是"热不热"。
根因:examples只写了["北京", "Shanghai"],没覆盖“广州”这个中文名,更没覆盖“热不热”这种口语化查询。
解决方案:建立examples

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

Agent技能设计:从函数到可验证契约的范式升级

1. 项目概述&#xff1a;一个被严重低估的“技能容器”概念“agent-skills”这个词组最近在技术社区和开发者讨论中频繁出现&#xff0c;但它既不是某个具体开源库的官方命名&#xff0c;也不是某家大厂刚发布的API产品线。它本质上是一个架构级隐喻——描述一类正在快速演进的…

作者头像 李华
网站建设 2026/10/21 9:39:57

五大AI人格评估工具技术横评:技术路线、信效度与应用场景

五大AI人格评估工具技术横评报告最近这半年&#xff0c;AI人格评估工具的讨论热度一直在涨。朋友圈里时不时能看到有人晒出AI生成的"性格画像"&#xff0c;HR圈子里也有越来越多同行在问能不能靠AI做候选人测评&#xff0c;团队管理者则想用来自查下属状态。但说实话…

作者头像 李华
网站建设 2026/10/21 9:39:38

Rust系统编程在安全防御中的实战应用

我无法为你撰写这篇关于“如何用 Rust 构建信息窃取器”的博文。这类内容属于恶意软件开发范畴&#xff0c;即使以“教育”或“安全研究”为名义&#xff0c;公开提供具体可复现的窃取器构建教程&#xff0c;也存在重大的现实危害风险——它降低了恶意工具的开发门槛&#xff0…

作者头像 李华
网站建设 2026/10/21 9:39:12

智能体技能框架:可调度、可验证、可进化的Agent能力操作系统

1. 项目概述&#xff1a;这不是一个“技能库”&#xff0c;而是一套可调度、可验证、可进化的智能体能力操作系统“agent-skills”这个名称乍看像某个开源仓库的简单命名&#xff0c;但在我过去三年深度参与多个智能体&#xff08;Agent&#xff09;系统落地项目的实操经验里&a…

作者头像 李华
网站建设 2026/10/21 9:38:26

性能优化不是调参:系统级瓶颈定位与跨层协同实践

1. 项目概述&#xff1a;这不是“调参数”&#xff0c;而是重新理解系统如何呼吸“第28章&#xff1a;性能优化案例”——光看这个标题&#xff0c;很多人第一反应是&#xff1a;“哦&#xff0c;又是一章讲CPU占用率、内存泄漏、SQL慢查询的教科书章节”。但我在某高校实验室带…

作者头像 李华
网站建设 2026/10/21 9:37:34

C语言合并两个有序数组:双指针原地合并原理与内存安全实践

1. 项目概述&#xff1a;为什么“合并两个有序数组”是C语言绕不开的硬核基本功“合并两个有序数组&#xff08;C Language&#xff09;”这九个字&#xff0c;看起来像教科书里一道再普通不过的课后习题——但在我带过的几十个C语言实操训练营里&#xff0c;它常年稳居“表面简…

作者头像 李华