在處理任務時,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 以使其正常運作。詳見處理澄清問題。要自動允許或拒絕工具而不提示使用者,請改用 hooks。Hooks 在 canUseTool 之前執行,可以根據您自己的邏輯允許、拒絕或修改請求。您也可以使用 PermissionRequest hook 在 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 hook 來保持串流開啟。沒有這個 hook,串流會在許可權回呼被呼叫之前關閉。
此範例使用 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
}
]
}完整欄位描述請參閱問題格式。
從使用者收集答案
將問題呈現給使用者並收集他們的選擇。具體方式取決於您的應用程式:終端機提示、網頁表單、行動裝置對話框等。
將答案回傳給 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,在這些 UI 中使用者在整個執行過程中與代理互動,而不僅僅是在審批檢查點。
當您需要以下功能時,使用自訂工具:
AskUserQuestion 的多選格式自訂工具讓您完全控制互動,但比使用內建的 canUseTool 回呼需要更多的實作工作。
Was this page helpful?