来源:互联网 更新时间:2026-06-11 14:08
将本地 Agent Skill 包装成 API,让业务能力从个人工具升级为团队服务,实现真正的价值复用。核心内容:1. Skill API 化的核心价值与接口设计2. LangGraph Agent Runtime 的关键实现原理3. 从架构设计到测试落地的完整流程

前面我们聊过 Skill 是什么,也聊过怎么写一个可复用的 Skill。
但只在本地 Agent 里能用,其实还不够。
很多真正有价值的 Skill,最后都会遇到同一个问题:
比如你写了一个 weather-query Skill,里面已经定义好了:
本地 Agent 能跑通,说明这套流程是有价值的。但如果只有你自己的电脑能用,它就还是一个“个人工具”;如果能包装成 HTTP 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-Meteon建议: 明天上午更适合跑步...","source":"langgraph-agent","error":null,"meta":{"elapsed_ms":12345}}
event: done
data: {"success":true}
后面我们就围绕这个接口,拆清楚为什么要这么设计、具体怎么实现,以及最后怎么测试通过。
本文目录:
一、为什么 Skill 需要 API 化
二、Skill 对外提供 API 的几种方式
三、LangGraph 到底是什么
四、为什么推荐 LangGraph Agent Runtime
五、整体架构怎么设计
六、关键实现细节和原理
七、一次请求是怎么跑完的
八、用一个例子测试通过
九、总结
这里先抛个结论:
一个成熟的 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/ 是确定性执行代码,比如请求天气接口、请求地理编码接口。这已经不是一个简单提示词了。它更像一个“会读文档、会调用工具、会组织回答的小型业务专家”。
问题来了:业务系统要怎么用它?
如果一个 Ja va 后端想回答“北京明天适合户外跑步吗?”,它并不想知道:
Ja va 后端最理想的调用方式是:
GET /query/stream?question=北京明天适合户外跑步吗?
然后通过 SSE 监听最终结果:
event: final
data: {"success":true,"answer":"来源: Open-Meteon查询意图: ...","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 方案 |
|---|---|
| 优点 | 改造少,最接近本地使用方式 |
| 缺点 | 服务边界不清晰,进程管理、并发、权限控制更复杂 |
| 适合场景 | 内部低频工具、个人自动化、快速验证 |
这里最大的风险是:
它能完成任务,但你还要额外处理很多服务化问题,比如并发请求、超时控制、日志、鉴权、文件权限、错误码转换等。
所以这个方案适合做验证,不一定适合长期承载业务接口。
第二种方式是更传统的后端思路:直接把 SKILL.md 里的流程翻译成 Ja va、Python 或 Go 代码。
比如:
如果问题命中天气查询
-> 先做城市地理编码
-> 再调天气预报接口
如果城市无法识别
-> 返回澄清问题
最后按固定模板给出天气建议
这个方案看起来很稳,因为它完全变成了普通工程代码。
但问题也很明显:
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
这个方案的关键点是:
它既保留了 Skill 的可维护性,也把对外服务边界做清楚了。
说到这里,有些同学可能会问:LangGraph 是什么?它和 LangChain 又是什么关系?
先不用被名字吓到。通俗地说:
LangGraph 是一个用来搭建 Agent 工作流的编排框架。它把一次复杂的 AI 任务,拆成多个节点、状态和工具调用,再按一定路线跑完整个流程。
如果说普通的大模型调用像这样:
用户问题 -> 模型 -> 回答
那 LangGraph 更像这样:
用户问题
|
v
节点 A:理解意图
|
v
节点 B:选择工具
|
v
节点 C:调用外部脚本或接口
|
v
节点 D:汇总结果并回答
它的核心不是“多调几次模型”,而是把 Agent 的行动过程变得更可控。
一个典型 LangGraph 应用里,通常会有这几个概念:
| 概念 | 可以怎么理解 |
|---|---|
| State | 当前任务的上下文,比如用户问题、工具结果、历史消息 |
| Node | 一个处理步骤,比如调用模型、执行工具、整理答案 |
| Edge | 步骤之间怎么流转,比如执行完工具后回到模型继续判断 |
| Tool | Agent 能调用的外部能力,比如读文件、查接口、跑脚本 |
| ReAct Agent | 一种常见模式:模型先推理,再选择工具,再根据工具结果继续推理 |
为什么这对 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 里的方案,我们可以把整个系统拆成四层:
调用方(Ja va后端/小程序后端)
|
| 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 包里的资料和脚本行动。
这和传统 Ja va Service 可以类比:
| Ja va 后端概念 | Skill API 方案 |
|---|---|
| Controller | FastAPI /query/stream |
| Service | run_agent_query |
| 策略编排代码 | LangGraph ReAct Agent |
| 接口文档/流程说明 | SKILL.md + references/ |
| DAO/Client | scripts/weather-api/query.py、scripts/geocoding/query.py |
| 第三方接口 | Open-Meteo 天气预报 API、Open-Meteo 地理编码 API |
区别在于:传统 Ja va 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-Meteon查询意图: 北京明天户外跑步天气查询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?
核心原因是:
一次 Skill 调用可能要经历读 SKILL.md、读参考文档、调用脚本、等待外部 API、失败重试、再组织回答。同步 JSON 接口要等所有步骤结束后才返回,调用方在这段时间里只能干等。如果执行超过网关或浏览器的超时时间,还容易被误判为失败。
SSE 更适合这种场景:
start,告诉调用方任务已经开始;progress,告诉前端“正在读取 Skill”“正在调用脚本”;final,调用方拿到 answer 后展示;done,明确这次流式响应结束;error,错误也能用统一事件格式处理。它比 WebSocket 更轻量,也比普通同步接口更适合“服务端持续输出、客户端只负责监听”的 Agent 查询场景。
事件协议建议提前约定清楚:
| 事件 | 含义 | 关键字段 |
|---|---|---|
start | 任务开始 | success、message、request_id |
progress | 中间进度 | message、step |
final | 最终答案 | success、answer、source、error、meta |
error | 执行失败 | success、error、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-Meteon查询意图: 北京明天户外跑步天气查询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 真正进入工程化阶段的一步:从“我本地能用”,走向“系统都能调用”。
和平精英如何做到压枪稳-和平精英怎样才能压枪稳
《Off Campus》第二季官宣:这对CP还在,但不再是主角
下载浏览器app下载安装选择推荐
免费影视剧APP推荐
儿子穿新中式现身大会堂 马斯克罕见用中文回应:他正在学习普通话
Elysium Above 履云录官网在哪下载 最新官方下载安装地址
DOTA2 TI时隔七年重返上海!门票6月10日开抢,国服享受优先购买!
客单价碾压宝马奥迪!极氪5月交付新车34377辆:连续4个月双增长
抖音最火沙雕男生网名(精选100个)
HBO 奇幻剧《龙之家族》第三季定档 6 月 22 日,最终预告片曝光喉道海战
阿里发布Qwen3.7-Max大模型,全球第五、国产第一
网络热词聊污是什么意思
SpaceX狂揽AI人才,马斯克亲自面试且不看简历背景
名单曝光!库克、马斯克等将随团到访中国 黄仁勋不在其中
帅气继父网名女生可爱英文(精选100个)
短剧《情绪超市》剧情介绍
免费看片软件下载地址推荐
洛克王国世界S2赛季狂欢怪谈介绍
免费看电影的软件推荐
网石18禁MMO《RAVEN2:渡鸦》大型更新推出全新职业“军阀”
手机号码测吉凶
本站所有软件,都由网友上传,如有侵犯你的版权,请发邮件haolingcc@hotmail.com 联系删除。 版权所有 Copyright@2012-2013 haoling.cc