Was this page helpful?
Ao trabalhar em uma tarefa, Claude às vezes precisa verificar com os usuários. Pode precisar de permissão antes de deletar arquivos ou precisar perguntar qual banco de dados usar para um novo projeto. Seu aplicativo precisa apresentar essas solicitações aos usuários para que Claude possa continuar com sua entrada.
Claude solicita entrada do usuário em duas situações: quando precisa de permissão para usar uma ferramenta (como deletar arquivos ou executar comandos) e quando tem perguntas de esclarecimento (via a ferramenta AskUserQuestion). Ambas acionam seu callback canUseTool, que pausa a execução até que você retorne uma resposta. Isso é diferente dos turnos de conversa normais onde Claude termina e aguarda sua próxima mensagem.
Para perguntas de esclarecimento, Claude gera as perguntas e opções. Seu papel é apresentá-las aos usuários e retornar suas seleções. Você não pode adicionar suas próprias perguntas a este fluxo; se precisar perguntar algo aos usuários, faça isso separadamente na lógica do seu aplicativo.
Este guia mostra como detectar cada tipo de solicitação e responder apropriadamente.
Passe um callback canUseTool nas opções de sua consulta. O callback é acionado sempre que Claude precisa de entrada do usuário, recebendo o nome da ferramenta e a entrada como argumentos:
async def handle_tool_request(tool_name, input_data, context):
# Solicitar ao usuário e retornar permitir ou negar
...
options = ClaudeAgentOptions(can_use_tool=handle_tool_request)O callback é acionado em dois casos:
tool_name para a ferramenta (por exemplo, "Bash", "Write").AskUserQuestion. Verifique se tool_name == "AskUserQuestion" para tratá-la diferentemente. Se você especificar um array tools, inclua AskUserQuestion para que isso funcione. Veja Lidar com perguntas de esclarecimento para detalhes.Para permitir ou negar ferramentas automaticamente sem solicitar aos usuários, use hooks em vez disso. Os hooks são executados antes de canUseTool e podem permitir, negar ou modificar solicitações com base em sua própria lógica. Você também pode usar o hook PermissionRequest para enviar notificações externas (Slack, email, push) quando Claude está aguardando aprovação.
Depois de passar um callback canUseTool nas opções de sua consulta, ele é acionado quando Claude quer usar uma ferramenta que não é aprovada automaticamente. Seu callback recebe dois argumentos:
| Argumento | Descrição |
|---|---|
toolName | O nome da ferramenta que Claude quer usar (por exemplo, "Bash", "Write", "Edit") |
input | Os parâmetros que Claude está passando para a ferramenta. O conteúdo varia por ferramenta. |
O objeto input contém parâmetros específicos da ferramenta. Exemplos comuns:
| Ferramenta | Campos de entrada |
|---|---|
Bash | command, description, timeout |
Write | file_path, content |
Edit | file_path, old_string, new_string |
Read | file_path, offset, limit |
Veja a referência do SDK para esquemas de entrada completos: Python | TypeScript.
Você pode exibir essas informações ao usuário para que ele possa decidir se permite ou rejeita a ação, e então retornar a resposta apropriada.
O exemplo a seguir pede ao Claude para criar e deletar um arquivo de teste. Quando Claude tenta cada operação, o callback imprime a solicitação de ferramenta no terminal e solicita aprovação s/n.
Em Python, can_use_tool requer modo de streaming e um hook PreToolUse que retorna {"continue_": True} para manter o stream aberto. Sem este hook, o stream fecha antes que o callback de permissão possa ser invocado.
Este exemplo usa um fluxo s/n onde qualquer entrada diferente de y é tratada como uma negação. Na prática, você pode construir uma UI mais rica que permite aos usuários modificar a solicitação, fornecer feedback ou redirecionar Claude completamente. Veja Responder a solicitações de ferramenta para todas as maneiras que você pode responder.
Seu callback retorna um de dois tipos de resposta:
| Resposta | Python | TypeScript |
|---|---|---|
| Permitir | PermissionResultAllow(updated_input=...) | { behavior: "allow", updatedInput } |
| Negar | PermissionResultDeny(message=...) | { behavior: "deny", message } |
Ao permitir, passe a entrada da ferramenta (original ou modificada). Ao negar, forneça uma mensagem explicando por quê. Claude vê esta mensagem e pode ajustar sua abordagem.
from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny
# Permitir que a ferramenta execute
return PermissionResultAllow(updated_input=input_data)
# Bloquear a ferramenta
return PermissionResultDeny(message="User rejected this action")Além de permitir ou negar, você pode modificar a entrada da ferramenta ou fornecer contexto que ajude Claude a ajustar sua abordagem:
Quando Claude precisa de mais direção em uma tarefa com múltiplas abordagens válidas, ele chama a ferramenta AskUserQuestion. Isso aciona seu callback canUseTool com toolName definido como AskUserQuestion. A entrada contém as perguntas de Claude como opções de múltipla escolha, que você exibe ao usuário e retorna suas seleções.
Perguntas de esclarecimento são especialmente comuns no modo plan, onde Claude explora a base de código e faz perguntas antes de propor um plano. Isso torna o modo plan ideal para fluxos de trabalho interativos onde você quer que Claude reúna requisitos antes de fazer alterações.
Os passos a seguir mostram como lidar com perguntas de esclarecimento:
A entrada contém as perguntas geradas por Claude em um array questions. Cada pergunta tem estes campos:
| Campo | Descrição |
|---|---|
question | O texto completo da pergunta a exibir |
header | Rótulo curto para a pergunta (máximo 12 caracteres) |
options | Array de 2-4 escolhas, cada uma com label e description |
multiSelect | Se true, os usuários podem selecionar múltiplas opções |
Aqui está um exemplo da estrutura que você receberá:
{
"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
}
]
}Retorne um objeto answers mapeando cada campo question da pergunta para o label da opção selecionada:
| Campo | Descrição |
|---|---|
questions | Passar o array de perguntas original (obrigatório para processamento de ferramenta) |
answers | Objeto onde as chaves são texto de pergunta e os valores são labels selecionados |
Para perguntas de seleção múltipla, junte múltiplos labels com ", ". Para entrada de texto livre, use o texto personalizado do usuário diretamente.
{
"questions": [...],
"answers": {
"How should I format the output?": "Summary",
"Which sections should I include?": "Introduction, Conclusion"
}
}As opções predefinidas de Claude nem sempre cobrem o que os usuários querem. Para deixar os usuários digitarem sua própria resposta:
Veja o exemplo completo abaixo para uma implementação completa.
Claude faz perguntas de esclarecimento quando precisa de entrada do usuário para prosseguir. Por exemplo, quando solicitado a ajudar a decidir sobre uma pilha de tecnologia para um aplicativo móvel, Claude pode perguntar sobre multiplataforma vs nativo, preferências de backend ou plataformas alvo. Essas perguntas ajudam Claude a tomar decisões que correspondem às preferências do usuário em vez de adivinhar.
Este exemplo lida com essas perguntas em um aplicativo de terminal. Aqui está o que acontece em cada etapa:
canUseTool verifica se o nome da ferramenta é "AskUserQuestion" e roteia para um manipulador dedicadoquestions e imprime cada pergunta com opções numeradasquestions original quanto o mapeamento answersAskUserQuestion não está disponível em subagentes gerados via a ferramenta TaskAskUserQuestion suporta 1-4 perguntas com 2-4 opções cadaO callback canUseTool e a ferramenta AskUserQuestion cobrem a maioria dos cenários de aprovação e esclarecimento, mas o SDK oferece outras maneiras de obter entrada dos usuários:
Use entrada de streaming quando você precisar:
Entrada de streaming é ideal para UIs conversacionais onde os usuários interagem com o agente durante toda a execução, não apenas em pontos de aprovação.
Use ferramentas personalizadas quando você precisar:
AskUserQuestionFerramentas personalizadas lhe dão controle total sobre a interação, mas requerem mais trabalho de implementação do que usar o callback canUseTool integrado.
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:
# Exibir a solicitação de ferramenta
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}")
# Obter aprovação do usuário
response = input("Allow this action? (y/n): ")
# Retornar permitir ou negar com base na resposta do usuário
if response.lower() == "y":
# Permitir: ferramenta executa com a entrada original (ou modificada)
return PermissionResultAllow(updated_input=input_data)
else:
# Negar: ferramenta não executa, Claude vê a mensagem
return PermissionResultDeny(message="User denied this action")
# Solução alternativa necessária: hook fictício mantém o stream aberto para 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())Passar um callback canUseTool
Passe um callback canUseTool nas opções de sua consulta. Por padrão, AskUserQuestion está disponível. Se você especificar um array tools para restringir as capacidades do Claude (por exemplo, um agente somente leitura com apenas Read, Glob e Grep), inclua AskUserQuestion nesse array. Caso contrário, Claude não será capaz de fazer perguntas de esclarecimento:
async for message in query(
prompt="Analyze this codebase",
options=ClaudeAgentOptions(
# Incluir AskUserQuestion na sua lista de ferramentas
tools=["Read", "Glob", "Grep", "AskUserQuestion"],
can_use_tool=can_use_tool,
),
):
# ...Detectar AskUserQuestion
Em seu callback, verifique se toolName é igual a AskUserQuestion para tratá-lo diferentemente de outras ferramentas:
async def can_use_tool(tool_name: str, input_data: dict, context):
if tool_name == "AskUserQuestion":
# Sua implementação para coletar respostas do usuário
return await handle_clarifying_questions(input_data)
# Lidar com outras ferramentas normalmente
return await prompt_for_approval(tool_name, input_data)Analisar a entrada da pergunta
A entrada contém as perguntas de Claude em um array questions. Cada pergunta tem uma question (o texto a exibir), options (as escolhas) e multiSelect (se múltiplas seleções são permitidas):
{
"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
}
]
}Veja Formato de pergunta para descrições completas de campos.
Coletar respostas do usuário
Apresente as perguntas ao usuário e colete suas seleções. Como você faz isso depende do seu aplicativo: um prompt de terminal, um formulário web, um diálogo móvel, etc.
Retornar respostas ao Claude
Construa o objeto answers como um registro onde cada chave é o texto question e cada valor é o label da opção selecionada:
| Do objeto de pergunta | Use como |
|---|---|
Campo question (por exemplo, "How should I format the output?") | Chave |
Campo label da opção selecionada (por exemplo, "Summary") | Valor |
Para perguntas de seleção múltipla, junte múltiplos labels com ", ". Se você suportar entrada de texto livre, use o texto personalizado do usuário como o valor.
return PermissionResultAllow(
updated_input={
"questions": input_data.get("questions", []),
"answers": {
"How should I format the output?": "Summary",
"Which sections should I include?": "Introduction, Conclusion"
}
}
)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:
"""Analisar entrada do usuário como número(s) de opção ou texto livre."""
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:
"""Exibir as perguntas de Claude e coletar respostas do usuário."""
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:
# Rotear AskUserQuestion para nosso manipulador de perguntas
if tool_name == "AskUserQuestion":
return await handle_ask_user_question(input_data)
# Auto-aprovar outras ferramentas para este exemplo
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"},
}
# Solução alternativa necessária: hook fictício mantém o stream aberto para 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())