Realtime 번역

Realtime 번역 (Realtime translation)

Realtime 번역은 전용 번역 세션에 원본 오디오를 스트리밍해서, 화자가 말하는 동안 바로 번역된 오디오와 대본 델타(transcript deltas)를 받을 수 있는 기능이에요. 실시간 통역, 다국어 통화, 방송, 회의, 강의, 화상 회의실에 쓰면 돼요.

출처: 문서

본문

gpt-realtime-translate는 앱이 사람이 말한 내용을 번역해야 할 때 쓰세요. 질문에 답하고, 도구를 호출하며, 대화를 관리하는 어시스턴트가 필요하면 표준 Realtime 세션으로 gpt-realtime-2.1을 쓰세요.

번역 세션은 어떻게 다른가요

Realtime 번역 세션은 음성 에이전트 세션과 다른 아키텍처를 사용해요.

음성 에이전트 세션 번역 세션
/v1/realtime에 연결해요. /v1/realtime/translations에 연결해요.
모델이 어시스턴트로 동작해요. 모델이 통역사로 동작해요.
대화·응답 수명주기를 사용해요. 들어오는 오디오에서 연속적으로 스트리밍해요.
도구를 호출하고 어시스턴트 턴을 만들 수 있어요. 번역된 오디오와 대본 델타를 만들어요.
response.create를 호출할 수 있어요. response.create를 호출하지 않아요.

번역은 오디오 스트림 자체에서 시작돼요. 문구 사이의 침묵을 포함해 오디오를 계속 추가하고, 도착하는 출력 이벤트를 처리하면 돼요.

전송 방식 고르기

브라우저가 오디오를 캡처하거나 재생할 때는 WebRTC를 쓰세요. WebRTC는 원본 오디오를 미디어 트랙으로 보내고 번역된 음성을 원격 오디오 트랙으로 받으므로, PCM 청크를 수동으로 리샘플링하거나 재생할 필요가 없어요.

서버가 이미 원시 오디오를 받을 때는 WebSockets을 쓰세요. 예: Twilio Media Streams, SIP 미디어, 방송 수집(ingest), 미디어 워커. WebSockets에서는 base64로 인코딩된 24 kHz PCM16 오디오를 보내고, 반환되는 오디오 델타를 직접 재생해요.

브라우저 WebRTC 세션 만들기

브라우저 앱에서는 서버에서 수명이 짧은 client secret을 만들어요. 브라우저에 표준 API 키를 노출하지 마세요.

app.post("/session", async (req, res) => {
  const language = req.body.targetLanguage ?? "es";

  const response = await fetch(
    "https://api.openai.com/v1/realtime/translations/client_secrets",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
        "Content-Type": "application/json",
        "OpenAI-Safety-Identifier": "hashed-user-id",
      },
      body: JSON.stringify({
        session: {
          model: "gpt-realtime-translate",
          audio: {
            output: { language },
          },
        },
      }),
    }
  );

  res.status(response.status).json(await response.json());
});

브라우저에서는 오디오를 캡처하고 peer connection을 만들고 SDP offer를 번역 호출 엔드포인트에 POST해요.

const { value: clientSecret } = await fetch("/session", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ targetLanguage: "es" }),
}).then((response) => response.json());

const sourceStream = await navigator.mediaDevices.getUserMedia({
  audio: true,
});

const pc = new RTCPeerConnection();
pc.addTrack(sourceStream.getAudioTracks()[0], sourceStream);

const translatedAudio = new Audio();
translatedAudio.autoplay = true;
pc.ontrack = ({ streams }) => {
  translatedAudio.srcObject = streams[0];
};

const events = pc.createDataChannel("oai-events");
events.onmessage = ({ data }) => {
  const event = JSON.parse(data);
  if (event.type === "session.output_transcript.delta") {
    subtitles.textContent += event.delta;
  }
};

const offer = await pc.createOffer();
await pc.setLocalDescription(offer);

const sdpResponse = await fetch(
  "https://api.openai.com/v1/realtime/translations/calls",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${clientSecret}`,
      "Content-Type": "application/sdp",
    },
    body: offer.sdp,
  }
);

if (!sdpResponse.ok) {
  throw new Error(await sdpResponse.text());
}

await pc.setRemoteDescription({
  type: "answer",
  sdp: await sdpResponse.text(),
});

WebSocket 세션 만들기

전용 번역 엔드포인트에 연결하고 URL에서 모델을 선택해요. 예제를 실행하기 전에 Node.js용 ws, Python용 websocket-client, Ruby용 async-websocket(gem install async-websocket)을 설치하세요.

import os
import websocket

ws = websocket.WebSocket()
ws.connect(
    "wss://api.openai.com/v1/realtime/translations?model=gpt-realtime-translate",
    header=[
        f"Authorization: Bearer ${os.environ['OPENAI_API_KEY']}",
        "OpenAI-Safety-Identifier: hashed-user-id",
    ],
)

소켓이 열린 뒤 대상 언어를 구성해요. 오디오를 보내고 번역 이벤트를 받는 동안 연결을 열어 둡니다.

import json

ws.send(
    json.dumps(
        {
            "type": "session.update",
            "session": {
                "audio": {
                    "output": {
                        "language": "es",
                    },
                },
            },
        }
    )
)

그다음 오디오를 계속 추가해요.

ws.send(
    json.dumps(
        {
            "type": "session.input_audio_buffer.append",
            "audio": base64_pcm16,
        }
    )
)

번역된 오디오와 대본을 들어요. session.output_audio.delta는 번역된 오디오(PCM16)를, session.output_transcript.delta는 번역 대본을, session.input_transcript.delta는 원본 대본을 제공해요.

while True:
    event = json.loads(ws.recv())

    if event["type"] == "session.output_audio.delta":
        play_pcm16(event["delta"])

    if event["type"] == "session.output_transcript.delta":
        print(event["delta"], end="", flush=True)

    if event["type"] == "session.input_transcript.delta":
        update_source_transcript(event["delta"])

WebSocket 세션 닫기

원본 스트림이 끝나면 WebSocket을 닫기 전에 session.close 이벤트를 보내요. 이 이벤트는 서비스가 보류 중인 입력 오디오를 비우고, 남은 번역 오디오·대본 출력을 내보낸 뒤 session.closed 이벤트를 보내도록 해요. session.close 이벤트는 번역 세션에서만 지원돼요.

session.close를 보낸 뒤에는 오디오 추가를 멈추고, session.closed를 받을 때까지 일반 수신 루프에서 이벤트를 계속 읽어요. 소켓을 즉시 닫으면 세션에서 아직 빠져나오는 번역 출력을 놓칠 수 있어요.

translation_session_closing = False


def close_translation_session():
    global translation_session_closing
    if translation_session_closing:
        return

    translation_session_closing = True
    ws.send(json.dumps({"type": "session.close"}))


# Call this when the source stream ends.
close_translation_session()

while True:
    event = json.loads(ws.recv())

    if event["type"] == "session.output_audio.delta":
        play_pcm16(event["delta"])

    if event["type"] == "session.output_transcript.delta":
        print(event["delta"], end="", flush=True)

    if event["type"] == "session.input_transcript.delta":
        update_source_transcript(event["delta"])

    if event["type"] == "session.closed":
        ws.close()
        break

Listen-along 번역 만들기

한 명의 발표자나 스트림의 번역 오디오가 청중에게 필요할 때 listen-along 번역을 쓰세요. 라이브스트림, 컨퍼런스 강연, 웨비나, 실적 발표, 강의, 영상이 그 예시예요.

source audio -> translation session -> translated audio + subtitles

대상 언어마다 번역 세션을 하나씩 만들어요. 같은 영어 원본에서 스페인어와 프랑스어가 필요하면 영어→스페인어 세션 하나, 영어→프랑스어 세션 하나를 만들면 돼요. 브라우저 listen-along 앱에서는 getDisplayMedia()로 탭 오디오를 캡처해 WebRTC로 보내고, 원격 번역 오디오 트랙을 재생해요. 프로덕션 방송에서는 서버 미디어 워커에서 번역을 실행하고 번역 오디오 트랙이나 캡션을 청취자에게 게시해요.

대화형 번역 만들기

둘 이상의 참여자가 다른 언어로 말할 때 대화형 번역을 쓰세요. 지원 통화, 영업 통화, 튜터링, 화상 회의실이 그 예시예요.

참여자 오디오 트랙을 분리해서 유지하세요. 화자를 한 스트림으로 섞으면 화자 식별, 화자 캡션, 겹치는 음성 처리가 어려워져요. 두 사람 통화에서는 방향별로 번역 세션을 하나씩 만들어요.

Caller A audio -> translate into Caller B language -> play to Caller B
Caller B audio -> translate into Caller A language -> play to Caller A

그룹 회의실에서는 세션 수가 활성 발화자와 대상 언어에 따라 달라져요.

translation sessions ~= active source speaker tracks x distinct target languages

작은 회의실에서는 각 청취자가 번역을 원하는 원격 발화자에 대해 브라우저 측 번역 sidecar를 만들 수 있어요. 더 큰 회의실에서는 각 원본 발화자를 한 번 구독하고, 대상 언어별로 번역 세션을 하나 만들고, 번역 트랙을 다시 게시하는 서버 측 참여자 또는 미디어 워커를 쓰세요.

품질과 지연 테스트

실제 오디오와 이중언어 검토로 번역을 테스트하세요. 자동 지표도 도움이 되지만 사용자가 눈치채는 모든 오류를 잡아주지는 않아요. 다음을 테스트하세요.

  • 언어 쌍 품질
  • 이름, 숫자, 날짜, 통화, 전화번호
  • 도메인 특화 용어
  • 코드 스위칭과 혼합 언어 대화
  • 억양, 빠른 말, 겹치는 음성
  • 첫 번역 오디오 지연
  • 발화 종료 지연
  • 자막 타이밍
  • 음성 일관성
  • 재연결 동작

정확한 이름이나 도메인 용어에 의존하는 용도라면, 출시 전에 골든 셋을 만들고 실패를 수동으로 검토하세요.

프로덕션 체크리스트

  • 브라우저 미디어에는 WebRTC, 서버 미디어에는 WebSockets을 선택하세요.
  • 전용 /v1/realtime/translations 엔드포인트를 쓰세요.
  • 문구 사이의 침묵을 포함해 오디오를 계속 스트리밍하세요.
  • WebSocket 세션을 닫기 전에 session.close를 보내고 session.closed를 기다리세요.
  • 대화형 번역에서는 화자 트랙을 분리해서 유지하세요.
  • 출력 언어마다 세션을 하나씩 쓰세요.
  • 유용할 때 원본·대상 대본을 모두 렌더링하세요.
  • 원본 오디오, 번역 오디오, 자막, 음소거, 볼륨 제어를 노출하세요.
  • 재연결, 지연, 사용 불가 상태를 표면화하세요.
  • 번역 품질과 별개로 지연을 추적하세요.

더 알아보기 (Learn more)