📌 本讲摘要
第 23 讲学了 SDK 基础------query() / ClaudeCodeOptions 11 字段 / 4 类消息 / 3 个基础场景。本讲进入高级应用:自定义工具(@tool 装饰器 + create_sdk_mcp_server)、多 Agent 编排(主 Agent + SubAgent 协同)、流式会话(resume 多轮 + 流式输出)、错误处理(try/except + 重试 + fallback)。项目实战:自动化测试修复 Agent(分析 → 修复两阶段工作流)、4 层权限管理(只读分析 → 编辑修复 → Hook 拦截 → 审计日志)。
本讲的三条主线:
第一、自定义工具 + 多 Agent 编排------@tool 装饰器把 Python 函数变 Claude 工具(自动生成 JSON schema)、create_sdk_mcp_server 打包 in-process MCP server;主 Agent 调 SubAgent 协同。
第二、流式会话 + 错误处理------resume 续跑旧会话 / 流式输出(逐块打印)/ 3 层错误处理(try/except + 指数退避重试 + fallback)。
第三、自动化测试修复 Agent 实战------分析(只读、plan 模式)→ 修复(acceptEdits 模式、Edit 改代码 + 跑测试);4 层权限管理(只读 / 编辑 / Hook / 日志)。
学完本讲、你应该能用 @tool 装饰器写自定义工具、能用 create_sdk_mcp_server 打包 in-process MCP server、能用 resume 续跑多轮对话、能做 3 层错误处理、能写完整的测试修复 Agent 两阶段工作流。
📖 详细内容
自定义工具:@tool 装饰器 + create_sdk_mcp_server
第 19 讲学了 MCP server------用 npx 启动 stdio 进程暴露工具。本讲学 in-process MCP server------用 @tool 装饰器把 Python 函数直接变成工具、在 SDK 进程内运行、无需 npx / 进程隔离。
@tool 装饰器
@tool 装饰器是 SDK 提供的装饰器、把 Python 函数变成 Claude 可调用的工具、自动生成 JSON schema(从 type hints 推断参数类型)。
用法:
from claude_code_sdk import tool
@tool
def get_user(user_id: int) -> dict:
"""根据用户 ID 获取用户对象。"""
return db.query("SELECT * FROM users WHERE id = ?", user_id)
装饰器自动:
-
从 type hints 推断参数 schema(
user_id: int→{"type": "integer"})。 -
从 docstring 推断工具 description(给 Claude 看)。
-
注册到 SDK 工具列表、Claude 推理时能看到
get_user(user_id: int)工具。
create_sdk_mcp_server
create_sdk_mcp_server 把多个 @tool 打包成 in-process MCP server、无需 stdio 进程。
用法:
from claude_code_sdk import create_sdk_mcp_server
server = create_sdk_mcp_server(
name="my-tools",
version="1.0.0",
tools=[get_user, create_user, update_user]
)
然后在 ClaudeCodeOptions.mcp_servers 里引用这个 server,Claude 就能调 my-tools 暴露的所有工具。
in-process MCP vs stdio MCP
in-process 优势:无需 npx / 启动快(进程内函数调用)/ 易调试(普通 Python 调试器)/ 单语言(纯 Python)。
stdio 优势:跨语言(任何语言写 server)、跨进程(隔离)、跨机器(远程 MCP)。
选择:工具用 Python 写 + 不需要跨进程 → in-process(本讲重点)。工具用其他语言写 + 需要隔离 / 跨机器 → stdio。
| 维度 | @tool 装饰器(in-process) | stdio MCP server |
|---|---|---|
| 集成方式 | Python 装饰器(同进程) | 命令行启动独立进程(npx / uvx) |
| 性能 | ~ms 级(进程内函数调用) | ~5-10ms(stdio IPC 开销) |
| 部署 | 同 Python 进程一起打包 | 需要 npm / npx 启动 |
| 调试 | 普通 Python 调试器(pdb / pydev) | stdio echo + mcp-inspector |
| 适用场景 | 工具用 Python 写 / 不需要跨进程 | 工具用其他语言写 / 需要隔离 / 跨机器 |
多 Agent 编排:主 Agent + SubAgent 协同
第 6 讲学过 SubAgent------独立上下文角色。本讲用 SDK 编排主 Agent + SubAgent------主 Agent 负责整体流程、SubAgent 负责隔离任务。
2 种协同模式
模式 1:顺序协同------主 Agent 调 SubAgent A → 等 A 完成 → 调 SubAgent B → 汇总结果。例:主 Agent 调 code-reviewer SubAgent 做代码审查 → 调 test-runner SubAgent 跑测试 → 汇总报告。
模式 2:并行协同------主 Agent 调多个 SubAgent 同时跑(用 asyncio.gather)→ 等所有完成 → 汇总结果。例:主 Agent 同时调 3 个 SubAgent(lint / format / test)→ 3 个并行跑 → 汇总报告、快 3 倍。
2 种模式的选择:SubAgent 之间有依赖(A 完成后 B 才能跑)→ 顺序。SubAgent 之间独立 → 并行。
主 Agent 汇总 SubAgent 报告
主 Agent 调 SubAgent 后、SubAgent 完成会输出报告(写到 .claude/state/.json 或 stdout)。主 Agent 读报告、整合到自己的输出。
3 个 SubAgent 报告整合:code-reviewer 报告(代码问题)+ test-runner 报告(测试结果)+ lint 报告(代码风格)→ 主 Agent 输出统一报告。
完整代码见后面多 Agent 编排完整代码代码块。

流式会话:resume + 流式输出
第 23 讲讲过 query() 是 async generator、本节深入两个高级用法:resume + 流式输出。
resume:续跑旧会话
query() 用 resume=session_id 续跑旧会话(从 ResultMessage 拿到 session_id)。
用法:query(prompt="继续上次的任务", options=ClaudeCodeOptions(resume="session_abc123")。Claude 加载旧会话的上下文(之前的对话 / 工具结果 / 推理状态)、继续执行。
适用场景:
• 多轮对话------用户多次问、Claude 累加上下文。
• 中断续跑------长任务跑一半中断(超时 / 错误)、后续跑时 resume 旧会话。
• 会话持久化------把 session_id 存数据库、下次读出来 resume。
流式输出:逐块打印
query() 异步流式返回 AssistantMessage、每条消息包含多个 content block(文本 / 工具调用)。逐块处理 = 实时反馈。
典型代码:async for msg in query(...): print(msg.content[0].text, end="", flush=True)------逐字符打印、用户实时看到 Claude 输出。
流式 vs 非流式:流式反馈好(用户等得不焦虑)/ 适合长任务;非流式简单 / 适合短任务。
中断与续跑
长任务(timeout / 错误)中断时、捕获 session_id 存数据库。下次调用时 resume 旧会话、从断点继续。避免长任务中断重头跑。
完整代码见后面流式会话 + 错误处理完整代码代码块。
错误处理:try/except + 重试 + fallback
SDK 跑 Claude 不可避免遇到错误(网络超时 / API 报错 / 任务失败)。生产环境必须有 3 层错误处理。
第 1 层:try/except 捕获异常
SDK 抛的异常类型:
• CLINotFoundError------Claude Code CLI 没安装。
• ProcessError------Claude Code 子进程错误。
• CLIJSONDecodeError------消息 JSON 解析失败。
• TimeoutError------超时(asyncio.TimeoutError)。
用 try/except 捕获、记录到日志 + 友好错误提示用户。
第 2 层:指数退避重试
网络超时 / API 临时错误(5xx)适合重试。指数退避策略:第 1 次重试等 1 秒、第 2 次等 2 秒、第 3 次等 4 秒......避免雪崩。
限制:重试 ≤ 3 次、过多会导致长任务变超长任务。
第 3 层:fallback 降级
Claude 跑失败(超过重试次数)时、降级到简单规则:
• 跑代码审查失败 → 回退到提示用户人工 review。
• 跑测试修复失败 → 回退到列出失败原因、让人修。
• 跑自动 commit 失败 → 回退到提示用户 commit。
fallback 不是放弃、而是用简单方式解决。3 层叠加 = 健壮的错误处理。
项目实战:自动化测试修复 Agent
综合运用 24 讲学的所有内容、做一个完整项目------自动化测试修复 Agent。
完整代码见后面自动化测试修复 Agent 完整代码代码块。
两阶段工作流
阶段 1:分析(只读)------用 plan 模式(只读不写),Claude 读测试失败信息 + 读代码 + 分析失败原因、输出修复计划。
阶段 2:修复(编辑)------用 acceptEdits 模式(允许 Edit),Claude 根据修复计划 Edit 改代码 + 跑测试验证。
2 阶段工作流:先分析后修复、避免边改边分析导致乱改。
4 层权限管理
层 1:只读分析 ------阶段 1 用 permission_mode="plan"(plan 模式只读),Claude 只能 Read / Grep / Glob、不能 Edit / Write / Bash。
层 2:编辑修复 ------阶段 2 用 permission_mode="acceptEdits"(自动接受 Edit),Claude 能 Edit / Write / Bash(白名单)。
层 3:Hook 拦截------用 .claude/settings.json 的 PreToolUse HW 拦危险命令(rm -rf / curl | sh),即使 acceptEdits 模式也拦。
层 4:审计日志------PostToolUse HW 记录所有 Edit / Write / Bash 到 audit log、出事后可追溯。
4 层叠加形成纵深防御------只读分析 → 编辑修复 → Hook 拦截 → 审计日志、任何一层失效其他层兜底。
| 权限层 | 机制 | 作用 |
|---|---|---|
| 层 1:只读分析 | permission_mode="plan" | 阶段 1 只读不写、只输出修复计划 |
| 层 2:编辑修复 | permission_mode="acceptEdits" | 阶段 2 允许 Edit/Write、实际改代码 |
| 层 3:HW 拦截 | PreToolUse deny-pipe-exec / deny-extra-dangerous | 危险命令根本跑不出来、纵深防御 |
| 层 4:审计日志 | PostToolUse audit-log | 所有 Edit/Write/Bash 记录到日志、出事后可追溯 |
SDK × 5 种扩展机制协同
SDK 是调用者侧------Python 代码调 Claude。5 种扩展机制是被调用者侧------Claude 推理时用。SDK + 5 种机制协同工作。
• SDK + Skills------SDK 启动时加载 .claude/skills/ 的 SKILL.md,Claude 推理时自动发现本地能力。
• SDK + SubAgent------SDK 启动时加载 .claude/agents/ 的 *.md,Claude 推理时用 @name 调 SubAgent。
• SDK + Hooks------SDK 启动时加载 .claude/settings.json 的 hooks 字段、工具调用前后触发拦截 / 记录。
• SDK + MCP------SDK 启动时加载 .mcp.json,Claude 推理时调 mcp__xxx__yyy 工具。
• SDK + Rules------SDK 启动时加载 .claude/rules/ 的 *.md,Claude 推理时遵守软规范。
SDK + 5 机制 = 完整工程化体系。SDK 是骨架,5 机制是血肉。
🛠️ 实战代码
📄 第 24 讲配套:自定义工具 + in-process MCP server 完整代码(@tool 装饰器 + 2 个 tool + create_sdk_mcp_server)
#!/usr/bin/env python3
# custom_tools.py
# 自定义工具 + in-process MCP server
import asyncio
from claude_code_sdk import (
query, ClaudeCodeOptions, tool, create_sdk_mcp_server
)
# === 自定义工具 1:查数据库 ===
@tool
def get_user(user_id: int) -> dict:
"""根据用户 ID 获取用户对象。
Args:
user_id: 用户 ID(整数)
Returns:
包含 id, name, email 的字典
"""
return {
"id": user_id,
"name": f"User {user_id}",
"email": f"user{user_id}@example.com"
}
# === 自定义工具 2:发邮件 ===
@tool
def send_email(to: str, subject: str, body: str) -> str:
"""发送邮件。
Args:
to: 收件人邮箱
subject: 邮件主题
body: 邮件正文
Returns:
发送结果("success" 或错误信息)
"""
# 实际场景调 SMTP
return f"Email sent to {to} with subject '{subject}'"
# === 打包成 in-process MCP server ===
custom_server = create_sdk_mcp_server(
name="my-custom-tools",
version="1.0.0",
tools=[get_user, send_email]
)
async def main():
options = ClaudeCodeOptions(
# 加载 in-process MCP server
mcp_servers={
"my-custom-tools": custom_server
},
max_turns=5
)
# Claude 调 get_user(1) 工具
async for msg in query(
prompt="查询用户 ID 1 的信息",
options=options
):
from claude_code_sdk import AssistantMessage
if isinstance(msg, AssistantMessage):
for block in msg.content:
if hasattr(block, "text"):
print(block.text, end="", flush=True)
asyncio.run(main()
# === @tool 装饰器自动做的事 ===
# 1. 从 type hints 推断参数 schema
# user_id: int → {"type": "integer"}
# to: str → {"type": "string"}
# 2. 从 docstring 推断 description
# "根据用户 ID 获取用户对象" → tool description
# 3. 注册到 SDK 工具列表
# Claude 看到 mcp__my-custom-tools__get_user(user_id: int)
# === in-process MCP vs stdio MCP ===
# in-process: 进程内函数调用,启动快,易调试,单语言
# stdio: 跨进程,跨语言,跨机器,隔离
📄 第 24 讲配套:多 Agent 编排完整代码(主 Agent + code-reviewer SubAgent + test-runner SubAgent + 汇总)
#!/usr/bin/env python3
# multi_agent.py
# 多 Agent 编排:主 Agent + 3 个 SubAgent
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions, AssistantMessage
async def run_subagent(name: str, prompt: str, options: ClaudeCodeOptions) -> str:
"""跑一个 SubAgent,返回报告"""
report = ""
async for msg in query(prompt=prompt, options=options):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if hasattr(block, "text"):
report += block.text
return report
async def main():
# === 阶段 1:并行调 3 个 SubAgent(快 3 倍)===
code_review_task = run_subagent(
name="code-reviewer",
prompt="审查 src/api/users.py 的代码",
options=ClaudeCodeOptions(
allowed_tools=["Read", "Grep"],
system_prompt="你是 code-reviewer,只输出代码问题",
max_turns=10
)
)
test_runner_task = run_subagent(
name="test-runner",
prompt="跑 pytest tests/test_users.py,报告结果",
options=ClaudeCodeOptions(
allowed_tools=["Bash(pytest:*)", "Read"],
system_prompt="你是 test-runner,只输出测试结果",
max_turns=10
)
)
lint_task = run_subagent(
name="linter",
prompt="跑 ruff check src/api/users.py,报告问题",
options=ClaudeCodeOptions(
allowed_tools=["Bash(ruff:*)"],
system_prompt="你是 linter,只输出 lint 问题",
max_turns=5
)
)
# 并行跑(asyncio.gather)
review_report, test_report, lint_report = await asyncio.gather(
code_review_task,
test_runner_task,
lint_task
)
# === 阶段 2:主 Agent 汇总 ===
print("=== code-reviewer 报告 ===")
print(review_report)
print("\n=== test-runner 报告 ===")
print(test_report)
print("\n=== linter 报告 ===")
print(lint_report)
# 写到 .claude/state/reports/latest.md
with open(".claude/state/reports/latest.md", "w") as f:
f.write(f"# 综合报告\n\n")
f.write(f"## 代码审查\n{review_report}\n\n")
f.write(f"## 测试\n{test_report}\n\n")
f.write(f"## Lint\n{lint_report}\n\n")
asyncio.run(main()
# === 2 种协同模式 ===
# 顺序: A 完成后 B,等所有完成
# 并行: asyncio.gather 并行跑,快 N 倍
# === 主 Agent 汇总 SubAgent 报告 ===
# 1. SubAgent 写报告到 .claude/state/<name>.json
# 2. 主 Agent 读报告
# 3. 主 Agent 整合到自己的输出
# 4. SubAgentStop HW 验证报告完整性(第 18 讲)
📄 第 24 讲配套:流式会话 + 错误处理完整代码(resume 续跑 + 流式输出 + try/except + 指数退避重试 + fallback)
#!/usr/bin/env python3
# streaming_errors.py
# 流式会话 + 错误处理(resume + 流式输出 + 3 层错误处理)
import asyncio
import time
from claude_code_sdk import (
query, ClaudeCodeOptions,
AssistantMessage, ResultMessage,
CLINotFoundError, ProcessError, CLIJSONDecodeError
)
async def stream_query(prompt: str, options: ClaudeCodeOptions):
"""流式 query():逐块打印 AssistantMessage"""
async for msg in query(prompt=prompt, options=options):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if hasattr(block, "text"):
# 流式输出:逐字符打印 + flush
print(block.text, end="", flush=True)
elif isinstance(msg, ResultMessage):
# 返回 session_id 供下次 resume
return msg.session_id, msg.total_cost_usd
async def query_with_retry(
prompt: str,
options: ClaudeCodeOptions,
max_retries: int = 3
):
"""3 层错误处理:try/except + 指数退避重试 + fallback"""
for attempt in range(max_retries):
try:
# 尝试跑
session_id, cost = await stream_query(prompt, options)
return session_id, cost
except (ProcessError, CLIJSONDecodeError) as e:
# 临时错误,指数退避重试
if attempt < max_retries - 1:
wait_time = 2 ** attempt # 1s, 2s, 4s
print(f"\n⚠️ 第 {attempt + 1} 次失败: {e}")
print(f" {wait_time}秒后重试...")
await asyncio.sleep(wait_time)
else:
# 重试耗尽,fallback
print(f"\n❌ 重试 {max_retries} 次仍失败,fallback")
return await fallback_handler(prompt)
except CLINotFoundError as e:
# CLI 没安装,不能重试
print(f"\n❌ Claude Code CLI 没安装: {e}")
return None, 0.0
except asyncio.TimeoutError as e:
# 超时
print(f"\n⚠️ 超时: {e}")
if attempt < max_retries - 1:
await asyncio.sleep(2 ** attempt)
else:
return await fallback_handler(prompt)
async def fallback_handler(prompt: str):
"""Fallback:降级到简单规则"""
print(f"\n🔄 Fallback:Claude 跑失败,降级到简单规则")
# 例:如果任务是代码审查,fallback 是"列出文件让人工审"
if "审查" in prompt:
return "session_fallback", 0.0 # 标记是 fallback
return "session_fallback", 0.0
async def main():
options = ClaudeCodeOptions(
allowed_tools=["Read", "Grep"],
max_turns=10
)
# 第 1 次:流式跑(实时反馈)
session_id, cost = await query_with_retry(
prompt="分析这个项目结构",
options=options
)
if session_id == "session_fallback":
print("使用了 fallback,跳过 resume")
return
# 第 2 次:resume 续跑(用第 1 次的 session_id)
options_resume = ClaudeCodeOptions(
resume=session_id, # ← 关键:续跑旧会话
allowed_tools=["Read", "Grep"],
max_turns=10
)
print("\n\n=== 续跑(基于上轮上下文)===\n")
session_id_2, cost_2 = await query_with_retry(
prompt="继续:具体说说 main.py 做了什么",
options=options_resume
)
print(f"\n\n📊 累计 cost: ${(cost + cost_2):.4f}")
asyncio.run(main()
# === resume 续跑 ===
# 1. 第 1 次 query() 拿到 session_id(ResultMessage.session_id)
# 2. 第 2 次 query() 用 resume=session_id 续跑
# 3. Claude 加载旧会话的上下文(对话历史 / 工具结果 / 推理状态)
# === 3 层错误处理 ===
# 第 1 层: try/except 捕获异常(4 种异常类型)
# 第 2 层: 指数退避重试(1s, 2s, 4s, ≤ 3 次)
# 第 3 层: fallback 降级(简单规则处理)
# === 流式输出 ===
# print(block.text, end="", flush=True)
# flush=True 关键,否则等缓冲区满才输出
📄 第 24 讲配套:自动化测试修复 Agent 完整代码(两阶段工作流 + 4 层权限管理 + 完整 Python)
#!/usr/bin/env python3
# test_fixer_agent.py
# 自动化测试修复 Agent(两阶段工作流 + 4 层权限管理)
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions, AssistantMessage
async def analyze_phase(test_output: str) -> str:
"""阶段 1:分析(只读,plan 模式)"""
options = ClaudeCodeOptions(
# 层 1:只读权限(plan 模式)
permission_mode="plan",
allowed_tools=["Read", "Grep", "Glob", "Bash(pytest:*)"],
system_prompt="""你是测试分析专家。分析测试失败原因:
1. 读测试输出,理解失败模式
2. 读相关源码,定位 bug
3. 输出修复计划(具体到 file:line + 修复方案)
不要修改任何文件,只分析。""",
max_turns=10
)
plan = ""
async for msg in query(
prompt=f"分析这个测试失败:\n{test_output}",
options=options
):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if hasattr(block, "text"):
plan += block.text
return plan
async def fix_phase(plan: str) -> bool:
"""阶段 2:修复(编辑,acceptEdits 模式)"""
options = ClaudeCodeOptions(
# 层 2:编辑权限(acceptEdits 模式)
permission_mode="acceptEdits",
allowed_tools=["Read", "Edit", "Write", "Bash(pytest:*)", "Bash(ruff:*)"],
system_prompt="""你是测试修复专家。根据修复计划:
1. 用 Edit 改代码(只改 plan 提到的位置)
2. 改完跑 pytest 验证
3. 如果测试还失败,继续修(最多 5 轮)
不要做 plan 之外的事。""",
max_turns=20 # 给修复更多轮次
# 层 3:HW 拦截(在 .claude/settings.json 配置 PreToolUse)
# 层 4:HW 审计日志(PostToolUse 记录所有 Edit/Write)
)
print("=== 阶段 2:修复 ===\n")
async for msg in query(
prompt=f"根据这个修复计划修复代码:\n\n{plan}",
options=options
):
from claude_code_sdk import AssistantMessage
if isinstance(msg, AssistantMessage):
for block in msg.content:
if hasattr(block, "text"):
print(block.text, end="", flush=True)
elif hasattr(block, "name"):
# 工具调用
print(f"\n🔧 工具: {block.name}")
# 验证测试通过
print("\n\n=== 验证 ===")
proc = await asyncio.create_subprocess_exec(
"pytest", "tests/", "-q",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await proc.communicate()
return proc.returncode == 0
async def main():
# 1. 跑测试,看失败
proc = await asyncio.create_subprocess_exec(
"pytest", "tests/", "-q",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await proc.communicate()
test_output = stdout.decode() + stderr.decode()
if proc.returncode == 0:
print("✅ 所有测试通过,无需修复")
return
print(f"❌ 测试失败,启动修复 Agent\n")
# 2. 阶段 1:分析
print("=== 阶段 1:分析(只读) ===\n")
plan = await analyze_phase(test_output)
print(f"\n\n📋 修复计划:\n{plan}\n")
# 3. 阶段 2:修复
success = await fix_phase(plan)
if success:
print("\n✅ 测试修复成功")
else:
print("\n❌ 修复失败,需要人工介入")
asyncio.run(main()
# === 配套 .claude/settings.json(层 3 + 层 4)===
# {
# "hooks": {
# "PreToolUse": [
# {
# "matcher": "Bash",
# "hooks": [
# {"type": "command", "command": "bash .claude/hooks/deny-pipe-exec.sh"},
# {"type": "command", "command": "bash .claude/hooks/deny-extra-dangerous.sh"}
# ]
# }
# ],
# "PostToolUse": [
# {
# "matcher": "Edit|Write",
# "hooks": [
# {"type": "command", "command": "bash .claude/hooks/audit-log.sh"}
# ]
# }
# ]
# }
# }
# === 4 层权限管理 ===
# 层 1: 只读分析(permission_mode="plan")
# 层 2: 编辑修复(permission_mode="acceptEdits")
# 层 3: HW 拦截(PreToolUse deny-pipe-exec / deny-extra-dangerous)
# 层 4: 审计日志(PostToolUse audit-log)
# === 两阶段工作流 ===
# 阶段 1: 只读分析(plan 模式)→ 输出修复计划
# 阶段 2: 编辑修复(acceptEdits 模式)→ Edit 改代码 + 跑测试
⚠️ 常见坑
⚠️ @tool 装饰器忘写 type hints(Claude 看不到参数 schema、工具无法调用、所有参数必须 type hints + docstring)
type hints 缺失陷阱:@tool 装饰器依赖 type hints 生成 JSON schema,如果函数参数没写 type hints(
def get_user(user_id)而非def get_user(user_id: int)),Claude 看不到参数类型、工具无法调用。修正:所有 @tool 函数必须 type hints(参数 + 返回值)+ docstring(描述工具用途)。判断标准:你的 @tool 函数有 type hints + docstring 吗?参数 / 返回值都标了吗?没标、补 type hints。⚠️ 流式输出忘 flush(实时反馈没生效、用户等很久才看到结果、print 加 flush=True)
流式变全量陷阱:流式输出时 print 没加
flush=True,Python print 默认行缓冲(等换行才输出),如果 Claude 输出的文本没换行、用户等很久才看到结果(等缓冲区满才输出)。修正:流式输出必须print(block.text, end="", flush=True)------end="" 避免换行、flush=True 立即输出。判断标准:你的流式 print 有 flush=True 吗?没有、加 flush。⚠️ 重试没限次数(网络持续失败导致死循环、重试 ≤ 3 次后 fallback)
死循环重试陷阱:网络持续失败时(Anthropic API 持续 5xx)、无限重试导致长任务变超长任务------用户等几小时还看不到结果。修正:重试 ≤ 3 次、第 4 次 fallback 降级。指数退避:1s / 2s / 4s、避免持续打 API。判断标准:你的重试有限次数吗?≤ 3 次?无限 = 改限次。
⚠️ SubAgent 报告没汇总(主 Agent 看不到 SubAgent 完整结果、SubAgentStop HW + 显式汇总)
SubAgent 报告丢失陷阱:主 Agent 调 3 个 SubAgent 并行跑、3 个 SubAgent 完成、主 Agent 只看到任务完成信号、看不到具体报告(报告写在 .claude/state/.json 但主 Agent 没读)。修正:主 Agent 显式读 .claude/state/.json + 汇总;或者配 SubAgentStop HW 验证报告完整性 + 自动汇总。判断标准:你的 SubAgent 报告汇总了吗?主 Agent 看到完整结果吗?没有、显式汇总。
💡 一句话备忘
Agent SDK 高级应用把 SDK 从基础调用升级到工程化项目------@tool 装饰器 + create_sdk_mcp_server 自定义工具、主 Agent + SubAgent 多 Agent 编排、resume + 流式输出 + 3 层错误处理、自动化测试修复 Agent(分析 → 修复两阶段 + 4 层权限管理)是完整项目实战。