Mientras trabaja en una tarea, Claude a veces necesita consultar con los usuarios. Podría necesitar permiso antes de eliminar archivos, o necesitar preguntar qué base de datos usar para un nuevo proyecto. Tu aplicación necesita presentar estas solicitudes a los usuarios para que Claude pueda continuar con su entrada.
Claude solicita entrada del usuario en dos situaciones: cuando necesita permiso para usar una herramienta (como eliminar archivos o ejecutar comandos), y cuando tiene preguntas aclaratorias (a través de la herramienta AskUserQuestion). Ambas activan tu callback canUseTool, que pausa la ejecución hasta que devuelvas una respuesta. Esto es diferente de los turnos de conversación normales donde Claude termina y espera tu próximo mensaje.
Para preguntas aclaratorias, Claude genera las preguntas y opciones. Tu rol es presentarlas a los usuarios y devolver sus selecciones. No puedes agregar tus propias preguntas a este flujo; si necesitas preguntarle algo a los usuarios tú mismo, hazlo por separado en la lógica de tu aplicación.
Esta guía te muestra cómo detectar cada tipo de solicitud y responder apropiadamente.
Pasa un callback canUseTool en tus opciones de consulta. El callback se activa siempre que Claude necesita entrada del usuario, recibiendo el nombre de la herramienta y la entrada como argumentos:
async def handle_tool_request(tool_name, input_data, context):
# Solicita al usuario y devuelve permitir o denegar
...
options = ClaudeAgentOptions(can_use_tool=handle_tool_request)El callback se activa en dos casos:
tool_name para la herramienta (por ejemplo, "Bash", "Write").AskUserQuestion. Verifica si tool_name == "AskUserQuestion" para manejarlo de manera diferente. Si especificas un array tools, incluye AskUserQuestion para que esto funcione. Consulta Manejar preguntas aclaratorias para más detalles.Para permitir o denegar herramientas automáticamente sin solicitar a los usuarios, usa hooks en su lugar. Los hooks se ejecutan antes de canUseTool y pueden permitir, denegar o modificar solicitudes basadas en tu propia lógica. También puedes usar el hook PermissionRequest para enviar notificaciones externas (Slack, correo electrónico, push) cuando Claude está esperando aprobación.
Una vez que hayas pasado un callback canUseTool en tus opciones de consulta, se activa cuando Claude quiere usar una herramienta que no está aprobada automáticamente. Tu callback recibe dos argumentos:
| Argumento | Descripción |
|---|---|
toolName | El nombre de la herramienta que Claude quiere usar (por ejemplo, "Bash", "Write", "Edit") |
input | Los parámetros que Claude está pasando a la herramienta. El contenido varía según la herramienta. |
El objeto input contiene parámetros específicos de la herramienta. Ejemplos comunes:
| Herramienta | Campos de entrada |
|---|---|
Bash | command, description, timeout |
Write | file_path, content |
Edit | file_path, old_string, new_string |
Read | file_path, offset, limit |
Consulta la referencia del SDK para esquemas de entrada completos: Python | TypeScript.
Puedes mostrar esta información al usuario para que pueda decidir si permitir o rechazar la acción, luego devuelve la respuesta apropiada.
El siguiente ejemplo le pide a Claude que cree y elimine un archivo de prueba. Cuando Claude intenta cada operación, el callback imprime la solicitud de herramienta en la terminal y solicita aprobación s/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:
# Muestra la solicitud de herramienta
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}")
# Obtén aprobación del usuario
response = input("Allow this action? (y/n): ")
# Devuelve permitir o denegar basado en la respuesta del usuario
if response.lower() == "y":
# Permitir: la herramienta se ejecuta con la entrada original (o modificada)
return PermissionResultAllow(updated_input=input_data)
else:
# Denegar: la herramienta no se ejecuta, Claude ve el mensaje
return PermissionResultDeny(message="User denied this action")
# Solución requerida: hook ficticio mantiene el flujo abierto 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())En Python, can_use_tool requiere modo de flujo y un hook PreToolUse que devuelva {"continue_": True} para mantener el flujo abierto. Sin este hook, el flujo se cierra antes de que se pueda invocar el callback de permiso.
Este ejemplo usa un flujo s/n donde cualquier entrada que no sea y se trata como una denegación. En la práctica, podrías construir una interfaz de usuario más rica que permita a los usuarios modificar la solicitud, proporcionar retroalimentación o redirigir a Claude completamente. Consulta Responder a solicitudes de herramientas para todas las formas en que puedes responder.
Tu callback devuelve uno de dos tipos de respuesta:
| Respuesta | Python | TypeScript |
|---|---|---|
| Permitir | PermissionResultAllow(updated_input=...) | { behavior: "allow", updatedInput } |
| Denegar | PermissionResultDeny(message=...) | { behavior: "deny", message } |
Al permitir, pasa la entrada de la herramienta (original o modificada). Al denegar, proporciona un mensaje explicando por qué. Claude ve este mensaje y puede ajustar su enfoque.
from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny
# Permite que la herramienta se ejecute
return PermissionResultAllow(updated_input=input_data)
# Bloquea la herramienta
return PermissionResultDeny(message="User rejected this action")Más allá de permitir o denegar, puedes modificar la entrada de la herramienta o proporcionar contexto que ayude a Claude a ajustar su enfoque:
Cuando Claude necesita más dirección en una tarea con múltiples enfoques válidos, llama a la herramienta AskUserQuestion. Esto activa tu callback canUseTool con toolName establecido en AskUserQuestion. La entrada contiene las preguntas de Claude como opciones de opción múltiple, que muestras al usuario y devuelves sus selecciones.
Las preguntas aclaratorias son especialmente comunes en modo plan, donde Claude explora la base de código y hace preguntas antes de proponer un plan. Esto hace que el modo plan sea ideal para flujos de trabajo interactivos donde quieres que Claude recopile requisitos antes de hacer cambios.
Los siguientes pasos muestran cómo manejar preguntas aclaratorias:
Pasar un callback canUseTool
Pasa un callback canUseTool en tus opciones de consulta. Por defecto, AskUserQuestion está disponible. Si especificas un array tools para restringir las capacidades de Claude (por ejemplo, un agente de solo lectura con solo Read, Glob y Grep), incluye AskUserQuestion en ese array. De lo contrario, Claude no podrá hacer preguntas aclaratorias:
async for message in query(
prompt="Analyze this codebase",
options=ClaudeAgentOptions(
# Incluye AskUserQuestion en tu lista de herramientas
tools=["Read", "Glob", "Grep", "AskUserQuestion"],
can_use_tool=can_use_tool,
),
):
# ...Detectar AskUserQuestion
En tu callback, verifica si toolName es igual a AskUserQuestion para manejarlo de manera diferente a otras herramientas:
async def can_use_tool(tool_name: str, input_data: dict, context):
if tool_name == "AskUserQuestion":
# Tu implementación para recopilar respuestas del usuario
return await handle_clarifying_questions(input_data)
# Maneja otras herramientas normalmente
return await prompt_for_approval(tool_name, input_data)Analizar la entrada de la pregunta
La entrada contiene las preguntas de Claude en un array questions. Cada pregunta tiene una question (el texto a mostrar), options (las opciones) y multiSelect (si se permiten múltiples selecciones):
{
"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
}
]
}Consulta Formato de pregunta para descripciones completas de campos.
Recopilar respuestas del usuario
Presenta las preguntas al usuario y recopila sus selecciones. Cómo lo hagas depende de tu aplicación: un aviso de terminal, un formulario web, un diálogo móvil, etc.
Devolver respuestas a Claude
Construye el objeto answers como un registro donde cada clave es el texto de question y cada valor es el label de la opción seleccionada:
| Del objeto de pregunta | Usar como |
|---|---|
Campo question (por ejemplo, "How should I format the output?") | Clave |
Campo label de la opción seleccionada (por ejemplo, "Summary") | Valor |
Para preguntas de selección múltiple, une múltiples etiquetas con ", ". Si admites entrada de texto libre, usa el texto personalizado del usuario como 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"
}
}
)La entrada contiene las preguntas generadas por Claude en un array questions. Cada pregunta tiene estos campos:
| Campo | Descripción |
|---|---|
question | El texto completo de la pregunta a mostrar |
header | Etiqueta corta para la pregunta (máximo 12 caracteres) |
options | Array de 2-4 opciones, cada una con label y description |
multiSelect | Si es true, los usuarios pueden seleccionar múltiples opciones |
Aquí hay un ejemplo de la estructura que recibirás:
{
"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
}
]
}Devuelve un objeto answers que mapee cada campo question de la pregunta al label de la opción seleccionada:
| Campo | Descripción |
|---|---|
questions | Pasa el array de preguntas original (requerido para procesamiento de herramientas) |
answers | Objeto donde las claves son texto de pregunta y los valores son etiquetas seleccionadas |
Para preguntas de selección múltiple, une múltiples etiquetas con ", ". Para entrada de texto libre, usa el texto personalizado del usuario directamente.
{
"questions": [...],
"answers": {
"How should I format the output?": "Summary",
"Which sections should I include?": "Introduction, Conclusion"
}
}Las opciones predefinidas de Claude no siempre cubrirán lo que los usuarios quieren. Para permitir que los usuarios escriban su propia respuesta:
Consulta el ejemplo completo a continuación para una implementación completa.
Claude hace preguntas aclaratorias cuando necesita entrada del usuario para proceder. Por ejemplo, cuando se le pide que ayude a decidir sobre una pila de tecnología para una aplicación móvil, Claude podría preguntar sobre multiplataforma vs nativo, preferencias de backend o plataformas objetivo. Estas preguntas ayudan a Claude a tomar decisiones que coincidan con las preferencias del usuario en lugar de adivinar.
Este ejemplo maneja esas preguntas en una aplicación de terminal. Aquí está lo que sucede en cada paso:
canUseTool verifica si el nombre de la herramienta es "AskUserQuestion" y enruta a un manejador dedicadoquestions e imprime cada pregunta con opciones numeradasquestions original como el mapeo answersimport asyncio
from claude_agent_sdk import ClaudeAgentOptions, query
from claude_agent_sdk.types import HookMatcher, PermissionResultAllow
def parse_response(response: str, options: list) -> str:
"""Analiza la entrada del usuario como número(s) de opción o texto libre."""
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:
"""Muestra las preguntas de Claude y recopila respuestas del usuario."""
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:
# Enruta AskUserQuestion a nuestro manejador de preguntas
if tool_name == "AskUserQuestion":
return await handle_ask_user_question(input_data)
# Aprueba automáticamente otras herramientas para este ejemplo
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"},
}
# Solución requerida: hook ficticio mantiene el flujo abierto 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())AskUserQuestion no está disponible actualmente en subagentos generados a través de la herramienta TaskAskUserQuestion admite 1-4 preguntas con 2-4 opciones cada unaEl callback canUseTool y la herramienta AskUserQuestion cubren la mayoría de escenarios de aprobación y aclaración, pero el SDK ofrece otras formas de obtener entrada de los usuarios:
Usa entrada de flujo cuando necesites:
La entrada de flujo es ideal para interfaces conversacionales donde los usuarios interactúan con el agente durante toda la ejecución, no solo en puntos de aprobación.
Usa herramientas personalizadas cuando necesites:
AskUserQuestionLas herramientas personalizadas te dan control total sobre la interacción, pero requieren más trabajo de implementación que usar el callback canUseTool incorporado.
Was this page helpful?