人間の介入(HITL)
このガイドでは、SDK に組み込まれた Human in the loop (人間の介入) サポートを使って、人の介入に基づいてエージェントの実行を一時停止・再開する方法を説明します。
現在の主なユースケースは、センシティブなツール実行に対する承認の取得です。
承認リクエスト
Section titled “承認リクエスト”needsApproval オプションを true、または真偽値を返す非同期関数に設定することで、承認が必要なツールを定義できます。
import { tool } from '@openai/agents';import z from 'zod';
const sensitiveTool = tool({ name: 'cancelOrder', description: 'Cancel order', parameters: z.object({ orderId: z.number(), }), // always requires approval needsApproval: true, execute: async ({ orderId }, args) => { // prepare order return },});
const sendEmail = tool({ name: 'sendEmail', description: 'Send an email', parameters: z.object({ to: z.string(), subject: z.string(), body: z.string(), }), needsApproval: async (_context, { subject }) => { // check if the email is spam return subject.includes('spam'); }, execute: async ({ to, subject, body }, args) => { // send email },});- エージェントがツール(複数も可)を呼び出すと判断した場合、
needsApprovalを評価してそのツールに承認が必要か確認します - 承認が必要な場合、エージェントは承認がすでに許可または拒否されているかを確認します
- 承認が許可または拒否されていない場合、ツールは「このツール呼び出しは実行できない」という固定メッセージをエージェントに返します
- 承認 / 拒否が未処理の場合、ツール承認リクエストをトリガーします
- エージェントはすべてのツール承認リクエストを収集し、実行を中断します
- 中断がある場合、エージェントの実行結果 に、保留中のステップを記述する
interruptions配列が含まれます。ツール呼び出しに確認が必要な場合、type: "tool_approval_item"を持つToolApprovalItemが表示されます - ツール呼び出しを承認または拒否するには、
result.state.approve(interruption)またはresult.state.reject(interruption)を呼び出します - すべての中断を処理したら、
result.stateをrunner.run(agent, state)に渡して実行を再開できます。ここでagentは全体の実行をトリガーした元のエージェントです - フローは手順 1 から再開します
以下は、ターミナルで承認を促し、状態を一時的にファイルへ保存する Human in the loop (人間の介入) フローの、より完全な例です。
import { z } from 'zod';import readline from 'node:readline/promises';import fs from 'node:fs/promises';import { Agent, run, tool, RunState, RunResult } from '@openai/agents';
const getWeatherTool = tool({ name: 'get_weather', description: 'Get the weather for a given city', parameters: z.object({ location: z.string(), }), needsApproval: async (_context, { location }) => { // forces approval to look up the weather in San Francisco return location === 'San Francisco'; }, execute: async ({ location }) => { return `The weather in ${location} is sunny`; },});
const dataAgentTwo = new Agent({ name: 'Data agent', instructions: 'You are a data agent', handoffDescription: 'You know everything about the weather', tools: [getWeatherTool],});
const agent = new Agent({ name: 'Basic test agent', instructions: 'You are a basic agent', handoffs: [dataAgentTwo],});
async function confirm(question: string) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, });
const answer = await rl.question(`${question} (y/n): `); const normalizedAnswer = answer.toLowerCase(); rl.close(); return normalizedAnswer === 'y' || normalizedAnswer === 'yes';}
async function main() { let result: RunResult<unknown, Agent<unknown, any>> = await run( agent, 'What is the weather in Oakland and San Francisco?', ); let hasInterruptions = result.interruptions?.length > 0; while (hasInterruptions) { // storing await fs.writeFile( 'result.json', JSON.stringify(result.state, null, 2), 'utf-8', );
// from here on you could run things on a different thread/process
// reading later on const storedState = await fs.readFile('result.json', 'utf-8'); const state = await RunState.fromString(agent, storedState);
for (const interruption of result.interruptions) { const confirmed = await confirm( `Agent ${interruption.agent.name} would like to use the tool ${interruption.rawItem.name} with "${interruption.rawItem.arguments}". Do you approve?`, );
if (confirmed) { state.approve(interruption); } else { state.reject(interruption); } }
// resume execution of the current state result = await run(agent, state); hasInterruptions = result.interruptions?.length > 0; }
console.log(result.finalOutput);}
main().catch((error) => { console.dir(error, { depth: null });});エンドツーエンドで動作する完全版は、完全なサンプルスクリプト を参照してください。
長時間の承認対応
Section titled “長時間の承認対応”Human in the loop (人間の介入) フローは、サーバーを起動し続けることなく、長時間の中断が可能なように設計されています。リクエストを一旦終了して後で続行する必要がある場合、状態をシリアライズして後から再開できます。
JSON.stringify(result.state) を使って状態をシリアライズし、後から RunState.fromString(agent, serializedState) にシリアライズ済み状態を渡して再開できます。ここで agent は全体の実行をトリガーしたエージェントのインスタンスです。
この方法により、シリアライズした状態をデータベースやリクエストと一緒に保存できます。
保留タスクのバージョニング
Section titled “保留タスクのバージョニング”承認リクエストに長い時間がかかり、エージェント定義の有意義なバージョニングや Agents SDK のバージョン更新を行いたい場合は、パッケージエイリアスを用いて 2 つの Agents SDK バージョンを並行インストールし、独自の分岐ロジックを実装することを現時点では推奨します。
実務上は、独自のコードにバージョン番号を割り当て、それをシリアライズした状態と一緒に保存し、デシリアライズをコードの正しいバージョンへ誘導することを意味します。