2026年6月4日 周四晚上19:30,报名腾讯会议了解“业务抓夹如何成为前线部署工程师(FDE)”(限30人)
免费POC, 零成本试错
FDE知识库

FDE知识库

学习大模型的前沿技术与行业落地应用


我要投稿

把 Agent Skill 包成 API:从本地调用到服务化落地

发布日期:2026-06-01 08:09:03 浏览次数: 1559
作者:AI应用开发科普与实战

微信搜一搜,关注“AI应用开发科普与实战”

推荐语

将本地 Agent Skill 包装成 API,让业务能力从个人工具升级为团队服务,实现真正的价值复用。

核心内容:
1. Skill API 化的核心价值与接口设计
2. LangGraph Agent Runtime 的关键实现原理
3. 从架构设计到测试落地的完整流程

杨芳贤
53AI创始人/腾讯云(TVP)最具价值专家

引子

前面我们聊过 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 需要 API 化

先说结论: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 的几种方式

要把 Skill 变成 API,大体有三种路线。

2.1 方案一:服务器上安装 Codex 这类 Agent

最直观的方式是:既然本地 Codex 能调用 Skill,那就在服务器上也安装 Codex 或类似 Agent,然后 API 服务收到请求后,去驱动这个 Agent。

流程大概是:

调用方
  |
  v
HTTP 服务
  |
  v
服务器上的 Codex / Agent
  |
  v
加载本地 Skill
  |
  v
执行任务并返回结果

这个方案的优点是上手快。你本地怎么跑,服务器上大体也可以照着配置。

但它也有几个问题:

维度
服务器安装 Agent 方案
优点
改造少,最接近本地使用方式
缺点
服务边界不清晰,进程管理、并发、权限控制更复杂
适合场景
内部低频工具、个人自动化、快速验证

这里最大的风险是:Agent 本身不是为稳定 HTTP 服务边界设计的。

它能完成任务,但你还要额外处理很多服务化问题,比如并发请求、超时控制、日志、鉴权、文件权限、错误码转换等。

所以这个方案适合做验证,不一定适合长期承载业务接口。

2.2 方案二:把 Skill 逻辑重写成普通后端代码

第二种方式是更传统的后端思路:直接把 SKILL.md 里的流程翻译成 Java、Python 或 Go 代码。

比如:

如果问题命中天气查询
  -> 先做城市地理编码
  -> 再调天气预报接口
如果城市无法识别
  -> 返回澄清问题
最后按固定模板给出天气建议

这个方案看起来很稳,因为它完全变成了普通工程代码。

但问题也很明显:你失去了 Skill 的灵活性。

Skill 原来最有价值的地方,是把“流程说明、接口文档、判断策略、输出要求”放在一个 Agent 能理解的工作包里。现在你把它重新写死在后端代码里,后续每次调整业务规则,都要改代码、发版、回归。

这就有点可惜了。

2.3 方案三:用 LangGraph 实现 Agent Skills 架构

第三种方式,也是这篇文章重点讲的方式:

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 到底是什么

说到这里,有些同学可能会问: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 Runtime

LangGraph 的价值不在于“多包装一层框架”,而在于它天然适合做 Agent 工作流。

一个 Skill API 服务至少需要这些能力:

  • 能给模型一个明确的 system prompt;
  • 能让模型根据用户问题选择工具;
  • 能限制工具权限;
  • 能控制最多执行多少步;
  • 能在工具调用和最终回答之间保留状态;
  • 能把 Agent 最后一条回答提取出来返回给 API。

这和 LangGraph 的 ReAct Agent 模式非常契合。

我们可以把 Skill 服务理解成一个小型运行时:

LangGraph Agent Runtime
├── 模型:负责理解问题、规划步骤、组织回答
├── 文件工具:负责读取 SKILL.md 和 references
├── 脚本工具:负责执行 skill/scripts 下的确定性代码
└── 控制层:负责超时、步数、错误和返回格式

这里有一个很重要的设计原则:

模型负责“判断和编排”,脚本负责“确定性执行”。

千万不要让模型直接猜接口参数,也不要让模型直接拼复杂业务请求。

更稳的方式是:

  • 接口调用、鉴权、分页、字段映射,放到 scripts/ 里;
  • 判断何时调用哪个脚本,放到 SKILL.md 里;
  • HTTP 服务只提供工具和运行环境。

这样边界才清楚。


五、整体架构怎么设计

根据 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 可以类比:

Java 后端概念
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

区别在于:传统 Java Service 把流程写死在代码里;Skill API 把流程留在 SKILL.md,由 Agent 运行时读取执行。


六、关键实现细节和原理

接下来我们看几个真正落地时绕不开的细节。

6.1 Skill 包必须保持完整目录结构

不要只复制 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.mdreferences/scripts/ 在同一个 skill 根目录;
  • HTTP 服务通过 WEATHER_SKILL_DIR 定位这个目录;
  • 新增业务能力优先改 Skill 包,而不是改服务框架。

6.2 System Prompt 要强制 Agent 先读 Skill

如果只是把用户问题丢给模型,它可能会直接回答,而不是按 Skill 流程走。

所以在 run_agent_query 里,需要构造明确的 system prompt:

你必须使用 Agent Skills 架构。
处理用户问题前,先读取 SKILL.md。
如 SKILL.md 要求读取 references,请继续读取相关资料。
如需要访问业务数据,只能通过 run_skill_script 执行 scripts/ 下的脚本。
不要直接编造接口结果。

这段话的作用不是“装饰”,而是在约束 Agent 的执行路径。

模型可以推理,但不能绕开 Skill 包。

6.3 文件读取工具必须做路径保护

这是服务化后非常重要的安全细节。

如果你给 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 能力越强,工具边界越要收紧。

6.4 脚本执行工具也要限制范围

同样,run_skill_script 不能变成一个万能 shell。

它应该只允许执行:

  • scripts/ 目录下的文件;
  • 后缀为 .py 的脚本;
  • 固定工作目录为 skill 根目录;
  • 固定超时时间;
  • 返回 stdoutstderrreturncode

执行结果可以像这样返回给 Agent:

{
  "returncode"0,
  "stdout""{\"items\":[...]}",
  "stderr"""
}

如果脚本报错,也不要直接把敏感信息原样返回。至少要对包含这些关键词的行做脱敏:

  • token
  • authorization
  • api_key
  • secret

这个细节很容易被忽略,但一旦服务对外提供,就必须考虑。

6.5 模型配置和业务接口配置要分离

这个方案里有两类配置。

第一类是 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

这样做的好处是很明显的:

  • 换模型,不影响业务接口脚本;
  • 换天气服务地址,不影响 Agent Runtime;
  • 本地配置和部署配置可以通过环境变量覆盖。

七、一次请求是怎么跑完的

我们把完整请求链路串起来。

用户从业务系统发起请求:

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
任务开始
success
messagerequest_id
progress
中间进度
message
step
final
最终答案
success
answersourceerrormeta
error
执行失败
success
errorcodemeta
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 能拒绝空问题;
  • Agent 会先读取 SKILL.md
  • 只能读取 skill 目录内文件;
  • 只能执行 scripts/ 下的 Python 脚本;
  • 脚本超时和报错能返回可理解的错误;
  • SSE 事件至少覆盖 startfinaldone,异常时能返回 error

做到这里,这个 Skill 就不再只是本地 Agent 的能力,而是一个可以被系统集成的 API 服务。


九、总结

这篇文章我们把 Skill API 化完整走了一遍。

简单来说,核心就三点:

  • Skill API 化的目标,是把个人 Agent 能力变成团队系统能力;
  • 推荐的架构,是 HTTP 服务 + LangGraph Agent Runtime + 完整 Skill 包;
  • 关键边界,是模型负责判断和编排,脚本负责确定性执行,服务负责权限、超时、错误和返回格式。

如果只是快速验证,可以在服务器上安装 Codex 这类 Agent 直接跑 Skill。

但如果要长期作为业务接口提供服务,更推荐把 Skill 放进一个清晰的 Agent Runtime 里,让它既保留 Skill 的灵活性,又具备后端服务应有的稳定边界。

这也是我理解的 Agent Skill 真正进入工程化阶段的一步:从“我本地能用”,走向“系统都能调用”。

53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询