🚀 Claude Agent SDK 使用指南:如何优雅地处理用户审批与提问 (User Input)

9 阅读4分钟

在使用 AI Agent(智能体)构建自动化流程时,最令人头疼的问题往往是: “如何防止 Agent 乱来?” 比如误删文件、执行了高危 Bash 命令,或者在模糊指令下“瞎猜”用户意图。

Anthropic 官方推出的 Claude Agent SDK 提供了一套非常成熟的机制:can_use_tool 回调与 AskUserQuestion 工具。本文将带你实战演练如何用 Python 实现这套“人在回路”(Human-in-the-Loop)的交互流程。


💡 核心概念:什么是 can_use_tool?

在 Claude Agent SDK 中,当 Agent 想要执行一个工具(Tool)时,它不会盲目执行,而是会触发一个 can_use_tool 回调。

这给了开发者三个核心权力:

  1. 审批制:展示 Tool 名称和参数,由用户决定 Allow(允许)还是 Deny(拒绝)。
  2. 干预制:在允许之前,悄悄修改 Agent 传入的参数(例如修正一个错误的文件路径)。
  3. 交互制:通过内置的 AskUserQuestion 工具,让 Claude 主动停下来向用户寻求澄清。

🛠️ 环境准备

首先,确保安装了最新的 Claude Agent SDK:

Bash

pip install claude-agent-sdk

🏗️ 实战一:实现工具调用审批 (Tool Approval)

假设我们让 Agent 创建并删除一个文件。为了安全,我们要求 Agent 在每一步操作前都必须获得用户的命令行确认。

代码实现

Python

import asyncio
from claude_agent_sdk import ClaudeAgentOptions, query
from claude_agent_sdk.types import (
    HookMatcher, 
    PermissionResultAllow, 
    PermissionResultDeny, 
    ToolPermissionContext
)

# 1. 定义审批回调函数
async def can_use_tool(
    tool_name: str, 
    input_data: dict, 
    context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
    
    print(f"\n📢 [安全检查] Claude 想要调用工具: {tool_name}")
    print(f"📥 输入参数: {input_data}")
    
    # 获取用户输入
    user_choice = input("是否允许此操作? (y/n): ").strip().lower()
    
    if user_choice == 'y':
        print("✅ 用户已授权")
        # 返回 Allow,可以选择修改 input_data
        return PermissionResultAllow(updated_input=input_data)
    else:
        print("❌ 用户已拒绝")
        # 返回 Deny,并附带理由,Claude 会看到这个理由并尝试调整策略
        return PermissionResultDeny(message="用户拒绝了该操作,请尝试其他方案。")

# 2. 必须的 Hack:定义一个 Hook 来保持流开启
async def dummy_hook(input_data, tool_use_id, context):
    return {"continue_": True}

async def main():
    # 模拟用户对话流
    async def prompt_stream():
        yield {
            "type": "user",
            "message": {
                "role": "user",
                "content": "帮我在 /tmp 目录下创建一个 test.txt,写入 'Hello Claude',然后删除它。",
            },
        }

    # 3. 运行查询
    async for message in query(
        prompt=prompt_stream(),
        options=ClaudeAgentOptions(
            can_use_tool=can_use_tool,
            # 注意:在 Python SDK 中目前需要挂载 PreToolUse hook 以支持 can_use_tool 的流式交互
            hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
        ),
    ):
        if hasattr(message, "result"):
            print(f"\n🏁 任务结果: {message.result}")

if __name__ == "__main__":
    asyncio.run(main())

🙋‍♂️ 实战二:处理 Claude 的反向提问 (Clarifying Questions)

有时候 Agent 不知道该怎么选(比如:你是要 React 还是 Vue 项目?)。这时它会调用内置的 AskUserQuestion 工具。

实现逻辑

tool_name == "AskUserQuestion" 时,我们需要解析 input_data 中的 questions 列表,渲染给用户选择,并将结果返回。

核心代码片段

Python

def parse_user_answer(user_input: str, options: list) -> str:
    """解析用户输入的数字索引或自由文本"""
    try:
        idx = int(user_input) - 1
        if 0 <= idx < len(options):
            return options[idx]["label"]
    except ValueError:
        pass
    return user_input # 如果不是数字,直接返回原始文本

async def handle_ask_user(input_data: dict):
    answers = {}
    for q in input_data.get("questions", []):
        print(f"\n❓ 提问: {q['question']}")
        for i, opt in enumerate(q['options']):
            print(f"  {i+1}. {opt['label']} ({opt['description']})")
        
        user_val = input("你的选择 (输入数字或直接输入想法): ")
        # 映射回 Claude 要求的格式
        answers[q['question']] = parse_user_answer(user_val, q['options'])
    
    return PermissionResultAllow(
        updated_input={
            "questions": input_data.get("questions", []),
            "answers": answers
        }
    )

# 在 can_use_tool 中路由:
# if tool_name == "AskUserQuestion":
#     return await handle_ask_user(input_data)

🌟 进阶技巧:修改 Agent 的输入 (Approve with Changes)

这是 SDK 最强大的地方之一。你可以拦截 Agent 的危险指令并进行修正。

场景:Agent 想要执行 rm -rf /

拦截逻辑

Python

if tool_name == "Bash" and "rm -rf /" in input_data.get("command", ""):
    print("⚠️ 检测到高危指令,已自动修正为删除特定临时文件。")
    input_data["command"] = "rm /tmp/safe_file.txt"
    return PermissionResultAllow(updated_input=input_data)

📝 总结

通过 Claude Agent SDK 的 can_use_tool 机制,我们可以轻松实现:

  • 安全性:敏感操作必经人工确认。
  • 互动性:让 Agent 能够像真人助手一样反向提问。
  • 可控性:动态修改 Agent 的行为逻辑。

避坑指南

  1. Python 特有 Hook:目前 Python SDK 在使用 can_use_tool 时,务必挂载一个返回 continue_PreToolUse Hook,否则流可能会提前关闭。
  2. AskUserQuestion 列表:如果你自定义了 tools 列表,记得一定要把 "AskUserQuestion" 放进去,否则 Claude 会发现自己“没法说话”。

希望这份教程能帮你构建出更安全、更聪明的 AI Agent!如果你觉得有用,欢迎在掘金点赞收藏。🚀