콘텐츠로 이동

에이전트 실행

에이전트는 스스로 아무것도 하지 않으며, Runner 클래스나 run() 유틸리티로 실행해야 합니다.

Simple 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 가 필요하지 않다면, 싱글턴 기본 Runner 인스턴스를 실행하는 run() 유틸리티를 사용할 수도 있습니다.

또는 자체 runner 인스턴스를 만들 수도 있습니다:

Simple run
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 runner
const 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 의 run 메서드를 사용할 때는 시작 에이전트와 입력을 전달합니다. 입력은 문자열(사용자 메시지로 간주)일 수도 있고, OpenAI Responses API 의 항목인 입력 항목 목록일 수도 있습니다.

그다음 runner 는 다음 루프를 실행합니다:

  1. 현재 입력으로 현재 에이전트의 모델을 호출합니다
  2. LLM 응답을 검사합니다
    • 최종 출력 → 반환
    • 핸드오프 → 새 에이전트로 전환, 누적된 대화 기록 유지, 1로 이동
    • 도구 호출 → 도구 실행, 결과를 대화에 추가, 1로 이동
  3. maxTurns 에 도달하면 MaxTurnsExceededError 를 발생시킵니다

앱이 시작될 때 Runner 를 생성하고 요청 전반에서 재사용하세요. 이 인스턴스는 모델 provider 및 트레이싱 옵션 같은 전역 설정을 저장합니다. 완전히 다른 설정이 필요할 때만 다른 Runner 를 만드세요. 단순 스크립트에서는 내부적으로 기본 runner 를 사용하는 run() 을 호출해도 됩니다.

run() 메서드의 입력은 실행을 시작할 초기 에이전트, 실행 입력, 그리고 옵션 집합입니다.

입력은 문자열(사용자 메시지로 간주), input items 목록, 또는 휴먼 인 더 루프 (HITL) 에이전트를 구축하는 경우 RunState 객체가 될 수 있습니다.

추가 옵션은 다음과 같습니다:

OptionDefaultDescription
streamfalsetrue 이면 호출이 StreamedRunResult 를 반환하고 모델에서 이벤트가 도착하는 대로 방출합니다
context모든 도구 / 가드레일 / 핸드오프로 전달되는 컨텍스트 객체입니다. 컨텍스트 관리 가이드에서 자세히 알아보세요
maxTurns10안전 제한입니다. 도달 시 MaxTurnsExceededError 를 발생시킵니다
signal취소용 AbortSignal
session세션 지속성 구현입니다. 세션 가이드를 참고하세요
sessionInputCallback세션 기록과 새 입력을 병합하는 커스텀 로직이며, 모델 호출 전에 실행됩니다. 세션을 참고하세요
callModelInputFilter모델 호출 직전에 모델 입력(항목 + 선택적 instructions)을 수정하는 hook 입니다. Call model input filter를 참고하세요
toolErrorFormatter모델에 반환되는 도구 승인 거부 메시지를 커스터마이즈하는 hook 입니다. 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 루프로 스트리밍 이벤트를 순회할 수 있습니다. 자세한 내용은 스트리밍 가이드를 참고하세요.

자체 Runner 인스턴스를 생성하는 경우 runner 설정을 위해 RunConfig 객체를 전달할 수 있습니다.

FieldTypePurpose
modelstring | Model실행의 모든 에이전트에 특정 모델을 강제합니다
modelProviderModelProvider모델 이름을 해석하며 기본값은 OpenAI provider 입니다
modelSettingsModelSettings에이전트별 설정을 덮어쓰는 전역 튜닝 매개변수
handoffInputFilterHandoffInputFilter핸드오프 수행 시 입력 항목을 변경합니다(핸드오프 자체에 이미 정의되어 있지 않은 경우)
inputGuardrailsInputGuardrail[]초기 사용자 입력에 적용되는 가드레일
outputGuardrailsOutputGuardrail[]최종 출력에 적용되는 가드레일
tracingDisabledbooleanOpenAI Tracing 을 완전히 비활성화합니다
traceIncludeSensitiveDataboolean스팬은 계속 방출하되 트레이스에서 LLM/도구 입력 및 출력을 제외합니다
workflowNamestringTraces 대시보드에 표시되며 관련 실행 그룹화에 도움이 됩니다
traceId / groupIdstringSDK 자동 생성 대신 trace 또는 group ID 를 수동 지정합니다
traceMetadataRecord<string, string>모든 스팬에 첨부할 임의 메타데이터
tracingTracingConfig실행별 트레이싱 오버라이드(예: export API key)
sessionInputCallbackSessionInputCallback이 runner 의 모든 실행에 대한 기본 기록 병합 전략
callModelInputFilterCallModelInputFilter각 모델 호출 전 모델 입력을 수정하는 전역 hook
toolErrorFormatterToolErrorFormatter모델에 반환되는 도구 승인 거부 메시지를 커스터마이즈하는 전역 hook
reasoningItemIdPolicyReasoningItemIdPolicy생성된 항목을 이후 모델 호출로 재생할 때 reasoning-item id 를 유지하거나 생략하는 기본 정책

runner.run()(또는 run() 유틸리티) 호출 한 번은 애플리케이션 수준 대화에서 한 을 나타냅니다. RunResult 중 최종 사용자에게 얼마나 보여줄지는 직접 결정합니다. finalOutput 만 보여줄 수도 있고, 생성된 모든 항목을 보여줄 수도 있습니다.

Example of carrying over the conversation history
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"

대화형 버전은 채팅 예제를 참고하세요.

매 턴마다 전체 로컬 대화록을 보내는 대신 OpenAI Responses API 가 대화 기록을 유지하도록 할 수 있습니다. 긴 대화나 여러 서비스를 조율할 때 유용합니다. 자세한 내용은 Conversation state guide를 참고하세요.

OpenAI 는 서버 측 상태를 재사용하는 두 가지 방법을 제공합니다:

Conversations API 로 대화를 한 번 생성한 뒤, 매 턴마다 해당 ID 를 재사용할 수 있습니다. SDK 는 새로 생성된 항목만 자동으로 포함합니다.

Reusing a server conversation
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 로 각 요청을 체이닝할 수 있습니다. 전체 conversation 리소스를 만들지 않고도 턴 간 컨텍스트를 유지합니다.

Chaining with previousResponseId
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);

모델 호출 직전 에 모델 입력을 수정하려면 callModelInputFilter 를 사용하세요. 이 hook 은 현재 에이전트, 컨텍스트, 결합된 입력 항목(세션 기록 포함 시 포함)을 받습니다. 민감 데이터 마스킹, 오래된 메시지 제거, 추가 시스템 가이드 삽입을 위해 업데이트된 input 배열과 선택적 instructions 를 반환하세요.

runner.run(..., { callModelInputFilter }) 로 실행별 설정하거나, Runner 설정(RunConfigcallModelInputFilter)에서 기본값으로 설정할 수 있습니다.

도구 호출이 거부되었을 때 모델로 다시 보내는 승인 거부 메시지를 커스터마이즈하려면 toolErrorFormatter 를 사용하세요. SDK 기본 메시지 대신 도메인 특화 문구(예: 컴플라이언스 가이드)를 반환할 수 있습니다.

formatter 는 실행별(runner.run(..., { toolErrorFormatter })) 또는 전역(RunConfigtoolErrorFormatter in new Runner(...))으로 설정할 수 있습니다.

현재 formatter 는 approval_rejected 이벤트에서 실행되며 다음을 받습니다:

  • kind (현재 항상 'approval_rejected')
  • toolType ('function', 'computer', 'shell', 또는 'apply_patch')
  • toolName
  • callId
  • defaultMessage (SDK fallback 메시지)
  • runContext

메시지를 덮어쓰려면 문자열을 반환하고, SDK 기본값을 유지하려면 undefined 를 반환하세요. formatter 가 예외를 던지거나(또는 문자열이 아닌 값을 반환하면) SDK 는 경고를 기록하고 기본 승인 거부 메시지로 대체합니다.

SDK 가 이전에 생성된 실행 항목을 이후 모델 입력용 AgentInputItem[] 로 다시 변환할 때, reasoning 항목의 id 필드를 유지할지 제어하려면 reasoningItemIdPolicy 를 사용하세요.

이는 SDK 가 생성된 모델 항목을 입력으로 재생하는 다음 경우에 영향을 줍니다:

  • 같은 실행 내 후속 모델 호출(예: 도구 실행 후)
  • 생성된 항목을 입력/기록으로 재사용하는 이후 턴
  • 저장된 RunState 에서 재개된 실행
  • result.history / result.output 같은 파생 결과 뷰(모델 입력 형태 배열)
  • 'preserve' (기본값)는 reasoning-item ID 를 유지합니다
  • 'omit' 은 입력으로 다시 보내기 전에 reasoning 항목에서 id 필드를 제거합니다
  • reasoning 이 아닌 항목은 영향이 없습니다

이 정책으로 변경되지 않는 항목:

  • 원문 모델 응답(result.rawResponses)
  • 실행 항목(result.newItems)
  • provider 가 반환한 모델의 현재 턴 출력

즉, 이 정책은 SDK 가 이전 생성 항목으로 다음 입력을 구성할 때 적용됩니다.

정책은 실행별(runner.run(..., { reasoningItemIdPolicy: 'omit' })) 또는 runner 기본값(new Runner({ reasoningItemIdPolicy: 'omit', ... }))으로 설정할 수 있습니다. 저장된 RunState 에서 재개할 때는 override 하지 않으면 이전에 해석된 정책이 재사용됩니다.

reasoningItemIdPolicycallModelInputFilter 보다 먼저 적용됩니다. 커스텀 동작이 필요하면 callModelInputFilter 에서 준비된 입력을 검사한 뒤 모델 호출 전에 reasoning ID 를 수동으로 다시 넣거나 제거할 수 있습니다.

재생된 reasoning 항목을 ID 없이 정규화하고 싶을 때 'omit' 을 사용하세요(예: 전달/재생되는 모델 입력을 단순하게 유지하거나 앱 파이프라인 통합 요구사항에 맞추기 위해).

백엔드/provider 가 재생된 reasoning 항목을 요청 검증 오류(예: 후속 입력의 reasoning item ID 관련 HTTP 400 오류)로 거부할 때도 유용한 문제 해결 옵션입니다. 이런 경우 'omit' 으로 재생 reasoning ID 를 제거하면 백엔드가 새 요청에서 유효하지 않다고 판단하는 ID 전송을 피할 수 있습니다.

재생 입력에서도 reasoning-item ID 를 유지하고 통합 환경이 이를 허용한다면 'preserve' 를 유지하세요.

지원되는 런타임 오류를 throw 대신 최종 출력으로 변환하려면 errorHandlers 를 사용하세요. 현재는 maxTurns 만 지원됩니다.

  • errorHandlers.maxTurns 는 max-turn 오류만 처리합니다
  • errorHandlers.default 는 지원되는 kind 에 대한 fallback 으로 사용됩니다
  • 핸들러는 { error, context, runData } 를 받고 { finalOutput, includeInHistory? } 를 반환할 수 있습니다

SDK 는 catch 가능한 소수의 오류를 발생시킵니다:

모든 오류는 기본 AgentsError 클래스를 확장하며, 현재 실행 상태에 접근할 수 있도록 state 속성을 제공할 수 있습니다.

다음은 GuardrailExecutionError 를 처리하는 코드 예제입니다. 입력 가드레일은 첫 사용자 입력에서만 실행되므로, 예제에서는 원래 입력과 컨텍스트로 실행을 다시 시작합니다. 또한 저장된 상태를 재사용해 모델을 다시 호출하지 않고 출력 가드레일을 재시도하는 방법도 보여줍니다:

Guardrail execution error
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 retry
Guardrail execution failed (output): Error: Output guardrail failed to complete: Error: Output guardrail crashed.
Output guardrail tripped after retry with saved state