在处理任务时,Claude 有时需要与用户确认。它可能需要在删除文件之前获得许可,或者需要询问新项目应使用哪个数据库。您的应用程序需要将这些请求呈现给用户,以便 Claude 能够根据他们的输入继续工作。
Claude 在两种情况下请求用户输入:当它需要使用工具的权限(如删除文件或运行命令)时,以及当它有澄清问题(通过 AskUserQuestion 工具)时。两者都会触发您的 canUseTool 回调,暂停执行直到您返回响应。这与正常的对话轮次不同,在正常轮次中 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)该回调在两种情况下触发:
tool_name 以获取工具名称(例如 "Bash"、"Write")。AskUserQuestion 工具。检查 tool_name == "AskUserQuestion" 以进行不同的处理。如果您指定了 tools 数组,请包含 AskUserQuestion 以使其正常工作。详情请参阅处理澄清问题。要自动允许或拒绝工具而不提示用户,请改用钩子。钩子在 canUseTool 之前执行,可以根据您自己的逻辑允许、拒绝或修改请求。您还可以使用 PermissionRequest 钩子在 Claude 等待审批时发送外部通知(Slack、电子邮件、推送)。
在查询选项中传递 canUseTool 回调后,当 Claude 想要使用未自动批准的工具时,它就会触发。您的回调接收两个参数:
| 参数 | 描述 |
|---|---|
toolName | Claude 想要使用的工具名称(例如 "Bash"、"Write"、"Edit") |
input | Claude 传递给工具的参数。内容因工具而异。 |
input 对象包含特定于工具的参数。常见示例:
| 工具 | 输入字段 |
|---|---|
Bash | command、description、timeout |
Write | file_path、content |
Edit | file_path、old_string、new_string |
Read | file_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。有关所有响应方式,请参阅响应工具请求。
您的回调返回以下两种响应类型之一:
| 响应 | Python | TypeScript |
|---|---|---|
| 允许 | 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 在一个有多种有效方法的任务上需要更多方向时,它会调用 AskUserQuestion 工具。这会触发您的 canUseTool 回调,其中 toolName 设置为 AskUserQuestion。输入包含 Claude 的问题作为多选选项,您将其显示给用户并返回他们的选择。
澄清问题在 plan 模式中特别常见,在该模式下 Claude 会探索代码库并在提出计划之前提问。这使得 plan 模式非常适合交互式工作流,在这种工作流中您希望 Claude 在进行更改之前收集需求。
以下步骤展示了如何处理澄清问题:
传递 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,
),
):
# ...检测 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)解析问题输入
输入在 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
}
]
}有关完整的字段描述,请参阅问题格式。
从用户收集答案
将问题呈现给用户并收集他们的选择。具体方式取决于您的应用程序:终端提示、Web 表单、移动对话框等。
将答案返回给 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 个字符) |
options | 2-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 做出符合用户偏好的决策,而不是猜测。
此示例在终端应用程序中处理这些问题。以下是每个步骤发生的事情:
canUseTool 回调检查工具名称是否为 "AskUserQuestion" 并路由到专用处理程序questions 数组并打印每个问题及其编号选项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 提供了其他从用户获取输入的方式:
在以下情况下使用流式输入:
流式输入非常适合对话式 UI,用户在整个执行过程中与代理交互,而不仅仅是在审批检查点。
在以下情况下使用自定义工具:
AskUserQuestion 的多选格式自定义工具让您完全控制交互,但比使用内置的 canUseTool 回调需要更多的实现工作。
Was this page helpful?