콘텐츠로 이동

에이전트 실행

에이전트는 스스로 아무 작업도 하지 않습니다. 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)을 편집하는 훅입니다. 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 루프로 스트리밍 이벤트를 순회할 수 있습니다. 자세한 내용은 스트리밍 가이드를 참고하세요.

직접 Runner 인스턴스를 만드는 경우, runner 를 설정하기 위해 RunConfig 객체를 전달할 수 있습니다.

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

다음 턴으로 상태를 전달하는 일반적인 방법은 네 가지입니다:

StrategyWhere state livesBest forWhat you pass on the next turn
result.history앱 메모리작은 채팅 루프, 완전한 수동 제어, 모든 providerresult.history
session사용자 스토리지 + SDK영속적인 채팅 상태, 재개 가능한 실행, 커스텀 스토어동일한 session 인스턴스(또는 스토어 기반 인스턴스)
conversationIdOpenAI Conversations API워커/서비스 간 공유되는 서버 측 상태동일한 conversationId 와 새 사용자 턴만
previousResponseIdOpenAI Responses API 전용대화를 만들지 않는 가장 단순한 서버 관리형 이어가기result.lastResponseId 와 새 사용자 턴만

result.historysession 은 클라이언트 관리형입니다. conversationIdpreviousResponseId 는 OpenAI 관리형이며 OpenAI Responses API 를 사용할 때만 적용됩니다. 대부분의 애플리케이션에서는 대화마다 하나의 지속성 전략을 선택하세요. 의도적으로 두 계층을 조정하는 경우가 아니라면 클라이언트 관리형 이력과 서버 관리형 상태를 섞으면 컨텍스트가 중복될 수 있습니다.

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"

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

매 턴마다 전체 로컬 대화 이력을 보내는 대신, OpenAI Responses API 가 대화 이력을 대신 저장하도록 할 수 있습니다. 이는 긴 대화나 여러 서비스를 조율할 때 유용합니다. 아래의 두 서버 관리형 접근 방식 모두에서 각 요청마다 새 턴의 입력만 전달하면 됩니다. 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 를 사용해 각 요청을 연결할 수 있습니다. 이렇게 하면 전체 대화 리소스를 만들지 않고도 턴 간 컨텍스트를 유지할 수 있습니다.

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);

conversationIdpreviousResponseId 는 함께 사용할 수 없습니다. 시스템 간에 공유할 수 있는 이름 있는 대화 리소스가 필요하면 conversationId 를, 한 응답에서 다음 응답으로 이어지는 가장 저렴한 SDK 수준 이어가기 기본 요소만 원하면 previousResponseId 를 사용하세요.

callModelInputFilter 를 사용하면 모델이 호출되기 직전 에 모델 입력을 편집할 수 있습니다. 이 훅은 현재 에이전트, 컨텍스트, 그리고 결합된 입력 항목(session 이 있으면 세션 이력 포함)을 받습니다. 민감한 데이터를 가리거나, 오래된 메시지를 제거하거나, 추가 시스템 가이드를 주입하려면 업데이트된 input 배열과 선택적 instructions 를 반환하세요.

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

반환값은 반드시 ModelInputData 객체여야 합니다: { input: AgentInputItem[], instructions? }. input 필드는 필수이며 배열이어야 합니다. 다른 형태를 반환하면 UserError 가 발생합니다.

SDK 는 필터를 호출하기 전에 준비된 턴 입력을 복제합니다. session 도 함께 사용 중이라면 필터링된 복제본이 저장되므로, 여기서 적용한 마스킹이나 잘라내기는 저장된 세션 이력에도 반영됩니다.

conversationId 또는 previousResponseId 를 사용할 때 이 훅은 다음 Responses API 호출을 위한 준비된 payload 에서 실행됩니다. 이전 서버 관리형 컨텍스트는 API 가 복원하므로, 해당 호출에 대한 필터링 배열은 이미 이전 이력 전체 재생이 아니라 새 턴의 delta 만 나타낼 수 있습니다. 이 최종 필터 단계 전에 저장된 이력과 현재 턴이 병합되는 방식을 바꾸고 싶다면 sessionInputCallback 을 사용하세요.

toolErrorFormatter 를 사용하면 도구 호출이 거부되었을 때 모델로 다시 전송되는 승인 거부 메시지를 커스터마이즈할 수 있습니다. 이를 통해 SDK 기본 메시지 대신 도메인별 표현(예: 컴플라이언스 가이드)을 반환할 수 있습니다.

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

이 formatter 는 승인 거부에 대한 전역 fallback 입니다. 특정 인터럽션을 result.state.reject(interruption, { message: '...' }) 로 거부하면, 호출별 messagetoolErrorFormatter 보다 우선합니다. 둘 다 제공되지 않으면 SDK 는 기본 거부 텍스트인 Tool execution was not approved. 를 사용합니다.

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

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

메시지를 재정의하려면 문자열을 반환하고, SDK 기본값을 유지하려면 undefined 를 반환하세요. formatter 가 예외를 발생시키거나(또는 문자열이 아닌 값을 반환하면) SDK 는 경고를 기록하고 기본 승인 거부 메시지로 fallback 합니다.

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

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

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

이 설정으로 변경되지 않는 것:

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

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

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

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

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

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

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

errorHandlers 를 사용하면 지원되는 런타임 오류를 throw 하지 않고 최종 출력으로 변환할 수 있습니다. 현재는 maxTurns 만 지원됩니다.

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

SDK 는 잡아서 처리할 수 있는 소수의 오류를 발생시킵니다:

모두 기본 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

  • 실행하기 전에 에이전트를 정의하려면 에이전트
  • finalOutput, 실행 항목, 인터럽션(중단 처리), 재개 상태는 실행 결과
  • 영속적인 SDK 관리형 메모리는 세션
  • 실행 루프 중 사용되는 기능은 도구
  • provider 설정과 Responses 전송은 모델
  • 프로덕션 준비를 위해 가드레일 또는 트레이싱 추가