콘텐츠로 이동

스트리밍

Agents SDK 는 모델과 기타 실행 단계의 출력을 점진적으로 전달할 수 있습니다. 스트리밍을 사용하면 UI 반응성을 유지하고, 전체 최종 결과를 기다리지 않고도 사용자에게 업데이트할 수 있습니다.

전체 결과 대신 스트리밍 객체를 얻으려면 Runner.run(){ stream: true } 옵션을 전달하세요:

스트리밍 활성화
import { Agent, run } from '@openai/agents';
const agent = new Agent({
name: 'Storyteller',
instructions:
'You are a storyteller. You will be given a topic and you will tell a story about it.',
});
const result = await run(agent, 'Tell me a story about a cat.', {
stream: true,
});

스트리밍이 활성화되면 반환되는 streamAsyncIterable 인터페이스를 구현합니다. 각 yield 이벤트는 실행 중 발생한 일을 설명하는 객체입니다. 스트림은 에이전트 실행의 서로 다른 부분을 설명하는 세 가지 이벤트 타입 중 하나를 yield 합니다. 다만 대부분의 애플리케이션은 모델의 텍스트만 필요하므로, 스트림은 이를 위한 헬퍼를 제공합니다.

stream.toTextStream() 을 호출해 출력된 텍스트의 스트림을 가져옵니다. compatibleWithNodeStreamstrue 이면 반환값은 일반 Node.js Readable 입니다. 이를 process.stdout 이나 다른 대상으로 직접 파이프할 수 있습니다.

도착하는 텍스트 바로 출력하기
import { Agent, run } from '@openai/agents';
const agent = new Agent({
name: 'Storyteller',
instructions:
'You are a storyteller. You will be given a topic and you will tell a story about it.',
});
const result = await run(agent, 'Tell me a story about a cat.', {
stream: true,
});
result
.toTextStream({
compatibleWithNodeStreams: true,
})
.pipe(process.stdout);

stream.completed 프라미스는 실행과 모든 대기 중인 콜백이 완료되면 resolve 됩니다. 더 이상 출력이 없음을 보장하려면 항상 이를 await 하세요. 여기에는 마지막 텍스트 토큰이 도착한 뒤 완료되는 세션 영속화 또는 히스토리 컴팩션 훅 같은 후처리 작업도 포함됩니다.

toTextStream() 은 어시스턴트 텍스트만 내보냅니다. 도구 호출, 핸드오프, 승인, 기타 런타임 이벤트는 전체 이벤트 스트림에서 확인할 수 있습니다.

for await 루프를 사용해 도착하는 각 이벤트를 확인할 수 있습니다. 하위 수준 모델 이벤트, 에이전트 전환, SDK 고유 실행 정보 등을 확인할 때 유용합니다:

모든 이벤트 수신하기
import { Agent, run } from '@openai/agents';
const agent = new Agent({
name: 'Storyteller',
instructions:
'You are a storyteller. You will be given a topic and you will tell a story about it.',
});
const result = await run(agent, 'Tell me a story about a cat.', {
stream: true,
});
for await (const event of result) {
// these are the raw events from the model
if (event.type === 'raw_model_stream_event') {
console.log(`${event.type} %o`, event.data);
}
// agent updated events
if (event.type === 'agent_updated_stream_event') {
console.log(`${event.type} %s`, event.agent.name);
}
// Agent SDK specific events
if (event.type === 'run_item_stream_event') {
console.log(`${event.type} %o`, event.item);
}
}

일반 텍스트 스트림과 원문 이벤트 스트림을 모두 출력하는 전체 스크립트는 스트리밍 예제를 참고하세요.

Responses WebSocket 전송 방식 (선택 사항)

섹션 제목: “Responses WebSocket 전송 방식 (선택 사항)”

이 페이지의 스트리밍 API 는 OpenAI Responses WebSocket 전송 방식에서도 동작합니다.

setOpenAIResponsesTransport('websocket') 으로 전역 활성화하거나, useResponsesWebSocket: true 를 사용한 사용자 정의 OpenAIProvider 를 사용하세요.

WebSocket 으로 스트리밍하기 위해 withResponsesWebSocketSession(...) 또는 사용자 정의 OpenAIProvider 가 반드시 필요한 것은 아닙니다. 실행 간 재연결을 허용할 수 있다면, 전송 방식을 활성화한 뒤에도 run() / Runner.run(..., { stream: true }) 를 계속 사용할 수 있습니다.

연결 재사용과 더 명시적인 provider 수명 주기 제어가 필요하면 withResponsesWebSocketSession(...) 또는 사용자 정의 OpenAIProvider / Runner 를 사용하세요.

previousResponseId 를 이용한 이어서 실행은 HTTP 전송 방식과 동일한 의미를 가집니다. 차이는 전송 방식과 연결 수명 주기뿐입니다.

provider 를 직접 구성했다면 종료 시 await provider.close() 를 호출해야 합니다. Websocket 기반 모델 래퍼는 기본적으로 재사용을 위해 캐시되며, provider 를 닫으면 해당 연결이 해제됩니다. withResponsesWebSocketSession(...) 은 같은 재사용 이점을 제공하면서 정리를 단일 콜백 범위로 자동 처리합니다.

스트리밍, 도구 호출, 승인, previousResponseId 를 포함한 전체 예제는 examples/basic/stream-ws.ts를 참고하세요.

스트림은 세 가지 이벤트 타입을 yield 합니다:

RunRawModelStreamEvent
import {
isOpenAIChatCompletionsRawModelStreamEvent,
isOpenAIResponsesRawModelStreamEvent,
type RunStreamEvent,
} from '@openai/agents';
export function logOpenAIRawModelEvent(event: RunStreamEvent) {
if (isOpenAIResponsesRawModelStreamEvent(event)) {
console.log(event.source);
console.log(event.data.event.type);
return;
}
if (isOpenAIChatCompletionsRawModelStreamEvent(event)) {
console.log(event.source);
console.log(event.data.event.object);
}
}

예시:

{
"type": "raw_model_stream_event",
"data": {
"type": "output_text_delta",
"delta": "Hello"
}
}

OpenAI provider 를 사용하는 경우, @openai/agents-openai@openai/agents 는 모두 agents-core 의 일반 RunRawModelStreamEvent 계약을 변경하지 않고 원문 OpenAI 페이로드를 좁혀주는 헬퍼를 내보냅니다.

OpenAI 원문 모델 이벤트 좁히기
import type { RunStreamEvent } from '@openai/agents';
import { isOpenAIResponsesRawModelStreamEvent } from '@openai/agents';
export function isOpenAIResponsesTextDelta(event: RunStreamEvent): boolean {
return (
isOpenAIResponsesRawModelStreamEvent(event) &&
event.data.event.type === 'response.output_text.delta'
);
}

전송 방식에 독립적인 스트리밍 코드만 필요하다면 event.type === 'raw_model_stream_event' 확인만으로도 충분합니다.

OpenAI 모델을 사용하면서 수동 캐스팅 없이 provider 고유 페이로드를 확인하고 싶다면 SDK 는 다음 좁히기 헬퍼도 제공합니다:

  • Responses 원문 이벤트용 isOpenAIResponsesRawModelStreamEvent(event)
  • Chat Completions 청크용 isOpenAIChatCompletionsRawModelStreamEvent(event)

이러한 OpenAI 모델 이벤트에서는 RunRawModelStreamEvent.source'openai-responses' 또는 'openai-chat-completions' 중 하나로 채워집니다.

이는 TypeScript 가 기본 이벤트 형태를 인지한 상태를 유지하면서 response.reasoning_summary_text.delta, response.output_item.done, MCP 인자 델타 같은 Responses 전용 이벤트를 확인하려는 경우 특히 유용합니다.

더 풍부한 OpenAI 전용 스트리밍 패턴은 examples/basic/stream-ws.ts, examples/tools/code-interpreter.ts, examples/connectors/index.ts를 참고하세요.

RunItemStreamEvent
import type { RunItemStreamEvent, RunStreamEvent } from '@openai/agents';
export function isRunItemStreamEvent(
event: RunStreamEvent,
): event is RunItemStreamEvent {
return event.type === 'run_item_stream_event';
}

name 은 어떤 종류의 항목이 생성되었는지 식별합니다:

name의미
message_output_created메시지 출력 항목이 생성되었습니다.
handoff_requested모델이 핸드오프를 요청했습니다.
handoff_occurred런타임이 다른 에이전트로의 핸드오프를 완료했습니다.
tool_search_calledtool_search_call 항목이 출력되었습니다.
tool_search_output_created로드된 도구 정의를 포함한 tool_search_output 항목이 출력되었습니다.
tool_called도구 호출 항목이 출력되었습니다.
tool_output도구 결과 항목이 출력되었습니다.
reasoning_item_created추론 항목이 출력되었습니다.
tool_approval_requested도구 호출이 사람 승인 대기 상태로 일시 중지되었습니다.

tool_search_* 이벤트는 실행 중 지연 도구를 로드하기 위해 toolSearchTool() 을 사용하는 Responses 실행에서만 나타납니다.

핸드오프 페이로드 예시:

{
"type": "run_item_stream_event",
"name": "handoff_occurred",
"item": {
"type": "handoff_call",
"id": "h1",
"status": "completed",
"name": "transfer_to_refund_agent"
}
}
RunAgentUpdatedStreamEvent
import type {
RunAgentUpdatedStreamEvent,
RunStreamEvent,
} from '@openai/agents';
export function isRunAgentUpdatedStreamEvent(
event: RunStreamEvent,
): event is RunAgentUpdatedStreamEvent {
return event.type === 'agent_updated_stream_event';
}

예시:

{
"type": "agent_updated_stream_event",
"agent": {
"name": "Refund Agent"
}
}

스트리밍 중 휴먼인더루프 (HITL)

섹션 제목: “스트리밍 중 휴먼인더루프 (HITL)”

스트리밍은 실행을 일시 중지하는 핸드오프(예: 도구 승인 필요)와 호환됩니다. 스트림 객체의 interruptions 필드는 대기 중인 승인을 노출하며, 각각에 대해 state.approve() 또는 state.reject() 를 호출해 실행을 계속할 수 있습니다. 스트림이 일시 중지되면 stream.completed 가 resolve 되고, stream.interruptions 에 처리할 승인이 담깁니다. 이후 { stream: true } 로 다시 실행하면 스트리밍 출력이 재개됩니다.

스트리밍 중 사람 승인 처리하기
import { Agent, run } from '@openai/agents';
const agent = new Agent({
name: 'Storyteller',
instructions:
'You are a storyteller. You will be given a topic and you will tell a story about it.',
});
let stream = await run(
agent,
'What is the weather in San Francisco and Oakland?',
{ stream: true },
);
stream.toTextStream({ compatibleWithNodeStreams: true }).pipe(process.stdout);
await stream.completed;
while (stream.interruptions?.length) {
console.log(
'Human-in-the-loop: approval required for the following tool calls:',
);
const state = stream.state;
for (const interruption of stream.interruptions) {
const approved = confirm(
`Agent ${interruption.agent.name} would like to use the tool ${interruption.name} with "${interruption.arguments}". Do you approve?`,
);
if (approved) {
state.approve(interruption);
} else {
state.reject(interruption);
}
}
// Resume execution with streaming output
stream = await run(agent, state, { stream: true });
const textStream = stream.toTextStream({ compatibleWithNodeStreams: true });
textStream.pipe(process.stdout);
await stream.completed;
}

사용자와 상호작용하는 더 자세한 예제는 human-in-the-loop-stream.ts입니다.

  • 모든 출력이 플러시되도록 종료 전에 stream.completed 를 기다리세요
  • 초기 { stream: true } 옵션은 해당 호출에만 적용됩니다. RunState 로 다시 실행할 때는 옵션을 다시 지정해야 합니다
  • 애플리케이션이 텍스트 결과만 필요하다면 개별 이벤트 객체를 다루지 않도록 toTextStream() 사용을 권장합니다

스트리밍과 이벤트 시스템을 사용하면 에이전트를 채팅 인터페이스, 터미널 애플리케이션, 또는 점진적 업데이트가 유용한 모든 환경에 통합할 수 있습니다.