从OpenAI Agents SDK迁移到Claude Agent SDK指南

本教程面向有编程基础的开发者,详细讲解如何将基于OpenAI Agents SDK的应用迁移到Claude Agent SDK。通过费用审批代理示例,逐一映射核心组件:工具定义、护栏、会话管理和追踪。强调Claude SDK的显式工具模式、进程内MCP服务器、普通函数护栏、内存/磁盘会话以及原生OpenTelemetry支持。提供完整代码对比、环境配置、实践步骤和常见问题排查,帮助开发者快速掌握迁移技巧并理解架构设计差异。

技术背景

OpenAI Agents SDK和Claude Agent SDK是构建AI代理的两大主流框架。OpenAI SDK提供简洁的装饰器风格API,但存在版本不稳定、工具模式依赖类型提示内省、追踪功能内置但封闭等问题。Claude Agent SDK与Claude Code共享运行时,提供内置工具(Read/Edit/Bash/Grep)、分层权限系统、自动提示缓存和原生OpenTelemetry支持,更适合生产环境集成。迁移需求源于开发者希望利用Claude的生态优势,同时保留现有业务逻辑。本教程旨在降低迁移成本,通过映射关系帮助开发者快速上手。

核心原理

核心原理是组件映射:OpenAI的@function_tool对应Claude的@tool+create_sdk_mcp_server,后者将工具打包为进程内MCP服务器,显式声明模式避免内省意外。Agent配置对应ClaudeAgentOptions,护栏从装饰器变为普通函数,Runner.run对应ClaudeSDKClient上下文管理器。Claude SDK采用事件流驱动,通过receive_response()获取消息序列,最终结果在ResultMessage中。会话管理支持内存复用和磁盘持久化(resume=session_id)。自动提示缓存减少重复输入成本。

架构分析

架构上,Claude Agent SDK采用显式配置与事件驱动循环。ClaudeAgentOptions承载模型、系统提示、MCP服务器和允许工具列表,权限模式控制工具调用。ClaudeSDKClient作为异步上下文管理器,管理持久传输和MCP握手。事件流包含SystemMessage、AssistantMessage、UserMessage和ResultMessage,便于流式处理和拦截。护栏作为普通函数在循环前后执行,保持控制权。子代理通过AgentDefinition定义,由编排器委派任务。追踪通过OpenTelemetry导出,与现有可观测性栈集成。

完整实战教程

从OpenAI Agents SDK迁移

如果你有一个基于OpenAI Agents SDK的应用,并希望将其移植到Claude Agent SDK,本笔记本通过一个费用审批代理示例,映射了每个原始组件。

迁移后你将获得什么。 Claude Agent SDK与Claude Code运行在相同的运行时上——你将继承其内置的Read、Edit、Bash和Grep工具,一个用于控制代理可访问内容的分层权限系统,自动提示缓存,以及直接访问事件流以进行进度流式传输或运行中拦截。工具定义是显式的(你声明模式,而不是依赖类型提示内省),并且循环由你驱动。大多数移植涉及每个工具更多的样板代码,而其他地方的样板代码更少。

学完本笔记本后,你将能够:

两个SDK均可实时运行。你需要在.env中设置OPENAI_API_KEY和ANTHROPIC_API_KEY。

| OpenAI Agents SDK | Claude Agent SDK |

|---|---|

| Agent(name, instructions, tools) | ClaudeAgentOptions + 系统提示 |

| @function_tool | @tool + create_sdk_mcp_server |

| @input_guardrail | 循环前的普通函数(或UserPromptSubmit钩子) |

| @output_guardrail | 对ResultMessage.result的普通函数 |

| Runner.run(agent, msg) | ClaudeSDKClient上下文管理器 |

| Sessions(客户端管理) | 重用同一个ClaudeSDKClient |

| conversation_id(服务器管理) | resume=session_id(磁盘支持) |

| 内置追踪仪表板 | OTel原生——插入你现有的Grafana/Datadog/Honeycomb |

| handoffs=[...] | AgentDefinition + Agent工具——见附录 |

示例:费用审批

一个智能体,负责审批或标记费用提交。它有一个工具(check_policy),一个输入护栏(如果没有金额则拒绝),以及一个循环。

我们先用 OpenAI Agents SDK 构建一次,然后移植每个原语。

前置要求

必备知识

必备工具

推荐:

openai-agents 固定为 0.9.3 版本——它处于 1.0 之前,API 变化频繁。如果你升级版本,请重新验证 Agent/Runner/@function_tool 的签名。
%%capture
# openai-agents pinned to 0.9.3 — see prereqs note before bumping
%pip install "openai-agents==0.9.3" claude-agent-sdk python-dotenv
import os
import re
from dataclasses import replace

from dotenv import load_dotenv

load_dotenv()

assert os.getenv("OPENAI_API_KEY"), "OPENAI_API_KEY missing from .env"
assert os.getenv("ANTHROPIC_API_KEY"), "ANTHROPIC_API_KEY missing from .env"

OAI_MODEL = "gpt-4.1"
CLAUDE_MODEL = "claude-sonnet-4-6"
# ===== OpenAI Agents SDK — full implementation =====
from agents import Agent, GuardrailFunctionOutput, Runner, function_tool, input_guardrail


@function_tool
def check_policy(category: str, amount: float) -> dict:
    """Look up the expense policy for a category. Returns the approval limit. Valid categories: meals, travel, software, other."""
    limits = {"meals": 75.0, "travel": 500.0, "software": 200.0, "other": 50.0}
    return {
        "category": category,
        "limit": limits.get(category.lower(), 50.0),
        "requires_receipt": amount > 25.0,
    }


@input_guardrail
def has_dollar_amount(ctx, agent, user_input: str) -> GuardrailFunctionOutput:
    has_amount = bool(re.search(r"\$\d+", user_input))
    return GuardrailFunctionOutput(
        output_info={"has_amount": has_amount},
        tripwire_triggered=not has_amount,
    )


expense_agent = Agent(
    name="Expense Approver",
    instructions=(
        "You approve or flag expense submissions. "
        "Always call check_policy first to get the limit for the expense category. "
        "Approve if the amount is under the limit; otherwise flag for manager review."
    ),
    tools=[check_policy],
    input_guardrails=[has_dollar_amount],
    model=OAI_MODEL,
)


async def run_oai(msg: str) -> str:
    result = await Runner.run(expense_agent, msg)
    return result.final_output


print(await run_oai("Lunch with Acme, $47"))

逐个移植原语

@function_tool → @tool + create_sdk_mcp_server

OpenAI: @function_tool 从类型提示和文档字符串中推导工具模式。装饰后的函数直接放入 tools=[...]。

Claude: @tool 将名称、描述和模式作为显式参数——你写什么,模型就看到什么。处理器是 async 的,接收一个 args 字典,并返回 {"content": [{"type": "text", "text": ...}]}。工具被捆绑到一个进程内的 MCP 服务器中(尽管名称如此,但没有子进程或网络传输),这是你传递给代理的单元。

函数内部的逻辑不变。只有包装器变了。

import json

from claude_agent_sdk import create_sdk_mcp_server, tool


@tool(
    "check_policy",
    "Look up the expense policy for a category. Returns the approval limit. Valid categories: meals, travel, software, other.",
    {"category": str, "amount": float},
)
async def check_policy_claude(args):
    limits = {"meals": 75.0, "travel": 500.0, "software": 200.0, "other": 50.0}
    category, amount = args["category"], args["amount"]
    result = {
        "category": category,
        "limit": limits.get(category.lower(), 50.0),
        "requires_receipt": amount > 25.0,
    }
    return {"content": [{"type": "text", "text": json.dumps(result)}]}


policy_server = create_sdk_mcp_server(name="expense", tools=[check_policy_claude])

Agent(...) → ClaudeAgentOptions

ClaudeAgentOptions 承载模型、工具和系统提示——即实际发送给 Claude 的配置。你的护栏逻辑保留在应用程序代码中,而不是注册在框架对象上。OpenAI 的 Agent 将所有这些捆绑在一起。

| Agent(...) 字段 | ClaudeAgentOptions 对应项 |

|---|---|

| instructions=... | system_prompt=... |

| tools=[fn] | mcp_servers={...} + allowed_tools=[...] |

| model=... | model=... |

| name=... | 不需要——选项是配置值,不是命名实体。子代理名称放在 AgentDefinition 上(见附录)。 |

| input_guardrails=[...] | 在你的运行函数中调用——见下一节 |

allowed_tools 中的工具名称遵循 mcp__{server_name}__{tool_name} 模式。

关于权限的说明: allowed_tools 是一个允许规则——它使工具对代理*可用*。代理是否可以在未经用户批准的情况下调用它取决于 permission_mode。像 check_policy 这样的只读自定义工具默认自由运行;写入文件或运行 shell 命令的工具会提示,除非你设置 permission_mode="acceptEdits" 或 "bypassPermissions"。如果你的工具不仅仅是读取,请参阅权限指南。
from claude_agent_sdk import ClaudeAgentOptions

expense_system_prompt = (
    "You approve or flag expense submissions. "
    "Always call check_policy first to get the limit for the expense category. "
    "Approve if the amount is under the limit; otherwise flag for manager review."
)

expense_options = ClaudeAgentOptions(
    model=CLAUDE_MODEL,
    system_prompt=expense_system_prompt,
    mcp_servers={"expense": policy_server},
    allowed_tools=["mcp__expense__check_policy"],
)
内置工具。 费用代理仅使用自定义工具,但 SDK 附带了 Read、Edit、Bash、Grep 等工具——通过名称将它们添加到 allowed_tools 中(例如,allowed_tools="Read", "Grep", "mcp__expense__check_policy"])。有关文件系统模式,请参阅 [notebook 00。

@input_guardrail / @output_guardrail → 调用前/调用后检查

OpenAI: @input_guardrail 验证用户消息;@output_guardrail 验证代理的最终答案。两者都返回 GuardrailFunctionOutput(tripwire_triggered=True) 来阻止,这会引发 InputGuardrailTripwireTriggered(或输出变体)。输入护栏与代理并发运行。

Claude: 你的检查是普通函数。在启动客户端之前调用输入检查,在循环结束后对 ResultMessage.result 调用输出检查。

| OAI | Claude |

|---|---|

| @input_guardrail 装饰器 | 循环前调用的普通函数 |

| @output_guardrail 装饰器 | 循环后对 result.result 调用的普通函数 |

| tripwire_triggered=True 引发异常 | 提前返回(输入)或覆盖结果(输出) |

| 在 Agent(input_guardrails=[...]) 上注册 | 在你的运行函数中调用 |

UserPromptSubmit 钩子替代方案

SDK 的 UserPromptSubmit 钩子在提示词到达 Claude 之前触发,并且可以阻止它——这是与 @input_guardrail 最接近的结构匹配。我们在下面展示了两种方式,但演示使用普通函数有两个原因:拒绝消息保持在你的控制中(当钩子阻止时,ResultMessage.result 返回空——原因不会显示给调用者),并且护栏逻辑在你的运行函数中保持可见,而不是注册在选项对象上。@output_guardrail 没有干净的钩子等价物——Stop 在响应完成后触发,但不会重写输出。有关 PreToolUse 钩子门控工具调用(这是不同的用例),请参阅 notebook 03。

def has_dollar_amount_check(user_input: str) -> tuple[bool, str | None]:
    """Input check — returns (allowed, rejection_message)."""
    if re.search(r"\$\d+", user_input):
        return True, None
    return False, "I need a dollar amount to process this. Please include one (e.g., '$47')."


def has_decision_check(result: str) -> tuple[bool, str | None]:
    """Output check — returns (allowed, override_message)."""
    # Stem match is intentionally permissive for a demo; tighten for production.
    if re.search(r"\b(approv|flag|review)", result, re.IGNORECASE):
        return True, None
    return False, "I couldn't reach a clear approve/flag decision. Please resubmit."

作为对比,下面是将同样的检查实现为 UserPromptSubmit 钩子的方式。回调接收 input_data["prompt"],返回 {} 表示允许,或返回 {"decision": "block", "reason": "..."} 表示阻止。这里的 HookMatcher 不需要 matcher 参数,因为 UserPromptSubmit 会在每次提示时触发——没有工具名称需要过滤。

要试用它,请在下一节的 ClaudeSDKClient(...) 调用中将 options=expense_options 替换为 options=hooked_options。一个注意事项:当 UserPromptSubmit 钩子阻止时,阻止原因目前不会显示在 ResultMessage.result 中——你会得到空字符串而不是拒绝文本。这就是为什么演示循环使用普通函数,因为它控制自己的返回值。

关于工具级别的护栏——保护代理*执行*的操作,而不是用户*发送*的内容——请参阅 notebook 03 中的 PreToolUse 钩子模式。

from claude_agent_sdk import HookMatcher


async def has_dollar_amount_hook(input_data, tool_use_id, context):
    # tool_use_id is None for UserPromptSubmit — it's a uniform signature across all hook types.
    if re.search(r"\$\d+", input_data["prompt"]):
        return {}
    return {"decision": "block", "reason": "I need a dollar amount to process this."}


hooked_options = ClaudeAgentOptions(
    model=CLAUDE_MODEL,
    system_prompt=expense_system_prompt,
    mcp_servers={"expense": policy_server},
    allowed_tools=["mcp__expense__check_policy"],
    hooks={"UserPromptSubmit": [HookMatcher(hooks=[has_dollar_amount_hook])]},
)

Runner.run() → ClaudeSDKClient

OpenAI: result = await Runner.run(agent, msg) 运行循环并返回结果对象。读取 result.final_output 获取文本。

Claude: ClaudeSDKClient 是一个异步上下文管理器。调用 .query(msg) 发送消息,然后迭代 .receive_response() 按顺序接收事件——你会看到每个工具调用、每个文本块以及最终结果。事件类型:

| 事件 | 包含内容 |

|---|---|

| SystemMessage | 会话初始化元数据 |

| AssistantMessage | 文本块或工具使用块 |

| UserMessage | 工具结果块 |

| ResultMessage | 总是最后一个:.result、.usage、.total_cost_usd |

最终答案是 messages[-1].result。

SDK 还暴露了一个无状态的 query() 函数(参见 notebook 00),用于使用内置工具进行一次性调用。当你通过 create_sdk_mcp_server 引入自定义工具时,ClaudeSDKClient 是正确的选择,因为其持久传输处理进程内 MCP 握手。
from utils.agent_visualizer import display_agent_response, print_activity, reset_activity_context

from claude_agent_sdk import ClaudeSDKClient


async def run_claude(msg: str) -> None:
    # Input guardrail — short-circuit before the agent runs
    allowed, rejection = has_dollar_amount_check(msg)
    if not allowed:
        print(rejection)
        return

    reset_activity_context()
    messages = []
    async with ClaudeSDKClient(options=expense_options) as client:
        await client.query(msg)
        async for event in client.receive_response():
            print_activity(event)
            messages.append(event)

    # receive_response() guarantees ResultMessage is the last event yielded
    final = messages[-1].result
    ok, override = has_decision_check(final or "")
    if not ok:
        print(override)
        return

    display_agent_response(messages)


await run_claude("Lunch with Acme, $47")

Sessions → ClaudeSDKClient(内存中)或 resume=(磁盘支持)

OpenAI 提供两种会话模式:

| OAI 模式 | 持久性 | Claude 等效项 |

|---|---|---|

| result.to_input_list() + 重新发送 | 客户端在内存中保存历史记录 | 复用同一个 ClaudeSDKClient — 在同一个打开的上下文上再次调用 .query() |

| conversation_id | 服务器端,进程重启后仍然存在 | resume=session_id — 会话记录写入本地磁盘,重启后仍然存在 |

内存中(如下): 保持 ClaudeSDKClient 上下文打开,并再次调用 .query()。无需重新发送历史记录。当你的进程结束时,它也随之消失。

磁盘支持: 每次运行都会产生一个 session_id(在 ResultMessage.session_id 上)。存储它,然后在下次运行时传递 ClaudeAgentOptions(resume=session_id, ...)。记录存储在本地文件系统上,而不是 Anthropic 的服务器上 — 它在重启后仍然存在,但不能跨机器。

对于在任一模式下超出上下文窗口的对话,请参阅 ../misc/session_memory_compaction.ipynb。

# --- In-memory: reuse the same client ---
async def run_claude_multiturn():
    async with ClaudeSDKClient(options=expense_options) as client:
        await client.query("Lunch with Acme, $47")
        async for _ in client.receive_response():
            pass  # consume the stream

        # Same client — remembers turn 1
        await client.query("What about $90?")
        turn2 = [m async for m in client.receive_response()]
        # receive_response() guarantees ResultMessage is the last event yielded
        return turn2[-1].result


print(await run_claude_multiturn())
# --- Disk-backed: capture session_id, resume later ---

# Turn 1: capture the session_id from the ResultMessage (always the last event)
async with ClaudeSDKClient(options=expense_options) as client:
    await client.query("Lunch with Acme, $47")
    turn1 = [m async for m in client.receive_response()]
session_id = turn1[-1].session_id

# Turn 2: new client, new process — resume from disk
resume_opts = replace(expense_options, resume=session_id)
async with ClaudeSDKClient(options=resume_opts) as client:
    await client.query("What about $90?")
    turn2 = [m async for m in client.receive_response()]
print(f"[resumed {session_id[:8]}...] {turn2[-1].result}")

并排对比

将三个测试输入分别通过两个 SDK 运行。我们预期两者都会批准 47 美元的午餐、标记 650 美元的航班,并拒绝没有美元金额的输入。

from agents.exceptions import InputGuardrailTripwireTriggered


async def run_claude_quiet(msg: str) -> str:
    # Same guardrail as run_claude — duplicated here for a minimal comparison helper
    allowed, rejection = has_dollar_amount_check(msg)
    if not allowed:
        return rejection
    async with ClaudeSDKClient(options=expense_options) as client:
        await client.query(msg)
        messages = [m async for m in client.receive_response()]
        # receive_response() guarantees ResultMessage is the last event yielded
        return messages[-1].result or ""


test_inputs = [
    ("Lunch with Acme, $47", "approve"),
    ("Flight to NYC, $650", "flag"),
    ("Need approval for the thing", "guardrail"),
]

for msg, expected in test_inputs:
    print(f"\n{'=' * 60}\nINPUT: {msg}  (expect: {expected})\n{'=' * 60}")

    try:
        oai_out = await run_oai(msg)
    except InputGuardrailTripwireTriggered:
        oai_out = "[guardrail: InputGuardrailTripwireTriggered]"

    claude_out = await run_claude_quiet(msg)

    print(f"OAI:    {str(oai_out)[:200]}")
    print(f"Claude: {str(claude_out)[:200]}")

追踪

Claude 会向你的团队已经在运行的系统中发送数据。

每次运行的指标已经掌握在你手中。 每个 ResultMessage 都带有 .total_cost_usd 和 .usage —— 输入/输出 token、缓存读取、缓存写入、轮次计数。你会在下面的缓存单元格中看到这些。如需以编程方式跟踪成本,请参阅 ../observability/usage_cost_api.ipynb。

OpenTelemetry 是原生支持的。 SDK 与 Claude Code 运行时共享,启用后即可导出 OTel 指标和事件。

export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://your-collector:4317

这些都不是移植所必需的——无论遥测是否开启,你的代理运行方式完全相同。

每次工具调用和 API 请求都会发出一个带有 prompt.id 标签的事件,因此你可以将所有活动关联回原始提示。将其指向你现有的 Grafana / Datadog / Honeycomb / Langfuse 堆栈。有关完整的事件模式,请参阅监控文档。

仅限 Claude:提示缓存

系统提示和工具模式在首次调用后会自动缓存——无需配置。后续使用相同前缀的调用,该前缀的输入令牌成本约为原来的 10%。

由于之前的单元格已经使用 expense_options 运行过,因此当我们到达这里时缓存已经是热的——所以即使第 1 次运行也显示了大量的 cache_read_input_tokens。在冷启动的笔记本中,第 1 次运行将主要是 cache_creation,第 2 次运行主要是 cache_read。工具调用循环每次查询会发出多个 API 请求,每个请求都会扩展缓存的前缀——因此即使在重复运行时 cache_creation 也不会降至零,但它应该会缩小,而 cache_read 会增长。
for i in range(2):
    async with ClaudeSDKClient(options=expense_options) as client:
        await client.query("Coffee, $6")
        messages = [m async for m in client.receive_response()]
    # receive_response() guarantees ResultMessage is the last event yielded
    usage = messages[-1].usage or {}
    print(
        f"Run {i + 1}: cache_creation={usage.get('cache_creation_input_tokens', 0):>5}  "
        f"cache_read={usage.get('cache_read_input_tokens', 0):>5}  "
        f"input={usage.get('input_tokens', 0):>5}"
    )

如果你的 OpenAI Agents SDK 应用使用了 handoffs=[...],那么这里就是思维模式转变的地方。

OpenAI: handoffs=[specialist] 暴露了一个 transfer_to_specialist 工具。当被调用时,专家 接管 对话。第一个代理的任务就结束了。

Claude: 子代理通过 AgentDefinition 以编程方式定义,并通过 ClaudeAgentOptions(agents={...}) 传入。编排器通过 Agent 工具 委派 任务,获取结果后继续保持控制。

对于纯路由器(分流 → 专家)来说,差异很小。对于“代理 B 接管且 A 不再运行”这种关键逻辑的应用,考虑使用一个轻量级的 Python 调度器,而不是由 LLM 决定路由。

你可能还会看到在 Claude Code CLI 工作流中,子代理被定义为 .claude/agents/*.md 文件(例如,notebook 01)。那是用于交互式使用的基于文件系统的方式。对于以代码形式发布的 SDK 应用,下面的编程式 agents= 参数是自然的选择——不依赖文件系统。
from claude_agent_sdk import AgentDefinition

approver = AgentDefinition(
    description="Approves expenses under policy limits. Use for any submission where the amount is at or under the limit returned by check_policy.",
    prompt="You approve expense submissions that are within policy. Confirm the amount and category, and remind the submitter if a receipt is required.",
    tools=["mcp__expense__check_policy"],
)

escalator = AgentDefinition(
    description="Escalates over-limit expenses to a manager. Use when the submitted amount exceeds the policy limit for its category.",
    prompt="You escalate over-limit expenses. Draft a one-line note to the manager: include the amount, the category, and how far over the limit it is.",
    tools=["mcp__expense__check_policy"],
)

triage_options = ClaudeAgentOptions(
    model=CLAUDE_MODEL,
    system_prompt="Route each expense to the appropriate subagent. Delegate to approver if under limit, escalator if over.",
    mcp_servers={"expense": policy_server},
    allowed_tools=["Agent", "mcp__expense__check_policy"],
    agents={"approver": approver, "escalator": escalator},
)

print(f"Orchestrator configured with subagents: {list(triage_options.agents)}")

# Config only — not executed here. For a full end-to-end multi-agent run,
# see ./01_The_chief_of_staff_agent.ipynb

结论

你构建了什么

一个费用审批代理,逐原语移植:@function_tool → @tool,Agent → ClaudeAgentOptions,@input_guardrail/@output_guardrail → 普通的前/后检查,Runner.run → ClaudeSDKClient,会话支持内存和磁盘备份。

关键要点

后续步骤

常见问题

常见错误:1) 忘记设置API密钥,需在.env中配置ANTHROPIC_API_KEY和OPENAI_API_KEY。2) 工具名称错误,allowed_tools需遵循mcp__{server_name}__{tool_name}格式。3) 护栏函数未正确返回元组,需返回(布尔, 消息)。4) 会话恢复时session_id无效,需从ResultMessage获取。5) 权限模式不当导致工具调用被阻止,需设置permission_mode。排查方法:检查环境变量、验证工具名称、打印事件流、查看ResultMessage的usage和cost。生产优化:启用OTel导出、利用提示缓存、合理设置权限模式、使用磁盘会话持久化。

企业应用扩展

进一步学习:多代理编排参考notebook 01,工具生命周期钩子(PreToolUse/PostToolUse)参考notebook 03,成本追踪参考observability/usage_cost_api.ipynb。可探索将Claude Agent SDK集成到现有监控栈(Grafana/Datadog),或利用Claude Code的CLI工作流定义子代理。对于复杂路由,可考虑Python调度器替代LLM决策。

总结

本教程面向有编程基础的开发者,详细讲解如何将基于OpenAI Agents SDK的应用迁移到Claude Agent SDK。通过费用审批代理示例,逐一映射核心组件:工具定义、护栏、会话管理和追踪。强调Claude SDK的显式工具模式、进程内MCP服务器、普通函数护栏、内存/磁盘会话以及原生OpenTelemetry支持。提供完整代码对比、环境配置、实践步骤和常见问题排查,帮助开发者快速掌握迁移技巧并理解架构设计差异。

内容来源与验证

继续系统学习

站内还有更多完整实战教程

加入会员,解锁全部教程、完整代码与持续更新内容。

加入会员
查看全部AI 智能体教程