مرجع أحداث Realtime STT
أحداث Socket.IO للنسخ الفوري وتمييز المتحدثين.
يصف هذا المرجع أحداث Socket.IO التي تستقبل مقاطع صوت PCM16 عبر audio_stream
وdiarization_stream وتعيد نتائج النسخ أو تمييز المتحدثين. أسماء الأحداث
والحقول الثنائية تبقى كما هي في البروتوكول.
الاتصال
- المضيف الإنتاجي:
https://api.voice.humain.com - المسار:
/socket.io - النقل:
websocketفقط - المصادقة: ترويسة
x-api-key - ترويسة
Origin: مطلوبة على الإنتاج؛ اضبطها علىhttps://api.voice.humain.com
import { io } from "socket.io-client";
const socket = io("https://api.voice.humain.com", {
path: "/socket.io",
transports: ["websocket"],
extraHeaders: {
"x-api-key": process.env.API_KEY!,
Origin: "https://api.voice.humain.com",
},
});اللغات
| القيمة | المفتاح | المعنى |
|---|---|---|
0 | ar | العربية |
1 | en | الإنجليزية |
2 | codeswitch | تبديل عربي-إنجليزي |
255 | auto | تلقائي؛ يُحل إلى الافتراضي المضبوط للبيئة |
الحدود
تنطبق أربعة حدود على الجلسة الفورية. وكلها شاملة: النجاح عند الحد بالضبط، والفشل عند تجاوزه فقط.
لكل حدث: 16 MiB. يجب ألا يتجاوز حدث audio_stream أو diarization_stream
واحد 16777216 بايت، محتسبةً ترويسة 18 بايتًا. وتجاوزه يصدر
PAYLOAD_TOO_LARGE مع data.bound بقيمة realtime_asr_frame_bytes أو
realtime_diarization_frame_bytes، ثم يغلق الاتصال. وهذا مطابق للسقف الذي
تفرضه مسارات HTTP المكافئة أصلًا.
لكل جلسة، إجمالي المحتوى الصوتي: 14400 ثانية (4 ساعات). وهو الصوت المقبول
المتراكم، وهو كمية مختلفة عن المدة التي بقيت فيها الجلسة مفتوحة. وتجاوزه يصدر
AUDIO_DURATION_EXCEEDED مع data.bound: session_audio_duration ويُنهي
الجلسة؛ فابدأ جلسة جديدة.
لكل جلسة، معدل الصوت: أربعة أضعاف الزمن الحقيقي. يمكن أن يصل الصوت بما يبلغ
128000 بايت في الثانية، مع سماح اندفاع 16 MiB — وهو إطار بالحجم الأقصى.
والعميل الذي يبث بالزمن الحقيقي فعلًا يستخدم ربع حصته ولا يمكن أن يبلغ هذا
الحد؛ أما العميل الذي يعوّض تأخرًا بعد تعطل شبكي فيفرّغ متأخراته بثلاثة أضعاف
الزمن الحقيقي. وتجاوزه يصدر SESSION_BYTE_RATE_EXCEEDED مع
data.bound: session_audio_rate_burst ومع retry_after_seconds (فليس لهذا
النقل ترويسة Retry-After، لذلك يسافر الانتظار داخل الإطار)، ولا يغلق الاتصال:
فتنجح الحمولة نفسها بمجرد أن يُعاد ملء الرصيد. ولا يستهلك الرفض أي رصيد ولا أي
حصة.
وسعة الدلو تغطي دائمًا أكبر إطار تقبله الواجهة، لذلك لا يُرفض إطار بالحجم
الأقصى أبدًا بسبب حد المعدل على جلسة بدأت للتو. أما على Socket.IO فالدلو يخص
الاتصال وتتشاركه كل أحداث الصوت عليه، لذلك قد يُحدَّد معدل إطار بالحجم الأقصى
أُرسل على اتصال بث صوتًا فعلًا؛ فالتزم بـretry_after_seconds وأعد إرساله دون
تغيير.
ولم تتغير حدود الجلسة الخاصة بالبايتات المتراكمة ولا بالمدة بالساعة الحقيقية ولا بالسكون.
أخطاء الرصيد والفوترة
يحجز تدفق ASR الفوري رصيدًا قابلًا للتجديد عند البدء، ثم يجدد الحجز أثناء التشغيل. وتُبلّغ النتيجتان التاليتان بالرموز نفسها عبر كل وسائل النقل وHTTP:
CREDITS_EXHAUSTED: نفد رصيد الحساب. يقابله HTTP 402 وretryable: false، لأن الإعادة الفورية لا تستعيد الرصيد.BILLING_AUTHORIZATION_UNAVAILABLE: تعذر الوصول إلى جهة اعتماد الفوترة أو لم تعطِ قرارًا حاسمًا، لذلك يفشل الطلب بأمان. يقابله HTTP 503 وretryable: true؛ أعد المحاولة بعد الانتظار.
قد يصل الخطأ عند بدء التدفق إذا رُفض الحجز، أو في منتصفه إذا رُفض تجديده أثناء
وصول الصوت. والخطأ في منتصف التدفق نهائي لذلك التدفق: يتوقف الخادم عن قبول
الصوت المدفوع، ويرسل حدث error النهائي أولًا، ثم يفصل الاتصال. ويُحاسب فقط
الصوت المقبول قبل الخطأ. أما رفض الحجز عند البدء فلا يغلق الاتصال، لأن المقبس
قد يحمل عمليات أخرى.
في واجهة WebSocket الخام يحمل إطار الإغلاق رمزًا خاصًا يساوي 4000 + HTTP status: الرمز 4402 لنفاد الرصيد و4503 لتعذر اعتماد الفوترة. ولا تحمل
واجهة Socket.IO رمز إغلاق على مستوى التطبيق؛ لذا يكون حدث error المنظم هو
الإشارة المعتمدة، وليس رمز الإغلاق.
التحقق من الإطار
يُتحقق من كل إطار قبل أي عمل للنموذج. ويُرفض الإطار عندما يكون أقصر من ترويسته
البالغة 18 بايتًا، أو عندما يكون UUID تدفقه أصفارًا كلها، أو عندما يضبط بت راية
غير معرَّف في تخطيط تلك الواجهة، أو عندما لا يكون بايت لغته أحد 0 أو 1
أو 2 أو 255، أو عندما يكون صوته فارغًا، أو عندما يكون طول صوته فرديًا.
ويُجاب على الإطار المقطوع، ولا يُقبل صامتًا أبدًا. وكل هذه أخطاء إدخال قابلة
للإصلاح من جهة العميل، ولا يُبلَّغ عن أي منها كخطأ خادم.
الأحداث
| الحدث | الاتجاه | المعنى |
|---|---|---|
audio_stream | العميل إلى الخادم | مقطع صوت للنسخ الفوري. |
speaker_id | العميل إلى الخادم | قديم؛ يعيد الخادم METHOD_NOT_ALLOWED. |
transcription_result | الخادم إلى العميل | نتيجة نسخ جزئية أو نهائية. |
speaker_id_result | الخادم إلى العميل | قديم؛ لا يصدر الخادم هذا الحدث. |
diarization_stream | العميل إلى الخادم | مقطع صوت لتمييز المتحدثين. |
diarization_result | الخادم إلى العميل | مقاطع المتحدثين النهائية أو النشطة. |
error | الخادم إلى العميل | خطأ منظم. |
إطار audio_stream
أرسل المقطع الأول مع is_start، والمقاطع الوسطية بلا رايات، والمقطع الأخير مع
is_final. عندما تكون diarization_enabled مفعّلة، ينسخ الخادم الصوت ويرسل
أيضًا أحداث diarization_result.
أرسل 1,600 عينة، أي 100 ms، في المقطع الموصى به. يخزن ASR المقاطع الأقصر حتى
1,600 عينة ويجزئ المقاطع الأطول إلى وحدات بهذا الحجم؛ ولا تنطبق قواعد التخزين
هذه على diarization_stream. يمكن لاتصال واحد حمل عدة تدفقات: أعد استخدام UUID
نفسه لتدفق واحد، واستخدم UUID جديدًا لكل تدفق جديد.
نتيجة النسخ
تصل transcription_result عادة بالشكل التالي:
{
"id": "7f51f2c2-e7bc-41c8-a850-f848df2ddfc8",
"seq": 0,
"transcription": "مرحبا بكم",
"words": [
{ "start_time": 0, "end_time": 1.2, "word": "مرحبا بكم" }
],
"is_final": false,
"is_speech_final": true
}تمثل is_speech_final: true حد نهاية كلام ويمكن أن يستمر التدفق بعده. ولا ينتهي
التدفق كله إلا عند is_final: true. عامل seq بوصفه قيمة تشخيصية؛ فلا يضمن
العقد العام ترتيبها أو تفرّدها.
إطار diarization_stream
يستخدم diarization_stream تخطيط البايتات نفسه المستخدم في audio_stream، لكن
مجموعة الرايات ليست نفسها: إطار تمييز المتحدثين يعرّف bit 0 وbit 1 فقط، أما
البتات المحجوزة 2..7 فيجب أن تكون أصفارًا، ويُرفض أي إطار يضبط أيًا منها. ولا
تضبط bit 2 هنا؛ فراية diarization_enabled تخص audio_stream وحده. ونموذج
تمييز المتحدثين يحل من model_config في جهة الخادم، لذلك يكون حقل اللغة موجودًا
للاتساق فقط، وأرسله صفرًا.
نتيجة تمييز المتحدثين
تحتوي diarization_result على مقاطع نهائية ونشطة:
{
"id": "7f51f2c2-e7bc-41c8-a850-f848df2ddfc8",
"is_final": false,
"final_segments": [
{ "speaker": "SPEAKER_00", "start_time": 0, "end_time": 1.5 }
],
"active_segments": [
{ "speaker": "SPEAKER_01", "start_time": 1.5, "end_time": 3 }
]
}الأخطاء
حمولة الحدث error كائن JSON منظم، وحقول code وmessage وretryable
وtimestamp كلها مطلوبة فيه. فرّع على code المعدود لا على نص message؛
فـmessage يحمل النص الحر نفسه الذي كان يُرسل سابقًا سلسلةً مجردة:
{
"code": "VALIDATION_INVALID_FORMAT",
"message": "Invalid data type",
"retryable": false,
"timestamp": "2025-05-07T10:00:00.000Z"
}code | المعنى |
|---|---|
AUTH_FORBIDDEN | مفتاح API لا يمنح الصلاحية |
VALIDATION_INVALID_FORMAT | تأطير أو نوع بيانات غير صالح |
VALIDATION_INVALID_LANGUAGE | قيمة لغة غير معروفة |
VALIDATION_INVALID_UUID | UUID تدفق غير صالح |
VALIDATION_FILE_CORRUPT | صوت تالف أو غير قابل لفك الترميز |
VALIDATION_REQUIRED_FIELD | حقل مطلوب مفقود |
PAYLOAD_TOO_LARGE | تجاوز الإطار سقف 16 MiB |
AUDIO_DURATION_EXCEEDED | تجاوزت الجلسة حصتها الصوتية |
SESSION_BYTE_RATE_EXCEEDED | الصوت يصل أسرع مما يسمح به معدل الجلسة |
SESSION_BYTES_EXCEEDED | تجاوز بايتات الجلسة المتراكمة |
SESSION_DURATION_EXCEEDED | تجاوز مدة الجلسة بالزمن الحقيقي |
SESSION_IDLE_TIMEOUT | انقضت مهلة سكون الجلسة |
SESSION_EXPIRED | الجلسة غير حيّة؛ ابدأ جلسة جديدة |
SESSION_SLOTS_EXHAUSTED | استُنفدت خانات الجلسات المتزامنة للحساب |
CONCURRENCY_LIMIT_EXCEEDED | بلغ الحساب حد العمليات المتزامنة في خطته |
CREDITS_EXHAUSTED | نفد رصيد الحساب؛ لا تفِد الإعادة الفورية |
BILLING_AUTHORIZATION_UNAVAILABLE | تعذر التحقق من الفوترة؛ أعد المحاولة بعد الانتظار |
ASR_TRANSCRIPTION_FAILED | فشل النسخ في الخلفية |
ASR_STREAM_EXPIRED | انتهى تدفق ASR؛ افتح تدفقًا جديدًا ولا تعد استخدام معرّفه |
DIARIZATION_FAILED | فشل تمييز المتحدثين في الخلفية |
DIARIZATION_MODEL_NOT_FOUND | نموذج تمييز المتحدثين غير مضبوط |
METHOD_NOT_ALLOWED | حدث مهجور، مثل speaker_id |
ويحمل رفض الحدود كائن data يسمّي الحد وقيمته المضبوطة والقيمة المرصودة. أما
رفض الحدود القابل لإعادة المحاولة فيحمل أيضًا retry_after_seconds:
{
"id": "7f51f2c2-e7bc-41c8-a850-f848df2ddfc8",
"code": "CONCURRENCY_LIMIT_EXCEEDED",
"message": "too many concurrent operations for this account",
"retryable": true,
"timestamp": "2026-01-15T10:30:00Z",
"retry_after_seconds": 5,
"data": {
"limit": 8,
"observed": 8,
"unit": "operations",
"bound": "account_concurrency_realtime_asr"
}
}حد العمليات المتزامنة لكل حساب قابل للفوترة، لذلك تتشارك عدة مفاتيح API تابعة لحساب واحد حصة واحدة. ولا يُغلق الاتصال: فالعمليات الأخرى المقبولة عليه تبقى تعمل.
ويحمل ASR_STREAM_EXPIRED كذلك reason بقيمة audio_inactivity أو
backend_sequence_lost، ويحمل retry_scope: "new_stream". افتح تدفقًا
جديدًا بمعرّف UUID جديد، ولا تعاود الإرسال على المعرّف المنتهي:
{
"id": "7f51f2c2-e7bc-41c8-a850-f848df2ddfc8",
"code": "ASR_STREAM_EXPIRED",
"message": "realtime ASR stream expired",
"retryable": true,
"timestamp": "2026-01-15T10:30:00Z",
"reason": "audio_inactivity",
"retry_scope": "new_stream"
}