mcpField
M
makers-migration

makers-migration

github.com/tencentedgeone开发1.8k152 次安装3 周前更新
代码仓库

关于 makers-migration

这是一份将 AI Agent 项目迁移到 EdgeOne Makers 平台的指南,涵盖 Python 和 Node/TypeScript 框架适配,以及通用平台规范与验证步骤。

主要功能

  1. 01按框架划分迁移步骤
  2. 02Python 与 Node/TypeScript 指南
  3. 03环境变量与 API 适配
  4. 04工具和对话存储映射
  5. 05转换为 SSE 响应

适用场景

  1. 01迁移 CrewAI 项目
  2. 02迁移 LangGraph Agent
  3. 03将 API 路由适配到 Makers
  4. 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.md
claude-agent-sdk-to-makers.md
crewai-to-makers.md
deepagents-to-makers.md
langgraph-to-makers.md
openai-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=Trueverbose=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
迁移步骤
  1. 将 Flask/FastAPI 入口替换为 async def handler(ctx):
  2. 从 ctx.env 读取环境变量,绝不使用 os.environ
  3. 使用 LLM(provider="openai", api_key=ctx.env["AI_GATEWAY_API_KEY"], base_url=ctx.env["AI_GATEWAY_BASE_URL"])
  4. 设置 Crew(memory=False, verbose=False)
  5. 使用 asyncio.to_thread() 包装 crew.kickoff()
  6. 将自定义工具替换为 ctx.tools.to_crewai_tools(BaseTool)
  7. 通过 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_idthread_id = context.conversation_id
Node — edgeone.json
{
  "agents": {
    "framework": "langgraph"
  }
}
Python — edgeone.json
{
  "buildCommand": "",
  "outputDirectory": "",
  "agents": {
    "framework": "langgraph"
  }
}
迁移步骤
  1. 将处理程序移至 agents/<name>/index.ts(或 .py)
  2. 替换模型初始化方式:使用 AI_GATEWAY_API_KEY + AI_GATEWAY_BASE_URL
  3. 替换检查点保存器:使用 context.store.langgraphCheckpointer,而不是 MemorySaver
  4. 替换存储:使用 context.store.langgraphStore
  5. 替换工具:使用 context.tools.toLangChainTools(tool),而不是自定义工具函数
  6. 设置 thread_id:{ configurable: { thread_id: context.conversation_id } }
  7. 将响应替换为 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"
  }
}
迁移步骤
  1. 将处理程序移至 agents/<name>/index.ts(或 .py)
  2. 从 context.env(而不是 process.env)创建 OpenAI 客户端
  3. 将工具替换为 context.tools.all()(返回 OpenAI 函数工具)
  4. 使用 context.store.openaiSession(conversationId) 管理会话(Node)
  5. 将流事件映射为 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"
  }
}
迁移步骤
  1. 将处理程序移至 agents/<name>/index.ts(或 .py)
  2. 通过 collectGatewayEnv(context.env) 将 AI_GATEWAY_* → ANTHROPIC_* 映射
  3. 将环境变量注入 query({ options: { env: collectGatewayEnv(...) } })
  4. 替换 MCP 工具:context.tools.toClaudeMcpServer('edgeone', { alwaysLoad: true })
  5. 仅限 Node:忽略 process.stdout 上的 EPIPE
  6. 设置可写配置目录: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.tsagents/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.signalcontext.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 请求头

另请参阅

详细的迁移前后对照文件

评论

支持 Markdown