Agent工坊

【Agent工坊】Claude Agent SDK 实战:用 Hooks + 子Agent 搭建生产级自动化流水线

Anthropic 把 Claude Code 打磨了两年的 Agent 引擎拆出来做成了 SDK。2026 年搜索量暴增 50,000%,6月15日计费独立拆分——官方正式承认"Agent 自动化"已是独立赛道。本文用 Python 带你从零写一条带安全护栏、自动日志、并行子Agent 的完整流水线。

为什么你需要关注 Claude Agent SDK

先看两个数字:2025 年 5 月,"claude agent sdk" 月搜索量 50 次;2026 年 4 月,这个数字是 14,800 次——接近 50,000% 的同比增长。这不只是搜索数据的狂欢,背后是开发者社区在用脚投票。

2026 年 6 月 15 日,Anthropic 做了一件影响所有 AI Agent 开发者的事:把 Claude Agent SDK 和 GitHub Actions 的 API 用量从交互式 Claude Code 的配额池里彻底独立拆分出来。翻译成人话:官方承认"无人值守 Agent 自动跑任务"不是"人机对话"的边角料,而是和"开发者坐在终端前敲命令"同等重要的第一公民场景。

Claude Agent SDK 到底是什么?

一句话定义:它是 Claude Code 的引擎层,打包成你可以编程调用的 Python/TypeScript 库。你给它一个 system prompt(告诉 Agent 它是谁、要遵守什么规则)、一组工具(内置的 + MCP 扩展的)、一个目标("审查这 200 个文件的安全漏洞并生成报告"),然后它就跑 autonomously 的 agent loop:

  1. 模型分析当前状态,决定调用哪个工具
  2. SDK 执行工具调用,拿到结果
  3. 结果喂回上下文窗口
  4. 模型基于新信息继续推理,决定下一步
  5. 循环直到任务完成或撞到上限

这整个过程全自动,不需要你守在屏幕前点"允许"。你写一次代码,它跑 N 次任务。

它的前身叫 "Claude Code SDK",2025 年 9 月发布,当月就紧急改名——因为太多开发者以为"这只是个写代码的工具"。实际上它可以做任何事:调 API、查数据库、发通知、操作文件系统、搜网页、生成报告。只要你能用工具函数表达的任务,它都能跑。

和原始 Anthropic API 的差别有多大?

如果你用过 /v1/messages 的原生 API,一定写过这样的循环:messages.create() → 检查 stop_reason → 解析 tool_use → 自己执行函数 → 构造 tool_result → 再调 messages.create() → 循环。还要自己管上下文窗口、自己写重试逻辑、自己做会话持久化。一个完整的 agent harness 轻松 600 行起。

Claude Agent SDK 的等价代码:20 行。而且这个 harness 已经被地球上所有 Claude Code 用户日夜测试了两年。

安装

# Python(推荐用 uv,Python 3.10+)

uv init && uv add claude-agent-sdk

# 或用传统 pip

pip install claude-agent-sdk

# TypeScript(Node.js 18+)

npm install @anthropic-ai/claude-agent-sdk

Python 版会自动打包 Claude Code CLI 二进制,你不需要额外装 Claude Code。只需要设置环境变量 ANTHROPIC_API_KEY 就可以开始写代码。

第一个 Agent:从零到跑通的完整过程

我们先从最简单的例子开始,带你理解 Agent loop 的每个环节。这个 Agent 的任务是"审查一个 Python 文件,找出可能导致崩溃的 bug 并修复"。

import asyncio

from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ResultMessage

async def main():

    # query() 返回一个 async generator,逐个吐出消息

    async for message in query(

        prompt="读取 utils.py,检查潜在的崩溃点(如除零、空列表、None引用),修复所有发现的问题",

        options=ClaudeAgentOptions(

            allowed_tools=["Read", "Edit", "Glob"],

            permission_mode="acceptEdits", # 自动批准文件编辑,开发环境适用

            max_turns=20, # ⚠️ 最多 20 轮,防止死循环烧 API 额度

        ),

    ):

        # 处理两种消息类型

        if isinstance(message, AssistantMessage):

            for block in message.content:

                if hasattr(block, "text"):

                    print(f"💬 {block.text}") # Claude 的思考过程

                elif hasattr(block, "name"):

                    print(f"🔧 调用工具: {block.name}")

        elif isinstance(message, ResultMessage):

            print(f"✅ 任务完成: {message.subtype}")

            # subtype 可能是:

            # "success" — 正常完成

            # "error_max_turns" — 轮次耗尽(你设的 max_turns 到了)

            # "error_max_budget_usd" — 预算耗尽(你设的 max_budget_usd 到了)

asyncio.run(main())

运行后会看到什么?

假设 utils.py 里有个函数用 len(numbers) 做除数但没检查空列表,Agent 的实际输出流会是:

💬 我来审查 utils.py 中的潜在崩溃点。先读取文件内容。

🔧 调用工具: Read

💬 我发现了两个问题:

    1. calculate_average 没有检查空列表,会触发 ZeroDivisionError

    2. get_user_name 对 None 调用 .upper(),会触发 AttributeError

    让我逐一修复。

🔧 调用工具: Edit

💬 已为 calculate_average 添加空列表检查。

🔧 调用工具: Edit

💬 已为 get_user_name 添加 None 检查。让我重新读取文件确认修复完整。

🔧 调用工具: Read

💬 所有修复已确认。文件现在包含完善的错误处理。

✅ 任务完成: success

关键理解

  • AssistantMessage 不是"最终结果"——它是 Agent 的实时思考流。你能看到它每步在想什么、为什么调这个工具
  • ResultMessage.subtype 区分正常完成和异常终止。生产环境必须检查 subtype,不能假设都是 success
  • max_turns 绝不是可选的。SDK 默认值是无限!一个陷入循环的 Agent 如果不设上限,会一直烧 API 额度直到你手动 kill 进程

权限模式速查表

模式行为适用场景
acceptEdits自动通过文件编辑和常用命令,其他需审批受信任的开发工作流
plan只读模式,所有写操作需 callback 审批先看方案再执行
dontAsk只跑 allowed_tools 里的工具,其余一律拒绝锁定式 headless 生产环境
auto模型分类器自动判断是否批准需要安全护栏的自治 Agent
bypassPermissions跳过所有权限检查沙箱 CI、完全可信环境

生产环境必须用 dontAsk + 精确 allowed_tools 列表。用 acceptEditsauto 跑无人值守任务,Agent 可能卡在权限弹窗上永远等不到你点"允许"。

实战一:自定义工具——让 Agent 调你的业务 API

内置工具覆盖了文件操作(Read/Write/Edit)、搜索(Glob/Grep)、Shell(Bash)、网络(WebSearch/WebFetch),但真正的生产环境 Agent 需要调你的业务系统——数据库、内部 API、第三方服务。这时需要自定义工具。

最重要的模式——isError: True

这可能是 Claude Agent SDK 自定义工具开发中唯一最重要的一行代码。假设你的工具函数抛了未捕获异常:

  • 整个 Agent loop 直接停机
  • Claude 永远看不到错误信息
  • query() 调用 直接失败,你拿到的是一个异常而不是结果

但如果你在 try/except 里捕获异常并返回 isError: True

  • Agent loop 继续运行
  • Claude 看到错误信息作为数据
  • Claude 可以决定重试或换备用方案

这就是"把错误当数据"和"错误杀死进程"的区别。生产环境的 Agent 可能跑几个小时处理几百个文件——中间一个工具调用失败不应该让整个任务报废。

from claude_agent_sdk import tool, create_sdk_mcp_server

import asyncio

@tool("check_stock", "查询指定股票在指定交易所的实时价格",

      {"symbol": str, "exchange": str})

async def check_stock(args: dict) -> dict:

    """

    这个函数模拟了一个真实的生产工具——调用第三方金融 API。

    """

    try:

        # 实际环境里这里调你的 REST API / gRPC 服务

        await asyncio.sleep(0.5) # 模拟网络延迟

        price = 187.35 # 模拟返回数据

        return {

            "content": [{

                "type": "text",

                "text": f"{args['symbol']}({args['exchange']})当前价: ¥{price},涨跌幅: +2.3%"

            }]

        }

    except ConnectionError as e:

        # ⚠️ 这一行救你的生产线:

        # 返回 isError=True 而不是 raise

        return {

            "content": [{

                "type": "text",

                "text": f"API 连接失败: {str(e)}。建议操作: 使用本地缓存数据或切换到备用数据源。"

            }],

            "isError": True # ← 告诉 Agent Loop "这是个可恢复错误,请继续"

        }

    except ValueError as e:

        # 参数错误——告诉 Agent 修正输入

        return {

            "content": [{

                "type": "text",

                "text": f"参数错误: {str(e)}。请检查股票代码和交易所名称是否正确。"

            }],

            "isError": True

        }

# 把工具注册为 MCP Server

server = create_sdk_mcp_server(

    name="stock-service",

    version="1.0.0",

    tools=[check_stock]

)

# 在 Agent 中加载

options = ClaudeAgentOptions(

    mcp_servers={"stock": server},

    allowed_tools=[

        "Read", "Bash", "Glob",

        "mcp__stock__check_stock", # ← MCP 工具的命名规范

    ],

)

踩坑提醒——MCP 工具名格式

MCP 工具在 allowed_tools 中的命名严格遵守 mcp__<服务器名>__<工具函数名> 格式,双下划线分隔。写错一个下划线、或者服务器名和注册时不一致,工具就调不到——而且不会有明确的报错,Agent 只会当作这个工具不存在。

排查技巧:在 Agent 启动时打印注册的 MCP 工具列表确认命名。

实战二:Hooks 拦截器——5 行代码防住一个生产事故

Hooks 是 Claude Agent SDK 最被低估的功能。它让你在 Agent 执行的关键节点插入自定义回调——相当于给 Agent 装了一层透明的"安检门"。

原理很简单:Agent 做事分阶段(启动→推理→调工具→收结果→继续推理→完成),每个阶段切换时 SDK 触发一个事件。你可以注册 hook 来监听这些事件,在回调里做三件事:

  1. 放行:返回空对象 {},Agent 正常继续
  2. 拒绝:返回 permissionDecision: "deny",阻止这个操作
  3. 修改:返回 permissionDecision: "allow" 并附带修改后的输入

场景一:阻止 Agent 修改敏感文件

假设你的项目里有 .env 文件存 API Key,你绝对不想 Agent 在任何情况下修改它。5 行代码解决:

from claude_agent_sdk import (

    ClaudeSDKClient, ClaudeAgentOptions, HookMatcher

)

async def protect_env(input_data, tool_use_id, context):

    """PreToolUse hook:在文件写入前拦截"""

    file_path = input_data["tool_input"].get("file_path", "")

    if file_path.endswith(".env"):

        return {

            "hookSpecificOutput": {

                "hookEventName": input_data["hook_event_name"],

                "permissionDecision": "deny",

                "permissionDecisionReason": "安全策略: 禁止修改 .env 文件"

            }

        }

    return {} # 空对象代表放行

options = ClaudeAgentOptions(

    hooks={

        "PreToolUse": [

            HookMatcher(

                matcher="Write|Edit", # 只在写文件操作时触发

                hooks=[protect_env]

            )

        ]

    }

)

重要细节:Hooks 拒绝一个操作后,Agent 不会直接报错退出——它会被告知"这个操作被拒绝了,原因是什么",然后尝试换一种方法完成任务。比如你拒绝了直接修改 .env,Agent 可能会建议"我无法修改 .env,但可以生成一个 .env.example 模板供你手动填入"。

场景二:全量审计日志

生产环境里你需要在事后回溯 Agent 做了哪些操作。PostToolUse hook 是最佳方案——它在工具执行完之后触发,不影响执行流程,纯粹做记录:

async def audit_log(input_data, tool_use_id, context):

    """PostToolUse hook:记录每一个工具调用的审计信息"""

    tool_name = input_data.get("tool_name", "unknown")

    tool_input = input_data.get("tool_input", {})

    # 生产环境写入数据库或日志系统

    log_entry = {

        "tool": tool_name,

        "input_summary": str(tool_input)[:200], # 截断防过长

        "timestamp": context.get("timestamp"),

        "session_id": context.get("session_id"),

    }

    print(f"[AUDIT] {log_entry['timestamp']} | {tool_name} | {log_entry['input_summary'][:80]}")

    return {} # PostToolUse 不需要返回决策,它是纯观察者

options = ClaudeAgentOptions(

    hooks={

        "PostToolUse": [

            HookMatcher(matcher="Write|Edit|Bash", hooks=[audit_log])

        ]

    }

)

场景三:拦截危险 Shell 命令

这是 hooks 最有价值的场景——阻止 Agent 执行破坏性命令:

DANGEROUS_PATTERNS = [

    "rm -rf /", "DROP TABLE", "DROP DATABASE",

    "format C:", "dd if=/dev/zero",

    "> /dev/sda", "chmod 777 /",

]

async def block_dangerous_cmd(input_data, tool_use_id, context):

    command = input_data["tool_input"].get("command", "")

    for pattern in DANGEROUS_PATTERNS:

        if pattern in command:

            return {

                "hookSpecificOutput": {

                    "hookEventName": input_data["hook_event_name"],

                    "permissionDecision": "deny",

                    "permissionDecisionReason": f"安全拦截: 命令包含危险模式 '{pattern}'"

                }

            }

    return {}

options = ClaudeAgentOptions(

    hooks={

        "PreToolUse": [

            HookMatcher(matcher="Bash", hooks=[block_dangerous_cmd])

        ]

    }

)

可用 Hook 事件完整列表

钩子事件PythonTypeScript触发时机典型用途
PreToolUse工具调用前拦截危险操作、修改参数
PostToolUse工具执行后审计日志、结果验证
PostToolUseFailure工具执行失败错误处理、告警
UserPromptSubmit用户提交提示词注入额外上下文
StopAgent 执行结束清理资源、发通知
SubagentStart子Agent 启动分配隔离环境
SubagentStop子Agent 完成收集结果、验证输出
PreCompact上下文压缩前归档完整对话
PermissionRequest权限弹窗出现自定义权限处理
Notification状态变化推送 Slack/微信通知
SessionStart会话初始化初始化日志
SessionEnd会话终止清理临时文件
PostToolBatch批量工具调用解析后批量注入规范
StopFailure异常终止告警通知

⚠️ Python SDK 不支持 SessionStart/SessionEnd 作为回调钩子。如需在 Python Agent 启动时执行初始化逻辑,在收到第一条 receive_response() 消息时触发。

实战三:子Agent 编排——让多个 Agent 并行干活

单个 Agent 处理复杂任务时会遇到上下文窗口瓶颈:一个超长任务的所有中间推理会塞满 token 预算。子Agent 是这个问题的标准解法——把大任务拆成独立子任务,每个子Agent 在自己的隔离上下文里跑,互不干扰。

场景:代码 PR 审查拆成 3 个并行子任务

async def main():

    async with ClaudeSDKClient(options=options) as client:

        # 主 Agent:分析 PR diff,把审查拆成并行子任务

        await client.query(

            "分析以下 PR 变更。把审查工作拆成 3 个并行子任务:\n"

            "1) 安全漏洞扫描(检查新增代码是否有 SQL 注入、XSS、路径遍历)\n"

            "2) 性能影响评估(循环复杂度、数据库查询次数、内存分配)\n"

            "3) 代码风格/最佳实践审查(命名规范、错误处理、类型安全)\n\n"

            "对每个子任务生成独立审查报告。"

        )

        async for msg in client.receive_response():

            if isinstance(msg, AssistantMessage):

                for block in msg.content:

                    if hasattr(block, "text"):

                        print(f"[主Agent] {block.text[:200]}")

            if isinstance(msg, ResultMessage):

                print(f"审查完成: {msg.subtype}")

asyncio.run(main())

子Agent 的核心行为规则

  1. 独立上下文窗口:每个子Agent 有自己的 token 预算,不会互相污染。主 Agent 给子Agent 的是"任务描述 + 必要上下文",不是整个对话历史
  2. 权限不自动继承:子Agent 不会自动获得父 Agent 的权限配置。你必须为子Agent 单独设置 allowed_toolspermission_mode
  3. 生命周期可追踪:子Agent 启动触发 SubagentStart,完成触发 SubagentStop。你可以在这两个钩子里挂接自己的逻辑

生产模式:钩子驱动的并行任务汇总

results_store = {} # 简单的内存存储,生产环境用 Redis/DB

async def collect_subagent_result(input_data, tool_use_id, context):

    """SubagentStop 钩子:自动收集每个子Agent 的输出"""

    subagent_id = input_data.get("subagent_id", "unknown")

    result = input_data.get("output_summary", "")

    results_store[subagent_id] = {

        "summary": result[:500],

        "timestamp": context.get("timestamp"),

    }

    print(f"[汇总] 子Agent {subagent_id[:8]} 完成 | 输出长度: {len(result)} 字符")

    return {}

options = ClaudeAgentOptions(

    hooks={

        "SubagentStop": [HookMatcher(hooks=[collect_subagent_result])],

        "SubagentStart": [HookMatcher(hooks=[lambda *a: print(f"[启动] 子Agent 开始工作") and {}])],

    }

)

生产部署——6 项上线前必检清单

在把 Agent 推到生产环境之前,以下 6 项必须逐一确认。漏掉任何一项都可能导致夜间报警电话:

1. 设 max_turns(默认是无限!)

SDK 的 max_turns 默认值不是 20,不是 50,是无限。一个陷入循环的 Agent(比如重复读同一个文件、反复调用同一个失败的 API)会一直烧 API 额度直到你手动 kill 进程。建议设 20-50,根据任务复杂度调整。

2. 设 max_budget_usd 做双保险

即使设了 max_turns,Agent 每轮的 token 消耗也不可控。max_budget_usd 直接限制单次执行的美元花费上限,比按轮次限制更精确。

3. 生产环境用 dontAsk + 精确 allowed_tools

这是最重要的权限配置原则。用 acceptEdits 跑无人值守任务,Agent 遇到不在白名单里的工具时会弹出权限请求——然后因为没有人在屏幕前点"允许",Agent 会永远卡住。dontAsk 模式下,不在 allowed_tools 里的工具调用会被直接拒绝,Agent 会收到拒绝通知并尝试换方法。

4. MCP Server 单个工具调用的超时控制

自定义 MCP Server 里的每个工具函数必须有自己的超时机制。一个卡住的 API 调用如果不设 timeout,会拖死整个 Agent loop——Agent 会永远等那个工具返回结果。建议每个外部调用包 asyncio.wait_for

5. 审计日志不可省略

最少要在 PostToolUse 钩子里记录:什么时间、用了哪个工具、操作了哪些文件、结果是否成功。Agent 跑完 2 小时后你发现文件被改坏了,没有审计日志你根本不知道是哪一步出的事。

6. 理解 6.15 计费变化

2026 年 6 月 15 日起,Agent SDK 和 GitHub Actions 的用量从交互式 Claude Code 配额中独立拆分。如果你在 Pro/Max 订阅上跑大量 headless Agent 任务,可能会比预期更早撞到配额上限。官方建议:重度 Agent SDK 用户走 API credit 直接按 token 计费,把订阅当开发环境用。

踩坑清单——来自生产线真实教训

这些坑每个都在真实项目中踩过,提前知道能省你大量调试时间:

症状正确做法
自定义工具抛未捕获异常Agent 直接退出,无任何错误提示必须 try/except → return {"isError": True, ...}
max_turns 未显式设置Agent 陷入无限循环,API 费用飙升生产环境永远显式设 max_turns=20
MCP 工具名写错下划线工具静默不可用,Agent 不知道它的存在严格 mcp__服务器名__函数名,双下划线
allowed_tools 语义误解以为没列的工具不能调,实际是走权限流程dontAsk 模式才能真正拒绝未列工具
Python SDK 无 SessionStart初始化逻辑没地方挂,hook 注册了不触发用首条消息触发初始化,或加载 settings.json
子Agent 权限假设继承子Agent 操作被拒,不知道为什么每个子Agent 独立配置 allowed_tools
Hook 回调里写同步阻塞代码Agent loop 卡死不动Hook 回调必须 async,IO 用 asyncio.to_thread()
permission_modeacceptEdits 跑 headlessAgent 卡在权限弹窗永远等不到批准headless 必须用 dontAskbypassPermissions

常见问题

Q: Claude Agent SDK 和直接调 Anthropic Messages API 有什么区别?

Messages API 是单轮推理——你发 prompt,拿 completion。要做多轮工具调用,你得自己写整个 loop:解析 tool_use → 执行 → 构造 tool_result → 再调 API → 重复。还要自己管上下文窗口、会话持久化、重试逻辑。Agent SDK 把这些全封装了,20 行代码替代 600 行自建 harness。

Q: 可以用 Claude Agent SDK 调其他模型吗?

不能。Claude Agent SDK 是为 Claude 系列模型(Sonnet 4.6、Opus 4.7、Opus 4.8)设计的,底层的 agent loop 行为依赖 Claude 的工具调用格式。如果你需要多模型灵活性,LangGraph 或 crewAI 是更合适的选择。

Q: Agent SDK 和 MCP 是什么关系?

MCP 是协议——定义工具暴露的标准方式。Agent SDK 是客户端——消费 MCP Server 的运行时。你可以把 MCP Server 想象成 USB 接口的标准,Agent SDK 是插 USB 的设备。写一次 MCP Server,所有 MCP 兼容客户端都能用。

Q: 子Agent 为什么需要独立配置权限?

安全隔离。主 Agent 有文件编辑权限不代表子Agent 也应该有——你可能想让子Agent 只读文件但能搜索,或者限制子Agent 只能操作特定目录。权限不继承是设计上的安全默认,这确保了即使一个子Agent 的行为失控,也不会影响其他正在执行的并行任务。

小结:你今天就能用的三个起点

如果你只有 10 分钟,不用通读全文,做这三件事就能立刻提升 Agent 的可靠性:

  1. 给现有 Agent 加一行 max_turns=20——这是最便宜的保险,防一个死循环烧掉整月 API 预算
  2. 把自定义工具的异常改成 return {"isError": True}——从此工具调用失败不会杀死整个 Agent,Claude 会把错误当数据继续推
  3. 加一个 PreToolUse hook 拦截 rm -rf——5 行代码防止一个误操作毁掉整个项目目录

这三件事做完,你的 Agent 就从"玩具级"提升到了"敢在半夜无人值守跑"的生产级水平。后续可以逐步叠加审计日志、MCP 自定义工具、并行子Agent 编排,最终建成完全自动化的 Agent 工厂。


*参考来源:*

  • Anthropic Claude Code 官方 Agent SDK 文档:code.claude.com/docs/en/agent-sdk
  • Totalum "Claude Agent SDK in 2026" 深度分析:totalum.app/blog/claude-agent-sdk-totalum-2026
  • Augment Code SDK 技术指南:augmentcode.com/guides/claude-agent-sdk-agent-loops-tool-calls
  • Hermes Agent v0.19.0 "Quicksilver" Release:github.com/NousResearch/hermes-agent/releases/tag/v2026.7.20

#AI创业 #Agent工坊 #ClaudeAgentSDK #AI自动化 #一人公司

本文由AI辅助创作,经人工审核编辑发布

更多一人公司案例与工具,微信搜索「AI创业内参」关注我们