关于 makers-migration
这是一份将 AI Agent 项目迁移到 EdgeOne Makers 平台的指南,涵盖 Python 和 Node/TypeScript 框架适配,以及通用平台规范与验证步骤。
主要功能
- 01按框架划分迁移步骤
- 02Python 与 Node/TypeScript 指南
- 03环境变量与 API 适配
- 04工具和对话存储映射
- 05转换为 SSE 响应
适用场景
- 01迁移 CrewAI 项目
- 02迁移 LangGraph Agent
- 03将 API 路由适配到 Makers
- 04部署前检查迁移结果
MCP 相关
指南说明如何通过 `context.tools.toClaudeMcpServer()` 将 Claude Agent SDK 项目中的 MCP 工具接入 Makers,也介绍了迁移时替换自定义 MCP 工具的方式。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tencentedgeone/ edgeone-makers-tools --skill makers-migration安装后在 Claude Code 中输入/makers-migration确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api-route-to-makers.mdclaude-agent-sdk-to-makers.mdcrewai-to-makers.mddeepagents-to-makers.mdlanggraph-to-makers.mdopenai-agents-to-makers.md详细介绍
EdgeOne Makers 迁移指南
将现有 AI Agent 项目迁移到 EdgeOne Makers 平台格式。涵盖结构转换、API 适配和平台能力注入。
迁移决策树
What type of project are you migrating?
├── Python project
│ ├── Using CrewAI → See §2 CrewAI
│ ├── Using LangChain/LangGraph/DeepAgents → See §3 LangGraph (Python)
│ ├── Using OpenAI Agents SDK → See §4 OpenAI Agents (Python)
│ └── Using Claude Agent SDK → See §5 Claude SDK (Python)
└── Node/TS project
├── Using Express/Next.js API routes → See §6 Express → Makers
├── Using LangGraph/DeepAgents → See §3 LangGraph (Node)
├── Using OpenAI Agents SDK → See §4 OpenAI Agents (Node)
└── Using Claude Agent SDK → See §5 Claude SDK (Node)
⚠️ 迁移检查清单(所有框架通用)
开始针对特定框架进行修改前,请检查以下全局事项:
- 创建
edgeone.json,并正确设置agents.framework和buildCommand/outputDirectory - 将后端代码从 Express 路由 / Next.js API 路由移至
agents/目录 - 将
process.env/os.environ替换为context.env/ctx.env - 将
req.headers.get('x')替换为context.request.headers['x'](Node)或直接访问字典(Python) - 将
await req.json()替换为context.request.body(已解析) - 将直接调用模型 API(OpenAI、Anthropic)替换为
AI_GATEWAY_*环境变量 - 为 AI 端点添加 SSE 流式传输(替换
res.json()/return {"data": ...}) - 在前端 fetch 调用中添加
makers-conversation-id请求头 - 通过
context.tools接入平台工具,而不是自定义工具实现 - 通过
context.store接入对话历史,而不是使用内存或自定义数据库 - 如果使用 web_search,请设置
WSA_API_KEY环境变量,并使用context.tools.get("web_search") - 设置
edgeone makers dev用于本地开发
§1. 标准 API 路由 → Makers 处理器
这是最常见的迁移模式,适用于 Express/Next.js API 路由、普通 HTTP 处理器等。
Node(Express/Next.js → Makers)
// ❌ Before: Next.js API route (app/api/chat/route.ts)
export async function POST(req: Request) {
const body = await req.json();
const headers = req.headers;
const apiKey = process.env.OPENAI_API_KEY;
// ... LLM call ...
return Response.json({ data: result });
}
// ✅ After: Makers agent handler (agents/chat/index.ts)
export async function onRequest(context: any) {
const body = context.request.body; // already parsed
const conversationId = context.conversation_id; // auto-injected from header
const env = context.env; // context.env, never process.env
// ... LLM call via AI_GATEWAY_* ...
return new Response(JSON.stringify({ data: result }), {
headers: { 'Content-Type': 'application/json' },
});
}
Python(Flask/FastAPI → Makers)
# ❌ Before: Flask route
@app.route('/chat', methods=['POST'])
def chat():
body = request.get_json()
api_key = os.environ.get('OPENAI_API_KEY')
# ... LLM call ...
return jsonify({'data': result})
# ✅ After: Makers agent handler (agents/chat/index.py)
async def handler(ctx):
body = ctx.request.body
conversation_id = ctx.conversation_id
api_key = ctx.env.get("AI_GATEWAY_API_KEY")
# ... LLM call via AI_GATEWAY_* ...
return {"data": result}
§2. CrewAI(Python)
主要变更
| 之前 | 之后 |
|---|---|
os.environ.get("OPENAI_API_KEY") | ctx.env.get("AI_GATEWAY_API_KEY") |
LLM(provider="openai", ...) — LiteLLM 分发 | LLM(provider="openai", base_url=ctx.env["AI_GATEWAY_BASE_URL"], ...) — 绕过 LiteLLM |
在 Crew 上设置 memory=True | 设置 memory=False + 使用 ctx.store |
verbose=True | verbose=False(事件通过 crewai_event_bus 传递) |
crew.kickoff()(阻塞) | await asyncio.to_thread(crew.kickoff) |
| 自定义搜索工具 | 使用 ctx.tools.to_crewai_tools(BaseTool) |
| Flask/FastAPI 处理器 | async def handler(ctx): → ctx.utils.stream_sse(gen()) |
edgeone.json
{
"buildCommand": "",
"outputDirectory": "",
"agents": {
"framework": "crewai"
}
}
要求
crewai>=1.14.5
openai>=1.50.0
迁移步骤
- 将 Flask/FastAPI 入口替换为
async def handler(ctx): - 从
ctx.env读取环境变量,绝不使用os.environ - 使用
LLM(provider="openai", api_key=ctx.env["AI_GATEWAY_API_KEY"], base_url=ctx.env["AI_GATEWAY_BASE_URL"]) - 设置
Crew(memory=False, verbose=False) - 使用
asyncio.to_thread()包装crew.kickoff() - 将自定义工具替换为
ctx.tools.to_crewai_tools(BaseTool) - 通过
ctx.utils.stream_sse(gen())返回 SSE
完整模式请参阅 makers-agents/skills/python-frameworks/crewai.md。 详细的迁移前后对比:references/crewai-to-makers.md
§3. LangGraph / DeepAgents(Node + Python)
主要变更
| 之前 | 之后 |
|---|---|
直接创建模型(new ChatOpenAI(...)) | 使用 AI_GATEWAY_* 设置 apiKey/baseURL |
MemorySaver(内存检查点保存器) | context.store.langgraphCheckpointer(持久化) |
| 自定义工具函数 | context.tools.toLangChainTools(tool) |
agent.stream() | 使用 createSSEResponse(gen, signal)(Node)或 ctx.utils.stream_sse(gen())(Python)进行 SSE 流式传输 |
手动管理 thread_id | thread_id = context.conversation_id |
Node — edgeone.json
{
"agents": {
"framework": "langgraph"
}
}
Python — edgeone.json
{
"buildCommand": "",
"outputDirectory": "",
"agents": {
"framework": "langgraph"
}
}
迁移步骤
- 将处理程序移至
agents/<name>/index.ts(或.py) - 替换模型初始化方式:使用
AI_GATEWAY_API_KEY+AI_GATEWAY_BASE_URL - 替换检查点保存器:使用
context.store.langgraphCheckpointer,而不是MemorySaver - 替换存储:使用
context.store.langgraphStore - 替换工具:使用
context.tools.toLangChainTools(tool),而不是自定义工具函数 - 设置
thread_id:{ configurable: { thread_id: context.conversation_id } } - 将响应替换为 SSE 流式传输模式
Node:makers-agents/skills/node-frameworks/langgraph.md Python:makers-agents/skills/python-frameworks/langgraph.md DeepAgents:makers-agents/skills/node-frameworks/deepagents.md 详细的迁移前后对比:references/langgraph-to-makers.md,references/deepagents-to-makers.md
§4. OpenAI Agents SDK(Node + Python)
主要变更
| 之前 | 之后 |
|---|---|
new OpenAI({ apiKey, baseURL }) | 从 context.env / ctx.env 读取 AI_GATEWAY_* |
Runner.run(agent, input, { tools }) | 从 context.tools.all() 获取工具(已采用 OpenAI 函数格式) |
| 会话管理 | context.store.openaiSession(convId)(Node) |
| Express 路由响应 | 通过 createSSEResponse(gen, signal)(Node)或 ctx.utils.stream_sse(gen())(Python)使用 SSE |
| 模型名称硬编码 | `ctx.env.AI_GATEWAY_MODEL |
Node — edgeone.json
{
"agents": {
"framework": "openai-agents-sdk"
}
}
Python — edgeone.json
{
"buildCommand": "",
"outputDirectory": "",
"agents": {
"framework": "openai-agents-sdk"
}
}
迁移步骤
- 将处理程序移至
agents/<name>/index.ts(或.py) - 从
context.env(而不是process.env)创建 OpenAI 客户端 - 将工具替换为
context.tools.all()(返回 OpenAI 函数工具) - 使用
context.store.openaiSession(conversationId)管理会话(Node) - 将流事件映射为 SSE:
output_text_delta→ai_response,tool_called→tool_call
Node:makers-agents/skills/node-frameworks/openai-agents.md Python:makers-agents/skills/python-frameworks/openai-agents.md 详细的迁移前后对比:references/openai-agents-to-makers.md
§5. Claude Agent SDK(Node + Python)
主要变更
| 之前 | 之后 |
|---|---|
ANTHROPIC_API_KEY 环境变量 | 通过 collectGatewayEnv() 从 AI_GATEWAY_* 映射而来 |
process.env | 将 context.env 注入 query().options.env |
| 自定义 MCP 工具 | context.tools.toClaudeMcpServer() |
| 会话 | context.store.claudeSessionStore()(Node) |
| Stdout EPIPE 崩溃 | 忽略 process.stdout 上的 EPIPE(Node) |
| 没有可写配置目录 | 设置 CLAUDE_CONFIG_DIR=/tmp/claude-agent-sdk、CLAUDE_CODE_TMPDIR=/tmp |
Node — edgeone.json
{
"agents": {
"framework": "claude-agent-sdk"
}
}
Python — edgeone.json
{
"buildCommand": "",
"outputDirectory": "",
"agents": {
"framework": "claude-agent-sdk"
}
}
迁移步骤
- 将处理程序移至
agents/<name>/index.ts(或.py) - 通过
collectGatewayEnv(context.env)将AI_GATEWAY_*→ANTHROPIC_*映射 - 将环境变量注入
query({ options: { env: collectGatewayEnv(...) } }) - 替换 MCP 工具:
context.tools.toClaudeMcpServer('edgeone', { alwaysLoad: true }) - 仅限 Node:忽略
process.stdout上的EPIPE - 设置可写配置目录:
CLAUDE_CONFIG_DIR=/tmp/claude-agent-sdk、CLAUDE_CODE_TMPDIR=/tmp
Node:makers-agents/skills/node-frameworks/claude-sdk.md Python:makers-agents/skills/python-frameworks/claude-sdk.md 详细的迁移前后对比:references/claude-agent-sdk-to-makers.md
§6. Express / Next.js API 路由(Node)
适用于任何基于 Express 或 Next.js API 路由的 Agent 的通用迁移指南。
7 步转换
| 步骤 | 转换前 | 转换后 |
|---|---|---|
| 1. 文件位置 | app/api/chat/route.ts 或 server/routes/chat.ts | agents/chat/index.ts |
| 2. 入口签名 | export async function POST(req) 或 app.post('/chat', handler) | export async function onRequest(context) |
| 3. 请求体解析 | await req.json() | context.request.body(已解析) |
| 4. 请求头 | req.headers.get('x-foo') | context.request.headers['x-foo'] |
| 5. 中止信号 | req.signal | context.request.signal(AbortSignal) |
| 6. 模型访问 | process.env.OPENAI_API_KEY → 直接调用 | context.env.AI_GATEWAY_* → AI Gateway |
| 7. 响应 | res.json() 或 return Response.json() | 通过 createSSEResponse(gen, signal) 返回 SSE 流 |
示例:Next.js API 路由 → Makers
// ❌ Before: Next.js (app/api/chat/route.ts)
import { NextRequest } from 'next/server';
import OpenAI from 'openai';
export async function POST(req: NextRequest) {
const { message } = await req.json();
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const response = await client.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: message }],
stream: true,
});
// ... stream back as Response
}
// ✅ After: EdgeOne Makers (agents/chat/index.ts)
import { createLogger, sseEvent, createSSEResponse } from '../_shared';
export async function onRequest(context: any) {
const { message } = context.request.body ?? {};
if (!message) return new Response('Missing message', { status: 400 });
const signal = context.request.signal as AbortSignal;
return createSSEResponse(async function* (sig) {
const response = await fetch(context.env.AI_GATEWAY_BASE_URL + '/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${context.env.AI_GATEWAY_API_KEY}`,
},
body: JSON.stringify({
model: context.env.AI_GATEWAY_MODEL || '@makers/deepseek-v4-flash',
messages: [{ role: 'user', content: message }],
stream: true,
}),
signal: sig,
});
// ... proxy SSE chunks ...
yield 'data: [DONE]\n\n';
}, signal);
}
§7. 客户端迁移
前端 fetch 调用
// ❌ Before: plain fetch without conversation-id
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message }),
});
// ✅ After: with makers-conversation-id header
const conversationId = getOrCreateConversationId(); // crypto.randomUUID() + localStorage
const response = await fetch('/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'makers-conversation-id': conversationId, // ⭐ required for all AI endpoints
},
body: JSON.stringify({ message }),
});
/stop 端点
// ✅ Always pass conversation_id in body for /stop
await fetch('/stop', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ conversation_id: conversationId }),
});
SSE 解析
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') return;
try {
const event = JSON.parse(data);
if (event.type === 'ai_response') { /* display text */ }
if (event.type === 'tool_call') { /* show tool call */ }
if (event.type === 'ping') { /* ignore heartbeat */ }
} catch { /* skip non-JSON */ }
}
}
}
§8. 迁移后验证
迁移完成后,请在部署前验证以下事项:
-
edgeone makers dev启动时没有错误 -
/chat端点返回 SSE 流(而非 JSON) - AI 响应端到端正常工作(前端 → Agent → 模型 → 前端)
- 所有位置均使用
context.env(搜索process.env/os.environ,应无匹配项) -
edgeone.json中的agents.framework正确 - 平台工具(
context.tools)至少在一个框架中正常工作 - 对话历史会在请求之间保留(通过
context.store) -
/stop端点会取消正在运行的任务 - 前端发送
makers-conversation-id请求头
另请参阅
- Agent 开发指南:makers-agents/SKILL.md
- 平台约定:makers-agents/references/platform/
- CLI 命令:makers-cli/SKILL.md
- 部署指南:makers-deploy/SKILL.md
详细的迁移前后对照文件
- references/api-route-to-makers.md — 通用 API 路由 → Makers 处理程序(无框架回退方案;Makers 支持不依赖框架的
onRequest/handler) - references/langgraph-to-makers.md — LangGraph(Node + Python)
- references/deepagents-to-makers.md — DeepAgents(Node + Python)
- references/openai-agents-to-makers.md — OpenAI Agents SDK(Node + Python)
- references/claude-agent-sdk-to-makers.md — Claude Agent SDK(Node + Python)