에이전트 실행
에이전트는 스스로 아무것도 하지 않으며, Runner 클래스 또는 run() 유틸리티로 실행합니다
턴 실행, 이벤트 스트리밍, 대화 상태 관리를 하려면 에이전트 다음으로 이 페이지를 읽으세요. 에이전트를 어떻게 정의할지 아직 결정 중이라면 먼저 에이전트부터 시작하세요
import { Agent, run } from '@openai/agents';
const agent = new Agent({ name: 'Assistant', instructions: 'You are a helpful assistant',});
const result = await run( agent, 'Write a haiku about recursion in programming.',);console.log(result.finalOutput);
// Code within the code,// Functions calling themselves,// Infinite loop's dance.커스텀 runner가 필요 없다면, 기본 singleton Runner 인스턴스를 실행하는 run() 유틸리티를 사용할 수도 있습니다
또는 직접 runner 인스턴스를 만들 수도 있습니다
import { Agent, Runner } from '@openai/agents';
const agent = new Agent({ name: 'Assistant', instructions: 'You are a helpful assistant',});
// You can pass custom configuration to the runnerconst runner = new Runner();
const result = await runner.run( agent, 'Write a haiku about recursion in programming.',);console.log(result.finalOutput);
// Code within the code,// Functions calling themselves,// Infinite loop's dance.에이전트를 실행하면 최종 출력과 전체 실행 이력을 담은 실행 결과 객체를 받게 됩니다
Runner 수명 주기 및 설정
섹션 제목: “Runner 수명 주기 및 설정”에이전트 루프
섹션 제목: “에이전트 루프”Runner의 run 메서드를 사용할 때 시작 에이전트와 입력을 전달합니다. 입력은 문자열(사용자 메시지로 간주) 또는 OpenAI Responses API의 항목 목록일 수 있습니다
그다음 runner는 루프를 실행합니다
- 현재 입력으로 현재 에이전트의 모델을 호출
- LLM 응답 검사
- 최종 출력 → 반환
- 핸드오프 → 새 에이전트로 전환, 누적된 대화 이력 유지, 1로 이동
- 도구 호출 → 도구 실행, 결과를 대화에 추가, 1로 이동
maxTurns에 도달하면MaxTurnsExceededError발생
Runner 수명 주기
섹션 제목: “Runner 수명 주기”앱 시작 시 Runner를 만들고 요청 전반에 재사용하세요. 인스턴스는 모델 provider, 트레이싱 옵션 같은 전역 설정을 저장합니다. 완전히 다른 구성이 필요할 때만 다른 Runner를 만드세요. 단순 스크립트에서는 내부적으로 기본 runner를 사용하는 run()을 호출해도 됩니다
Run 인자
섹션 제목: “Run 인자”run() 메서드 입력은 실행을 시작할 초기 에이전트, 실행 입력, 옵션 집합입니다
입력은 문자열(사용자 메시지로 간주), input items 목록, 또는 휴먼 인 더 루프 (HITL) 에이전트를 만드는 경우 RunState 객체일 수 있습니다
추가 옵션은 다음과 같습니다
| Option | Default | Description |
|---|---|---|
stream | false | true이면 호출은 StreamedRunResult를 반환하고 모델에서 이벤트가 도착하는 대로 내보냅니다 |
context | – | 모든 도구 / 가드레일 / 핸드오프로 전달되는 컨텍스트 객체입니다. 자세한 내용은 컨텍스트 관리 가이드를 참고하세요 |
maxTurns | 10 | 안전 제한값입니다. 도달 시 MaxTurnsExceededError를 발생시킵니다 |
signal | – | 취소용 AbortSignal |
session | – | 세션 영속화 구현입니다. 세션 가이드를 참고하세요 |
sessionInputCallback | – | 세션 이력과 새 입력의 커스텀 병합 로직이며, 모델 호출 전에 실행됩니다. 세션을 참고하세요 |
callModelInputFilter | – | 모델 호출 직전에 모델 입력(항목 + 선택적 instructions)을 수정하는 훅입니다. Call model input filter를 참고하세요 |
toolErrorFormatter | – | 모델에 반환되는 도구 승인 거부 메시지를 커스터마이즈하는 훅입니다. Tool error formatter를 참고하세요 |
reasoningItemIdPolicy | – | 이전 실행 항목을 다시 모델 입력으로 변환할 때 reasoning-item id를 유지할지 생략할지 제어합니다. Reasoning item ID policy를 참고하세요 |
tracing | – | 실행별 트레이싱 설정 재정의(예: export API key) |
errorHandlers | – | 지원되는 런타임 오류(현재 maxTurns)용 핸들러입니다. Error handlers를 참고하세요 |
conversationId | – | 서버 측 대화를 재사용합니다(OpenAI Responses API + Conversations API 전용) |
previousResponseId | – | 대화를 생성하지 않고 이전 Responses API 호출에서 이어갑니다(OpenAI Responses API 전용) |
스트리밍
섹션 제목: “스트리밍”스트리밍을 사용하면 LLM 실행 중 스트리밍 이벤트를 추가로 받을 수 있습니다. 스트림이 시작되면 StreamedRunResult에 새로 생성된 모든 출력을 포함한 실행 전체 정보가 담깁니다. for await 루프로 스트리밍 이벤트를 순회할 수 있습니다. 자세한 내용은 스트리밍 가이드를 참고하세요
Run 설정
섹션 제목: “Run 설정”직접 Runner 인스턴스를 만들 때 runner를 설정하기 위해 RunConfig 객체를 전달할 수 있습니다
| Field | Type | Purpose |
|---|---|---|
model | string | Model | 실행 내 모든 에이전트에 특정 모델을 강제 적용 |
modelProvider | ModelProvider | 모델 이름을 해석하며 기본값은 OpenAI provider |
modelSettings | ModelSettings | 에이전트별 설정을 덮어쓰는 전역 튜닝 매개변수입니다. opt-in 재시도 설정을 포함한 자세한 내용은 모델 가이드를 참고하세요 |
handoffInputFilter | HandoffInputFilter | 핸드오프 수행 시 입력 항목을 변경합니다(핸드오프 자체에 이미 정의되어 있지 않은 경우) |
inputGuardrails | InputGuardrail[] | 초기 사용자 입력에 적용되는 가드레일 |
outputGuardrails | OutputGuardrail[] | 최종 출력에 적용되는 가드레일 |
tracingDisabled | boolean | OpenAI 트레이싱을 완전히 비활성화 |
traceIncludeSensitiveData | boolean | span은 유지하면서 트레이스에서 LLM/도구 입출력을 제외 |
workflowName | string | Traces 대시보드에 표시되어 관련 실행 그룹화에 도움 |
traceId / groupId | string | SDK가 생성하도록 두는 대신 trace/group ID를 수동 지정 |
traceMetadata | Record<string, string> | 모든 span에 붙일 임의 메타데이터 |
tracing | TracingConfig | 실행별 트레이싱 재정의(예: export API key) |
sessionInputCallback | SessionInputCallback | 이 runner의 모든 실행에 대한 기본 이력 병합 전략 |
callModelInputFilter | CallModelInputFilter | 각 모델 호출 전 모델 입력을 수정하는 전역 훅 |
toolErrorFormatter | ToolErrorFormatter | 모델에 반환되는 도구 승인 거부 메시지를 커스터마이즈하는 전역 훅 |
reasoningItemIdPolicy | ReasoningItemIdPolicy | 생성된 항목을 이후 모델 호출로 재생할 때 reasoning-item id를 유지/생략하는 기본 정책 |
상태 및 대화 관리
섹션 제목: “상태 및 대화 관리”메모리 전략 선택
섹션 제목: “메모리 전략 선택”다음 턴으로 상태를 전달하는 일반적인 방법은 네 가지입니다
| Strategy | Where state lives | Best for | What you pass on the next turn |
|---|---|---|---|
result.history | 앱 메모리 | 작은 채팅 루프, 완전 수동 제어, 모든 provider | result.history |
session | 저장소 + SDK | 영속 채팅 상태, 재개 가능한 실행, 커스텀 스토어 | 동일한 session 인스턴스(또는 store-backed 인스턴스) |
conversationId | OpenAI Conversations API | 워커/서비스 간 공유되는 서버 측 상태 | 동일한 conversationId와 새 사용자 턴만 |
previousResponseId | OpenAI Responses API 전용 | 대화를 만들지 않는 가장 단순한 서버 관리 연속성 | result.lastResponseId와 새 사용자 턴만 |
result.history와 session은 클라이언트 관리 방식입니다. conversationId와 previousResponseId는 OpenAI 관리 방식이며 OpenAI Responses API를 사용할 때만 적용됩니다. 대부분의 애플리케이션에서는 대화마다 하나의 영속화 전략을 선택하세요. 의도적으로 두 레이어를 조정하지 않으면 클라이언트 관리 이력과 서버 관리 상태를 섞을 때 컨텍스트가 중복될 수 있습니다
대화 / 채팅 스레드
섹션 제목: “대화 / 채팅 스레드”runner.run()(또는 run() 유틸리티) 한 번 호출은 애플리케이션 수준 대화의 한 턴을 의미합니다. RunResult 중 최종 사용자에게 얼마나 보여줄지는 직접 선택합니다. finalOutput만 보여줄 수도 있고 생성된 모든 항목을 보여줄 수도 있습니다
import { Agent, run } from '@openai/agents';import type { AgentInputItem } from '@openai/agents';
let thread: AgentInputItem[] = [];
const agent = new Agent({ name: 'Assistant',});
async function userSays(text: string) { const result = await run( agent, thread.concat({ role: 'user', content: text }), );
thread = result.history; // Carry over history + newly generated items return result.finalOutput;}
await userSays('What city is the Golden Gate Bridge in?');// -> "San Francisco"
await userSays('What state is it in?');// -> "California"대화형 버전은 chat 예제를 참고하세요
서버 관리 대화
섹션 제목: “서버 관리 대화”매 턴마다 로컬 대화 이력 전체를 보내는 대신 OpenAI Responses API가 대화 이력을 저장하도록 할 수 있습니다. 긴 대화나 여러 서비스를 조율할 때 유용합니다. 아래 두 서버 관리 방식 모두 요청마다 새 턴 입력만 전달하세요. API가 이전 상태를 재사용합니다. 자세한 내용은 Conversation state guide를 참고하세요
OpenAI는 서버 측 상태를 재사용하는 두 가지 방법을 제공합니다
1. 전체 대화용 conversationId
섹션 제목: “1. 전체 대화용 conversationId”Conversations API로 대화를 한 번 만들고 모든 턴에서 같은 ID를 재사용할 수 있습니다. SDK는 새로 생성된 항목만 자동으로 포함합니다
import { Agent, run } from '@openai/agents';import { OpenAI } from 'openai';
const agent = new Agent({ name: 'Assistant', instructions: 'Reply very concisely.',});
async function main() { // Create a server-managed conversation: const client = new OpenAI(); const { id: conversationId } = await client.conversations.create({});
const first = await run(agent, 'What city is the Golden Gate Bridge in?', { conversationId, }); console.log(first.finalOutput); // -> "San Francisco"
const second = await run(agent, 'What state is it in?', { conversationId }); console.log(second.finalOutput); // -> "California"}
main().catch(console.error);2. 마지막 턴에서 이어가는 previousResponseId
섹션 제목: “2. 마지막 턴에서 이어가는 previousResponseId”Responses API로만 시작하려면 이전 응답에서 반환된 ID로 각 요청을 체이닝할 수 있습니다. 전체 대화 리소스를 만들지 않고도 턴 간 컨텍스트를 유지합니다
import { Agent, run } from '@openai/agents';
const agent = new Agent({ name: 'Assistant', instructions: 'Reply very concisely.',});
async function main() { const first = await run(agent, 'What city is the Golden Gate Bridge in?'); console.log(first.finalOutput); // -> "San Francisco"
const previousResponseId = first.lastResponseId; const second = await run(agent, 'What state is it in?', { previousResponseId, }); console.log(second.finalOutput); // -> "California"}
main().catch(console.error);conversationId와 previousResponseId는 동시에 사용할 수 없습니다. 시스템 간 공유 가능한 이름 있는 대화 리소스가 필요하면 conversationId를, 한 응답에서 다음 응답으로 가장 저렴한 SDK 수준 연속성 primitive가 필요하면 previousResponseId를 사용하세요
훅 및 커스터마이징
섹션 제목: “훅 및 커스터마이징”Call model input filter
섹션 제목: “Call model input filter”모델 호출 직전 모델 입력을 수정하려면 callModelInputFilter를 사용하세요. 이 훅은 현재 에이전트, 컨텍스트, 결합된 입력 항목(세션 이력이 있으면 포함)을 받습니다. 민감 정보 마스킹, 오래된 메시지 제거, 추가 시스템 가이드 주입을 위해 업데이트된 input 배열과 선택적 instructions를 반환하세요
runner.run(..., { callModelInputFilter })로 실행별 설정을 하거나 Runner 설정(RunConfig의 callModelInputFilter)에서 기본값으로 설정할 수 있습니다
반환값은 ModelInputData 객체여야 합니다: { input: AgentInputItem[], instructions? }. input 필드는 필수이며 배열이어야 합니다. 다른 형태를 반환하면 UserError가 발생합니다
SDK는 필터 호출 전에 준비된 턴 입력을 복제합니다. session도 사용 중이라면 필터링된 복제본이 저장되므로, 여기서 적용한 마스킹/잘라내기는 저장된 세션 이력에도 반영됩니다
conversationId 또는 previousResponseId를 사용할 때 훅은 다음 Responses API 호출을 위한 준비된 payload에서 실행됩니다. 이전 서버 관리 컨텍스트는 API가 복원하므로, 해당 호출의 필터링 배열은 이전 이력 전체 재생이 아니라 새 턴 델타만 나타낼 수 있습니다. 최종 필터 단계 전에 저장된 이력과 현재 턴의 병합 방식을 바꾸려면 sessionInputCallback을 사용하세요
Tool error formatter
섹션 제목: “Tool error formatter”도구 호출이 거부될 때 모델로 되돌아가는 승인 거부 메시지를 커스터마이즈하려면 toolErrorFormatter를 사용하세요. SDK 기본 메시지 대신 도메인별 문구(예: 컴플라이언스 가이드)를 반환할 수 있습니다
포매터는 실행별(runner.run(..., { toolErrorFormatter })) 또는 전역(RunConfig의 toolErrorFormatter, new Runner(...))으로 설정할 수 있습니다
이 포매터는 승인 거부의 전역 fallback입니다. 특정 인터럽션을 result.state.reject(interruption, { message: '...' })로 거부하면 호출별 message가 toolErrorFormatter보다 우선합니다. 둘 다 없으면 SDK 기본 거부 문구 Tool execution was not approved.를 사용합니다
현재 포매터는 approval_rejected 이벤트에서 실행되며 다음을 받습니다
kind(현재 항상'approval_rejected')toolType('function','computer','shell', 또는'apply_patch')toolNamecallIddefaultMessage(SDK fallback 메시지, 현재Tool execution was not approved.)runContext
메시지를 덮어쓰려면 문자열을 반환하고, SDK 기본값을 유지하려면 undefined를 반환하세요. 포매터가 예외를 던지거나 문자열이 아닌 값을 반환하면 SDK는 경고를 기록하고 기본 승인 거부 메시지로 fallback합니다
Reasoning item ID 정책
섹션 제목: “Reasoning item ID 정책”SDK가 이전에 생성된 실행 항목을 이후 모델 입력용 AgentInputItem[]로 변환할 때 reasoning 항목의 id 필드를 유지할지 제어하려면 reasoningItemIdPolicy를 사용하세요
이는 SDK가 생성된 모델 항목을 입력으로 재생하는 다음 경우에 영향을 줍니다
- 동일 실행 내 후속 모델 호출(예: 도구 실행 후)
- 생성된 항목을 입력/이력으로 재사용하는 다음 턴
- 저장된
RunState에서 재개된 실행 result.history/result.output같은 파생 결과 뷰(모델 입력 형태 배열)'preserve'(기본값): reasoning 항목 ID 유지'omit': 입력으로 다시 보낼 때 reasoning 항목의id필드 제거- reasoning이 아닌 항목은 영향 없음
다음은 변경하지 않습니다
- 원시 모델 응답(
result.rawResponses) - 실행 항목(
result.newItems) - provider가 반환한 현재 턴 모델 출력
즉, 이 정책은 SDK가 이전 생성 항목으로 다음 입력을 구성할 때 적용됩니다
정책은 실행별(runner.run(..., { reasoningItemIdPolicy: 'omit' })) 또는 runner 기본값(new Runner({ reasoningItemIdPolicy: 'omit', ... }))으로 설정할 수 있습니다. 저장된 RunState에서 재개하면 재정의하지 않는 한 이전에 결정된 정책을 재사용합니다
callModelInputFilter와의 상호작용
섹션 제목: “callModelInputFilter와의 상호작용”reasoningItemIdPolicy는 callModelInputFilter보다 먼저 적용됩니다. 커스텀 동작이 필요하면 callModelInputFilter에서 준비된 입력을 검사한 뒤 모델 호출 전에 reasoning ID를 수동으로 다시 추가하거나 제거할 수 있습니다
'omit' 사용 시점
섹션 제목: “'omit' 사용 시점”재생된 reasoning 항목을 ID 없이 정규화하고 싶을 때 'omit'을 사용하세요(예: 전달/재생되는 모델 입력을 더 단순하게 유지하거나 앱 파이프라인 통합 요구사항에 맞추기 위해)
백엔드/provider가 재생된 reasoning 항목을 요청 검증 오류로 거부할 때도 유용한 문제 해결 옵션입니다(예: 후속 입력의 reasoning 항목 ID 관련 HTTP 400 오류). 이런 경우 'omit'으로 재생된 reasoning ID를 제거하면 백엔드가 새 요청에 유효하지 않다고 판단하는 ID 전송을 피할 수 있습니다
통합이 reasoning 항목 ID를 허용하고 재생 입력에서도 유지하고 싶다면 'preserve'를 유지하세요
오류 및 복구
섹션 제목: “오류 및 복구”오류 핸들러
섹션 제목: “오류 핸들러”지원되는 런타임 오류를 예외 발생 대신 최종 출력으로 변환하려면 errorHandlers를 사용하세요. 현재는 maxTurns만 지원됩니다
errorHandlers.maxTurns는 max-turn 오류만 처리errorHandlers.default는 지원되는 종류의 fallback으로 사용- 핸들러는
{ error, context, runData }를 받아{ finalOutput, includeInHistory? }를 반환 가능
SDK는 catch할 수 있는 소수의 오류를 발생시킵니다
MaxTurnsExceededError–maxTurns도달ModelBehaviorError– 모델이 유효하지 않은 출력 생성(예: 잘못된 JSON, 알 수 없는 도구)InputGuardrailTripwireTriggered/OutputGuardrailTripwireTriggered– 가드레일 위반ToolInputGuardrailTripwireTriggered/ToolOutputGuardrailTripwireTriggered– 도구 가드레일 위반GuardrailExecutionError– 가드레일 실행 실패ToolTimeoutError– 함수 도구가timeoutMs를 초과하고timeoutBehavior: 'raise_exception'사용ToolCallError– 함수 도구 실행이 타임아웃 외 오류로 실패UserError– 설정 또는 사용자 입력 기반으로 발생한 모든 오류
모두 기본 AgentsError 클래스를 확장하며, 현재 실행 상태에 접근할 수 있는 state 속성을 제공할 수 있습니다
아래는 GuardrailExecutionError를 처리하는 코드 예제입니다. 입력 가드레일은 첫 사용자 입력에서만 실행되므로 예제는 원래 입력과 컨텍스트로 실행을 재시작합니다. 또한 모델을 다시 호출하지 않고 출력 가드레일을 재시도하기 위해 저장된 상태를 재사용하는 방법도 보여줍니다
import { Agent, GuardrailExecutionError, InputGuardrail, InputGuardrailTripwireTriggered, OutputGuardrail, OutputGuardrailTripwireTriggered, run,} from '@openai/agents';import { z } from 'zod';
// Shared guardrail agent to avoid re-creating it on every fallback run.const guardrailAgent = new Agent({ name: 'Guardrail check', instructions: 'Check if the user is asking you to do their math homework.', outputType: z.object({ isMathHomework: z.boolean(), reasoning: z.string(), }),});
async function main() { const input = 'Hello, can you help me solve for x: 2x + 3 = 11?'; const context = { customerId: '12345' };
// Input guardrail example
const unstableInputGuardrail: InputGuardrail = { name: 'Math Homework Guardrail (unstable)', execute: async () => { throw new Error('Something is wrong!'); }, };
const fallbackInputGuardrail: InputGuardrail = { name: 'Math Homework Guardrail (fallback)', execute: async ({ input, context }) => { const result = await run(guardrailAgent, input, { context }); const isMathHomework = result.finalOutput?.isMathHomework ?? /solve for x|math homework/i.test(JSON.stringify(input)); return { outputInfo: result.finalOutput, tripwireTriggered: isMathHomework, }; }, };
const agent = new Agent({ name: 'Customer support agent', instructions: 'You are a customer support agent. You help customers with their questions.', inputGuardrails: [unstableInputGuardrail], });
try { // Input guardrails only run on the first turn of a run, so retries must start a fresh run. await run(agent, input, { context }); } catch (e) { if (e instanceof GuardrailExecutionError) { console.error(`Guardrail execution failed (input): ${e}`); try { agent.inputGuardrails = [fallbackInputGuardrail]; // Retry from scratch with the original input and context. await run(agent, input, { context }); } catch (ee) { if (ee instanceof InputGuardrailTripwireTriggered) { console.log('Math homework input guardrail tripped on retry'); } else { throw ee; } } } else { throw e; } }
// Output guardrail example
const replyOutputSchema = z.object({ reply: z.string() });
const unstableOutputGuardrail: OutputGuardrail<typeof replyOutputSchema> = { name: 'Answer review (unstable)', execute: async () => { throw new Error('Output guardrail crashed.'); }, };
const fallbackOutputGuardrail: OutputGuardrail<typeof replyOutputSchema> = { name: 'Answer review (fallback)', execute: async ({ agentOutput }) => { const outputText = typeof agentOutput === 'string' ? agentOutput : (agentOutput?.reply ?? JSON.stringify(agentOutput)); const flagged = /math homework|solve for x|x =/i.test(outputText); return { outputInfo: { flaggedOutput: outputText }, tripwireTriggered: flagged, }; }, };
const agent2 = new Agent<unknown, typeof replyOutputSchema>({ name: 'Customer support agent (output check)', instructions: 'You are a customer support agent. Answer briefly.', outputType: replyOutputSchema, outputGuardrails: [unstableOutputGuardrail], });
try { await run(agent2, input, { context }); } catch (e) { if (e instanceof GuardrailExecutionError && e.state) { console.error(`Guardrail execution failed (output): ${e}`); try { agent2.outputGuardrails = [fallbackOutputGuardrail]; // Output guardrails can be retried using the saved state without another model call. await run(agent2, e.state); } catch (ee) { if (ee instanceof OutputGuardrailTripwireTriggered) { console.log('Output guardrail tripped after retry with saved state'); } else { throw ee; } } } else { throw e; } }}
main().catch(console.error);입력 재시도 vs 출력 재시도
- 입력 가드레일은 실행의 첫 사용자 입력에서만 실행되므로 재시도하려면 동일 입력/컨텍스트로 새 실행을 시작해야 하며, 저장된
state를 전달해도 입력 가드레일이 다시 트리거되지 않음 - 출력 가드레일은 모델 응답 후 실행되므로
GuardrailExecutionError의 저장된state를 재사용해 추가 모델 호출 없이 출력 가드레일을 다시 실행 가능
위 예제를 실행하면 다음 출력이 표시됩니다
Guardrail execution failed (input): Error: Input guardrail failed to complete: Error: Something is wrong!Math homework input guardrail tripped on retryGuardrail execution failed (output): Error: Output guardrail failed to complete: Error: Output guardrail crashed.Output guardrail tripped after retry with saved state