タスクの作業中、Claudeはユーザーに確認が必要になることがあります。ファイルを削除する前に許可が必要だったり、新しいプロジェクトでどのデータベースを使用するか確認が必要だったりします。アプリケーションはこれらのリクエストをユーザーに表示し、Claudeがユーザーの入力を受けて作業を続行できるようにする必要があります。
Claudeがユーザー入力をリクエストするのは2つの状況です:ツールの使用許可が必要な場合(ファイルの削除やコマンドの実行など)と、確認質問がある場合(AskUserQuestionツール経由)です。どちらもcanUseToolコールバックをトリガーし、レスポンスを返すまで実行が一時停止します。これは、Claudeが処理を完了して次のメッセージを待つ通常の会話ターンとは異なります。
確認質問の場合、Claudeが質問と選択肢を生成します。あなたの役割は、それらをユーザーに提示し、選択結果を返すことです。このフローに独自の質問を追加することはできません。ユーザーに何か質問する必要がある場合は、アプリケーションロジックで別途行ってください。
このガイドでは、各タイプのリクエストを検出し、適切に応答する方法を説明します。
クエリオプションにcanUseToolコールバックを渡します。コールバックはClaudeがユーザー入力を必要とするたびに発火し、ツール名と入力を引数として受け取ります:
async def handle_tool_request(tool_name, input_data, context):
# ユーザーにプロンプトを表示し、許可または拒否を返す
...
options = ClaudeAgentOptions(can_use_tool=handle_tool_request)コールバックは2つのケースで発火します:
tool_nameでツールを確認します(例:"Bash"、"Write")。AskUserQuestionツールを呼び出します。tool_name == "AskUserQuestion"かどうかを確認して、異なる処理を行います。tools配列を指定する場合は、これが機能するようにAskUserQuestionを含めてください。詳細は確認質問の処理を参照してください。ユーザーにプロンプトを表示せずにツールを自動的に許可または拒否するには、代わりにフックを使用してください。フックはcanUseToolの前に実行され、独自のロジックに基づいてリクエストを許可、拒否、または変更できます。また、PermissionRequestフックを使用して、Claudeが承認を待っているときに外部通知(Slack、メール、プッシュ通知)を送信することもできます。
クエリオプションにcanUseToolコールバックを渡すと、Claudeが自動承認されていないツールを使用しようとしたときに発火します。コールバックは2つの引数を受け取ります:
| 引数 | 説明 |
|---|---|
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:
# ツールリクエストを表示
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}")
# ユーザーの承認を取得
response = input("Allow this action? (y/n): ")
# ユーザーの応答に基づいて許可または拒否を返す
if response.lower() == "y":
# 許可:ツールは元の(または変更された)入力で実行される
return PermissionResultAllow(updated_input=input_data)
else:
# 拒否:ツールは実行されず、Claudeにメッセージが表示される
return PermissionResultDeny(message="User denied this action")
# 必要な回避策:ダミーフックが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以外の入力はすべて拒否として扱うy/nフローを使用しています。実際には、ユーザーがリクエストを変更したり、フィードバックを提供したり、Claudeを完全にリダイレクトしたりできる、より豊富なUIを構築することもあります。応答方法のすべてについては、ツールリクエストへの応答を参照してください。
コールバックは2つのレスポンスタイプのいずれかを返します:
| レスポンス | Python | TypeScript |
|---|---|---|
| 許可 | PermissionResultAllow(updated_input=...) | { behavior: "allow", updatedInput } |
| 拒否 | PermissionResultDeny(message=...) | { behavior: "deny", message } |
許可する場合は、ツール入力(元のまたは変更済み)を渡します。拒否する場合は、理由を説明するメッセージを提供します。Claudeはこのメッセージを確認し、アプローチを調整する場合があります。
from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny
# ツールの実行を許可
return PermissionResultAllow(updated_input=input_data)
# ツールをブロック
return PermissionResultDeny(message="User rejected this action")許可または拒否に加えて、ツールの入力を変更したり、Claudeがアプローチを調整するのに役立つコンテキストを提供したりできます:
Claudeが複数の有効なアプローチがあるタスクでより多くの方向性を必要とする場合、AskUserQuestionツールを呼び出します。これにより、toolNameがAskUserQuestionに設定された状態でcanUseToolコールバックがトリガーされます。入力にはClaudeの質問が多肢選択式のオプションとして含まれており、ユーザーに表示して選択結果を返します。
確認質問はplanモードで特に一般的です。このモードでは、Claudeがコードベースを探索し、プランを提案する前に質問します。これにより、planモードは、Claudeに変更を加える前に要件を収集させたいインタラクティブなワークフローに最適です。
以下の手順で確認質問を処理します:
canUseToolコールバックを渡す
クエリオプションにcanUseToolコールバックを渡します。デフォルトでは、AskUserQuestionは利用可能です。Claudeの機能を制限するためにtools配列を指定する場合(例えば、Read、Glob、Grepのみの読み取り専用エージェント)、その配列にAskUserQuestionを含めてください。そうしないと、Claudeは確認質問をすることができません:
async for message in query(
prompt="Analyze this codebase",
options=ClaudeAgentOptions(
# ツールリストにAskUserQuestionを含める
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":
# ユーザーから回答を収集する実装
return await handle_clarifying_questions(input_data)
# 他のツールは通常通り処理
return await prompt_for_approval(tool_name, input_data)質問入力を解析する
入力にはClaudeの質問がquestions配列に含まれています。各質問には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"
}
}
)入力にはClaudeが生成した質問がquestions配列に含まれています。各質問には以下のフィールドがあります:
| フィールド | 説明 |
|---|---|
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
}
]
}各質問のquestionフィールドを選択されたオプションのlabelにマッピングするanswersオブジェクトを返します:
| フィールド | 説明 |
|---|---|
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:
"""ユーザー入力をオプション番号またはフリーテキストとして解析する。"""
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:
"""Claudeの質問を表示し、ユーザーの回答を収集する。"""
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:
# AskUserQuestionを質問ハンドラーにルーティング
if tool_name == "AskUserQuestion":
return await handle_ask_user_question(input_data)
# この例では他のツールを自動承認
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"},
}
# 必要な回避策:ダミーフックが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呼び出しは、それぞれ2〜4個のオプションを持つ1〜4個の質問をサポートしますcanUseToolコールバックとAskUserQuestionツールはほとんどの承認と確認のシナリオをカバーしますが、SDKにはユーザーから入力を取得する他の方法もあります:
以下の場合にストリーミング入力を使用します:
ストリーミング入力は、承認チェックポイントだけでなく、実行全体を通じてユーザーがエージェントとやり取りする会話型UIに最適です。
以下の場合にカスタムツールを使用します:
AskUserQuestionの多肢選択形式を超えるフォーム、ウィザード、またはマルチステップワークフローを構築するカスタムツールはインタラクションを完全に制御できますが、組み込みのcanUseToolコールバックを使用するよりも多くの実装作業が必要です。
Was this page helpful?