Agent SDK智能体开发套件2
7 Agent SDK中的Hooks:程序化的拦截
7.1 PreToolUse:执行前拦截
点击查看代码
from claude_agent_sdk import ClaudeAgentOptions, HookMatcher
async def block_dangerous_bash(input_data, tool_use_id, context):
"""拦截危险的 Bash 命令"""
# 仅处理 Bash 工具调用
if input_data["tool_name"] != "Bash":
return {}
command = input_data["tool_input"].get("command", "")
# 定义危险命令特征列表
dangerous = ["rm -rf", "sudo", "chmod 777", "> dev", "mkfs", "dd if="]
for pattern in dangerous:
if pattern in command:
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": f"Blocked: {pattern}",
}
}
return {}
async def protect_config_files(input_data, tool_use_id, context):
"""保护关键配置文件不被修改"""
tool_name = input_data["tool_name"]
# 仅处理文件写入类工具
if tool_name not in ["Write", "Edit"]:
return {}
file_path = input_data["tool_input"].get("file_path", "")
# 定义受保护的路径或文件名特征
protected = [".env", "secrets", "production.yaml", "database/migrations"]
for p in protected:
if p in file_path:
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": f"Protected file: {file_path}",
}
}
return {}
# === 配置 Agent 选项,注册安全拦截 Hook ===
options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
HookMatcher(matcher="Bash", hooks=[block_dangerous_bash]),
HookMatcher(matcher="Write", hooks=[protect_config_files]),
HookMatcher(matcher="Edit", hooks=[protect_config_files]),
],
},
)
7.2 PostToolUse:执行后处理
点击查看代码
import json
import subprocess
from datetime import datetime
from claude_agent_sdk import ClaudeAgentOptions, HookMatcher
async def auto_format_on_write(input_data, tool_use_id, context):
"""写入文件后自动触发代码格式化"""
# 仅处理文件写入类工具
if input_data["tool_name"] not in ["Write", "Edit"]:
return {}
file_path = input_data["tool_input"].get("file_path", "")
# 根据文件扩展名选择对应的格式化工具
if file_path.endswith(".py"):
# 使用 Black 格式化 Python 代码
subprocess.run(["black", file_path], capture_output=True)
elif file_path.endswith((".ts", ".js", ".tsx", ".jsx")):
# 使用 Prettier 格式化前端代码
subprocess.run(["prettier", "--write", file_path], capture_output=True)
return {}
async def audit_all_tools(input_data, tool_use_id, context):
"""记录所有工具调用的审计日志"""
audit_entry = {
"timestamp": datetime.now().isoformat(),
"tool": input_data["tool_name"],
"input": input_data["tool_input"],
"tool_use_id": tool_use_id,
}
# 将审计日志追加写入 JSONL 文件
with open("agent-audit.jsonl", "a", encoding="utf-8") as f:
f.write(json.dumps(audit_entry) + "\n")
return {}
# === 配置 Agent 选项,注册后置 Hook ===
options = ClaudeAgentOptions(
hooks={
"PreToolUse": [...], # 此处可接前面定义的安全拦截 Hook
"PostToolUse": [
HookMatcher(matcher="Write", hooks=[auto_format_on_write]),
HookMatcher(matcher="Edit", hooks=[auto_format_on_write]),
HookMatcher(matcher="*", hooks=[audit_all_tools]),
],
},
)
7.3 Agent SDK Hooks与Shell Hooks的关系
8 4道安全防线
Agent SDK构建了由4道安全防线组成的纵深防御体系
点击查看代码
from claude_agent_sdk import ClaudeAgentOptions, HookMatcher
async def can_use_tool(tool_name: str, tool_input: dict) -> dict:
"""运行时权限检查回调"""
# 场景1:文件写入保护 - 防止敏感配置文件被篡改
if tool_name in ["Write", "Edit"]:
file_path = tool_input.get("file_path", "")
if ".env" in file_path or "secrets" in file_path:
return {
"allowed": False,
"reason": "Access to sensitive files denied",
}
# 场景2:网络命令封锁 - 即使白名单允许 Bash,也要禁止特定网络操作
if tool_name == "Bash":
command = tool_input.get("command", "")
if any(cmd in command for cmd in ["curl", "wget", "ssh"]):
return {
"allowed": False,
"reason": "Network commands not allowed",
}
return {"allowed": True}
# === 配置 Agent 选项 ===
options = ClaudeAgentOptions(
permission_mode="acceptEdits",
allowed_tools=["Read", "Write", "Edit", "Grep", "Glob"],
can_use_tool=can_use_tool,
hooks={
"PreToolUse": [
HookMatcher(matcher="*", hooks=[audit_all_tools]),
],
},
)
- permission_mode确立全局基调,
- allowed_tools剔除冗余工具,
- canUseTool在运行时对每次调用实施动态校验,
- 而PreToolUse Hooks则负责最细粒度的参数审查、输入修正及日志记录审计。
9 结构化输出:强制JSON Schema
- Python
点击查看代码
from pydantic import BaseModel
from claude_agent_sdk import ClaudeAgentOptions, query
class SecurityReport(BaseModel):
"""安全审查报告结构化输出模型"""
summary: str
issues: list[dict] # 结构示例:[{severity, file, line, description}]
risk_score: float # 范围:0.0 ~ 10.0
# === 配置 Agent 选项 ===
options = ClaudeAgentOptions(
output_format={
"type": "json_schema",
"schema": SecurityReport.model_json_schema(),
},
max_turns=10,
allowed_tools=["Read", "Grep", "Glob"],
)
async def run_security_audit():
"""执行安全审查并解析结构化结果"""
async for message in query(prompt="对 src/ 进行安全审查", options=options):
if message.type == "result" and message.structured_output:
report = SecurityReport.model_validate(message.structured_output)
# report 是类型安全的 Pydantic 对象
print(f"风险评分:{report.risk_score}")
for issue in report.issues:
severity = issue["severity"]
file_path = issue["file"]
line = issue.get("line", "?")
description = issue["description"]
print(f"[{severity}] {file_path}:{line} - {description}")
- TypeScript
点击查看代码
import { z } from 'zod';
import type { ClaudeAgentOptions } from 'claude-agent-sdk';
// === 定义安全审查报告结构化输出模型 ===
const SecurityReport = z.object({
summary: z.string(),
issues: z.array(
z.object({
severity: z.enum(['critical', 'high', 'medium', 'low']),
file: z.string(),
description: z.string(),
}),
),
riskScore: z.number().min(0).max(10),
});
// === 配置 Agent 选项 ===
const options: ClaudeAgentOptions = {
outputFormat: {
type: 'json_schema',
schema: z.toJSONSchema(SecurityReport),
},
};
10 实战:构建代码分析Web服务
点击查看代码
#!/usr/bin/env python3
"""代码分析Web服务——完整运行示例"""
import asyncio
import json
import sys
from datetime import datetime
from claude_agent_sdk import query, ClaudeAgentOptions
async def analyze_codebase(directory: str, focus: str = "general"):
"""
分析指定目录的代码库。
Args:
directory: 要分析的本地目录路径
focus: 分析重点领域 - "security" / "performance" / "quality" / "general"
"""
# 定义不同关注点的 System Prompt
focus_prompts = {
"security": "专注于安全漏洞:SQL 注入、XSS、敏感信息硬编码、权限控制缺失等。",
"performance": "专注于性能问题:N+1 查询、内存泄漏、冗余计算、缓存策略缺失等。",
"quality": "专注于代码质量:命名规范、DRY原则、圈复杂度、测试覆盖率建议等。",
"general": "进行全面代码审查:涵盖安全性、性能、代码质量及架构合理性。",
}
options = ClaudeAgentOptions(
model="claude-sonnet-4-6",
max_turns=15,
max_budget_usd=0.50,
allowed_tools=["Read", "Grep", "Glob"],
permission_mode="plan", # 只读模式
cwd=directory,
append_system_prompt=focus_prompts.get(focus, focus_prompts["general"]),
)
# 初始化收集器
output_text = []
tools_used = []
metadata = {}
# 执行流式查询
async for message in query(
prompt=f"分析当前项目的代码,{focus_prompts.get(focus, '')}。请直接输出Markdown格式的分析报告。",
options=options,
):
if message.type == "assistant":
for block in message.content:
if hasattr(block, "text"):
output_text.append(block.text)
# 【关键点】流式输出:在实际 Web 服务中,此处应为 yield SSE 事件
print(block.text, end="", flush=True)
elif hasattr(block, "name"):
tools_used.append(block.name)
elif message.type == "result":
metadata = {
"session_id": message.session_id,
"cost_usd": message.total_cost_usd,
"turns": message.num_turns,
"duration_ms": message.duration_ms,
"success": not message.is_error,
}
# 返回结构化结果
return {
"report": "\n".join(output_text),
"tools_used": tools_used,
"metadata": metadata,
}
async def main():
directory = sys.argv[1] if len(sys.argv) > 1 else "."
focus = sys.argv[2] if len(sys.argv) > 2 else "general"
print(f"分析 {directory}(重点:{focus})...\n")
result = await analyze_codebase(directory, focus)
print("\n\n--- 会话统计 ---")
print(f"状态: {'成功' if result['metadata']['success'] else '失败'}")
print(f"费用:${result['metadata'].get('cost_usd', 0):.4f}")
print(f"耗时:{result['metadata'].get('duration_ms', 0) / 1000:.1f}s")
print(f"交互轮数: {result['metadata'].get('turns', 0)}")
print(f"使用工具:{', '.join(result['tools_used'])}")
if __name__ == "__main__":
asyncio.run(main())
- 以下是一个基于FastAPI的完整示例。
点击查看代码
import json
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from claude_agent_sdk import query, ClaudeAgentOptions
app = FastAPI()
class AnalyzeRequest(BaseModel):
"""代码分析请求体"""
prompt: str
directory: str = "."
focus: str = "general"
def build_options(request: AnalyzeRequest) -> ClaudeAgentOptions:
"""根据请求参数构建 Agent 配置"""
focus_prompts = {
"security": "专注于安全漏洞:SQL 注入、XSS、敏感信息硬编码、权限控制缺失等。",
"performance": "专注于性能问题:N+1 查询、内存泄漏、冗余计算、缓存策略缺失等。",
"quality": "专注于代码质量:命名规范、DRY原则、圈复杂度、测试覆盖率建议等。",
"general": "进行全面代码审查:涵盖安全性、性能、代码质量及架构合理性。",
}
return ClaudeAgentOptions(
model="claude-sonnet-4-6",
max_turns=15,
max_budget_usd=0.50,
allowed_tools=["Read", "Grep", "Glob"],
permission_mode="plan",
cwd=request.directory,
append_system_prompt=focus_prompts.get(request.focus, focus_prompts["general"]),
)
@app.post("/api/analyze")
async def analyze(request: AnalyzeRequest):
options = build_options(request)
async def event_stream():
async for message in query(prompt=request.prompt, options=options):
if message.type == "assistant":
for block in message.content:
if hasattr(block, "text"):
payload = {"type": "text", "content": block.text}
yield f"data: {json.dumps(payload, ensure_ascii=False)}\n\n"
elif message.type == "result":
payload = {"type": "done", "cost": message.total_cost_usd}
yield f"data: {json.dumps(payload, ensure_ascii=False)}\n\n"
return StreamingResponse(
event_stream(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no", # 防止 Nginx 缓冲 SSE 流
},
)
11 Agent SDK与Headless CLI:如何选型
- 如果你的逻辑可以用一行Shell命令描述,请选用CLI;如果需要写if/else判断或循环,请选用Agent SDK。
本章小结
- query函数是Agent SDK的入口,其功能对标Headless模式的claude -p命令,但以异步生成器的形式返回消息流,支持实时处理Claude的每一步输出。该消息流包含5种类型:system/init(初始化信号)、assistant(Claude的响应,可能包含文本内容及工具调用请求)、user(工具执行后的反馈结果)和result(任务终止信号,携带成本、轮数、耗时等元数据)。
- ClaudeAgentOptions则提供了与Headless CLI参数对等的编程接口,涵盖模型选择、工具权限、权限模式、Prompt定制、会话管理及结构化输出等功能。相较于命令行,编程接口的核心优势在于动态配置能力:开发者可根据用户角色动态调整权限模式,依据任务类型灵活切换模型,或基于上一轮的执行结果实时优化下一轮的Prompt。
- 自定义工具通过@tool装饰器与进程内MCP服务器实现,赋予Claude调用任意自定义函数的能力,无论是查询数据库、调用内部API,还是发送通知。与此同时,4道安全防线(权限模式、工具白名单、canUseTool回调、PreToolUse Hooks)确保Claude在应用中只做该做的事。
- 从CLAUDE.md配置、Skills定义,到Hooks拦截、MCP集成,再到Headless模式与Agent SDK的全面掌控,每深入一层,意味着我们距离构建成熟的“产品”而非单一的“工具”更近一步。

浙公网安备 33010602011771号