从OpenAI Agents SDK迁移到Claude Agent SDK指南
技术背景
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工具,一个用于控制代理可访问内容的分层权限系统,自动提示缓存,以及直接访问事件流以进行进度流式传输或运行中拦截。工具定义是显式的(你声明模式,而不是依赖类型提示内省),并且循环由你驱动。大多数移植涉及每个工具更多的样板代码,而其他地方的样板代码更少。
学完本笔记本后,你将能够:
- 用Claude等价物替换
@function_tool、护栏和Runner.run,而无需重写业务逻辑 - 移植单代理应用:自定义工具、输入/输出护栏、多轮会话和持久恢复
- 知道何时使用
ClaudeSDKClient与无状态query()函数 - 将SDK的OpenTelemetry导出连接到现有的可观测性栈
两个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 构建一次,然后移植每个原语。
前置要求
必备知识
- 熟悉 Python 中的
async/await - 熟悉你正在迁移的 OpenAI Agents SDK 原语
必备工具
推荐:
- 略读 notebook 00 以了解
query()和ClaudeSDKClient的基础知识
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}"
)
- --
附录:迁移 handoffs
如果你的 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,会话支持内存和磁盘备份。
关键要点
- 工具: 显式模式意味着你写的就是模型看到的——没有内省意外。将其捆绑为进程内 MCP 服务器,并通过
ClaudeSDKClient传递。 - 护栏: 普通函数将拒绝流程放在你的代码中,而不是框架中。当你希望在选项上注册
UserPromptSubmit钩子时,它就在那里。 - 会话: 一个
ClaudeSDKClient用于内存多轮对话。使用resume=session_id进行磁盘备份对话,可在重启后存活。 - 追踪: 原生支持 OpenTelemetry,因此它可以插入你已运行的堆栈。每次
ResultMessage都包含每轮成本和令牌使用情况——无需额外接线。
后续步骤
- 多代理编排:01_The_chief_of_staff_agent.ipynb
- 工具生命周期钩子(
PreToolUse/PostToolUse):03_The_site_reliability_agent.ipynb - 成本和用量追踪模式:../observability/usage_cost_api.ipynb
常见问题
常见错误: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支持。提供完整代码对比、环境配置、实践步骤和常见问题排查,帮助开发者快速掌握迁移技巧并理解架构设计差异。
- --
内容来源与验证
- 内容等级:PREMIUM_A
- 质量等级:S_CANDIDATE
- 难度:中级
- 验证等级:V1
- 原文作者:preston-tuggle
- 原文日期:2026-03-09
- 来源许可:MIT
- 来源版本:
d5a072cac433e5eedff0a09e7c0a23896fba71e5 - 查看原文
站内还有更多完整实战教程
加入会员,解锁全部教程、完整代码与持续更新内容。
加入会员