휴먼 인 더 루프 (HITL)
이 가이드는 SDK에 내장된 휴먼인더루프 (HITL) 지원을 사용해 사람의 개입에 따라 에이전트 실행을 일시 중지하고 재개하는 방법을 보여줍니다.
현재 주요 사용 사례는 민감한 도구 실행에 대한 승인을 요청하는 것입니다.
승인 요청
섹션 제목: “승인 요청”needsApproval
옵션을 true
또는 boolean을 반환하는 async 함수로 설정하여 승인이 필요한 도구를 정의할 수 있습니다.
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단계부터 다시 시작됩니다.
아래는 터미널에서 승인을 요청하고 상태를 파일에 임시 저장하는 휴먼인더루프 (HITL) 플로우의 좀 더 완전한 예제입니다.
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 });});
동작하는 엔드 투 엔드 버전은 전체 예제 스크립트를 참고하세요.
긴 승인 시간 처리
섹션 제목: “긴 승인 시간 처리”휴먼인더루프 (HITL) 플로우는 서버를 계속 실행하지 않고도 오랜 시간 동안 인터럽션이 가능하도록 설계되었습니다. 요청을 종료하고 나중에 계속해야 한다면 상태를 직렬화하여 이후에 재개할 수 있습니다.
JSON.stringify(result.state)
를 사용해 상태를 직렬화하고, 나중에 직렬화된 상태를 RunState.fromString(agent, serializedState)
에 전달하여 재개할 수 있습니다. 여기서 agent
는 전체 실행을 트리거한 에이전트 인스턴스입니다.
이렇게 하면 직렬화된 상태를 데이터베이스나 요청과 함께 저장할 수 있습니다.
보류 중 작업의 버전 관리
섹션 제목: “보류 중 작업의 버전 관리”승인 요청에 오랜 시간이 걸리고 에이전트 정의를 의미 있게 버전 관리하거나 Agents SDK 버전을 올릴 계획이라면, 패키지 별칭을 사용해 두 가지 버전의 Agents SDK를 병렬로 설치하여 자체 분기 로직을 구현하는 것을 권장합니다.
실무적으로는 코드에 자체 버전 번호를 부여하고 이를 직렬화된 상태와 함께 저장한 다음, 역직렬화를 코드의 올바른 버전으로 안내하는 방식을 의미합니다.