Loading...
    • 开发者指南
    • API 参考
    • MCP
    • 资源
    • 更新日志
    Search...
    ⌘K
    入门
    Claude 简介快速开始
    模型与定价
    模型概览选择模型Claude 4.6 新特性迁移指南模型弃用定价
    使用 Claude 构建
    功能概览使用 Messages API处理停止原因提示词最佳实践
    上下文管理
    上下文窗口压缩上下文编辑
    能力
    提示缓存扩展思考自适应思考推理力度流式消息批量处理引用多语言支持Token 计数嵌入视觉PDF 支持Files API搜索结果结构化输出
    工具
    概览如何实现工具使用细粒度工具流式传输Bash 工具代码执行工具程序化工具调用计算机使用工具文本编辑器工具网页抓取工具网页搜索工具记忆工具工具搜索工具
    Agent Skills
    概览快速开始最佳实践企业级 Skills通过 API 使用 Skills
    Agent SDK
    概览快速开始TypeScript SDKTypeScript V2(预览版)Python SDK迁移指南
    流式输入实时流式响应处理停止原因处理权限用户审批与输入使用钩子控制执行会话管理文件检查点SDK 中的结构化输出托管 Agent SDK安全部署 AI 智能体修改系统提示词SDK 中的 MCP自定义工具SDK 中的子智能体SDK 中的斜杠命令SDK 中的 Agent Skills跟踪成本与用量待办事项列表SDK 中的插件
    API 中的 MCP
    MCP 连接器远程 MCP 服务器
    第三方平台上的 Claude
    Amazon BedrockMicrosoft FoundryVertex AI
    提示工程
    概览提示词生成器使用提示词模板提示词优化器清晰直接使用示例(多样本提示)让 Claude 思考(思维链)使用 XML 标签赋予 Claude 角色(系统提示词)链式复杂提示长上下文技巧扩展思考技巧
    测试与评估
    定义成功标准开发测试用例使用评估工具降低延迟
    加强安全护栏
    减少幻觉提高输出一致性防范越狱攻击流式拒绝减少提示词泄露保持 Claude 角色设定
    管理与监控
    Admin API 概览数据驻留工作空间用量与成本 APIClaude Code Analytics API零数据留存
    Console
    Log in
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...

    Solutions

    • AI agents
    • Code modernization
    • Coding
    • Customer support
    • Education
    • Financial services
    • Government
    • Life sciences

    Partners

    • Amazon Bedrock
    • Google Cloud's Vertex AI

    Learn

    • Blog
    • Catalog
    • Courses
    • Use cases
    • Connectors
    • Customer stories
    • Engineering at Anthropic
    • Events
    • Powered by Claude
    • Service partners
    • Startups program

    Company

    • Anthropic
    • Careers
    • Economic Futures
    • Research
    • News
    • Responsible Scaling Policy
    • Security and compliance
    • Transparency

    Learn

    • Blog
    • Catalog
    • Courses
    • Use cases
    • Connectors
    • Customer stories
    • Engineering at Anthropic
    • Events
    • Powered by Claude
    • Service partners
    • Startups program

    Help and security

    • Availability
    • Status
    • Support
    • Discord

    Terms and policies

    • Privacy policy
    • Responsible disclosure policy
    • Terms of service: Commercial
    • Terms of service: Consumer
    • Usage policy
    指南

    处理审批和用户输入

    将 Claude 的审批请求和澄清问题呈现给用户,然后将他们的决定返回给 SDK。

    在处理任务时,Claude 有时需要与用户确认。它可能需要在删除文件之前获得许可,或者需要询问新项目应使用哪个数据库。您的应用程序需要将这些请求呈现给用户,以便 Claude 能够根据他们的输入继续工作。

    Claude 在两种情况下请求用户输入:当它需要使用工具的权限(如删除文件或运行命令)时,以及当它有澄清问题(通过 AskUserQuestion 工具)时。两者都会触发您的 canUseTool 回调,暂停执行直到您返回响应。这与正常的对话轮次不同,在正常轮次中 Claude 完成后等待您的下一条消息。

    对于澄清问题,Claude 会生成问题和选项。您的角色是将它们呈现给用户并返回他们的选择。您不能在此流程中添加自己的问题;如果您需要自己向用户提问,请在应用程序逻辑中单独处理。

    本指南向您展示如何检测每种类型的请求并做出适当的响应。

    检测 Claude 何时需要输入

    在查询选项中传递 canUseTool 回调。每当 Claude 需要用户输入时,该回调就会触发,接收工具名称和输入作为参数:

    async def handle_tool_request(tool_name, input_data, context):
        # Prompt user and return allow or deny
        ...
    
    options = ClaudeAgentOptions(can_use_tool=handle_tool_request)

    该回调在两种情况下触发:

    1. 工具需要审批:Claude 想要使用一个未被权限规则或模式自动批准的工具。检查 tool_name 以获取工具名称(例如 "Bash"、"Write")。
    2. Claude 提出问题:Claude 调用 AskUserQuestion 工具。检查 tool_name == "AskUserQuestion" 以进行不同的处理。如果您指定了 tools 数组,请包含 AskUserQuestion 以使其正常工作。详情请参阅处理澄清问题。

    要自动允许或拒绝工具而不提示用户,请改用钩子。钩子在 canUseTool 之前执行,可以根据您自己的逻辑允许、拒绝或修改请求。您还可以使用 PermissionRequest 钩子在 Claude 等待审批时发送外部通知(Slack、电子邮件、推送)。

    处理工具审批请求

    在查询选项中传递 canUseTool 回调后,当 Claude 想要使用未自动批准的工具时,它就会触发。您的回调接收两个参数:

    参数描述
    toolNameClaude 想要使用的工具名称(例如 "Bash"、"Write"、"Edit")
    inputClaude 传递给工具的参数。内容因工具而异。

    input 对象包含特定于工具的参数。常见示例:

    工具输入字段
    Bashcommand、description、timeout
    Writefile_path、content
    Editfile_path、old_string、new_string
    Readfile_path、offset、limit

    有关完整的输入模式,请参阅 SDK 参考:Python | TypeScript。

    您可以将此信息显示给用户,以便他们决定是否允许或拒绝该操作,然后返回适当的响应。

    以下示例要求 Claude 创建并删除一个测试文件。当 Claude 尝试每个操作时,回调会将工具请求打印到终端并提示进行 y/n 审批。

    import asyncio
    
    from claude_agent_sdk import ClaudeAgentOptions, query
    from claude_agent_sdk.types import (
        HookMatcher,
        PermissionResultAllow,
        PermissionResultDeny,
        ToolPermissionContext,
    )
    
    
    async def can_use_tool(
        tool_name: str, input_data: dict, context: ToolPermissionContext
    ) -> PermissionResultAllow | PermissionResultDeny:
        # Display the tool request
        print(f"\nTool: {tool_name}")
        if tool_name == "Bash":
            print(f"Command: {input_data.get('command')}")
            if input_data.get("description"):
                print(f"Description: {input_data.get('description')}")
        else:
            print(f"Input: {input_data}")
    
        # Get user approval
        response = input("Allow this action? (y/n): ")
    
        # Return allow or deny based on user's response
        if response.lower() == "y":
            # Allow: tool executes with the original (or modified) input
            return PermissionResultAllow(updated_input=input_data)
        else:
            # Deny: tool doesn't execute, Claude sees the message
            return PermissionResultDeny(message="User denied this action")
    
    
    # Required workaround: dummy hook keeps the stream open for can_use_tool
    async def dummy_hook(input_data, tool_use_id, context):
        return {"continue_": True}
    
    
    async def prompt_stream():
        yield {
            "type": "user",
            "message": {
                "role": "user",
                "content": "Create a test file in /tmp and then delete it",
            },
        }
    
    
    async def main():
        async for message in query(
            prompt=prompt_stream(),
            options=ClaudeAgentOptions(
                can_use_tool=can_use_tool,
                hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
            ),
        ):
            if hasattr(message, "result"):
                print(message.result)
    
    
    asyncio.run(main())

    在 Python 中,can_use_tool 需要流式模式和一个返回 {"continue_": True} 的 PreToolUse 钩子来保持流的打开状态。没有这个钩子,流会在权限回调被调用之前关闭。

    此示例使用 y/n 流程,其中除 y 以外的任何输入都被视为拒绝。在实践中,您可能会构建更丰富的 UI,让用户修改请求、提供反馈或完全重定向 Claude。有关所有响应方式,请参阅响应工具请求。

    响应工具请求

    您的回调返回以下两种响应类型之一:

    响应PythonTypeScript
    允许PermissionResultAllow(updated_input=...){ behavior: "allow", updatedInput }
    拒绝PermissionResultDeny(message=...){ behavior: "deny", message }

    允许时,传递工具输入(原始或修改后的)。拒绝时,提供解释原因的消息。Claude 会看到此消息并可能调整其方法。

    from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny
    
    # Allow the tool to execute
    return PermissionResultAllow(updated_input=input_data)
    
    # Block the tool
    return PermissionResultDeny(message="User rejected this action")

    除了允许或拒绝之外,您还可以修改工具的输入或提供帮助 Claude 调整方法的上下文:

    • 批准:让工具按照 Claude 请求的方式执行
    • 带修改的批准:在执行前修改输入(例如,清理路径、添加约束)
    • 拒绝:阻止工具并告诉 Claude 原因
    • 建议替代方案:阻止但引导 Claude 朝用户想要的方向前进
    • 完全重定向:使用流式输入向 Claude 发送全新的指令

    处理澄清问题

    当 Claude 在一个有多种有效方法的任务上需要更多方向时,它会调用 AskUserQuestion 工具。这会触发您的 canUseTool 回调,其中 toolName 设置为 AskUserQuestion。输入包含 Claude 的问题作为多选选项,您将其显示给用户并返回他们的选择。

    澄清问题在 plan 模式中特别常见,在该模式下 Claude 会探索代码库并在提出计划之前提问。这使得 plan 模式非常适合交互式工作流,在这种工作流中您希望 Claude 在进行更改之前收集需求。

    以下步骤展示了如何处理澄清问题:

    1. 1

      传递 canUseTool 回调

      在查询选项中传递 canUseTool 回调。默认情况下,AskUserQuestion 是可用的。如果您指定了 tools 数组来限制 Claude 的功能(例如,仅包含 Read、Glob 和 Grep 的只读代理),请在该数组中包含 AskUserQuestion。否则,Claude 将无法提出澄清问题:

      async for message in query(
          prompt="Analyze this codebase",
          options=ClaudeAgentOptions(
              # Include AskUserQuestion in your tools list
              tools=["Read", "Glob", "Grep", "AskUserQuestion"],
              can_use_tool=can_use_tool,
          ),
      ):
          # ...
    2. 2

      检测 AskUserQuestion

      在您的回调中,检查 toolName 是否等于 AskUserQuestion,以便与其他工具进行不同的处理:

      async def can_use_tool(tool_name: str, input_data: dict, context):
          if tool_name == "AskUserQuestion":
              # Your implementation to collect answers from the user
              return await handle_clarifying_questions(input_data)
          # Handle other tools normally
          return await prompt_for_approval(tool_name, input_data)
    3. 3

      解析问题输入

      输入在 questions 数组中包含 Claude 的问题。每个问题都有一个 question(要显示的文本)、options(选项)和 multiSelect(是否允许多选):

      {
        "questions": [
          {
            "question": "How should I format the output?",
            "header": "Format",
            "options": [
              { "label": "Summary", "description": "Brief overview" },
              { "label": "Detailed", "description": "Full explanation" }
            ],
            "multiSelect": false
          },
          {
            "question": "Which sections should I include?",
            "header": "Sections",
            "options": [
              { "label": "Introduction", "description": "Opening context" },
              { "label": "Conclusion", "description": "Final summary" }
            ],
            "multiSelect": true
          }
        ]
      }

      有关完整的字段描述,请参阅问题格式。

    4. 4

      从用户收集答案

      将问题呈现给用户并收集他们的选择。具体方式取决于您的应用程序:终端提示、Web 表单、移动对话框等。

    5. 5

      将答案返回给 Claude

      将 answers 对象构建为一个记录,其中每个键是 question 文本,每个值是所选选项的 label:

      来自问题对象用作
      question 字段(例如 "How should I format the output?")键
      所选选项的 label 字段(例如 "Summary")值

      对于多选问题,用 ", " 连接多个标签。如果您支持自由文本输入,请使用用户的自定义文本作为值。

      return PermissionResultAllow(
          updated_input={
              "questions": input_data.get("questions", []),
              "answers": {
                  "How should I format the output?": "Summary",
                  "Which sections should I include?": "Introduction, Conclusion"
              }
          }
      )

    问题格式

    输入在 questions 数组中包含 Claude 生成的问题。每个问题都有以下字段:

    字段描述
    question要显示的完整问题文本
    header问题的简短标签(最多 12 个字符)
    options2-4 个选项的数组,每个选项都有 label 和 description
    multiSelect如果为 true,用户可以选择多个选项

    以下是您将收到的结构示例:

    {
      "questions": [
        {
          "question": "How should I format the output?",
          "header": "Format",
          "options": [
            { "label": "Summary", "description": "Brief overview of key points" },
            { "label": "Detailed", "description": "Full explanation with examples" }
          ],
          "multiSelect": false
        }
      ]
    }

    响应格式

    返回一个 answers 对象,将每个问题的 question 字段映射到所选选项的 label:

    字段描述
    questions传递原始问题数组(工具处理所需)
    answers键为问题文本、值为所选标签的对象

    对于多选问题,用 ", " 连接多个标签。对于自由文本输入,直接使用用户的自定义文本。

    {
      "questions": [...],
      "answers": {
        "How should I format the output?": "Summary",
        "Which sections should I include?": "Introduction, Conclusion"
      }
    }

    支持自由文本输入

    Claude 的预定义选项并不总能涵盖用户想要的内容。要让用户输入自己的答案:

    • 在 Claude 的选项之后显示一个接受文本输入的额外"其他"选项
    • 使用用户的自定义文本作为答案值(而不是"其他"这个词)

    有关完整实现,请参阅下面的完整示例。

    完整示例

    当 Claude 需要用户输入才能继续时,它会提出澄清问题。例如,当被要求帮助决定移动应用的技术栈时,Claude 可能会询问跨平台与原生、后端偏好或目标平台。这些问题帮助 Claude 做出符合用户偏好的决策,而不是猜测。

    此示例在终端应用程序中处理这些问题。以下是每个步骤发生的事情:

    1. 路由请求:canUseTool 回调检查工具名称是否为 "AskUserQuestion" 并路由到专用处理程序
    2. 显示问题:处理程序遍历 questions 数组并打印每个问题及其编号选项
    3. 收集输入:用户可以输入数字选择选项,或直接输入自由文本(例如 "jquery"、"i don't know")
    4. 映射答案:代码检查输入是数字(使用选项的标签)还是自由文本(直接使用文本)
    5. 返回给 Claude:响应包含原始 questions 数组和 answers 映射
    import asyncio
    
    from claude_agent_sdk import ClaudeAgentOptions, query
    from claude_agent_sdk.types import HookMatcher, PermissionResultAllow
    
    
    def parse_response(response: str, options: list) -> str:
        """Parse user input as option number(s) or free text."""
        try:
            indices = [int(s.strip()) - 1 for s in response.split(",")]
            labels = [options[i]["label"] for i in indices if 0 <= i < len(options)]
            return ", ".join(labels) if labels else response
        except ValueError:
            return response
    
    
    async def handle_ask_user_question(input_data: dict) -> PermissionResultAllow:
        """Display Claude's questions and collect user answers."""
        answers = {}
    
        for q in input_data.get("questions", []):
            print(f"\n{q['header']}: {q['question']}")
    
            options = q["options"]
            for i, opt in enumerate(options):
                print(f"  {i + 1}. {opt['label']} - {opt['description']}")
            if q.get("multiSelect"):
                print("  (Enter numbers separated by commas, or type your own answer)")
            else:
                print("  (Enter a number, or type your own answer)")
    
            response = input("Your choice: ").strip()
            answers[q["question"]] = parse_response(response, options)
    
        return PermissionResultAllow(
            updated_input={
                "questions": input_data.get("questions", []),
                "answers": answers,
            }
        )
    
    
    async def can_use_tool(tool_name: str, input_data: dict, context) -> PermissionResultAllow:
        # Route AskUserQuestion to our question handler
        if tool_name == "AskUserQuestion":
            return await handle_ask_user_question(input_data)
        # Auto-approve other tools for this example
        return PermissionResultAllow(updated_input=input_data)
    
    
    async def prompt_stream():
        yield {
            "type": "user",
            "message": {"role": "user", "content": "Help me decide on the tech stack for a new mobile app"},
        }
    
    
    # Required workaround: dummy hook keeps the stream open for can_use_tool
    async def dummy_hook(input_data, tool_use_id, context):
        return {"continue_": True}
    
    
    async def main():
        async for message in query(
            prompt=prompt_stream(),
            options=ClaudeAgentOptions(
                can_use_tool=can_use_tool,
                hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
            ),
        ):
            if hasattr(message, "result"):
                print(message.result)
    
    
    asyncio.run(main())

    限制

    • 子代理:AskUserQuestion 目前在通过 Task 工具生成的子代理中不可用
    • 问题限制:每次 AskUserQuestion 调用支持 1-4 个问题,每个问题 2-4 个选项

    获取用户输入的其他方式

    canUseTool 回调和 AskUserQuestion 工具涵盖了大多数审批和澄清场景,但 SDK 提供了其他从用户获取输入的方式:

    流式输入

    在以下情况下使用流式输入:

    • 在任务中途中断代理:在 Claude 工作时发送取消信号或改变方向
    • 提供额外上下文:添加 Claude 需要的信息而无需等待它询问
    • 构建聊天界面:让用户在长时间运行的操作期间发送后续消息

    流式输入非常适合对话式 UI,用户在整个执行过程中与代理交互,而不仅仅是在审批检查点。

    自定义工具

    在以下情况下使用自定义工具:

    • 收集结构化输入:构建表单、向导或多步骤工作流,超越 AskUserQuestion 的多选格式
    • 集成外部审批系统:连接到现有的工单、工作流或审批平台
    • 实现特定领域的交互:创建针对您应用程序需求的工具,如代码审查界面或部署检查清单

    自定义工具让您完全控制交互,但比使用内置的 canUseTool 回调需要更多的实现工作。

    相关资源

    • 配置权限:设置权限模式和规则
    • 使用钩子控制执行:在代理生命周期的关键点运行自定义代码
    • TypeScript SDK 参考:完整的 canUseTool API 文档

    Was this page helpful?

    • 检测 Claude 何时需要输入