微信扫码
添加专属顾问
我要投稿
将本地 Agent Skill 包装成 API,让业务能力从个人工具升级为团队服务,实现真正的价值复用。 核心内容: 1. Skill API 化的核心价值与接口设计 2. LangGraph Agent Runtime 的关键实现原理 3. 从架构设计到测试落地的完整流程
前面我们聊过 Skill 是什么,也聊过怎么写一个可复用的 Skill。
但只在本地 Agent 里能用,其实还不够。
很多真正有价值的 Skill,最后都会遇到同一个问题:我已经把业务流程沉淀成 Skill 了,能不能让 Java 后端、小程序、内部系统也调用它?
比如你写了一个 weather-query Skill,里面已经定义好了:
本地 Agent 能跑通,说明这套流程是有价值的。但如果只有你自己的电脑能用,它就还是一个“个人工具”;如果能包装成 HTTP API,它才开始变成一个“团队能力”。
这篇文章就围绕这个问题展开:如何把我们写的 Skill,不只是给本地 Agent 调用,而是包装成 API,对外提供服务。
先看最终接口效果预览:
GET /query/stream?question=北京明天适合户外跑步吗?
服务端通过 SSE 持续推送事件,调用方只需要监听最终的 final 事件:
event: start
data: {"success":true,"message":"agent query started","request_id":"req_123"}
event: progress
data: {"message":"正在读取 Skill","step":"read_skill"}
event: final
data: {"success":true,"answer":"来源: Open-Meteo\n建议: 明天上午更适合跑步...","source":"langgraph-agent","error":null,"meta":{"elapsed_ms":12345}}
event: done
data: {"success":true}
后面我们就围绕这个接口,拆清楚为什么要这么设计、具体怎么实现,以及最后怎么测试通过。
本文目录:
一、为什么 Skill 需要 API 化
二、Skill 对外提供 API 的几种方式
三、LangGraph 到底是什么
四、为什么推荐 LangGraph Agent Runtime
五、整体架构怎么设计
六、关键实现细节和原理
七、一次请求是怎么跑完的
八、用一个例子测试通过
九、总结
先说结论:Skill 的价值不只是“提示词复用”,而是“业务能力复用”。
一个成熟的 Skill,通常不只是几段提示词。它往往包含三类东西:
Skill = 工作流程 + 参考资料 + 可执行脚本
以 weather-query 为例,它可能长这样:
skills/
└── weather-query/
├── SKILL.md
├── references/
│ └── open-meteo-api.md
└── scripts/
├── weather-api/
│ └── query.py
└── geocoding/
└── query.py
其中:
SKILL.md 是工作流说明,告诉 Agent 先做什么、后做什么;references/ 是上下文资料,比如接口文档、字段说明、口径规则;scripts/ 是确定性执行代码,比如请求天气接口、请求地理编码接口。这已经不是一个简单提示词了。它更像一个“会读文档、会调用工具、会组织回答的小型业务专家”。
问题来了:业务系统要怎么用它?
如果一个 Java 后端想回答“北京明天适合户外跑步吗?”,它并不想知道:
Java 后端最理想的调用方式是:
GET /query/stream?question=北京明天适合户外跑步吗?
然后通过 SSE 监听最终结果:
event: final
data: {"success":true,"answer":"来源: Open-Meteo\n查询意图: ...","source":"langgraph-agent","error":null}
event: done
data: {"success":true}
这就是 Skill API 化的核心目标:
把本来只能在 Agent 环境里触发的业务能力,封装成稳定的 HTTP 服务,让普通业务系统也能调用。
要把 Skill 变成 API,大体有三种路线。
最直观的方式是:既然本地 Codex 能调用 Skill,那就在服务器上也安装 Codex 或类似 Agent,然后 API 服务收到请求后,去驱动这个 Agent。
流程大概是:
调用方
|
v
HTTP 服务
|
v
服务器上的 Codex / Agent
|
v
加载本地 Skill
|
v
执行任务并返回结果
这个方案的优点是上手快。你本地怎么跑,服务器上大体也可以照着配置。
但它也有几个问题:
这里最大的风险是:Agent 本身不是为稳定 HTTP 服务边界设计的。
它能完成任务,但你还要额外处理很多服务化问题,比如并发请求、超时控制、日志、鉴权、文件权限、错误码转换等。
所以这个方案适合做验证,不一定适合长期承载业务接口。
第二种方式是更传统的后端思路:直接把 SKILL.md 里的流程翻译成 Java、Python 或 Go 代码。
比如:
如果问题命中天气查询
-> 先做城市地理编码
-> 再调天气预报接口
如果城市无法识别
-> 返回澄清问题
最后按固定模板给出天气建议
这个方案看起来很稳,因为它完全变成了普通工程代码。
但问题也很明显:你失去了 Skill 的灵活性。
Skill 原来最有价值的地方,是把“流程说明、接口文档、判断策略、输出要求”放在一个 Agent 能理解的工作包里。现在你把它重新写死在后端代码里,后续每次调整业务规则,都要改代码、发版、回归。
这就有点可惜了。
第三种方式,也是这篇文章重点讲的方式:
HTTP 服务本身不重写 Skill 业务流程,而是实现一个轻量 Agent Runtime。Runtime 读取完整 Skill 包,让 Agent 按
SKILL.md决策、读资料、调脚本、组织答案。
也就是说,我们不是把 Skill “翻译成代码”,而是把 Skill “放进一个可服务化的 Agent 运行时”。
整体感觉像这样:
调用方
|
v
FastAPI /query/stream
|
v
LangGraph Agent Runtime
|
v
读取 SKILL.md / references / scripts
|
v
调用脚本查询业务接口
|
v
持续返回 SSE 事件,最终通过 final 事件给出 answer
这个方案的关键点是:HTTP 服务只负责运行时能力,业务流程仍然留在 Skill 包里。
它既保留了 Skill 的可维护性,也把对外服务边界做清楚了。
说到这里,有些同学可能会问:LangGraph 是什么?它和 LangChain 又是什么关系?
先不用被名字吓到。通俗地说:
LangGraph 是一个用来搭建 Agent 工作流的编排框架。它把一次复杂的 AI 任务,拆成多个节点、状态和工具调用,再按一定路线跑完整个流程。
如果说普通的大模型调用像这样:
用户问题 -> 模型 -> 回答
那 LangGraph 更像这样:
用户问题
|
v
节点 A:理解意图
|
v
节点 B:选择工具
|
v
节点 C:调用外部脚本或接口
|
v
节点 D:汇总结果并回答
它的核心不是“多调几次模型”,而是把 Agent 的行动过程变得更可控。
一个典型 LangGraph 应用里,通常会有这几个概念:
为什么这对 Skill API 很重要?
因为 Skill API 不是简单的问答接口。它需要让 Agent 先读 SKILL.md,再按需读 references/,然后执行 scripts/,最后组织答案。
这天然就是一个多步骤流程。
如果只用普通的模型调用,你要自己维护这些步骤之间的状态和循环;用 LangGraph,就可以把这个过程变成一个明确的 Agent Runtime。
简单来说:
普通模型调用:适合一次问答
LangGraph:适合多步骤、有工具、有状态的 Agent 任务
这也是为什么本文会选择 LangGraph 来包装 Skill,而不是只写一个普通的 LLM 调用接口。
LangGraph 的价值不在于“多包装一层框架”,而在于它天然适合做 Agent 工作流。
一个 Skill API 服务至少需要这些能力:
这和 LangGraph 的 ReAct Agent 模式非常契合。
我们可以把 Skill 服务理解成一个小型运行时:
LangGraph Agent Runtime
├── 模型:负责理解问题、规划步骤、组织回答
├── 文件工具:负责读取 SKILL.md 和 references
├── 脚本工具:负责执行 skill/scripts 下的确定性代码
└── 控制层:负责超时、步数、错误和返回格式
这里有一个很重要的设计原则:
模型负责“判断和编排”,脚本负责“确定性执行”。
千万不要让模型直接猜接口参数,也不要让模型直接拼复杂业务请求。
更稳的方式是:
scripts/ 里;SKILL.md 里;这样边界才清楚。
根据 skill-api/README.md 里的方案,我们可以把整个系统拆成四层:
调用方(Java后端/小程序后端)
|
| HTTP GET /query/stream (SSE)
v
FastAPI Controller
|
| 参数校验、错误码转换
v
LangGraph Agent Service
|
| 读取 system prompt + 用户问题
v
LangGraph ReAct Agent
|
| tool call
+-----------------------------+
| |
v v
Skill 文件工具 Skill 脚本执行工具
read_skill_file run_skill_script
list_skill_files |
| v
v scripts/weather-api/query.py
SKILL.md / references/ scripts/geocoding/query.py
| |
+--------------+---------------+
v
Agent 汇总最终回答
|
v
FastAPI 持续返回 SSE 事件
|
v
调用方监听 final 事件并展示 answer
这张图里最关键的是两个工具:
read_skill_file:让 Agent 读取 Skill 包内的说明和参考资料;run_skill_script:让 Agent 执行 Skill 包内的脚本。也就是说,Agent 并不是凭空“脑补”答案,而是按 Skill 包里的资料和脚本行动。
这和传统 Java Service 可以类比:
/query/stream | |
run_agent_query | |
SKILL.mdreferences/ | |
scripts/weather-api/query.pyscripts/geocoding/query.py | |
区别在于:传统 Java Service 把流程写死在代码里;Skill API 把流程留在 SKILL.md,由 Agent 运行时读取执行。
接下来我们看几个真正落地时绕不开的细节。
不要只复制 SKILL.md。
一个可服务化的 Skill 应该作为完整目录部署:
skills/weather-query/
├── SKILL.md
├── references/
└── scripts/
原因很简单:Skill 不是孤立提示词。
SKILL.md 可能会要求 Agent 读取 references/open-meteo-api.md,也可能会要求执行 scripts/weather-api/query.py。如果只拿走一个 Markdown 文件,运行时就失去了上下文和执行能力。
所以部署时要保证:
SKILL.md、references/、scripts/ 在同一个 skill 根目录;WEATHER_SKILL_DIR 定位这个目录;如果只是把用户问题丢给模型,它可能会直接回答,而不是按 Skill 流程走。
所以在 run_agent_query 里,需要构造明确的 system prompt:
你必须使用 Agent Skills 架构。
处理用户问题前,先读取 SKILL.md。
如 SKILL.md 要求读取 references,请继续读取相关资料。
如需要访问业务数据,只能通过 run_skill_script 执行 scripts/ 下的脚本。
不要直接编造接口结果。
这段话的作用不是“装饰”,而是在约束 Agent 的执行路径。
模型可以推理,但不能绕开 Skill 包。
这是服务化后非常重要的安全细节。
如果你给 Agent 一个任意文件读取工具,它理论上可能读取到服务目录外的配置文件、密钥、日志等敏感内容。
正确做法是:文件工具只能访问 skill 根目录内的文件。
伪代码大概是:
def resolve_skill_path(relative_path: str) -> Path:
target = (SKILL_DIR / relative_path).resolve()
skill_root = SKILL_DIR.resolve()
try:
target.relative_to(skill_root)
except ValueError:
raise ValueError("只能读取 skill 目录内的文件")
return target
实际实现建议用 Path.relative_to 做目录包含判断,而不是简单判断字符串前缀。否则 /tmp/skill 和 /tmp/skill-evil 这类路径,很容易出现前缀误判。
这里的重点不是代码写法,而是原则:
Agent 能力越强,工具边界越要收紧。
同样,run_skill_script 不能变成一个万能 shell。
它应该只允许执行:
scripts/ 目录下的文件;.py 的脚本;stdout、stderr、returncode。执行结果可以像这样返回给 Agent:
{
"returncode": 0,
"stdout": "{\"items\":[...]}",
"stderr": ""
}
如果脚本报错,也不要直接把敏感信息原样返回。至少要对包含这些关键词的行做脱敏:
tokenauthorizationapi_keysecret这个细节很容易被忽略,但一旦服务对外提供,就必须考虑。
这个方案里有两类配置。
第一类是 Agent 模型配置:
{
"provider": "openai-compatible",
"model": "your-agent-model",
"base_url": "https://api.example.com/v1",
"wire_api": "chat_completions",
"api_key": "你的模型 API Key"
}
它影响的是:Agent 用哪个模型、怎么调用模型、温度多少、最多跑多少步。
第二类是业务接口配置:
WEATHER_API_URL
WEATHER_API_TIMEOUT
GEOCODING_API_URL
GEOCODING_API_TIMEOUT
GEOCODING_LANGUAGE
它影响的是:脚本如何访问天气预报和地理编码服务。
这两类配置不要混在一起。
Agent 模型配置
-> server/main.py
-> ChatOpenAI / LangGraph Agent
业务接口配置
-> skills/weather-query/scripts/*/config.json
-> query.py
-> Open-Meteo 天气 API / 地理编码 API
这样做的好处是很明显的:
我们把完整请求链路串起来。
用户从业务系统发起请求:
GET /query/stream?question=北京明天适合户外跑步吗?
第一步,FastAPI Controller 接收 SSE 请求,校验 question 不能为空。
第二步,Controller 调用 stream_agent_query(question),先向调用方推送 start 事件。
第三步,Service 检查 Skill 包:
skills/weather-query/SKILL.md 是否存在
skills/weather-query/scripts/ 是否存在
skills/weather-query/references/ 是否存在
第四步,Service 构造 system prompt,并启动 LangGraph Agent。
第五步,Agent 首先调用:
read_skill_file("SKILL.md")
第六步,Agent 根据 SKILL.md 判断:这个问题属于天气数据查询,需要读取接口说明。
于是继续调用:
read_skill_file("references/open-meteo-api.md")
第七步,Agent 决定先调用地理编码脚本,把城市名转换成经纬度:
run_skill_script(
"scripts/geocoding/query.py",
["北京"]
)
然后调用天气查询脚本:
run_skill_script(
"scripts/weather-api/query.py",
["--latitude", "39.90", "--longitude", "116.40", "--date", "tomorrow"]
)
第八步,如果天气 API 返回有效数据,Agent 按 Skill 要求组织回答。
如果城市无法识别,Agent 不继续猜测,而是返回澄清问题,比如“你说的是北京,中国,还是其他同名城市?”
第九步,Agent 输出最终回答。
第十步,FastAPI 通过 SSE 推送最终事件:
event: final
data: {"success":true,"answer":"来源: Open-Meteo\n查询意图: 北京明天户外跑步天气查询\n建议: 明天上午更适合跑步,注意风速和体感温度...","source":"langgraph-agent","error":null,"meta":{"skill_path":".../skills/weather-query","elapsed_ms":12345,"recursion_limit":30}}
event: done
data: {"success":true}
这就是一次完整闭环。
这里为什么要从普通 POST /query 改成 SSE?
核心原因是:Agent 任务不是普通数据库查询,它的执行时间和中间步骤都不稳定。
一次 Skill 调用可能要经历读 SKILL.md、读参考文档、调用脚本、等待外部 API、失败重试、再组织回答。同步 JSON 接口要等所有步骤结束后才返回,调用方在这段时间里只能干等。如果执行超过网关或浏览器的超时时间,还容易被误判为失败。
SSE 更适合这种场景:
start,告诉调用方任务已经开始;progress,告诉前端“正在读取 Skill”“正在调用脚本”;final,调用方拿到 answer 后展示;done,明确这次流式响应结束;error,错误也能用统一事件格式处理。它比 WebSocket 更轻量,也比普通同步接口更适合“服务端持续输出、客户端只负责监听”的 Agent 查询场景。
事件协议建议提前约定清楚:
start | successmessage、request_id | |
progress | messagestep | |
final | successanswer、source、error、meta | |
error | successerror、code、meta | |
done | success |
这里还有个坑,大家一定要注意:GET /query/stream?question=... 很适合演示和轻量调用,但生产环境要处理三个问题。
第一,question 必须做 URL 编码,中文、空格和特殊符号不能直接裸传。
第二,GET 参数通常会进入浏览器历史、网关日志和访问日志,所以不适合传敏感问题。
第三,URL 有长度限制。问题很长时,建议保留 SSE 思路,但改成 POST 流式响应;或者先创建任务拿到 query_id,再用 GET /query/stream?query_id=... 监听结果。
最后看怎么验证这个服务。
先安装依赖:
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
准备 Agent 配置:
cp config/agent.example.json config/agent.local.json
配置内容示例:
{
"provider": "openai-compatible",
"model": "your-agent-model",
"base_url": "https://api.example.com/v1",
"wire_api": "chat_completions",
"api_key": "你的模型 API Key"
}
然后启动服务:
HOST=0.0.0.0 PORT=8000 .venv/bin/python -m server.main
先检查健康状态:
curl http://127.0.0.1:8000/healthz
如果返回类似下面结果,说明运行时和 Skill 包路径都正常:
{
"success": true,
"status": "ok",
"skill_exists": true
}
再发起一次真实查询:
curl -N "http://127.0.0.1:8000/query/stream?question=北京明天适合户外跑步吗?"
期望返回:
event: start
data: {"success":true,"message":"agent query started"}
event: progress
data: {"message":"正在读取 Skill 并执行查询"}
event: final
data: {"success":true,"answer":"来源: Open-Meteo\n查询意图: 北京明天户外跑步天气查询\n建议: 明天上午更适合跑步,注意风速和体感温度...","source":"langgraph-agent","error":null,"meta":{"skill_path":".../skills/weather-query","elapsed_ms":12345,"recursion_limit":30}}
event: done
data: {"success":true}
如果你想做自动化测试,可以跑:
.venv/bin/python -m pytest -q
测试的重点不只是接口有没有返回 200,而是要覆盖这几件事:
/healthz 能检查 Skill 包是否存在;/query/stream 能拒绝空问题;SKILL.md;scripts/ 下的 Python 脚本;start、final、done,异常时能返回 error。做到这里,这个 Skill 就不再只是本地 Agent 的能力,而是一个可以被系统集成的 API 服务。
这篇文章我们把 Skill API 化完整走了一遍。
简单来说,核心就三点:
如果只是快速验证,可以在服务器上安装 Codex 这类 Agent 直接跑 Skill。
但如果要长期作为业务接口提供服务,更推荐把 Skill 放进一个清晰的 Agent Runtime 里,让它既保留 Skill 的灵活性,又具备后端服务应有的稳定边界。
这也是我理解的 Agent Skill 真正进入工程化阶段的一步:从“我本地能用”,走向“系统都能调用”。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2026-06-01
全是 Web 没 CLI 怎么行:一次把 StarAgent WebTerminal 改造成
2026-05-30
如何从零开发一个工业级的 SKILL
2026-05-30
3人团队搞定500+接口:用Skills构建可复用的“测试技能库”,复用率提升80%
2026-05-29
微软悄然开源了一款 Skill 神器
2026-05-29
人才+1,有人把申请专利也做成了skill,知识产权的普及度再次增加
2026-05-29
手搓Skill串联成专属 SubAgent:打造前端代码审查→修复→提交自动化流水线
2026-05-29
让 Skill 自己训练自己:8阶段Loop与自进化机制
2026-05-29
Codex 必装十大 Skills,我挨个翻车之后,重新排了一次顺序
2026-04-05
2026-03-05
2026-03-17
2026-03-04
2026-03-17
2026-03-26
2026-03-10
2026-03-05
2026-05-15
2026-04-09