query-loop-implementation について
AI アプリのクエリループを実装するエンジニア向けのスキルです。会話状態、ターン制御、ツール実行を分離し、安全性と終了条件を明示します。
主な機能
- 01会話・ループ・ツールランタイムの分離
- 02構造化されたツール呼び出しフロー
- 03スキーマ検証と権限チェック
- 04ターン・タイムアウト・予算の制限
- 05構造化された終了理由
活用シーン
- 01AI アプリのクエリエンジン設計
- 02既存のモデル・ツールループのレビュー
- 03登録済みツールへの安全な実行制御の追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ simbajigege/ book2skills --skill query-loop-implementationインストール後、Claude Code で/query-loop-implementationを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEREADME.mdagents/openai.yamlreferences/query-loop-patterns.md詳細
クエリループの実装
基本的な考え方
ループはプロンプトのつなぎ合わせではなく、プロダクトのインフラストラクチャとして構築します。
ConversationManager -> QueryLoop -> ToolRuntime
ConversationManagerは永続的な状態(セッション ID、メッセージ、ユーザー設定、予算、永続化)を管理します。QueryLoopは 1 回のタスクターンを管理します。モデルを呼び出し、ツール呼び出しを検出し、ツールを実行して、ツールの結果を追加し、繰り返します。ToolRuntimeは登録済みツール(スキーマ、権限チェック、実行、エラーの整形)を管理します。
メンタルモデルとして ReAct を使います。
Thought -> Action -> Observation -> Thought -> Answer
構造化された API トラフィックとして実装します。
model thinking/text -> tool_call -> tool_result -> next model call -> final text
実装ワークフロー
-
ユーザーの技術スタックと現在の LLM 呼び出し箇所を調べます。
メッセージを構築する場所、モデルを呼び出す場所、ツール/関数呼び出しがすでに設定されているかを確認します。 -
最小限のクエリループを導入します。
最初のバージョンは範囲を絞り、モデル呼び出し関数を 1 つ、ツールレジストリを 1 つ、明示的な終了条件を用意します。 -
メッセージの形式を統一します。
利用可能な場合は、プロバイダーの構造化されたツール呼び出し形式を使います。プロバイダーに関数/ツール呼び出し API がない場合を除き、自由形式のAction:テキストを解析するのは避けます。 -
ツール実行の安全性を確保します。
スキーマに照らしてツール入力を検証し、リスクの高いツールには権限チェックを適用し、失敗をツール結果としてラップし、すべての呼び出しをログに記録します。 -
機能を拡張する前に、終了条件と予算ガードを追加します。
必ずmaxTurns、タイムアウト/キャンセルのサポート、トークン/コスト予算のチェック、致命的エラー時の処理経路を含めます。 -
コンテキストウィンドウの戦略は、このスキルの対象外とします。
ループの入力としてmessagesを受け取り、更新後のmessagesを返します。ただし、トリミング、検索、要約、圧縮は別のコンテキスト管理レイヤーに任せます。
最小限のループ
この形式をユーザーの言語と SDK に合わせて調整します。
async function runQueryLoop({
initialMessages,
model,
tools,
maxTurns = 10,
signal,
}: {
initialMessages: Message[]
model: ModelClient
tools: ToolRegistry
maxTurns?: number
signal?: AbortSignal
}) {
let messages = [...initialMessages]
for (let turn = 1; turn <= maxTurns; turn++) {
if (signal?.aborted) return { status: "aborted", messages }
const response = await model.generate({
messages,
tools: tools.definitions(),
signal,
})
messages.push(response.message)
const toolCalls = extractToolCalls(response.message)
if (toolCalls.length === 0) {
return {
status: "completed",
finalMessage: response.message,
messages,
}
}
for (const call of toolCalls) {
const result = await tools.execute(call, { signal, messages })
messages.push(makeToolResultMessage(call.id, result))
}
}
return { status: "max_turns", messages }
}
「モデルが判断を続ける」ステップは独立した関数ではありません。tool_result メッセージを追加した後、ループが再びモデルを呼び出すときに発生します。
必須の終了条件
リリース前に、以下を実装します。
- 正常終了:モデルの応答にツール呼び出しがない。
- ターン数の上限:モデルとツールのサイクルを固定回数実行したら停止する。
- 中断/タイムアウト:ユーザーがキャンセルした場合、またはリクエストが期限を超えた場合に停止する。
- 権限拒否:プロダクトの安全ポリシーに応じて、停止するかツールエラーを返す。
- 予算超過:トークン、コスト、または実行時間の予算を使い切ったら停止する。
- 致命的なツールエラー:モデルが修正できないツールの失敗が発生したら停止する。
構造化された終了理由を返すようにします。
type TerminalReason =
| "completed"
| "max_turns"
| "aborted"
| "timeout"
| "permission_denied"
| "budget_exceeded"
| "fatal_tool_error"
ツールランタイムの契約
各ツールでは、以下を定義します。
type Tool = {
name: string
description: string
inputSchema: unknown
risk: "read" | "write" | "execute" | "external"
validate(input: unknown): ValidatedInput
canUse(input: ValidatedInput, ctx: ToolContext): Promise<PermissionDecision>
call(input: ValidatedInput, ctx: ToolContext): Promise<ToolResult>
}
実行順序:
find tool by name
-> schema validate model input
-> run tool-specific validation
-> check permission
-> call tool
-> format success or error as tool_result
モデルが復旧できる可能性が十分にある場合は、無効な引数、ファイルが見つからない場合、検索結果が空の場合、一時的な API エラーなどのツールエラーをモデルに返します。セキュリティ違反、失敗の繰り返し、認証情報の不足、予算超過の場合はループを停止します。
プロダクト設計の指針
「インテリジェンス」はモデルに、「信頼性」はコードに担わせます。
- 追加のツール呼び出しが必要かどうかは、モデルに判断させます。
- スキーマ、権限、予算、ループの終了条件は、コードで強制します。
- プロンプトはツールのセマンティクスとタスクのポリシーに集中させます。
- 実装は決定論的な制御フローに集中させます。
シンプルな AI アプリでは、最初はサブエージェント、ワークツリー、ツール実行のストリーミングを避けます。並列作業、分離、または長時間実行タスクがプロダクトで実際に必要になった場合にのみ追加します。
さらに詳しい情報が必要な場合
新しいクエリエンジンを設計するとき、既存の実装をレビューするとき、または ReAct からクエリループへのアーキテクチャを別のエンジニアに説明するときは、references/query-loop-patterns.md を参照してください。