Saat mengerjakan tugas, Claude kadang-kadang perlu menghubungi pengguna. Mungkin perlu izin sebelum menghapus file, atau perlu menanyakan database mana yang akan digunakan untuk proyek baru. Aplikasi Anda perlu menampilkan permintaan ini kepada pengguna sehingga Claude dapat melanjutkan dengan input mereka.
Claude meminta input pengguna dalam dua situasi: ketika membutuhkan izin untuk menggunakan alat (seperti menghapus file atau menjalankan perintah), dan ketika memiliki pertanyaan klarifikasi (melalui alat AskUserQuestion). Keduanya memicu callback canUseTool Anda, yang menghentikan eksekusi sampai Anda mengembalikan respons. Ini berbeda dari putaran percakapan normal di mana Claude selesai dan menunggu pesan berikutnya Anda.
Untuk pertanyaan klarifikasi, Claude menghasilkan pertanyaan dan opsi. Peran Anda adalah menyajikannya kepada pengguna dan mengembalikan pilihan mereka. Anda tidak dapat menambahkan pertanyaan Anda sendiri ke alur ini; jika Anda perlu menanyakan sesuatu kepada pengguna, lakukan itu secara terpisah dalam logika aplikasi Anda.
Panduan ini menunjukkan cara mendeteksi setiap jenis permintaan dan merespons dengan tepat.
Berikan callback canUseTool dalam opsi kueri Anda. Callback dipicu setiap kali Claude membutuhkan input pengguna, menerima nama alat dan input sebagai argumen:
async def handle_tool_request(tool_name, input_data, context):
# Minta pengguna dan kembalikan izin atau tolak
...
options = ClaudeAgentOptions(can_use_tool=handle_tool_request)Callback dipicu dalam dua kasus:
tool_name untuk alat (misalnya, "Bash", "Write").AskUserQuestion. Periksa apakah tool_name == "AskUserQuestion" untuk menanganinya secara berbeda. Jika Anda menentukan array tools, sertakan AskUserQuestion agar ini berfungsi. Lihat Menangani pertanyaan klarifikasi untuk detail.Untuk secara otomatis mengizinkan atau menolak alat tanpa meminta pengguna, gunakan hook sebagai gantinya. Hook dijalankan sebelum canUseTool dan dapat mengizinkan, menolak, atau memodifikasi permintaan berdasarkan logika Anda sendiri. Anda juga dapat menggunakan hook PermissionRequest untuk mengirim notifikasi eksternal (Slack, email, push) ketika Claude menunggu persetujuan.
Setelah Anda melewatkan callback canUseTool dalam opsi kueri Anda, callback dipicu ketika Claude ingin menggunakan alat yang tidak disetujui secara otomatis. Callback Anda menerima dua argumen:
| Argumen | Deskripsi |
|---|---|
toolName | Nama alat yang ingin digunakan Claude (misalnya, "Bash", "Write", "Edit") |
input | Parameter yang Claude teruskan ke alat. Isi bervariasi menurut alat. |
Objek input berisi parameter khusus alat. Contoh umum:
| Alat | Bidang input |
|---|---|
Bash | command, description, timeout |
Write | file_path, content |
Edit | file_path, old_string, new_string |
Read | file_path, offset, limit |
Lihat referensi SDK untuk skema input lengkap: Python | TypeScript.
Anda dapat menampilkan informasi ini kepada pengguna sehingga mereka dapat memutuskan apakah akan mengizinkan atau menolak tindakan, kemudian kembalikan respons yang sesuai.
Contoh berikut meminta Claude untuk membuat dan menghapus file uji. Ketika Claude mencoba setiap operasi, callback mencetak permintaan alat ke terminal dan meminta persetujuan y/n.
Di Python, can_use_tool memerlukan mode streaming dan hook PreToolUse yang mengembalikan {"continue_": True} untuk menjaga aliran tetap terbuka. Tanpa hook ini, aliran ditutup sebelum callback izin dapat dipanggil.
Contoh ini menggunakan alur y/n di mana input apa pun selain y diperlakukan sebagai penolakan. Dalam praktik, Anda mungkin membangun UI yang lebih kaya yang memungkinkan pengguna memodifikasi permintaan, memberikan umpan balik, atau mengarahkan Claude sepenuhnya. Lihat Merespons permintaan alat untuk semua cara Anda dapat merespons.
Callback Anda mengembalikan salah satu dari dua jenis respons:
| Respons | Python | TypeScript |
|---|---|---|
| Izinkan | PermissionResultAllow(updated_input=...) | { behavior: "allow", updatedInput } |
| Tolak | PermissionResultDeny(message=...) | { behavior: "deny", message } |
Saat mengizinkan, berikan input alat (asli atau dimodifikasi). Saat menolak, berikan pesan yang menjelaskan alasannya. Claude melihat pesan ini dan mungkin menyesuaikan pendekatannya.
from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny
# Izinkan alat untuk dijalankan
return PermissionResultAllow(updated_input=input_data)
# Blokir alat
return PermissionResultDeny(message="User rejected this action")Selain mengizinkan atau menolak, Anda dapat memodifikasi input alat atau memberikan konteks yang membantu Claude menyesuaikan pendekatannya:
Ketika Claude membutuhkan lebih banyak arahan tentang tugas dengan beberapa pendekatan yang valid, Claude memanggil alat AskUserQuestion. Ini memicu callback canUseTool Anda dengan toolName diatur ke AskUserQuestion. Input berisi pertanyaan Claude sebagai opsi pilihan ganda, yang Anda tampilkan kepada pengguna dan kembalikan pilihan mereka.
Pertanyaan klarifikasi sangat umum dalam mode plan, di mana Claude menjelajahi basis kode dan mengajukan pertanyaan sebelum mengusulkan rencana. Ini membuat mode plan ideal untuk alur kerja interaktif di mana Anda ingin Claude mengumpulkan persyaratan sebelum membuat perubahan.
Langkah-langkah berikut menunjukkan cara menangani pertanyaan klarifikasi:
Input berisi pertanyaan yang dihasilkan Claude dalam array questions. Setiap pertanyaan memiliki bidang-bidang ini:
| Bidang | Deskripsi |
|---|---|
question | Teks pertanyaan lengkap untuk ditampilkan |
header | Label pendek untuk pertanyaan (maks 12 karakter) |
options | Array 2-4 pilihan, masing-masing dengan label dan description |
multiSelect | Jika true, pengguna dapat memilih beberapa opsi |
Berikut adalah contoh struktur yang akan Anda terima:
{
"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
}
]
}Kembalikan objek answers yang memetakan bidang question setiap pertanyaan ke label opsi yang dipilih:
| Bidang | Deskripsi |
|---|---|
questions | Teruskan array pertanyaan asli (diperlukan untuk pemrosesan alat) |
answers | Objek di mana kunci adalah teks pertanyaan dan nilai adalah label yang dipilih |
Untuk pertanyaan multi-pilih, gabungkan beberapa label dengan ", ". Untuk input teks bebas, gunakan teks kustom pengguna secara langsung.
{
"questions": [...],
"answers": {
"How should I format the output?": "Summary",
"Which sections should I include?": "Introduction, Conclusion"
}
}Opsi yang telah ditentukan Claude tidak akan selalu mencakup apa yang diinginkan pengguna. Untuk memungkinkan pengguna mengetik jawaban mereka sendiri:
Lihat contoh lengkap di bawah untuk implementasi lengkap.
Claude mengajukan pertanyaan klarifikasi ketika membutuhkan input pengguna untuk melanjutkan. Misalnya, ketika diminta membantu memutuskan tech stack untuk aplikasi seluler, Claude mungkin menanyakan tentang lintas platform vs native, preferensi backend, atau platform target. Pertanyaan-pertanyaan ini membantu Claude membuat keputusan yang sesuai dengan preferensi pengguna daripada menebak.
Contoh ini menangani pertanyaan-pertanyaan tersebut dalam aplikasi terminal. Berikut yang terjadi di setiap langkah:
canUseTool memeriksa apakah nama alat adalah "AskUserQuestion" dan rute ke handler khususquestions dan mencetak setiap pertanyaan dengan opsi bernomorquestions asli dan pemetaan answersAskUserQuestion saat ini tidak tersedia di subagents yang dihasilkan melalui alat TaskAskUserQuestion mendukung 1-4 pertanyaan dengan 2-4 opsi masing-masingCallback canUseTool dan alat AskUserQuestion mencakup sebagian besar skenario persetujuan dan klarifikasi, tetapi SDK menawarkan cara lain untuk mendapatkan input dari pengguna:
Gunakan input streaming ketika Anda perlu:
Input streaming ideal untuk UI percakapan di mana pengguna berinteraksi dengan agen sepanjang eksekusi, bukan hanya di titik persetujuan.
Gunakan alat kustom ketika Anda perlu:
AskUserQuestionAlat kustom memberi Anda kontrol penuh atas interaksi, tetapi memerlukan lebih banyak pekerjaan implementasi daripada menggunakan callback canUseTool bawaan.
Was this page helpful?
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:
# Tampilkan permintaan alat
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}")
# Dapatkan persetujuan pengguna
response = input("Allow this action? (y/n): ")
# Kembalikan izin atau tolak berdasarkan respons pengguna
if response.lower() == "y":
# Izinkan: alat dijalankan dengan input asli (atau dimodifikasi)
return PermissionResultAllow(updated_input=input_data)
else:
# Tolak: alat tidak dijalankan, Claude melihat pesan
return PermissionResultDeny(message="User denied this action")
# Solusi kerja yang diperlukan: hook dummy menjaga aliran tetap terbuka untuk 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())Berikan callback canUseTool
Berikan callback canUseTool dalam opsi kueri Anda. Secara default, AskUserQuestion tersedia. Jika Anda menentukan array tools untuk membatasi kemampuan Claude (misalnya, agen baca-saja dengan hanya Read, Glob, dan Grep), sertakan AskUserQuestion dalam array itu. Jika tidak, Claude tidak akan dapat mengajukan pertanyaan klarifikasi:
async for message in query(
prompt="Analyze this codebase",
options=ClaudeAgentOptions(
# Sertakan AskUserQuestion dalam daftar alat Anda
tools=["Read", "Glob", "Grep", "AskUserQuestion"],
can_use_tool=can_use_tool,
),
):
# ...Deteksi AskUserQuestion
Dalam callback Anda, periksa apakah toolName sama dengan AskUserQuestion untuk menanganinya secara berbeda dari alat lain:
async def can_use_tool(tool_name: str, input_data: dict, context):
if tool_name == "AskUserQuestion":
# Implementasi Anda untuk mengumpulkan jawaban dari pengguna
return await handle_clarifying_questions(input_data)
# Tangani alat lain secara normal
return await prompt_for_approval(tool_name, input_data)Analisis input pertanyaan
Input berisi pertanyaan Claude dalam array questions. Setiap pertanyaan memiliki question (teks untuk ditampilkan), options (pilihan), dan multiSelect (apakah beberapa pilihan diizinkan):
{
"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
}
]
}Lihat Format pertanyaan untuk deskripsi bidang lengkap.
Kumpulkan jawaban dari pengguna
Presentasikan pertanyaan kepada pengguna dan kumpulkan pilihan mereka. Cara Anda melakukan ini tergantung pada aplikasi Anda: prompt terminal, formulir web, dialog seluler, dll.
Kembalikan jawaban ke Claude
Bangun objek answers sebagai catatan di mana setiap kunci adalah teks question dan setiap nilai adalah label opsi yang dipilih:
| Dari objek pertanyaan | Gunakan sebagai |
|---|---|
Bidang question (misalnya, "How should I format the output?") | Kunci |
Bidang label opsi yang dipilih (misalnya, "Summary") | Nilai |
Untuk pertanyaan multi-pilih, gabungkan beberapa label dengan ", ". Jika Anda mendukung input teks bebas, gunakan teks kustom pengguna sebagai nilainya.
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:
"""Analisis input pengguna sebagai nomor opsi atau teks bebas."""
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:
"""Tampilkan pertanyaan Claude dan kumpulkan jawaban pengguna."""
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:
# Rute AskUserQuestion ke handler pertanyaan kami
if tool_name == "AskUserQuestion":
return await handle_ask_user_question(input_data)
# Auto-approve alat lain untuk contoh ini
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"},
}
# Solusi kerja yang diperlukan: hook dummy menjaga aliran tetap terbuka untuk 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())