1. 项目概述:Agent-Skills 不是插件,而是能力调度中枢
“Agent-Skills”这个词最近在开发者圈子里频繁刷屏,但很多人一搜就懵——它既不是某个具体开源库的官方名称,也不是某家大厂发布的标准产品,而是一类面向智能体(Agent)的能力封装范式的统称。我从去年底开始系统性地落地多个 Agent 架构项目,从金融风控助手到内部知识运维机器人,核心痛点从来不是模型多强,而是“让 Agent 真正干成事”的那一层能力组织逻辑。而“Agent-Skills”正是这个环节的实践结晶:它把 API 调用、CLI 工具执行、文件操作、数据库查询、甚至人工审批流程,全部抽象为可注册、可发现、可编排、可审计的“技能单元”(Skill)。你不需要写一堆 if-else 判断该调哪个接口,也不用硬编码路径去执行 shell 命令;你只需要告诉 Agent:“我要查用户余额”,它自动匹配bank_balance_query这个 Skill,并注入参数、执行、返回结构化结果——整个过程对上层逻辑完全透明。
这背后解决的是 Agent 工程化落地中最顽固的三类问题:第一是能力碎片化——每个业务线都有一堆零散脚本和内部 API,没人统一管理;第二是调用黑盒化——前端调 CLI 工具要记命令、参数、环境变量,出错连日志都找不到源头;第三是权限失控化——一个能执行rm -rf /的 CLI Skill 如果没做沙箱隔离,就是生产事故定时炸弹。所以,“Agent-Skills”本质是一套轻量级能力治理协议,它不绑定任何 LLM 框架,不强制要求你用 LangChain 或 LlamaIndex,甚至不用改一行模型推理代码——你只需定义 Skill 的元信息(名称、描述、输入 Schema、执行入口)、实现其执行逻辑(Python 函数、Shell 脚本、HTTP 请求封装),再注册进 Skill Registry,Agent 就能基于自然语言指令自动路由、安全执行、错误重试。我团队目前维护的 47 个 Skills 中,32 个是封装内部 HTTP API,9 个是封装 CLI 工具(如git,jq,curl,kubectl),6 个是纯 Python 逻辑(如日期计算、JSON 格式校验、敏感词过滤)。它们共用同一套注册机制、同一套参数校验规则、同一套执行上下文隔离策略,这才是真正让 Agent “稳下来、管得住、扩得开”的底层支点。
2. 核心设计逻辑:为什么必须绕开“插件思维”,走向“技能契约”
2.1 插件(Plugin)与技能(Skill)的本质区别
很多初学者看到 “Agent-Skills” 第一反应是:“哦,不就是 LangChain 的 Tool 或 OpenAI 的 Function Calling 吗?”——这是最危险的认知偏差。插件(Plugin)是宿主驱动的被动加载机制:你得先装好插件包,配置好 manifest.json,等宿主启动时扫描目录、加载类、注册方法。它天然带有耦合性——LangChain 的 Tool 必须继承BaseTool,OpenAI 的 Function 必须符合 JSON Schema 规范,Claude 的 Tool Use 必须走特定 message role。一旦换框架,整套插件就得重写。而 Skill 是契约驱动的主动声明机制:它不关心你用什么框架、什么模型、什么运行时,只约定三件事——“我是谁”(name + description)、“我能做什么”(input_schema + output_schema)、“我怎么被调用”(executor + runtime_context)。我举个真实例子:我们有个jira_issue_searchSkill,它的 executor 是一个纯 Python 函数:
def jira_issue_search(query: str, project: str = "OPS") -> List[Dict]: url = f"https://jira.internal/rest/api/3/search" params = {"jql": f"project = {project} AND text ~ '{query}'"} headers = {"Authorization": f"Bearer {os.getenv('JIRA_TOKEN')}"} resp = requests.get(url, params=params, headers=headers, timeout=30) resp.raise_for_status() return resp.json().get("issues", [])这个函数本身没有任何框架依赖。它被注册进 Skill Registry 时,只传入:
- name:
"jira_issue_search" - description:
"在 Jira 中按关键词搜索工单,支持指定项目" - input_schema:
{"query": "string", "project": "string"} - executor:
jira_issue_search - runtime_context:
{"timeout": 30, "max_retries": 2}
Agent 在解析用户说“帮我找下上周关于数据库慢查的工单”时,会基于 description 做语义匹配(我们用 Sentence-BERT 微调的小模型),识别出最相关 Skill 是jira_issue_search,再根据 input_schema 自动提取query="数据库慢查"、project="DBA",最后调用 executor 执行。整个过程不涉及任何框架 API,不依赖任何 SDK,甚至不关心 executor 是 Python 函数、Bash 脚本还是远程 gRPC 服务。这才是 Skill 的灵魂:解耦于运行时,统一于契约。
2.2 CLI 技能为何成为高频刚需?——终端即能力入口
为什么热词里 “CLI” 和 “slash commands” 高频出现?因为 CLI 是企业内部最成熟、最广泛、最无需教育成本的能力载体。运维有kubectl,开发有git,数据有spark-sql,安全有nmap,财务有sap-cli……这些工具已经稳定运行十年以上,文档齐全、权限清晰、日志完备。强行把它们重写成 REST API,不仅成本高,还引入新故障点。而 Skill 的价值,就是让 Agent 直接“学会用命令行”。我们封装kubectl get pods -n default为k8s_list_podsSkill 时,不是简单地os.system(),而是做了三层加固:
- 参数白名单校验:
k8s_list_pods只允许namespace、label_selector、field_selector三个参数,且namespace必须来自预设列表(["default", "monitoring", "logging"]),禁止传入--kubeconfig或-o yaml等危险选项; - 执行沙箱隔离:所有 CLI Skill 默认在 Docker 容器中运行(镜像基于
alpine:latest+ 预装必要二进制),容器挂载只读/etc/resolv.conf和最小必要 volume,网络仅允许访问内网 K8s API Server; - 输出结构化归一:无论
kubectl返回 raw text 还是 JSON,Skill executor 内部统一用json.loads()解析,失败则捕获subprocess.CalledProcessError并返回标准化错误码(如ERR_K8S_PERMISSION_DENIED),供 Agent 上层做重试或降级。
实测下来,这种封装方式比重写 API 快 5 倍,稳定性提升 3 个 9(P999)。更重要的是,它让一线工程师能直接贡献 Skills:运维小哥写个 Bash 脚本封装df -h查磁盘,提交 PR 到 Skills 仓库,CI 自动构建镜像、跑单元测试、更新 Registry,第二天 Agent 就能听懂“服务器磁盘快满了”并自动告警。这种低门槛、高复用、强管控的模式,才是 CLI Skills 成为基建核心的真实原因。
2.3 Slash Commands:从 Slack 到全平台的技能触发协议
Slash Commands(斜杠命令)常被误解为 Slack 专属功能,其实它是一种轻量级人机交互协议。它的本质是:用户输入/xxx [args]→ 客户端截获 → 发送 HTTP POST 到预设 Webhook → 服务端执行逻辑 → 返回富文本响应。这个协议之所以被 Skills 体系广泛采用,是因为它完美契合“意图明确、参数简洁、反馈即时”的技能调用场景。我们内部所有 Skills 都默认支持 Slash Command 接口,比如/db-query "SELECT COUNT(*) FROM users WHERE created_at > '2024-01-01'",Agent 收到后不做 NLU 解析,直接按/db-query匹配 Skill 名,按空格分割参数,注入sql字段执行。相比纯自然语言,“/db-query” 这种显式前缀消除了歧义(避免把“查用户数”误判为“查用户列表”),也规避了 LLM token 浪费(不用让模型理解“查”=“SELECT”、“用户数”=“COUNT(*)”)。
更关键的是,Slash Command 天然支持跨平台迁移。我们同一套 Skills 后端,通过不同 Adapter 接入:
- Slack:用 Slack App 的 Slash Command 配置,Webhook URL 指向 Skills Gateway;
- 飞书:配置自定义机器人,接收
/command格式消息; - 内部 Web UI:前端拦截
<input>的keydown.enter,检测/开头,调用 Skills API; - CLI 终端:
codex cli工具内置codex /db-query ...命令,直连本地 Skills Server。
这意味着,一个 Skill 只需写一次 executor,就能在所有触点生效。我们上线第一个/jira-createSkill 时,运维、开发、产品三个角色当天就各自用上了:运维在 Slack 里/jira-create '修复登录页 500 错误',开发在终端里codex /jira-create --assignee dev-team,产品在后台页面点按钮触发。这种“一次开发,全端可用”的体验,远超任何单一框架的 Plugin 生态。
3. 实操落地:从零搭建可生产级的 Agent-Skills 系统
3.1 技术选型决策树:为什么选 FastAPI + SQLite + Docker,而不是 LangChain + Redis + Kubernetes?
很多团队一上来就想搞“高大上”,用 LangChain 做 Orchestrator,Redis 存 Registry,K8s 部署 Skills。我带队踩过这个坑——三个月没跑通一个稳定流程。根本原因是:Skills 的核心诉求是确定性,不是扩展性。一个 Skill 的执行必须满足:100ms 内返回、99.99% 成功率、参数校验无遗漏、错误信息可追溯。而 LangChain 的 Tool Chain 有太多隐式状态(memory、callback handler、agent_executor),Redis 的序列化可能丢失类型信息,K8s 的 Pod 启动延迟让 CLI 执行超时。所以我们反其道而行,选择极简栈:
- API 层:FastAPI —— 自动生成 OpenAPI 文档,Pydantic Schema 校验开箱即用,async 支持高并发 CLI 调用;
- Registry 层:SQLite —— 单文件、零配置、ACID 事务、支持 FTS5 全文搜索(用于 Skill description 匹配),比 Redis 更可靠;
- 执行层:Docker CLI + Alpine 镜像 —— 每个 Skill 对应一个独立镜像,
docker run --rm --network host --read-only ...保证隔离; - Agent 集成层:纯 HTTP Client —— Agent 用
requests.post("http://skills-gateway/skill/jira_search", json={...})调用,不引入任何 SDK。
这套组合的优势在于:部署只需pip install fastapi uvicorn+docker pull alpine,调试时直接curl测试 Skill,上线后用systemctl管理进程,连 Prometheus 都不用接——我们用 FastAPI 的@app.middleware("http")记录每个 Skill 的耗时、成功率、错误码,存到 SQLite 的metrics表,每天凌晨用sqlite3 skills.db ".dump metrics"备份。实测 200 QPS 下 P99 延迟 87ms,错误率 0.02%,比 LangChain + Redis 方案稳定 10 倍。记住:对于 Skills,少即是多,确定性压倒一切。
3.2 Skill 开发全流程:以/github-pr-review为例
我们以一个真实 Skills:/github-pr-review(自动获取 PR 详情并生成评审建议)为例,走一遍完整开发流程。这个 Skill 要实现:接收 PR URL,调 GitHub API 获取 diff,用 LLM 分析代码变更,返回结构化评审意见。
Step 1:定义 Skill 元信息(skills/github_pr_review/skill.yaml)
name: github_pr_review description: "分析 GitHub Pull Request 的代码变更,生成安全与质量评审建议" input_schema: pr_url: type: string description: "GitHub PR 的完整 URL,如 https://github.com/org/repo/pull/123" model: type: string default: "qwen2.5-7b" enum: ["qwen2.5-7b", "deepseek-coder-32b", "llama3-70b"] output_schema: summary: type: string description: "PR 的一句话摘要" security_issues: type: array items: type: object properties: file: {type: string} line: {type: integer} issue: {type: string} quality_suggestions: type: array items: type: string runtime_context: timeout: 120 max_retries: 1 docker_image: "ghcr.io/ourorg/skills-github:latest"提示:YAML 是唯一允许的元信息格式,因为它天然支持注释、枚举、默认值,比 JSON 更适合人类编辑。所有字段都经过 Pydantic 模型校验,缺失
pr_url会直接 422 返回。
Step 2:编写 Executor(skills/github_pr_review/executor.py)
import re import requests from typing import Dict, List, Any def github_pr_review(pr_url: str, model: str = "qwen2.5-7b") -> Dict[str, Any]: # 1. 解析 URL 获取 owner/repo/number match = re.match(r"https://github\.com/([^/]+)/([^/]+)/pull/(\d+)", pr_url) if not match: raise ValueError("Invalid PR URL format") owner, repo, number = match.groups() # 2. 调 GitHub API 获取 PR 详情和 files headers = {"Authorization": f"token {os.getenv('GITHUB_TOKEN')}"} pr_resp = requests.get( f"https://api.github.com/repos/{owner}/{repo}/pulls/{number}", headers=headers, timeout=30 ) pr_resp.raise_for_status() pr_data = pr_resp.json() files_resp = requests.get( pr_data["url"] + "/files", headers=headers, timeout=30 ) files_resp.raise_for_status() files = files_resp.json() # 3. 提取 diff 内容(简化版,实际用 git diff --no-index) diffs = [] for f in files[:5]: # 限制最多分析 5 个文件 if f["patch"]: diffs.append(f["filename"] + "\n" + f["patch"]) # 4. 调用本地 LLM API(非外部服务,避免 key 泄露) llm_resp = requests.post( "http://localhost:8000/v1/chat/completions", json={ "model": model, "messages": [{ "role": "user", "content": f"请分析以下 GitHub PR 的代码变更,指出安全风险和质量改进建议:\n{''.join(diffs)}" }] }, timeout=90 ) llm_resp.raise_for_status() llm_result = llm_resp.json() # 5. 解析 LLM 输出为结构化 JSON(用正则兜底,不依赖 schema) try: # 假设 LLM 返回 Markdown 格式,用正则提取 content = llm_result["choices"][0]["message"]["content"] summary = re.search(r"## 摘要\n(.*?)(\n##|$)", content, re.DOTALL).group(1).strip() security = re.findall(r"- \*\*文件\*\*: (.*?)\n\s+- \*\*行号\*\*: (\d+)\n\s+- \*\*问题\*\*: (.*?)\n", content) quality = re.findall(r"- (.*?)(?=\n- |\Z)", content.split("## 质量建议")[1]) return { "summary": summary, "security_issues": [{"file": s[0], "line": int(s[1]), "issue": s[2]} for s in security], "quality_suggestions": quality } except Exception as e: raise RuntimeError(f"LLM output parsing failed: {e}")注意:Executor 必须是纯函数,无全局状态,所有依赖(requests、re)都应在函数内导入。我们禁用
import os,改用os.getenv()是为了兼容 Docker 环境变量注入,但实际部署时GITHUB_TOKEN由 Skills Gateway 注入,Executor 里不硬编码。
Step 3:构建 Docker 镜像(skills/github_pr_review/Dockerfile)
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["uvicorn", "executor:app", "--host", "0.0.0.0:8000", "--port", "8000"]requirements.txt只含requests==2.31.0,绝不引入langchain、openai等重型包。镜像大小控制在 120MB 以内,docker build -t ghcr.io/ourorg/skills-github:latest .一键构建。
Step 4:注册到 Registry(skills/gateway/register.py)
from skill_registry import SkillRegistry from skills.github_pr_review.skill import skill_config registry = SkillRegistry(db_path="/data/skills.db") registry.register(skill_config) # 自动解析 YAML,校验 Schema,存入 SQLite运行python skills/gateway/register.py,Registry 会将 Skill 元信息写入skills.db的skills表,并建立 FTS5 全文索引。此时/github-pr-review就已可被 Agent 调用。
3.3 Agent 集成:如何让 LLM “看懂” Skill 并正确调用
Agent 调用 Skill 的关键,在于让 LLM 理解 Skill 的能力边界和调用语法。我们不用 Function Calling 的 JSON Schema,而是用更鲁棒的“自然语言描述 + 示例”提示工程:
System Prompt 片段:
你是一个技能调度 Agent,可调用以下技能: - github_pr_review: 分析 GitHub PR 的代码变更,生成安全与质量评审建议。调用格式:/github-pr-review "https://github.com/org/repo/pull/123" --model qwen2.5-7b - jira_issue_search: 在 Jira 中按关键词搜索工单。调用格式:/jira-issue-search "数据库慢查" --project DBA - k8s_list_pods: 列出 Kubernetes Pod。调用格式:/k8s-list-pods --namespace monitoring 请严格按格式调用,不要添加额外文字。如果用户请求超出技能范围,请回复“暂不支持该操作”。User Prompt 示例:
帮我看看这个 PR 有没有安全问题:https://github.com/myorg/backend/pull/456LLM 输出(必须严格匹配):
/github-pr-review "https://github.com/myorg/backend/pull/456"我们训练了一个轻量级分类模型(DistilBERT 微调),专门判断用户输入是否匹配某个 Skill 的 description。当匹配度 > 0.85 时,才触发 Slash Command 调用;否则走 fallback 流程。这样既避免 LLM 幻觉乱调,又保留自然语言的灵活性。实测在 1000 条测试 query 中,Skill 调用准确率 98.2%,错误调用率 0.3%(主要是 URL 格式错误,由 input_schema 校验拦截)。
4. 生产级避坑指南:那些只有踩过才懂的细节
4.1 CLI Skill 的三大隐形陷阱与解决方案
陷阱一:Shell 注入(Shell Injection)你以为subprocess.run(["ls", "-l", user_input])很安全?错。如果user_input = "..; rm -rf /",ls会报错,但;后的命令仍会被 shell 解释执行。我们所有 CLI Skill 都强制使用shell=False(默认),且参数必须是 list,绝不用shell=True。对于必须用 shell 的场景(如管道|),我们用shlex.quote()严格转义:
import shlex user_path = "../; rm -rf /" safe_cmd = f"ls -l {shlex.quote(user_path)} | head -20" subprocess.run(safe_cmd, shell=True, capture_output=True, timeout=30)陷阱二:环境变量泄露CLI 工具常依赖HOME、PATH、AWS_PROFILE等环境变量。如果 Skills 容器共享宿主机环境,kubectl可能用错 kubeconfig。我们的解法是:Docker run 时显式传入最小集环境变量:
docker run --rm \ -e "PATH=/usr/local/bin:/usr/bin:/bin" \ -e "HOME=/tmp" \ -v /etc/ssl/certs:/etc/ssl/certs:ro \ ghcr.io/ourorg/skills-k8s:latest所有敏感变量(如KUBECONFIG)由 Skills Gateway 注入,Executor 内绝不读取os.environ。
陷阱三:信号处理失效CLI 进程被SIGTERM杀死时,若没正确处理,可能留下僵尸进程或未释放锁。我们在所有 Executor 的main()函数里加信号钩子:
import signal import sys def signal_handler(signum, frame): print(f"Received signal {signum}, cleaning up...") # 关闭数据库连接、释放文件锁等 sys.exit(0) signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGINT, signal_handler)4.2 API Skill 的熔断与降级实战
内部 API 不稳定是常态。我们给每个 API Skill 配置三级熔断:
- 一级(客户端校验):FastAPI 的 Pydantic
Field(..., min_length=1)拦截空参数; - 二级(网关限流):Skills Gateway 用
slowapi限流,@limiter.limit("10/minute")防止刷爆下游; - 三级(服务端熔断):Executor 内用
tenacity库实现指数退避重试:
from tenacity import retry, stop_after_attempt, wait_exponential @retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10), reraise=True ) def call_internal_api(url: str) -> dict: resp = requests.get(url, timeout=10) if resp.status_code == 429: raise Exception("Rate limited") resp.raise_for_status() return resp.json()降级策略更关键:当jira_issue_search熔断时,Agent 不报错,而是返回缓存结果(SQLite 的cache表,TTL 5 分钟)或 fallback 文本:“Jira 服务暂不可用,已为您记录需求,恢复后将自动处理”。
4.3 Slash Command 的安全性加固清单
Slack/飞书的 Slash Command Webhook 是公开 URL,极易被恶意调用。我们强制四层防护:
- Token 校验:每个 Webhook 配置唯一
SLASH_COMMAND_TOKEN,Gateway 收到请求后if request.headers.get("X-Slack-Signature") != expected:拒绝; - IP 白名单:Cloudflare Firewall 规则只放行 Slack/飞书官方 IP 段(如 Slack 的
192.168.3.11/22); - 参数签名:对
/github-pr-review的pr_url参数,用 HMAC-SHA256 签名,Executor 验证签名有效性; - 审计日志:所有 Slash Command 调用记录
user_id、channel_id、command、args、ip、timestamp到 SQLite,支持溯源。
曾有一次,外部爬虫批量请求/db-query,因缺少X-Slack-Signature头被第一层拦截,日志显示 237 次失败请求,0 次穿透。安全不是靠运气,是靠层层设防。
5. 常见问题速查表与独家调试技巧
| 问题现象 | 根本原因 | 排查步骤 | 解决方案 | 我的实操心得 |
|---|---|---|---|---|
Skill 调用返回 500,日志显示ConnectionRefusedError | Docker 容器未启动或端口未暴露 | 1.docker ps | grep skills-github看容器是否 running2. docker logs <container_id>查启动错误3. docker exec -it <container_id> curl http://localhost:8000/health测试内部连通性 | 在 Dockerfile 添加 `HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 CMD curl -f http://localhost:8000/health | |
Agent 总是调用错误 Skill,比如把/jira-create当成/jira-search | Description 语义相似度过高,FTS5 索引权重不合理 | 1.sqlite3 skills.db "SELECT name, description FROM skills;"查所有 description2. sqlite3 skills.db "SELECT * FROM skills WHERE skills MATCH 'create OR new';"测试全文检索3. 调整 skill_registry.py中的rank函数,对create、new等动词加权 | 在 description 末尾加区分性短语,如jira_create的 description 改为 "...创建新工单(动词:create)",jira_search改为 "...搜索已有工单(动词:search)" | FTS5 的bm25算法对动词不敏感,必须人工强化关键词。我们用sqlite-utils工具批量更新 description,效率提升 10 倍。 |
CLI Skill 执行超时,但docker run手动测试很快 | 容器网络策略限制,DNS 解析慢 | 1.docker run --rm -it --network host alpine nslookup api.github.com测试 DNS2. docker run --rm -it --network host alpine ping -c 3 api.github.com测试连通性3. docker inspect <container_id> | grep -A 10 NetworkSettings查网络配置 | 在 Docker run 命令中显式指定 DNS:--dns 10.0.0.2 --dns 8.8.8.8,或在/etc/docker/daemon.json配置全局 DNS | 内网 DNS 服务器有时响应慢,导致curl卡在 DNS 阶段。我们统一用 CoreDNS 集群,所有 Skills 容器指向10.96.0.10。 |
Slash Command 在飞书正常,在 Slack 报invalid_auth | Slack App 的 Bot Token 权限不足或过期 | 1. 进入 Slack App Settings → OAuth & Permissions → Bot Token Scopes 2. 确认已勾选 chat:write、commands3. 在 credentials.py中检查SLACK_BOT_TOKEN是否为最新 | 每次 Slack App 更新权限,必须重新安装 App 到 Workspace,Bot Token 会刷新。我们用 Terraform 管理 Slack App 配置,每次变更自动更新密钥 | Slack 的 Token 管理是黑洞,建议用 HashiCorp Vault 存储,CI/CD 流程中自动注入。别手动生成,容易漏。 |
Skill 执行成功,但 Agent 返回null结果 | Executor 返回值未被 Skills Gateway 正确序列化 | 1.curl -X POST http://localhost:8000/skill/github_pr_review -d '{"pr_url":"..."}'直接调用 Gateway2. 查 Gateway 日志,看是否 json.dumps(result)报错3. 检查 Executor 返回的 dict 是否含不可序列化对象(如 datetime、numpy.ndarray) | 所有 Executor 返回值必须是 JSON-native 类型(str, int, float, list, dict, bool, None)。含 datetime 的字段用.isoformat()转字符串;含 bytes 的用.decode()。 | 我们写了个json_safe_dump()工具函数,自动递归转换,集成到 Gateway 的 response middleware 中,一劳永逸。 |
注意:所有调试技巧都源于真实生产事故。比如那个
datetime问题,我们花了 6 小时定位,最后发现是pr_data["created_at"]是datetime对象,json.dumps()直接抛TypeError。从此立下铁律:Executor 的 return value 必须是dict,且所有值都过一遍json.dumps(value)测试。
6. 进阶演进:从 Skills 到 Skill Graph 的能力网络
当 Skills 数量超过 50,单纯靠 description 匹配就力不从心了。我们正在落地Skill Graph——把每个 Skill 当作图节点,用属性边(property edge)和关系边(relation edge)连接:
- 属性边:
github_pr_review→has_input→pr_url(类型 string); - 关系边:
github_pr_review→requires→github_api_token(Secret); - 关系边:
jira_issue_search→outputs_to→confluence_page(另一个 Skill)。
构建方式很简单:在skill.yaml中新增relations字段:
relations: requires: - secret: github_api_token - secret: jira_basic_auth outputs_to: - skill: confluence_create_page - skill: slack_post_messageSkills Gateway 启动时,用 NetworkX 构建图谱,Agent 调用前先做图遍历:如果github_pr_review需要github_api_token,而当前会话未提供,则自动跳转到认证流程;如果github_pr_review的输出格式匹配confluence_create_page的输入格式,则可自动编排为 pipeline。这已不是简单的“调用”,而是“能力编织”(Capability Orchestration)。
目前我们 Graph 中有 127 个节点、302 条边,自动化编排率从 12% 提升到 68%。最典型的场景是“发布新版本”:用户说/release v1.2.0,Agent 自动触发git_tag_create→build_docker_image→push_to_registry→deploy_to_k8s→post_slack_announcement全链路,无需人工干预。这不是 AI 的胜利,而是 Skills 作为能力原子,被图谱精准调度的结果。
我个人在实际操作中的体会是:Agent-Skills 的价值,从来不在“炫技”,而在“让复杂变简单,让不可控变可控”。它不追求模型多大,而追求每一次调用都可预期、可审计、可回滚。当你不再为“Agent 调错了 API”而半夜救火,当你能用/db-query三秒查出线上 bug 根因,当你看到运维小哥自己提交的disk_usage_alertSkill 被全公司调用——你就知道,这套看似朴素的契约,正在静默地重塑人与机器协作的底层逻辑。