يتيح بناء مسارات نقل الصوت ذات زمن الانتقال المنخفض للغاية باستخدام واجهة برمجة تطبيقات OpenAI Realtime API الجديدة للمطورين إطلاق وكلاء صوتيين حواريين يشبهون البشر في بيئات الإنتاج. تقليديًا، كان بناء واجهة صوتية يعني ربط ثلاث طبقات منفصلة من النماذج: التعرف التلقائي على الكلام (ASR)، وطبقة منطق النص المستندة إلى النماذج اللغوية الكبيرة (LLM)، وتوليف تحويل النص إلى كلام (TTS). وقد تسببت تلك المسارات متعددة الخطوات في حدوث تأخيرات شبكية كبيرة، مما جعل المحادثات الطبيعية أمرًا مستحيلاً. يغير معالجة الصوت الأصلي (native audio) عبر اتصال WebSocket مستمر هذا الواقع، مما يقلل زمن انتقال الشبكة إلى أقل من 300 مللي ثانية. يشرح هذا الدليل كيفية إعداد حالات الاتصال، وبث مخازن الصوت المؤقتة الخام، وتحسين تكوينات الجلسة.
[!IMPORTANT] تحذير أمني لواجهة برمجة التطبيقات (API): لا تقم مطلقًا بكشف مفتاح API الخاص بـ OpenAI مباشرة داخل البرمجيات النصية للمتصفح من جهة العميل. قم دائمًا بتوجيه اتصال WebSocket عبر خادم وسيط آمن على الحافة (مثل Cloudflare Worker) يقوم بإلحاق ترويسات التفويض قبل توجيه الحزم إلى OpenAI.
النقاط الرئيسية:
- اتصال WebSocket: اتصل مباشرة ببوابة WebSocket الخاصة بالوقت الفعلي من OpenAI باستخدام وكلاء الحافة (edge proxies).
- الوسائط الأصلية: حدد كل من النص والصوت
textوaudioفي حمولة تكوين تحديث الجلسة الأولية.- تنسيق الصوت: قم ببث كلام المستخدم كأجزاء أحادية القناة PCM16 مشفرة بصيغة base64 بتردد 16 كيلوهرتز.
- مقاطعة الكلام: راقب إشارات بدء الكلام الصادرة من الخادم لإيقاف تشغيل العميل فورًا.
بنية نماذج الصوت الأصلية للذكاء الاصطناعي (Native Audio LLMs)
تعامل الأنظمة الصوتية التقليدية كلا من نماذج التعرف على الكلام وتوليفه كأغلفة خارجية حول نموذج نصي مركزي. بينما تلغي نماذج الصوت الأصلية هذا العبء الإضافي عن طريق معالجة الكلام مباشرة.
ومع نماذج OpenAI الفورية (realtime)، تعالج الشبكة المدخلات والمخرجات الصوتية بشكل أصيل. يتلقى النموذج موجات صوتية خام، ويحلل النبرة، وتغيرات الصوت، والمحتوى، ويولد مخرجات صوتية طبيعية مباشرة. ونتيجة لذلك، يلغي هذا المسار أخطاء نسخ ASR واختناقات توليف TTS.
تعتمد إدارة هذا الاتصال المستمر على تقنية WebSockets؛ حيث يظل الاتصال مفتوحًا طوال المكالمة، مما يسمح للوكيل بمقاطعة مخرجاته إذا اكتشف كلام المستخدم، بحيث تطابق التجربة مكالمة هاتفية حقيقية تمامًا.
احصل على خدمات دمج الذكاء الاصطناعيالمتطلبات الأساسية
قبل كتابة أي سطر برمجيات، تأكد من أن بيئتك جاهزة. يفترض هذا البناء أنك مرتاح مع لغة JavaScript غير المتزامنة وأنك قمت بإعداد ما يلي:
- حساب OpenAI مع إمكانية الوصول إلى خدمة Realtime ومفتاح واجهة برمجة تطبيقات مفعل ومشحون برصيد.
- إصدار Node.js 20 أو أحدث، أو مشروع Cloudflare Workers تم إنشاؤه عبر الأمر
npm create cloudflare@latest. - عميل WebSocket — حزمة
wsلبوابة Node.js، أو كائنWebSocketالأصلي المتاح داخل Workers. - واجهة أمامية للمتصفح يمكنها التقاط صوت الميكروفون عبر واجهة برمجة تطبيقات Web Audio API (باستخدام
getUserMediaبالإضافة إلىAudioWorkletلإعادة أخذ العينات). - معرفة عملية بصيغة base64 وصوت PCM، حيث أن كل إطار ترسله أو تستقبله هو حمولة PCM16 مشفرة بصيغة base64.
خصص بعض الوقت لإعداد الحساب أيضًا: يجب تمكين الوصول إلى الوقت الفعلي والفوترة في مؤسستك قبل أن تقبل البوابة الجلسة.
إنشاء اتصالات WebSocket
للبدء، تفتح اتصالاً ببوابة OpenAI Realtime، محددًا النموذج الفوري (realtime) في الترويسات الخاصة بك.
يوضح كود JavaScript أدناه كيفية تهيئة الاتصال، وتكوين وسائط الجلسة، والتعامل مع تدفق مخازن الصوت المؤقتة الواردة والصادرة:
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}
عند بناء هذا المعالج، تأكد من بقاء مفتاح API الخاص بك مخفيًا عن متصفح العميل. وبشكل محدد، أنشئ خادمًا وسيطًا على الحافة (edge middleware) لمعالجة التفويض قبل توجيه اتصال WebSocket. لمعرفة المزيد حول تكوينات واجهات برمجة التطبيقات على الحافة، اقرأ دليلنا حول بناء واجهة برمجة تطبيقات بدون خادم باستخدام Cloudflare Workers .
بناء خادم الحافة الوسيط الآمن (Secure Edge Proxy)
يتصل الجزء البرمجي أعلاه من خادم موثوق، ولكنه لم يوضح الجزء الذي يحافظ على أمان مفتاحك: الخادم الوسيط نفسه. في Cloudflare Workers لا يمكنك إرفاق ترويسات مخصصة إلى منشئ new WebSocket()، لذا تقوم بفتح الاتصال الصاعد باستخدام fetch وترويسة Upgrade. يقبل الـ Worker مقبس (socket) المتصفح، ويتصل بـ OpenAI مع إرفاق مفتاحك السري، ثم يمرر الإطارات بين الاثنين.
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};
قم بتخزين المفتاح كسر مشفر باستخدام الأمر wrangler secret put OPENAI_API_KEY بدلاً من وضعه في ملف wrangler.toml لضمان عدم وصوله إلى مستودع الكود الخاص بك. يتصل المتصفح الآن بـ wss://your-worker.workers.dev ولا يرى أبدًا بيانات الاعتماد. توثق Cloudflare هذا النمط ثنائي الاتجاه في مرجع WebSockets لـ Workers
.
معالجة بث صوت المستخدم
بمجرد قبول تحديث الجلسة، يجب على العميل التقاط إدخال الميكروفون، وضغطه إلى بيانات PCM16 أحادية القناة بتردد 16 كيلوهرتز، وبثه كأجزاء مشفرة بصيغة base64. ونظرًا لأن الاتصال يظل مستمرًا، فإن التعامل مع انقطاعات الشبكة أمر بالغ الأهمية. يضمن وجود مخزن مؤقت محلي للأجزاء الصوتية عدم فقدان حزم الصوت أو تقطع استجابات الوكيل نتيجة انقطاعات الشبكة الخلوية القصيرة: حيث يحتفظ العميل بالمخزن المؤقت ويعيد إرساله فور استعادة الاتصال.
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}
عندما يتوقف المستخدم عن الكلام، يقوم الخادم تلقائيًا بمعالجة مخزن الصوت المؤقت المتراكم ويبدأ في توليد استجابة النموذج. لمراجعة قائمة أحداث واجهة برمجة التطبيقات الكاملة، راجع دليل OpenAI Realtime .
بالإضافة إلى ذلك، قم بتنفيذ تقنية إلغاء صدى الصوت في قارئ الواجهة الأمامية. فإذا التقط الميكروفون صوت المخرجات الصادرة من مكبر الصوت، فسيقوم الوكيل بتفسير صوته الخاص على أنه مقاطعة من المستخدم، مما يؤدي إلى فشل حلقة الجلسة. لمعرفة المزيد حول تحسين الواجهة الأمامية، راجع دليلنا حول المقارنة بين WordPress وتطوير الويب المخصص .
إدارة اكتشاف دور الكلام والمقاطعات
افتراضيًا، يتعين عليك إخبار النموذج بنهاية دور الكلام. بينما يؤدي تفعيل ميزة اكتشاف النشاط الصوتي (VAD) من جهة الخادم إلى إسناد هذه المهمة إلى OpenAI؛ حيث تراقب البوابة المخزن المؤقت الوارد، وتحدد متى توقف المستخدم عن الكلام، وتطلق الاستجابة تلقائيًا. أضف كتلة turn_detection إلى تحديث الجلسة الذي ترسلها أثناء المصافحة (handshake).
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};
عند تفعيل VAD، يرسل الخادم الحدث input_audio_buffer.speech_started في اللحظة التي يسمع فيها كلام المستخدم متداخلاً مع صوت الوكيل. تعامل مع هذا الحدث كإيقاف فوري تام: قم بإفراغ كل جزء صوتي مصفوف في قارئك، وإلا ستستمر الاستجابة السابقة بالعمل بينما تصل الاستجابة الجديدة.
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});
يؤدي زيادة قيمة silence_duration_ms إلى جعل الوكيل ينتظر لفترة أطول قبل الرد، وهو ما يناسب المتحدثين ببطء أو بتردد؛ بينما يجعل خفضها الحوار أكثر حيوية ولكنه قد يعرض المتحدثين للمقاطعة في منتصف الجملة.
خطوات نشر الوكيل الصوتي بالتفصيل
لنشر البرمجية الوسيطة للوكيل الصوتي، ابدأ بتهيئة بوابة توجيه WebSocket آمنة على عقد الحافة بدون خادم لحماية بيانات اعتماد OpenAI الخاصة بك. هذا يمنع برمجيات التجميع من اكتشاف مفاتيحك.
بعد ذلك، صغ الأوامر الموجهة للنظام والقواعد بعناية. حدد النبرة، والمفردات، وإرشادات الاستجابة داخل حدث تحديث جلسة الإرسال الأولية. وبالتالي، يؤسس هذا نطاقًا واضحًا للتدفقات الحوارية.
ثم قم بتنفيذ منطق قوي لمقاطعة مكبر الصوت. يجب عليك الاستماع لحدث input_audio_buffer.speech_started وإيقاف تشغيل الصوت في قارئ الواجهة الأمامية فورًا. وأخيرًا، قم بتحسين زمن الانتقال العالمي عن طريق نشر Workers في مواقع إقليمية قريبة من المستخدمين لتقليل قفزات التوجيه. لاستكشاف خيارات الاستضافة على الحافة، اقرأ شرح Cloudflare Workers AI
.
المشاكل الشائعة وإصلاحها
تأتي معظم إخفاقات التشغيل الأول من مجموعة من الأخطاء المتوقعة. يوضح الجدول أدناه الأعراض التي ستواجهها مع أسبابها المحتملة وطرق إصلاحها.
| العرض | السبب المحتمل | الحل |
|---|---|---|
ينغلق الاتصال برمز 401 فورًا | ترويسة Authorization مفقودة أو غير صحيحة | تأكد من إرفاق الخادم الوسيط لـ Bearer <key> وأن المفتاح لديه حق الوصول لخدمة Realtime |
| الوكيل لا يسمع سوى الصمت أو كلام مشوش | تم إرسال الصوت بمعدل عينات أو عمق بت غير صحيح | أعد أخذ العينات بتردد 16 كيلوهرتز أحادي القناة PCM16 قبل تشفير base64 |
| النموذج لا يستجيب أبدًا بعد كلام المستخدم | ميزة turn_detection معطلة ولم يتم إرسال التزام يدوي | قم بتفعيل server_vad أو أرسل input_audio_buffer.commit يليه response.create |
| الوكيل يتحدث متداخلاً مع كلام المستخدم | معالج speech_started لا يفرغ قائمة الانتظار | أفرغ المخزن المؤقت للتشغيل وأوقف مكبر الصوت عند هذا الحدث |
| تنقطع الإجابات في منتصف الجملة | تم ضبط max_response_output_tokens بقيمة منخفضة للغاية | ارفع الحد المسموح به أو اتركه كقيمة لانهائية inf |
هناك مشكلتان أكثر دقة تستحقان الانتباه. الأولى: وصول حدث rate_limits.updated مع رصيد متبقٍ منخفض يعد تحذيرًا مبكرًا بأن الجلسات المتزامنة على وشك أن تُقيد؛ سجل ذلك وقم بالحد من الطلبات بدلاً من تكرار محاولات الاتصال بعشوائية. الثانية: إذا كان الوكيل يجيب أحيانًا على جملته الأخيرة، فإن الميكروفون يلتقط صوت مكبر الصوت، لذا يجب تحسين ميزة إلغاء الصدى أو جعل المختبرين يستخدمون سماعات الرأس.
اعتبارات الاختبار والإنتاج
يعد الاختبار المحلي صعبًا لأنك تحتاج إلى تدفق صوتي حقيقي في كلا الاتجاهين. أسرع حلقة اختبار هي تشغيل الـ Worker باستخدام wrangler dev وتوجيه صفحة متصفح بسيطة إليه ومراقبة تدفق الأحداث في وحدة التحكم قبل القلق بشأن جودة الصوت. قم بتسجيل كل نوع حدث وارد أثناء التطوير؛ بمجرد أن يظهر ترتيب الأحداث session.updated وspeech_started وresponse.audio.delta وresponse.done بشكل صحيح، ستعرف أن البنية الأساسية سليمة.
قبل الشحن الفعلي للبرمجيات، ضع في اعتبارك بعض حقائق الإنتاج:
- التكلفة: يتم احتساب تكلفة الصوت في الوقت الفعلي لكل دقيقة من رموز الصوت الواردة والصادرة، وهي أغلى بكثير من الرموز النصية العادية. ضع حدًا لطول الجلسة، واعتمد خيار النص البديل للإجابات المعلوماتية الطويلة.
- زمن الانتقال: انشر الخادم الوسيط بالقرب من المستخدمين واجعله بسيطًا. كل قفزة إضافية بين المتصفح والحافة وOpenAI تزيد من زمن الرحلة الدائرية الذي عملت جاهدًا لتقليصه.
- إعادة الاتصال: تنقطع المقابس (sockets) على شبكات الهاتف المحمول. احتفظ بمخزن مؤقت دوار قصير على جهة العميل وأعد تشغيله عند إعادة الاتصال حتى لا يؤدي فقدان إطار صوتي إلى تخريب المحادثة.
- إمكانية المراقبة: أطلق سجلات منظمة لبدء الجلسة وعدد المقاطعات وأحداث الأخطاء حتى تتمكن من رصد الأداء المتراجع قبل أن يشتكي المستخدمون.
- الأمان والخصوصية: الصوت يمثل بيانات شخصية؛ لذا اجعل سياسة الاحتفاظ بالبيانات صريحة ومفهومة، وأضف ميزة الإشراف من جهة الخادم على النصوص المنسوخة إذا كان وكيلك يتعامل مع عمليات حساسة.
ستظهر تجربة تجريبية قصيرة مع متصلين حقيقيين الحالات الاستثنائية المتعلقة باللكنة والضوضاء والمقاطعة التي لا يغطيها أي اختبار برمجي، لذا قم بإجراء واحدة قبل أي إطلاق واسع النطاق.
أهم النقاط المستفادة
- تعالج واجهة برمجة تطبيقات OpenAI Realtime API الصوت بشكل أصيل عبر WebSockets مستمرة، مما يحافظ على زمن انتقال أقل من 300 مللي ثانية.
- قم دائمًا بتكوين الوسائط والتنسيقات والأوامر الموجهة أثناء المصافحة الأولية لـ WebSocket.
- قم ببث صوت المستخدم كأجزاء أحادية القناة PCM16 base64 في الوقت الفعلي.
- تعامل مع أحداث مقاطعة المتحدث من خلال مراقبة مخرجات حدث بدء الكلام.
- حافظ على أمان بيانات اعتماد واجهة برمجة التطبيقات (API key) عن طريق توجيه تفاصيل الاتصال عبر خوادم وسيطة على الحافة.
الأسئلة الشائعة (FAQ)
ما هي واجهة برمجة تطبيقات OpenAI Realtime API؟ هي واجهة WebSocket تسمح للمطورين ببث الصوت الخام من وإلى النموذج، متجاوزة وحدات ASR/TTS المنفصلة. ومن خلال معالجة الصوت بشكل أصيل، يحتفظ النموذج بالنبرة العاطفية، وتغيرات اللكنة، والفروق الدقيقة في الكلام، محققًا زمن انتقال يقل عن 300 مللي ثانية.
كيف يمكنني التعامل مع مقاطعات الصوت؟
استمع لحدث الخادم input_audio_buffer.speech_started؛ وعند استقباله، قم بإفراغ مخازن الصوت المؤقتة في الواجهة الأمامية وأوقف تشغيل مكبر الصوت فورًا. يؤدي هذا إلى إنشاء تدفق حواري طبيعي، يسمح لوكيل الذكاء الاصطناعي بالتوقف عن الكلام فورًا عندما يبدأ المستخدم في التحدث.
ما هي تنسيقات الصوت التي تدعمها واجهة برمجة التطبيقات؟ تدعم واجهة برمجة التطبيقات بشكل أصيل تنسيقات الصوت 16 كيلوهرتز أحادي القناة PCM16 (بيانات أرقام صحيحة خام ذات إشارة بقيمة 16 بت) و G.711 (u-law و a-law). يجب على المطورين التقاط بيانات الميكروفون، وتحويلها إلى هذه التنسيقات المحددة من جهة العميل، وبثها كسلاسل نصية مشفرة بصيغة base64 داخل إطارات JSON WebSocket.
هل أحتاج إلى خادم منفصل لتنسيق حركة مرور WebSocket؟ نعم، يوصى بشدة بتشغيل منسق حافة بدون خادم (مثل Cloudflare Workers أو بوابة Node.js خفيفة). يستقبل الخادم الوسيط مدخلات الميكروفون من متصفحات العملاء، ويرفق ترويسات التفويض الآمنة، ويوجه البث الثنائي إلى بوابة OpenAI.
كيف يمكنني منع حلقات صدى الصوت في وكلاء الصوت الفعليين؟ تحدث حلقات الصدى عندما يتسرب صوت مكبر الصوت عائدًا إلى ميكروفون العميل، مما يؤدي إلى إطلاق أحداث مقاطعة كلام خاطئة. يجب على المطورين تنفيذ خوارزميات إلغاء الصدى من جهة العميل أو استخدام سماعات الرأس أثناء الاختبار لمنع حلقات التغذية الراجعة من إفساد الجلسات.
التعليقات