وصفات

النسخ الفوري

ابث صوت PCM16 عند وصوله، ووفّق النص المؤقت والنهائي، وصدّر WebVTT النهائي باستخدام SDK 0.18.0.

تبني هذه الوصفة جلسة نسخ مباشر واحدة بشكل مناسب للإنتاج. تبدأ بينما لا يزال الصوت يصل، وتفصل حالة واجهة الاستخدام المؤقتة عن النص المثبت، وتكتب الترجمات من الكلمات النهائية، وتغلق كل مورد عند حد محدد.

متى تستخدم هذه الوصفة

اختر حسب حالة الصوت:

حالة الصوتاستخدمإدخال نموذجي
ما زال يصلRealtimeClient (هذه الوصفة)ميكروفون أو مكالمة أو مصدر مباشر آخر
مكتمل ومحدود وحساس لزمن الاستجابةFastTranscriptionClientدور محادثة واحد مكتمل لوكيل ذكاء اصطناعي
مكتمل وطويلBatchTranscribeClientاجتماع أو مقابلة أو بودكاست أو تسجيل أرشيفي

يستقبل النسخ السريع وحدة صوتية مكتملة. ويمتلك Batch التسجيلات الطويلة المكتملة. لا يحل أي منهما محل Realtime ASR عندما يجب أن يرسل المنتج الصوت قبل انتهاء الكلام.

قبل أن تبدأ

تحتاج إلى:

  • JavaScript @humain-voice/sdk@0.18.0 أو Python humain-voice==0.18.0 في وقت تشغيل موثوق على الخادم.
  • القيم المخصصة API_URL وAPI_KEY. يستخدم تطبيقا RealtimeClient المنشوران المسار /socket.io افتراضيًا.
  • مصدر يستطيع توفير صوت PCM16 little-endian خام بتردد 16 kHz وأحادي القناة.
  • مهلة للجلسة على مستوى التطبيق، ومكان يفصل الحالة المؤقتة والمثبتة وحالة الخطأ.
export API_URL="https://api.voice.humain.com"
export API_KEY="YOUR_API_KEY"

أبقِ مفتاح API خارج شيفرة المتصفح وتطبيق الجوال. راجع المصادقة لمسار بيانات الاعتماد في مؤسستك.

1. حضّر إدخال PCM خامًا

عقد صوت Realtime ASR محدد:

الخاصيةالقيمة المطلوبة
الترميزPCM16 little-endian بإشارة
معدل العينات16,000 Hz
القنواتأحادية
وتيرة المثال المختبر3,200 بايت كل 100 ms

لاختبار قابل للتكرار، حوّل تسجيلًا إلى الصيغة الخام نفسها التي يجب أن ينتجها مسار الميكروفون أو المكالمة:

ffmpeg -i input.wav -f s16le -acodec pcm_s16le -ar 16000 -ac 1 speech.pcm

أرسل البايتات الخام، لا ترويسة WAV أو حاوية مضغوطة. حجم 3,200 بايت والوتيرة 100 ms هما اختيار التأطير في المثال المختبر؛ أما صيغة الإدخال فهي عقد الخدمة.

2. شغّل المسار الناجح المختبر

تقرأ البرامج speech.pcm، وترسله بوتيرة منتج مباشر، وتصنف كل استجابة، وتجمع كلمات الأحداث النهائية بترتيب الوصول، وتنتظر الحالة النهائية حتى خمس ثوان، وتعرض speech.vtt باستخدام Subtitles، وتنظف العميل.

realtime-transcription.ts
import { readFile, writeFile } from 'node:fs/promises';

import {
  type ErrorResponse,
  Language,
  RealtimeClient,
  Subtitles,
  type WordSegment,
} from '@humain-voice/sdk';

const CHUNK_BYTES = 3_200; // 100 ms of PCM16LE, 16 kHz, mono audio.

function requiredEnv(name: 'API_KEY' | 'API_PATH' | 'API_URL'): string {
  const value = process.env[name];
  if (!value) throw new Error(`${name} is required`);
  return value;
}

async function pause(milliseconds: number): Promise<void> {
  await new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
}

async function main(): Promise<void> {
  const inputPath = process.argv[2] ?? 'speech.pcm';
  const outputPath = process.argv[3] ?? 'speech.vtt';
  const client = new RealtimeClient({
    api_url: requiredEnv('API_URL'),
    api_path: requiredEnv('API_PATH'),
    api_key: requiredEnv('API_KEY'),
  });
  const finalizedWords: WordSegment[] = [];
  let serverError: ErrorResponse | undefined;
  let protocolFinalObserved = false;

  try {
    const stream = await client.startStream(Language.ArEn, {
      onResponse: (response) => {
        const kind = response.is_final
          ? 'final'
          : response.is_speech_final
            ? 'speech-final'
            : 'partial';
        console.info(`${kind}:`, response.transcription);
        if (response.is_final) protocolFinalObserved = true;
        if (response.is_final || response.is_speech_final) {
          // The current public Realtime contract does not guarantee increasing
          // seq values, so collect final words in arrival order instead of
          // asking RealtimeSubtitles to deduplicate by id:seq.
          finalizedWords.push(...response.words);
        }
      },
      // The released SDK can invoke a stream handler more than once for one
      // routed error, so keep this callback idempotent.
      onError: (error) => {
        serverError = error;
      },
    });

    const pcm = await readFile(inputPath);
    for (let offset = 0; offset < pcm.length; offset += CHUNK_BYTES) {
      await stream.send(pcm.subarray(offset, offset + CHUNK_BYTES));
      await pause(100);
    }

    // close() sends the last frame and waits for protocol is_final, a routed
    // error, or this timeout. It resolves rather than throwing on timeout.
    await stream.close(5);
    if (serverError) {
      throw new Error(serverError.message ?? serverError.code ?? 'Realtime stream failed');
    }
    if (!protocolFinalObserved) {
      throw new Error('Realtime stream ended before protocol is_final');
    }
    await writeFile(outputPath, Subtitles.fromWords(finalizedWords).toVtt(), 'utf8');
  } finally {
    await client.disconnect();
  }
}

void main().catch((error: unknown) => {
  console.error(error);
  process.exitCode = 1;
});

للتشغيل الناجح هذه النتائج الملحوظة:

  1. تُطبع كل استجابة باسم partial أوfinal أوspeech-final حسب راياتها.
  2. لا يبقى خطأ خادم مسجل عند انتهاء التدفق.
  3. يحتوي speech.vtt إشارات مبنية من توقيت الكلمات النهائية فقط.
  4. يفصل عميل JavaScript داخل finally؛ ويغلق سياق Python غير المتزامن موارد Socket.IO وHTTP الداخلية.

يجعل ملف PCM المحفوظ الاختبار قابلًا للتكرار. في الإنتاج، استبدل قراءة الملف والمؤقت بمصدر الميكروفون أو المكالمة، مع إبقاء حدود الحالة والإنهاء والتنظيف نفسها.

3. وفّق النص المؤقت والنهائي

النص الفوري حالة متغيرة، وليس سلسلة إلحاق واحدة. وجّه الاستجابات حسب id للبث، وعيّن رقم وصول محليًا في التطبيق، وطبق جدول الانتقال التالي. أبقِ seq القادمة من الخادم لبيانات التشخيص فقط لأن ترتيبها وتفرّدها ليسا جزءًا من العقد العام الحالي.

الإشارةالحالةإجراء واجهة الاستخدام والتخزين
is_final=false, is_speech_final=falseمؤقتةاستبدل العرض المؤقت الحالي لذلك التدفق؛ ولا تلحقه بالنص المثبت.
is_final=true مع أي قيمة لـis_speech_finalنتيجة نهائيةثبّت ذلك الحدث مرة بترتيب الوصول المرصود، ثم احذف القيمة المؤقتة التي تحل النتيجة محلها.
is_final=false وis_speech_final=trueنتيجة نهاية كلامثبّت كلمات ذلك الحدث بترتيب الوصول، وامسح النص المؤقت المستبدل، وسجل حد الكلام.
onError / on_error موجّهتدفق فاشلسجل الخطأ المنظم مرة، وأوقف تغذية الصوت، وابدأ التنظيف.

اجعل استدعاء الخطأ قابلًا للتكرار بأمان. قد يستدعي SDK 0.18.0 معالج خطأ التدفق أكثر من مرة لخطأ موجّه واحد.

لا تعرض استجابة مؤقتة متأخرة فوق نص مثبت. احتفظ بآخر نسخة مثبتة بصورة مستقلة عن السطر المؤقت القابل للتغيير كي لا يمحو انقطاع الاتصال النتائج المستقرة.

4. أنشئ الترجمات من الكلمات النهائية

في استدعاء الاستجابة، تجاهل الكلمات ما دام علما النهاية false. ألحق كلمات الأحداث النهائية أو أحداث نهاية الكلام بمصفوفة يملكها التطبيق حسب ترتيب الوصول المرصود. بعد الإنهاء، مرر المصفوفة إلى Subtitles.fromWords() / Subtitles.from_words() واعرض WebVTT.

يعرض SDK 0.18.0 أيضًا RealtimeSubtitles، الذي يتجاهل الأحداث المؤقتة ويزيل تكرار الأحداث النهائية حسب id:seq. لا تستخدمه لجمع عدة أحداث نهائية مع العقد السلكي الحالي، لأن قيم seq المتميزة غير مضمونة. تستخدم الأمثلة مسار التجميع الذي يملكه التطبيق، ولا تكتب ملف الترجمات إلا بعد إغلاق التدفق وفحص الأخطاء المسجلة.

5. أنهِ ضمن مهلة ونظف الموارد

عندما لا يبقى لدى المنتج صوت، استدعِ close(5) في JavaScript أو close(timeout_seconds=5.0) في Python. في الإصدار 0.18.0، يقوم الإغلاق بما يلي:

  1. يرسل إطار نهاية التدفق؛
  2. ينتظر is_final على مستوى البروتوكول أو خطأً موجهًا أو انتهاء المهلة الممررة؛ و
  3. ينجح عند انتهاء الانتظار بدل رفع خطأ مهلة.

الانتظار الافتراضي للنتيجة النهائية في SDK ثانية واحدة؛ تمرر الأمثلة خمس ثوان عمدًا. تحد هذه المهلة انتظار الإغلاق فقط. احتفظ بمهلة تطبيق مستقلة للاتصال وإنتاج الصوت والإرسال والجلسة كاملة.

لا يثبت نجاح الإغلاق وصول is_final. تمثل is_speech_final حد كلام ولا تنهي انتظار الإغلاق. افحص حالة النهاية على مستوى البروتوكول والخطأ التي سجلتها الاستدعاءات. إذا وجب التخلي عن التدفق، تزيل stop() / stop_sync() سياقه من دون الانتظار النهائي.

نفذ دائمًا تنظيف العميل بعد إنهاء التدفق. قد يزيل الخطأ الموجه سياق التدفق قبل تشغيل الإغلاق، لكن يجب أن تستدعي JavaScript الدالة disconnect() داخل finally، ويجب أن تخرج Python من سياق العميل رغم ذلك.

6. تعافَ عبر حد تدفق جديد فقط

لا يحدد العقد العام استئنافًا شفافًا للجلسة أو حالة الخادم الباقية بعد انقطاع الاتصال. عند خطأ موجّه أو انقطاع:

  1. أوقف تغذية التدفق القديم واحتفظ بالنتائج النهائية المثبتة فقط.
  2. تجاهل النص المؤقت غير المحسوم ونظف العميل القديم.
  3. إذا سمحت مهلة التطبيق وسياسة الإعادة، فأعد الاتصال بتراجع محدود مع عشوائية وأنشئ تدفقًا جديدًا بمعرّف جديد.
  4. أبقِ نتائج التدفق الجديد منفصلة حتى يدمج التطبيق الخطين الزمنيين المثبتين صراحة.

لا تفترض أن إعادة إرسال المقاطع السابقة آمنة؛ لا يوفر العقد موضع استئناف أو إقرارًا لكل مقطع. قد يلزم الاحتفاظ بالصوت الملتقط حول الانقطاع ومعالجته على حدة. راجع دليل دورة حياة Realtime لحد التعافي الكامل.

الخطوات التالية

في هذه الصفحة