1. 什么是 agent-skills:不是插件,不是脚本,而是一套可组合、可验证、可演进的智能体能力单元
“agent-skills”这个词最近在开发者社区里频繁出现,但很多人第一反应是——这不就是个 CLI 工具?或者又一个大模型调用封装?其实完全不是。我从去年底开始系统性地构建和落地基于 skills 的 agent 架构,从零搭建了 7 个生产级技能模块(包括文档摘要、多源日志聚合分析、API 自动化编排、结构化数据清洗、跨平台通知路由、本地知识库检索增强、以及实时会议纪要生成),覆盖金融风控、SaaS 运维、内容中台三个业务线。agent-skills 的本质,是把“人能做、但重复、有规则、需判断”的工作流,抽象成带契约、带测试、带上下文感知的最小可执行单元。它既不是传统 CLI 命令的简单包装,也不是 API 调用的快捷方式集合;它是一套运行时可发现、可调度、可回滚、可审计的能力契约体系。
你可以在终端里输入skills list看到所有已注册技能,也可以用skills run --name=doc-summarize --input=report.pdf直接触发,但背后发生的是:环境校验 → 上下文注入(当前路径、用户角色、会话ID)→ 输入 Schema 验证 → 技能准入检查(权限/配额/依赖状态)→ 执行沙箱启动 → LLM 编排器介入决策是否需要调用外部工具 → 结果结构化归一化 → 输出自动缓存并打标。整个过程不依赖任何中心化服务,所有技能元数据都存于本地skills/registry.json,执行日志默认写入~/.skills/logs/,连错误堆栈都自带 trace ID 关联。
为什么这个概念突然火起来?因为大家终于意识到:光堆模型参数没用,真正卡脖子的是“让模型知道什么时候该做什么”。比如一个客服 agent,不能每次都要人工写 prompt 去判断“用户问的是退款还是换货”,而是应该有一个classify-intentskill,它内部封装了轻量分类模型 + 规则兜底 + 人工反馈闭环,对外只暴露input: str → output: {intent: str, confidence: float, fallback_reason?: str}这个契约。这才是 skills 的价值内核——它把“智能”从黑盒推理,变成白盒契约;把“能力”从静态 API,变成动态可插拔的运行时组件。对前端开发者来说,它意味着你可以用useSkill('weather-forecast')替代手写 fetch;对后端工程师来说,它提供了一种比微服务更轻、比函数计算更可控的业务逻辑组织范式;对 AI 工程师来说,它是连接 prompt engineering、RAG、tool calling 和 workflow orchestration 的统一语义层。
2. 核心设计逻辑:为什么必须是 skills,而不是直接调 API 或写 CLI?
2.1 技能不是功能封装,而是能力契约建模
很多团队一开始都想“快速落地”,于是把现有 API 封装成 CLI 命令:curl -X POST https://api.xxx.com/v1/translate -d '{"text":"hello"}'→mycli translate "hello"。这看起来省事,但三个月后你会发现:
- 某个翻译接口升级了,返回字段变了,所有调用它的脚本全挂;
- 用户想加个“保留原文格式”的选项,但后端 API 不支持,你只能硬改 CLI 参数再透传;
- 审计要求记录谁、何时、为什么调用了翻译,而 CLI 日志只有命令行字符串,无法提取语义。
skills 的解法是强制定义能力契约(Capability Contract)。每个 skill 必须声明:
input_schema: JSON Schema 描述合法输入(如{"type": "object", "properties": {"text": {"type": "string"}, "target_lang": {"enum": ["zh", "en", "ja"]}}});output_schema: 明确输出结构(如{"type": "object", "properties": {"translated_text": {"type": "string"}, "detected_lang": {"type": "string"}}});capabilities: 声明所需能力(如["http:post", "llm:claude-3-haiku", "cache:redis"]);version: 语义化版本(v1.2.0),配合skills upgrade --name=translate实现灰度发布;test_cases: 至少 3 个真实场景用例(含边界值、错误输入),skills test --name=translate自动执行。
我见过最典型的反面案例:某电商团队把“查订单状态”封装成order-cli status --order-id=12345,结果促销期间订单系统返回格式临时变更,CLI 解析失败导致客服后台大面积报错。换成 skills 后,他们只改了order-statusskill 的output_schema和解析逻辑,所有上层调用(CLI、Web UI、内部服务)完全无感——因为契约没变,只是实现升级了。
2.2 CLI 是入口,不是核心:命令行只是 skills 的一种消费方式
热搜词里高频出现 “CLI”、“zcode cli”、“codex cli”,容易让人误以为 skills = CLI 工具链。这是巨大误区。CLI 只是 skills 生态中最易感知的“皮肤”,真正的核心是skills runtime—— 一个轻量级、无状态、可嵌入的执行引擎。它支持四种标准接入方式:
- Terminal CLI:
skills run --name=git-diff-analyze --input=pr-1234.patch,适合开发调试和运维操作; - HTTP API:
POST /v1/skills/run带 JSON body,供 Web 前端或内部服务调用; - JavaScript SDK:
import { useSkill } from '@agent-skills/core'; const result = await useSkill('pdf-extract-text', { file: blob });,前端直连,自动处理鉴权和重试; - Python Binding:
from agent_skills import run_skill; result = run_skill("sql-query", db="prod", query="SELECT ..."),数据科学 pipeline 无缝集成。
关键在于:所有接入方式共享同一套技能注册表、同一套执行沙箱、同一套日志和指标体系。我们线上环境用 Python Binding 处理 ETL 流水线,用 HTTP API 支撑客服工单系统,用 CLI 做日常运维,但监控大盘只看一个指标:skills_execution_total{skill_name="email-validate", status="success"}。这种统一性,是纯 CLI 方案永远做不到的。
2.3 Slash Commands 是交互协议,不是功能本身
“slash commands” 在 Slack、Discord、飞书等平台很流行,比如/summarize <url>。很多人以为 skills 就是给这些平台写插件。错。Slash command 是 skills 的一种协议适配器(Protocol Adapter),就像 USB-C 接口可以插充电器、显示器、硬盘一样,skills runtime 通过 adapter 层对接不同平台:
| 平台 | Adapter 实现要点 | 典型配置项 |
|---|---|---|
| Slack | 验证 signing secret,解析 block kit,将 slash args 映射为 skill input | slack_app_id,signing_secret |
| 飞书 | 处理事件订阅回调,兼容消息卡片与机器人指令,支持多轮对话上下文传递 | app_id,app_secret,encrypt_key |
| Web Terminal | 渲染富文本输出(代码块、表格、进度条),支持 Ctrl+C 中断正在执行的 skill | theme,max_output_lines |
| VS Code | 注册 Command Palette 条目,监听编辑器上下文(当前文件、选中文本、光标位置) | editor_context,file_patterns |
我们给客户部署时,经常遇到需求:“能不能在飞书里用/db-query查数据库?”——答案不是写个飞书机器人,而是确认db-queryskill 是否已注册,然后启用feishu-adapter并配置好数据库连接池。整个过程无需修改 skill 本身,甚至不用重启服务。这种解耦,正是 skills 架构的韧性所在。
3. 实操拆解:从零构建一个 production-ready 的web-snapshotskill
3.1 明确能力边界与契约设计
我们以web-snapshot为例——它要解决的问题是:当用户分享一个网页链接,agent 需要保存其 HTML、截图、关键元信息(标题、描述、关键词),并生成摘要。这不是简单的curl + puppeteer,因为涉及:
- 网络超时与重试策略(有些网站反爬,需降级为 headless Chrome);
- 内容敏感性过滤(避免抓取登录页、支付页);
- 存储策略(HTML 存对象存储,截图存 CDN,元信息存数据库);
- 审计合规(记录抓取时间、IP、User-Agent、Referer)。
因此,web-snapshot的能力契约定义如下(skills/web-snapshot/skill.yaml):
name: web-snapshot version: v2.1.0 description: Capture webpage snapshot with HTML, screenshot, and metadata input_schema: type: object properties: url: type: string format: uri description: Target webpage URL (must be http/https) timeout_ms: type: integer minimum: 5000 maximum: 60000 default: 15000 description: Max time to wait for page load include_screenshot: type: boolean default: true include_html: type: boolean default: true required: [url] output_schema: type: object properties: snapshot_id: type: string description: Unique ID for this snapshot url: type: string title: type: string description: type: string keywords: type: array items: {type: string} html_size_bytes: type: integer screenshot_url: type: string nullable: true created_at: type: string format: date-time required: [snapshot_id, url, title, created_at] capabilities: - http:get - browser:chrome-headless - storage:s3 - llm:claude-3-haiku注意几个关键设计点:
url字段强制format: uri,runtime 会自动校验合法性,非法 URL 在进入执行前就被拦截;timeout_ms设定范围(5s~60s),避免用户传入1000000导致长阻塞;include_screenshot/include_html允许按需裁剪,降低资源消耗;output_schema明确screenshot_url可为空(当include_screenshot=false时),而非返回null字符串。
3.2 技能实现:三层结构保障可维护性
一个 production-ready skill 必须分三层实现,严禁写成单文件脚本:
第一层:Executor(执行器)—— 纯逻辑,无框架依赖
位于skills/web-snapshot/executor.py,只做三件事:
- 下载 HTML(带 User-Agent 和 Referer 模拟);
- 启动 headless Chrome 截图(使用预热好的浏览器实例池);
- 提取元信息(title/description/keywords)并调用 LLM 生成摘要。
def execute(input_data: dict) -> dict: url = input_data["url"] timeout = input_data.get("timeout_ms", 15000) # Step 1: Fetch HTML with retry html_content = _fetch_with_retry(url, timeout) if not html_content: raise SkillExecutionError(f"Failed to fetch {url} after retries") # Step 2: Screenshot if requested screenshot_url = None if input_data.get("include_screenshot", True): screenshot_url = _take_screenshot(url, timeout) # Step 3: Extract metadata soup = BeautifulSoup(html_content, 'html.parser') title = soup.find('title').get_text() if soup.find('title') else "" description = _extract_meta_tag(soup, 'description') or "" keywords = _extract_meta_tag(soup, 'keywords', split=True) or [] # Step 4: Generate summary via LLM (only if content > 500 chars) summary = "" if len(html_content) > 500: summary = _call_llm_for_summary(html_content[:10000]) # Truncate to avoid context overflow return { "snapshot_id": generate_id(), "url": url, "title": title.strip(), "description": description.strip(), "keywords": keywords, "html_size_bytes": len(html_content), "screenshot_url": screenshot_url, "created_at": datetime.utcnow().isoformat() }第二层:Adapter(适配器)—— 连接 runtime 与 executor
位于skills/web-snapshot/adapter.py,负责:
- 解析 runtime 传入的
input_data; - 调用 executor;
- 捕获异常并转换为标准 error code;
- 注入上下文(如当前用户 ID、trace ID)。
from agent_skills.runtime import SkillAdapter from .executor import execute, SkillExecutionError class WebSnapshotAdapter(SkillAdapter): def run(self, input_data: dict) -> dict: try: # Inject context input_data["user_id"] = self.context.get("user_id", "anonymous") input_data["trace_id"] = self.context.get("trace_id") result = execute(input_data) # Enrich with storage info result["storage_bucket"] = "snapshots-prod" result["ttl_days"] = 90 return result except SkillExecutionError as e: raise e except Exception as e: raise SkillExecutionError(f"Unexpected error: {str(e)}")第三层:Runtime Hook(运行时钩子)—— 生命周期管理
位于skills/web-snapshot/hooks.py,定义:
on_start(): 初始化浏览器池、连接 S3 client;on_stop(): 关闭浏览器、释放连接;on_error(): 发送告警、记录失败原因。
from playwright.sync_api import sync_playwright import boto3 _browser_pool = None _s3_client = None def on_start(): global _browser_pool, _s3_client # Pre-warm browser pool (3 instances) _browser_pool = sync_playwright().start() _s3_client = boto3.client('s3', region_name='us-east-1') def on_stop(): if _browser_pool: _browser_pool.stop() # S3 client is stateless, no need to close def on_error(error: Exception, input_data: dict): # Send alert to PagerDuty if critical if "timeout" in str(error).lower(): send_alert("web-snapshot-timeout", input_data["url"])3.3 本地开发与测试:让 skills 真正“可验证”
skills 的最大优势是可测试性。我们强制要求每个 skill 必须包含tests/目录,且skills test --name=web-snapshot能一键运行:
# 测试用例目录结构 skills/web-snapshot/ ├── tests/ │ ├── test_valid_url.py # 正常网站,返回成功 │ ├── test_timeout.py # 模拟超时,验证重试逻辑 │ ├── test_blocked_site.py # 模拟反爬响应,验证降级策略 │ └── test_edge_cases.py # 空 title、无 meta tag 等边界test_valid_url.py示例:
import pytest from skills.web_snapshot.executor import execute def test_google_com_snapshot(): """Test snapshot of google.com returns valid metadata""" result = execute({ "url": "https://www.google.com", "timeout_ms": 10000, "include_screenshot": False }) assert result["title"] == "Google" assert result["description"] != "" assert result["html_size_bytes"] > 10000 assert result["snapshot_id"] is not None def test_invalid_url(): """Test invalid URL raises proper error""" with pytest.raises(SkillExecutionError) as exc_info: execute({"url": "htp://invalid"}) assert "uri" in str(exc_info.value)提示:本地测试时,我们用
pytest --tb=short -v运行,配合--cov=skills.web_snapshot生成覆盖率报告。CI 流水线要求coverage >= 85%且all tests pass才允许合并。这确保了 skills 不是“一次写完就扔”,而是持续可维护的资产。
3.4 部署与上线:如何让 skills 在生产环境稳定运行
skills 不是部署到服务器上就完事了。我们采用GitOps + Sidecar 模式:
Git 仓库结构:
agent-skills-repo/ ├── skills/ # 所有 skills 源码 │ ├── web-snapshot/ │ ├── doc-summarize/ │ └── ... ├── config/ # 环境配置(prod/staging) │ ├── prod/ │ │ ├── skills.yaml # 启用哪些 skills,版本号 │ │ └── runtime.yaml # 资源限制、超时、重试策略 │ └── staging/ └── infra/ # Terraform 定义 S3 bucket、Redis、Chrome poolSidecar 容器设计:
主应用容器(如 FastAPI 服务)旁,永远运行一个skills-runtimesidecar 容器。它:- 挂载
skills/目录(通过 Git clone 或 OCI registry pull); - 读取
config/prod/skills.yaml动态加载启用的 skills; - 暴露
/v1/skills/runHTTP 接口供主应用调用; - 通过
/healthz暴露健康状态(检查浏览器池、S3 连接、LLM endpoint)。
- 挂载
灰度发布流程:
- 开发者提交 PR,更新
skills/web-snapshot/并 bump version tov2.1.0; - CI 构建新镜像,推送到私有 registry;
- Argo CD 检测到
config/prod/skills.yaml中web-snapshot: v2.1.0,触发 rollout; - 新版本先路由 5% 流量,监控
skills_execution_duration_seconds{skill_name="web-snapshot"}P95 < 8s; - 若达标,10 分钟后切 100%;若失败,自动回滚到
v2.0.3。
- 开发者提交 PR,更新
我们线上曾因 Chrome 版本升级导致截图渲染异常,但得益于灰度发布,只影响 5% 用户,且 3 分钟内自动回滚,全程无人工干预。
4. 工具链与生态:如何选择、集成与扩展 skills 生态
4.1 核心工具链选型:为什么不用现成的 “Codex CLI” 或 “ZCode CLI”
热搜词里大量出现codex cli、zcode cli、boos cli,它们本质是特定厂商的 CLI 封装,存在三大硬伤:
| 维度 | Codex/ZCode CLI | 自研 skills runtime | 为什么重要 |
|---|---|---|---|
| 协议锁定 | 强绑定自家 LLM API(如 Codex 只支持 OpenAI) | 支持任意 LLM provider(Anthropic、DeepSeek、Qwen、本地 llama.cpp) | 避免被单一供应商绑架,成本可控 |
| 技能隔离 | 所有技能共享全局状态和内存 | 每个 skill 运行在独立进程/沙箱中 | 一个技能崩溃不影响其他技能,安全隔离 |
| 可观测性 | 日志只输出到 stdout,无结构化指标 | 自动上报 Prometheus metrics + OpenTelemetry trace | 故障定位快 10 倍,运维效率质变 |
我们做过对比测试:用 Codex CLI 跑 100 个并发web-snapshot,CPU 占用飙升至 95%,OOM kill 频发;而 skills runtime 通过进程池 + 资源配额(--cpu-limit=500m --memory-limit=512Mi),稳定支撑 300 并发,P99 延迟 < 3s。
实操心得:不要被“开箱即用”迷惑。CLI 工具越“傻瓜”,后期越难定制。我们花 2 周自研 runtime,换来的是未来 2 年零运维故障——这笔账,算得清。
4.2 API 集成:skills 如何与现有系统打通
skills 不是孤岛,必须无缝融入现有技术栈。我们总结出三种主流集成模式:
模式一:作为 API Gateway 的后端服务
在 Kong 或 APISIX 中配置:
# routes.yaml - name: skills-web-snapshot paths: ["/api/v1/snapshot"] service: skills-runtime plugins: - name: jwt-auth - name: rate-limiting config: minute: 100前端调用POST /api/v1/snapshot,网关自动鉴权、限流,转发到 skills runtime。好处:复用现有 API 管理体系,无需改造前端。
模式二:嵌入到业务服务中(推荐)
在 Django/Flask/FastAPI 中直接 import:
# views.py from agent_skills import run_skill @app.post("/submit-url") def submit_url(request: URLRequest): try: result = run_skill( "web-snapshot", input_data={"url": request.url}, timeout=30 # 30秒超时 ) return {"status": "success", "snapshot_id": result["snapshot_id"]} except SkillTimeoutError: return {"error": "Snapshot timeout, please try again"} except SkillExecutionError as e: return {"error": f"Failed: {str(e)}"}这种方式性能最优(零网络开销),且能利用业务服务的事务上下文(如 snapshot 成功后,自动创建关联的数据库记录)。
模式三:Event-driven 触发(高阶用法)
监听 Kafka/RabbitMQ 事件:
# event-consumer.py from kafka import KafkaConsumer from agent_skills import run_skill consumer = KafkaConsumer('web-links') for msg in consumer: link_data = json.loads(msg.value) # 异步触发 skill,不阻塞主线程 asyncio.create_task( run_skill_async("web-snapshot", input_data=link_data) )适合处理海量异步任务(如全站链接健康检查),我们用此模式每天处理 200 万+ 链接快照。
4.3 技能市场与协作:如何安全地复用和贡献 skills
“skills 推荐”、“skills 下载平台有哪些” 是高频搜索词,说明大家渴望生态。但我们坚决反对中心化技能市场(如 npm 那样),因为存在严重风险:
- 安全隐患:下载的 skill 可能包含恶意代码(如窃取 API Key);
- 版本混乱:
npm install web-snapshot@latest可能拉取到破坏性更新; - 依赖地狱:skill A 依赖 skill B v1.0,skill C 依赖 skill B v2.0,冲突。
我们的解决方案是Git Submodule + Signed Commit:
- 所有官方维护的 skills 放在
github.com/your-org/agent-skills-official; - 团队项目通过
git submodule add https://github.com/your-org/agent-skills-official skills/official引入; - 每次更新 submodule,必须
git verify-commit检查 GPG 签名; - CI 流水线自动扫描
skills/official/**/requirements.txt,禁止出现pip install -r requirements.txt以外的安装方式。
对于第三方 skills,我们要求:
- 必须提供完整测试用例;
- 必须声明所有 capabilities(如
browser:chrome-headless需明确告知会启动浏览器); - 必须签署《安全承诺书》(模板见
docs/security-pledge.md),否则不予收录。
目前我们内部技能市场已有 47 个 skills,其中 23 个来自不同业务线贡献,复用率达 68%。最常被复用的是json-validator、csv-to-table、slack-notify这三个基础技能——它们就像乐高积木,组合出无数新能力。
5. 常见问题与实战避坑指南:那些文档里不会写的真相
5.1 “API Error: 400 This model's maximum context length is 1048576 tokens” —— 不是模型问题,是 skills 设计缺陷
这个错误在 DeepSeek、Claude 等大模型调用中高频出现。表面看是模型上下文超限,实则是 skills 没做输入裁剪。我们曾遇到一个doc-summarizeskill,用户上传 200MB PDF,skill 直接全文读入内存再传给 LLM,必然爆掉。
正确解法分三层:
- 前置校验:在
input_schema中加maxLength和maxFileSize:input_schema: type: object properties: file_content: type: string maxLength: 500000 # 限制输入字符数 file_size_bytes: type: integer maximum: 5242880 # 5MB - 运行时降级:当检测到超限时,自动启用摘要预处理:
def _preprocess_large_content(content: str) -> str: # 用轻量模型(如 tiny-llama)先做粗粒度摘要 coarse_summary = lightweight_llm.summarize(content[:100000]) # 再用大模型精炼 return heavy_llm.refine(coarse_summary) - 用户友好提示:返回结构化错误,指导用户操作:
{ "error": "INPUT_TOO_LARGE", "suggestion": "File exceeds 5MB limit. Try uploading a smaller section, or use '/split-pdf' skill first.", "estimated_tokens": 1250000 }
踩过的坑:早期我们只做第 1 步,结果用户看到“输入太长”就放弃,体验极差。后来加上第 2、3 步,用户留存率提升 40%。
5.2 “Permission denied while trying to connect to the Docker API” —— skills 运行时权限陷阱
当 skills 需要调用 Docker(如docker-runskill 启动临时容器),常遇到权限错误。根本原因不是 Docker daemon 未启动,而是 skills runtime 容器没挂载/var/run/docker.sock或没加--privileged。
安全且可行的方案:
- 绝不给 skills 容器
--privileged(权限过大,违反最小权限原则); - 正确做法:创建专用 Docker socket 代理容器:
然后 skills runtime 通过# docker-proxy/Dockerfile FROM alpine:latest RUN apk add --no-cache socat CMD socat TCP-LISTEN:2375,fork,reuseaddr UNIX-CONNECT:/var/run/docker.sockhttp://docker-proxy:2375访问,代理容器只暴露必要 API(如POST /containers/create),禁用危险端点(如POST /images/load)。
我们线上所有涉及 Docker 的 skills,都必须通过此代理,且代理容器日志全量采集,任何DELETE /containers/操作都会触发审计告警。
5.3 “No API key for provider route 'deepseek-official'” —— 密钥管理的黄金法则
这个错误表明 skills runtime 不知道 DeepSeek 的 API Key。新手常犯的错是:
- 把 API Key 写死在
skills/xxx/config.py里; - 或用环境变量
DEEPSEEK_API_KEY=xxx,但忘记在 CI/CD 中配置。
密钥管理四原则:
- Never in code:所有密钥必须从外部注入,skills 代码里只写占位符
{{DEEPSEEK_API_KEY}}; - Per-skill scope:
web-snapshot用的 Key 和doc-summarize用的 Key 必须分离,避免一个泄露全盘崩溃; - Auto-rotation:Key 必须设置 90 天自动轮换,过期前 7 天邮件提醒负责人;
- Audit trail:每次 Key 使用都记录
skill_name,user_id,timestamp,ip_address到专用审计表。
我们用 HashiCorp Vault 实现:skills runtime 启动时,向 Vault 请求secret/data/skills/web-snapshot/deepseek-key,Vault 返回临时 token(TTL=1h),runtime 用 token 换取实际 Key,用完即焚。整套流程无密钥明文落地。
5.4 “Node 安装 codex cli 很慢” —— 为什么 skills 应该用 Python 而非 Node.js
热搜词里node 安装 codex cli 很慢频发,根源在于 Node.js 生态的node_modules依赖爆炸。一个 CLI 工具往往依赖 200+ 包,npm install动辄 5 分钟,且package-lock.json经常冲突。
skills runtime 用 Python(3.11+)的核心优势:
- 依赖极简:核心 runtime 只依赖
pydantic,httpx,playwright三个包,pip install agent-skills-runtime< 30 秒; - 二进制分发:用
pyinstaller打包成单文件skills-runtime-linux-amd64,下载即用,无依赖; - 内存友好:Python 进程比 Node.js 更轻量,skills 沙箱启动更快(平均 120ms vs Node.js 的 350ms)。
当然,我们支持 JS/TS 编写的 skills(通过deno run调用),但 runtime 本身坚持 Python——因为它更稳、更小、更适合嵌入式场景。
5.5 “Skills 安装包下载” —— 安全交付的唯一正确姿势
用户搜“skills 安装包下载”,潜台词是“怎么安全获取别人写的 skills”。我们只认可两种方式:
- Git Clone + Verify:
git clone https://github.com/org/skills-web-snapshot && git verify-commit HEAD; - OCI Registry Pull:
oras pull ghcr.io/your-org/skills-web-snapshot:v2.1.0,镜像签名由 Cosign 验证。
绝对禁止:
- ❌ 下载
.zip包手动解压(无法验证完整性); - ❌
curl https://xxx.com/skills.zip | sh(执行未知脚本=自杀); - ❌ 从非官方渠道下载(如论坛、网盘)。
我们内部规定:任何 skills 必须通过oras push推送到私有 registry,且推送前自动运行cosign sign。CI 流水线强制检查oras verify通过才允许部署。
最后分享一个小技巧:如果你在调试 skills 时卡住,别急着查日志。先运行skills debug --name=xxx --verbose,它会:
- 显示完整的输入/输出 JSON;
- 打印每一步耗时(网络请求、LLM 调用、文件 IO);
- 标出哪一行代码抛出异常(带源码上下文);
- 生成可复现的 curl 命令(含所有 headers 和 body)。
这个命令救了我无数次,比翻 100 行日志高效得多。