Xây dựng các luồng truyền tải âm thanh có độ trễ cực thấp với OpenAI Realtime API mới cho phép các nhà phát triển triển khai các voice agent đàm thoại giống như con người vào môi trường production. Theo cách truyền thống, việc xây dựng một giao diện thoại đồng nghĩa với việc kết chuỗi ba lớp mô hình riêng biệt: nhận dạng giọng nói tự động (ASR), lớp logic LLM dựa trên văn bản và tổng hợp văn bản thành giọng nói (TTS). Quy trình nhiều bước đó đã tạo ra độ trễ mạng khứ hồi lớn, khiến cuộc hội thoại tự nhiên trở nên bất khả thi. Việc xử lý âm thanh gốc qua kết nối WebSocket persistent thay đổi điều này, giảm độ trễ mạng xuống dưới 300 mili giây. Hướng dẫn này giải thích cách thiết lập trạng thái kết nối, truyền tải các buffer âm thanh gốc và tối ưu hóa cấu hình phiên.

[!IMPORTANT] Cảnh báo bảo mật API: Không bao giờ để lộ trực tiếp khóa API OpenAI bên trong các script trình duyệt phía client. Hãy luôn proxy kết nối WebSocket qua một middleware edge an toàn (chẳng hạn như Cloudflare Worker) để gắn thêm các header ủy quyền trước khi chuyển tiếp gói tin đến OpenAI.

Các điểm chính cần lưu ý:

  • Kết nối WebSocket: Kết nối trực tiếp đến cổng WebSocket thời gian thực của OpenAI bằng các proxy ở biên.
  • Các modality gốc: Chỉ định cả textaudio trong payload cấu hình cập nhật phiên ban đầu của bạn.
  • Định dạng âm thanh: Truyền tải giọng nói của người dùng dưới dạng các phân đoạn mono PCM16 được mã hóa base64 ở tần số 24kHz.
  • Ngắt lời thoại: Theo dõi các tín hiệu bắt đầu phát âm thanh của máy chủ để dừng phát lại phía client ngay lập tức.

Kiến trúc của các LLM âm thanh gốc

Các hệ thống giọng nói truyền thống xử lý cả mô hình nhận dạng giọng nói và tổng hợp giọng nói như các lớp bọc bên ngoài một mô hình văn bản trung tâm. Các mô hình âm thanh gốc loại bỏ chi phí thừa đó bằng cách xử lý trực tiếp giọng nói.

Với các mô hình realtime của OpenAI, mạng lưới xử lý đầu vào và đầu ra âm thanh một cách trực tiếp. Mô hình nhận các dạng sóng âm thanh gốc, phân tích tông giọng, ngữ điệu cũng như nội dung và trực tiếp tạo ra giọng nói tự nhiên đầu ra. Nhờ đó, quy trình loại bỏ hoàn toàn các lỗi phiên dịch ASR và các điểm nghẽn tổng hợp giọng nói TTS.

Việc quản lý kết nối persistent này dựa trên WebSockets. Kết nối luôn mở trong suốt cuộc gọi, cho phép agent ngắt giọng nói đầu ra của mình nếu phát hiện thấy giọng nói của người dùng, mang lại trải nghiệm giống như một cuộc điện thoại thực sự.

Nhận dịch vụ tích hợp AI

Điều kiện tiên quyết

Trước khi viết bất kỳ dòng mã nào, hãy đảm bảo môi trường của bạn đã sẵn sàng. Hướng dẫn này giả định bạn đã quen thuộc với JavaScript bất đồng bộ và có sẵn những thứ sau:

  • Tài khoản OpenAI có quyền truy cập Realtime cùng khóa API đã nạp tiền và đang hoạt động.
  • Node.js 20 trở lên, hoặc một dự án Cloudflare Workers được khởi tạo bằng lệnh npm create cloudflare@latest.
  • Một WebSocket client — gói ws cho một Node.js gateway, hoặc biến môi trường WebSocket gốc có sẵn bên trong Workers.
  • Một trình duyệt frontend có thể thu âm thanh từ micro qua Web Audio API (getUserMedia cộng với một AudioWorklet để thực hiện việc tái mẫu).
  • Kiến thức thực tế về base64 và âm thanh PCM, vì mọi frame bạn gửi hoặc nhận đều là một payload PCM16 được mã hóa base64.

Hãy dành chút thời gian cho việc thiết lập tài khoản: quyền truy cập Realtime và thanh toán phải được bật trên tài khoản tổ chức của bạn trước khi gateway chấp nhận một phiên kết nối.


Thiết lập kết nối WebSocket

Để bắt đầu, bạn mở một kết nối đến OpenAI Realtime gateway, chỉ định mô hình realtime trong các header của bạn.

Đoạn mã JavaScript bên dưới trình bày cách khởi tạo kết nối, cấu hình các modality của phiên và xử lý việc truyền phát các buffer âm thanh đầu vào và đầu ra:

 1import WebSocket from "ws";
 2
 3export async function startVoiceAgent(env) {
 4  // Connect to the OpenAI Realtime WebSocket gateway
 5  const url = "wss://api.openai.com/v1/realtime?model=gpt-realtime";
 6  const ws = new WebSocket(url, {
 7    headers: {
 8      "Authorization": `Bearer ${env.OPENAI_API_KEY}`,
 9      "OpenAI-Beta": "realtime=v1"
10    }
11  });
12
13  ws.on("open", () => {
14    console.log("WebSocket connection established with OpenAI Realtime API");
15    
16    // Configure session modalities and voice parameters
17    const sessionConfig = {
18      type: "session.update",
19      session: {
20        modalities: ["text", "audio"],
21        instructions: "You are a helpful customer service assistant for Mecanik.",
22        voice: "alloy",
23        input_audio_format: "pcm16",
24        output_audio_format: "pcm16",
25        temperature: 0.7
26      }
27    };
28    ws.send(JSON.stringify(sessionConfig));
29  });
30
31  ws.on("message", (data) => {
32    const event = JSON.parse(data);
33    
34    // Handle incoming audio content from the server
35    if (event.type === "response.audio.delta") {
36      const audioBuffer = Buffer.from(event.delta, "base64");
37      // Output buffer to client audio player
38      playAudioChunk(audioBuffer);
39    }
40  });
41}

Khi xây dựng handler này, hãy đảm bảo khóa API của bạn được ẩn khỏi trình duyệt client. Cụ thể, thiết lập một edge middleware trên máy chủ của bạn để xử lý việc ủy quyền trước khi proxy kết nối WebSocket. Để tìm hiểu thêm về các cấu hình edge API, hãy đọc hướng dẫn của chúng tôi về xây dựng API serverless với Cloudflare Workers .


Xây dựng Edge Proxy an toàn

Đoạn mã ở trên thực hiện kết nối từ một máy chủ đáng tin cậy, nhưng nó chưa thể hiện phần giúp giữ an toàn cho khóa của bạn: chính là proxy. Trên Cloudflare Workers, bạn không thể gắn các header tùy chỉnh vào hàm dựng new WebSocket(), vì vậy bạn mở kết nối upstream bằng fetch và một header Upgrade. Worker sẽ chấp nhận socket của trình duyệt, gọi đến OpenAI với khóa bí mật được gắn kèm, sau đó chuyển phát các frame dữ liệu giữa hai bên.

 1export default {
 2  async fetch(request, env) {
 3    if (request.headers.get("Upgrade") !== "websocket") {
 4      return new Response("Expected a WebSocket upgrade", { status: 426 });
 5    }
 6
 7    // 1. Accept the browser <-> Worker socket
 8    const [client, server] = Object.values(new WebSocketPair());
 9    server.accept();
10
11    // 2. Open the Worker <-> OpenAI socket with the secret key attached
12    const upstreamResponse = await fetch(
13      "https://api.openai.com/v1/realtime?model=gpt-realtime",
14      {
15        headers: {
16          Upgrade: "websocket",
17          Authorization: `Bearer ${env.OPENAI_API_KEY}`,
18          "OpenAI-Beta": "realtime=v1"
19        }
20      }
21    );
22
23    const upstream = upstreamResponse.webSocket;
24    if (!upstream) {
25      return new Response("Upstream refused the upgrade", { status: 502 });
26    }
27    upstream.accept();
28
29    // 3. Pipe frames in both directions
30    server.addEventListener("message", (e) => upstream.send(e.data));
31    upstream.addEventListener("message", (e) => server.send(e.data));
32
33    const close = () => { try { server.close(); upstream.close(); } catch {} };
34    server.addEventListener("close", close);
35    upstream.addEventListener("close", close);
36
37    return new Response(null, { status: 101, webSocket: client });
38  }
39};

Hãy lưu trữ khóa dưới dạng một secret được mã hóa bằng lệnh wrangler secret put OPENAI_API_KEY thay vì ghi trực tiếp trong wrangler.toml, để nó không bao giờ bị đưa lên kho mã nguồn của bạn. Trình duyệt lúc này kết nối đến wss://worker-cua-ban.workers.dev và không bao giờ nhìn thấy thông tin xác thực. Cloudflare tài liệu hóa mô hình hai chiều này trong Workers WebSockets reference của họ.


Xử lý luồng truyền âm thanh của người dùng

Khi bản cập nhật phiên được chấp nhận, client của bạn phải thu âm micro đầu vào, nén thành dữ liệu mono PCM16 24kHz và truyền phát dưới dạng các phân đoạn base64. Vì kết nối luôn được duy trì, việc xử lý khi mạng bị rớt là rất quan trọng. Một bộ đệm phân đoạn cục bộ đảm bảo rằng việc rớt kết nối di động ngắn hạn không dẫn đến mất gói tin âm thanh hoặc các phản hồi giật cục từ agent: client sẽ giữ lại bộ đệm và phát lại ngay lập tức khi kết nối lại.

1// Example of streaming user microphone data
2function streamMicrophoneChunk(ws, base64AudioChunk) {
3  const audioEvent = {
4    type: "input_audio_buffer.append",
5    audio: base64AudioChunk
6  };
7  ws.send(JSON.stringify(audioEvent));
8}

Bất cứ khi nào người dùng ngừng nói, máy chủ sẽ tự động xử lý bộ đệm âm thanh tích lũy và kích hoạt phản hồi từ mô hình. Để xem danh sách sự kiện API đầy đủ, hãy xem qua OpenAI Realtime Guide .

Ngoài ra, hãy triển khai tính năng khử tiếng vọng (echo cancellation) trong trình phát frontend của bạn. Nếu micro thu lại âm thanh phát ra từ loa, agent sẽ hiểu nhầm giọng nói của chính mình là người dùng đang ngắt lời, khiến vòng lặp phiên bị lỗi. Để tìm hiểu thêm về tối ưu hóa frontend, hãy xem hướng dẫn của chúng tôi về so sánh WordPress và phát triển web tùy chỉnh .


Quản lý phát hiện lượt thoại và ngắt lời

Theo mặc định, bạn phải báo cho mô hình biết khi nào lượt nói kết thúc. Việc bật tính năng phát hiện hoạt động giọng nói (VAD) phía máy chủ sẽ giao việc đó cho OpenAI: gateway theo dõi bộ đệm đầu vào, quyết định khi nào người dùng ngừng nói và tự động kích hoạt phản hồi. Thêm khối turn_detection vào bản cập nhật phiên bạn gửi trong quá trình bắt tay kết nối.

 1const sessionConfig = {
 2  type: "session.update",
 3  session: {
 4    modalities: ["text", "audio"],
 5    voice: "alloy",
 6    input_audio_format: "pcm16",
 7    output_audio_format: "pcm16",
 8    turn_detection: {
 9      type: "server_vad",
10      threshold: 0.5,
11      prefix_padding_ms: 300,
12      silence_duration_ms: 500
13    }
14  }
15};

Khi VAD được bật, máy chủ phát ra sự kiện input_audio_buffer.speech_started ngay khi phát hiện người dùng nói xen vào lúc agent đang phát âm thanh. Hãy xử lý sự kiện đó như một lệnh dừng khẩn cấp: xóa sạch mọi phân đoạn âm thanh đang xếp hàng trong trình phát của bạn, nếu không phản hồi trước đó sẽ tiếp tục phát song song với phản hồi mới đang gửi đến.

 1let playbackQueue = [];
 2
 3ws.on("message", (raw) => {
 4  const event = JSON.parse(raw);
 5  if (event.type === "input_audio_buffer.speech_started") {
 6    // User is interrupting: drop everything still buffered
 7    playbackQueue = [];
 8    stopSpeaker();
 9  }
10});

Việc tăng silence_duration_ms khiến agent đợi lâu hơn trước khi trả lời, phù hợp với những người nói chậm hoặc ngập ngừng; việc giảm thông số này giúp cuộc đối thoại diễn ra nhanh hơn nhưng có nguy cơ ngắt lời mọi người khi họ chưa nói hết câu.


Quy trình triển khai Voice Agent từng bước

Để triển khai middleware cho voice agent của bạn, hãy bắt đầu bằng cách cấu hình một cổng điều hướng WebSocket an toàn trên các node edge serverless để bảo vệ thông tin xác thực OpenAI của bạn. Điều này ngăn chặn các bên thu thập thông tin quét tìm thấy khóa của bạn.

Tiếp theo, hãy soạn thảo cẩn thận các prompt hệ thống và các quy tắc của bạn. Thiết lập tông giọng, từ vựng và hướng dẫn phản hồi bên trong sự kiện cập nhật phiên ban đầu. Do đó, điều này thiết lập một phạm vi rõ ràng cho các luồng đàm thoại.

Sau đó, triển khai logic ngắt lời phát âm thanh một cách chặt chẽ. Bạn nên lắng nghe sự kiện input_audio_buffer.speech_started và dừng phát âm thanh ngay lập tức trong trình phát frontend của bạn. Cuối cùng, tối ưu hóa độ trễ toàn cầu bằng cách triển khai các Workers của bạn ở các vùng địa lý gần nhất với người dùng để giảm các bước chuyển tiếp mạng. Để khám phá các tùy chọn lưu trữ biên, hãy đọc hướng dẫn Cloudflare Workers AI của chúng tôi.


Các lỗi thường gặp và cách khắc phục

Hầu hết các lỗi trong lần chạy đầu tiên đều đến từ một số lỗi phổ biến có thể dự đoán được. Bảng dưới đây ánh xạ triệu chứng lỗi bạn gặp phải với nguyên nhân và cách xử lý tương ứng.

Triệu chứngNguyên nhân có thểCách xử lý
Kết nối đóng ngay lập tức với lỗi 401Thiếu hoặc sai định dạng header AuthorizationXác nhận proxy có gắn kèm Bearer <key> và khóa đó có quyền truy cập Realtime
Agent chỉ nghe thấy tiếng im lặng hoặc giọng bị méoÂm thanh được gửi sai tần số lấy mẫu hoặc độ sâu bitThực hiện tái mẫu về mono PCM16 24kHz trước khi mã hóa base64
Mô hình không bao giờ phản hồi sau khi người dùng nóiturn_detection bị tắt và không có lệnh commit thủ công nào được gửiBật server_vad, hoặc gửi sự kiện input_audio_buffer.commit sau đó đến response.create
Agent nói đè lên giọng người dùngHandler speech_started không xóa hàng đợi phátXóa sạch bộ đệm phát lại và dừng loa ngay khi nhận được sự kiện đó
Câu trả lời bị cắt ngang giữa câuCấu hình max_response_output_tokens quá thấpTăng giới hạn hoặc để giá trị mặc định là inf

Có hai vấn đề tinh vi hơn cần lưu ý. Sự kiện rate_limits.updated gửi về với số dư khả dụng thấp là cảnh báo sớm rằng các phiên đồng thời sắp bị giới hạn băng thông; hãy ghi log lại và giãn cách tần suất gọi thay vì liên tục thử kết nối lại. Và nếu thỉnh thoảng agent lại tự trả lời câu nói cuối cùng của chính mình, nghĩa là micro đang thu lại âm thanh phát ra từ loa, vì vậy hãy tăng cường bộ khử tiếng vọng hoặc yêu cầu người dùng thử nghiệm đeo tai nghe.


Thử nghiệm và các lưu ý khi chạy Production

Việc kiểm thử cục bộ thường gặp khó khăn vì bạn cần luồng âm thanh thực truyền phát theo cả hai hướng. Quy trình nhanh nhất là chạy Worker với lệnh wrangler dev, trỏ một trang trình duyệt đơn giản vào đó và theo dõi luồng sự kiện trong console trước khi lo lắng về chất lượng âm thanh. Ghi log mọi loại sự kiện đầu vào trong quá trình phát triển; khi trình tự session.updated, speech_started, response.audio.delta, và response.done hiển thị chính xác, bạn biết rằng phần kết nối cơ bản đã ổn định.

Trước khi phát hành, hãy cân nhắc một số thực tế sản xuất:

  • Chi phí. Âm thanh thời gian thực được tính phí theo số phút của token âm thanh đầu vào và đầu ra, đắt hơn nhiều so với token văn bản thông thường. Hãy giới hạn thời lượng phiên và cân nhắc phương án chuyển sang văn bản dự phòng cho các câu trả lời cung cấp thông tin dài.
  • Độ trễ. Triển khai proxy gần với người dùng của bạn nhất có thể và giữ cho nó nhẹ nhàng. Mỗi bước trung chuyển bổ sung giữa trình duyệt, biên và OpenAI đều làm tăng thêm thời gian phản hồi khứ hồi mà bạn đã rất nỗ lực cắt giảm.
  • Kết nối lại. Các socket thường bị ngắt trên mạng di động. Hãy duy trì một bộ đệm xoay vòng ngắn trên client và phát lại khi kết nối lại để tránh việc mất một frame làm hỏng cuộc trò chuyện.
  • Khả năng quan sát. Phát ra các bản ghi nhật ký (log) có cấu trúc cho các sự kiện bắt đầu phiên, số lần ngắt lời và lỗi để bạn có thể phát hiện hiệu năng bị suy giảm trước khi người dùng phàn nàn.
  • An toàn và quyền riêng tư. Giọng nói là dữ liệu cá nhân. Hãy minh bạch về thời gian lưu trữ dữ liệu và bổ sung kiểm duyệt nội dung phía máy chủ trên các bản dịch transcript nếu agent của bạn xử lý các quy trình nhạy cảm.

Một giai đoạn chạy thử nghiệm ngắn với những người gọi thực tế sẽ giúp làm lộ ra các trường hợp biên về giọng địa phương, tiếng ồn và ngắt lời thoại mà không một kịch bản kiểm thử nào bao quát hết, vì vậy hãy thực hiện kiểm thử thực tế trước khi phát hành rộng rãi.


Các điểm mấu chốt cần nhớ

  • OpenAI Realtime API xử lý âm thanh trực tiếp qua WebSockets persistent, giữ độ trễ dưới 300ms.
  • Luôn cấu hình định dạng, phương thức truyền và prompt trong quá trình bắt tay WebSocket ban đầu.
  • Truyền âm thanh người dùng dưới dạng các phân đoạn base64 mono PCM16 trong thời gian thực.
  • Xử lý các sự kiện ngắt lời bằng cách theo dõi các tín hiệu bắt đầu giọng nói từ máy chủ.
  • Duy trì an toàn thông tin xác thực API bằng cách proxy các chi tiết kết nối qua các bộ định tuyến edge worker.

Câu hỏi thường gặp (FAQ)

OpenAI Realtime API là gì? OpenAI Realtime API là một giao diện WebSocket cho phép các nhà phát triển truyền tải âm thanh gốc vào và ra khỏi mô hình, bỏ qua các mô-đun ASR/TTS riêng biệt. Bằng cách xử lý âm thanh trực tiếp, mô hình giữ được tông giọng cảm xúc, ngữ điệu giọng nói và các chi tiết biểu cảm, đạt được độ trễ dưới 300 mili giây.

Tôi xử lý việc ngắt lời nói như thế nào? Hãy lắng nghe sự kiện máy chủ input_audio_buffer.speech_started. Khi nhận được, hãy xóa bộ đệm âm thanh frontend của bạn và dừng phát loa ngay lập tức. Do đó, điều này tạo ra một luồng đàm thoại tự nhiên, cho phép AI agent dừng nói ngay lập tức khi người dùng bắt đầu nói.

API hỗ trợ các định dạng âm thanh nào? API hỗ trợ trực tiếp các định dạng âm thanh 24kHz mono PCM16 (dạng số nguyên có dấu 16-bit gốc) và G.711 (u-law và a-law). Nhà phát triển phải thu dữ liệu micro, chuyển đổi sang các định dạng cụ thể này phía client và truyền phát chúng dưới dạng chuỗi mã hóa base64 bên trong các frame WebSocket JSON.

Tôi có cần một máy chủ riêng để điều phối lưu lượng WebSocket không? Có. Việc chạy một bộ điều phối biên không máy chủ (chẳng hạn như Cloudflare Workers hoặc một cổng Node.js dung lượng nhẹ) được khuyến khích mạnh mẽ. Proxy nhận dữ liệu micro từ trình duyệt client, gắn các header ủy quyền an toàn và chuyển tiếp luồng dữ liệu nhị phân đến cổng kết nối của OpenAI.

Làm cách nào để ngăn chặn các vòng lặp tiếng vọng trong các real-time voice agent? Vòng lặp tiếng vọng xảy ra khi âm thanh từ loa bị thu ngược lại vào micro client, kích hoạt các sự kiện ngắt giọng giả. Nhà phát triển phải triển khai các thuật toán khử tiếng vọng phía client hoặc sử dụng tai nghe trong quá trình thử nghiệm để ngăn chặn các vòng lặp phản hồi làm hỏng phiên làm việc.