AWS تمكّن وكلاء الذكاء الاصطناعي الصوتيين من الكلام منذ الجملة الأولى عبر Kokoro-82M

وصفت AWS مخططاً لا ينتظر فيه الوكيل الصوتي المعتمد على الذكاء الاصطناعي حتى ينتهي النموذج من كتابة الرد كله. فكل جملة جاهزة تُرسل فوراً إلى Kokoro-82M المنشور على SageMaker AI، فيبدأ النطق من العبارة الأولى. وكان العميل سابقاً يبقى صامتاً نحو خمس ثوانٍ، مع أن الرد القصير يُولَّد في 600 مللي ثانية. يبلغ حجم النموذج 82 مليون معامل فقط، ويعمل على وحدة GPU واحدة من نوع ml.g5.xlarge. وتبقى جلسة WebSocket ثنائية الاتجاه واحدة بين العميل والحاوية: يمر الصوت وأوامر JSON عبر قناة واحدة، دون base64 ودون إعادة اتصال متكررة. • يضخّم Base64 في SSE حجم الصوت بنحو 33%: ملف MP3 48k حجمه 308 304 بايت، وبعد الترميز يصبح 411 072 • يظهر الخطأ KeyError('text') لأن SageMaker لا ترسل سوى إطارات ثنائية • الوكيل المبني على Strands + Bedrock AgentCore يعمل في وضع bidi فيُداخل بين التوليد النصي والتركيب الصوتي

معالج بواسطة الذكاء الاصطناعي من AWS Machine Learning Blog؛ بتحرير Hamidun News
AWS تمكّن وكلاء الذكاء الاصطناعي الصوتيين من الكلام منذ الجملة الأولى عبر Kokoro-82M
المصدر: AWS Machine Learning Blog. كولاج: Hamidun News.
استمع للمقال
بطاقة الحقائق والأرقام
المعيارالتفاصيل
المصدر الأصليAWS Machine Learning Blog ↗
رئيس التحرير
يضخّم Base64 في SSE حجم الصوت بنحو 33%ملف MP3 48k حجمه 308 304 بايت، وبعد الترميز يصبح 411 072
حقيقة رئيسية #2يظهر الخطأ KeyError('text') لأن SageMaker لا ترسل سوى إطارات ثنائية
حقيقة رئيسية #3الوكيل المبني على Strands + Bedrock AgentCore يعمل في وضع bidi فيُداخل بين التوليد النصي والتركيب الصوتي

يطرح المستخدم سؤالاً على وكيل صوتي وينتظر. الرد مولَّد جزئياً بالفعل، لكن لا صوت بعد: فتكاملات تحويل النص إلى كلام (text-to-speech) تنتظر عادةً انتهاء النص كله. يُنتج النموذج جملة واحدة في 600 مللي ثانية، بينما يستغرق الرد الطويل كاملاً نحو خمس ثوانٍ. وطوال هذه المدة يسمع العميل الصمت. وفي مدونتها بيّنت AWS كيف يمكن سدّ هذه الفجوة: إرسال كل جملة إلى المُركِّب الصوتي Kokoro-82M على Amazon SageMaker AI فور أن ينهيها الوكيل.

نموذج صغير، وحدة GPU واحدة، ومساران

يضم Kokoro-82M ‏82 مليون معامل، وهذا يكفي لتشغيله على نسخة (instance) متوسطة الفئة ببطاقة رسومية واحدة. يعمل النموذج في حاوية مخصصة على نقطة نهاية real-time في SageMaker AI، ونوع النسخة ml.g5.xlarge. وتتيح الحاوية مسارين: POST /invocations للرد المتدفق على إدخال واحد مكتمل، وWS /invocations-bidirectional-stream حيث يبقى اتصال واحد مفتوحاً لرسائل كثيرة في الاتجاهين. ويجب أن تعلن الحاوية عن وسم Docker هو com.amazonaws.sagemaker.capabilities.bidirectional-streaming=true.

يقتصر النشر على أمرين مبنيين على AWS CDK: الأمر `uv run speech-infra deploy kokoro-82m` يُنشئ نقطة النهاية (أدوار IAM، وذاكرة تخزين مؤقت للأوزان في S3، وصورة الحاوية، والنموذج، والإعدادات)، أما الأمر `uv run agent-infra deploy --bedrock-model-id <id>` فينشر الوكيل المبني على Strands في بيئة Amazon Bedrock AgentCore. الترتيب مهم: فالوكيل يتصل بنقطة نهاية موجودة مسبقاً. ويلزم Python 3.12+ لأن عميل HTTP/2 لا يأتي في حزم wheel جاهزة إلا لهذه الإصدارات. ويوجّه المؤلفون تحذيراً منفصلاً: المثال يثبّت إصداراً قديماً من PyTorch فيه ثغرة معروفة PYSEC-2026-2286 (أُصلحت في torch 2.10.0)، ويجب تحديثه قبل أي استخدام يتجاوز مرحلة proof-of-value.

ثلاثة أفخاخ في البث ثنائي الاتجاه

الفخ الأول هو نوع إطار WebSocket. تسلّم SageMaker كل رسالة من العميل على هيئة إطار ثنائي. أما المعالج المعتاد على Starlette فيستدعي receive_text()، ويقرأ message["text"]، ثم يتعطل بالخطأ KeyError. وإذا أرسل معالج الاستثناءات العام str(e) إلى العميل، فسيرى الخطأ «'text'» الذي يبدو كحقل مفقود لا كمشكلة في تأطير الرسائل. ومع ذلك تعرض نقطة النهاية الحالة InService بصدق: فهي تقبل الاتصالات وترد على كل رسالة، لكن بخطأ. والحل أن يقبل المعالج النوعين من الإطارات، وأن يفك ترميز bytes بصيغة UTF-8 إذا غاب الحقل text.

الفخ الثاني هو الإغلاق المبكر للجلسة. بعد إرسال الصوت وإطار التحكم synthesis_complete يجب أن تعود الحاوية إلى receive() وتنتظر الرسالة التالية. ولا ينبغي إغلاق الاتصال إلا عند وصول {"type": "close"} الصريح. وإلا فسيلزم اتصال جديد لكل جملة، ويضيع المكسب في زمن الاستجابة.

الفخ الثالث هو تقطيع التدفق إلى جمل. يستقبل الصنف IncrementalSentenceChunker النص على دفعات عبر feed()، ويفصل الجمل المكتملة بتعبير نمطي، ويحتفظ بالذيل غير المكتمل. وفي النهاية يأخذ flush() الباقي الذي لا تنتهي بنقطة. ويشدد المؤلفون بصراحة على أن هذا ليس تركيباً صوتياً متوازياً. فالمعالج متسلسل بصرامة: استلام الرسالة، ثم التركيب، ثم إرسال الصوت، ثم إرسال synthesis_complete. والمكسب الوحيد هو أن تركيب الجملة الأولى يبدأ فور ظهورها. وعلى نقطة نهاية حية اختُبرت ثلاث جمل ضمن جلسة واحدة:

  • «Hello there.» — 73 200 بايت من الصوت، 1,525 ثانية
  • «This is a post deployment check.» — 108 000 بايت، 2,250 ثانية
  • «It has three sentences.» — 98 400 بايت، 2,050 ثانية

SSE مع base64 مقابل التعدد الثنائي

لتسليم الصوت إلى المتصفح قارنت AWS بين نهجين. الأول هو SSE المألوف: يُرمَّز الصوت بصيغة base64 ويُغلَّف في حدث JSON. وعلى 24 مقطعاً قيس ثمن هذا الحل (ولم تتأثر الجودة المقيسة عبر Amazon Transcribe بطريقة الترميز):

  • Raw PCM: ‏2 396 400 بايت، وبعد base64 ‏3 195 200، ومعدل البت 384 كيلوبت/ث
  • MP3 48k: ‏308 304 بايت، وبعد base64 ‏411 072، و49,5 كيلوبت/ث
  • AAC 32k fMP4: ‏258 039 بايت، وبعد base64 ‏344 084، و41,6 كيلوبت/ث

يضيف Base64 نحو 33% المتوقعة تقريباً. وهناك مشكلة أقل وضوحاً: فحدود أحداث SSE (السطر الفارغ) لا تتطابق مع حدود إطارات MP3 ولا مع الطريقة التي يقطّع بها وسيط النقل البيانات. ففي أحد القياسات وصلت أربعة إطارات MP3 منطقية على هيئة ستة أجزاء (chunks)، انقطع اثنان منها في منتصف إطار. والمستهلك الذي يعدّ كل جزء إطاراً كاملاً يتعطل حتى مع حركة بيانات سليمة. ويُعالَج ذلك بمخزن مؤقت: نجمع البايتات ولا نعالج الإطار إلا بعد العثور على الفاصل `\n\n`.

أما النهج الثاني فهو التخلي تماماً عن base64 وSSE في المقطع الممتد حتى النموذج. يمر الصوت الثنائي وأوامر JSON عبر جلسة واحدة ثنائية الاتجاه، ويفرّق بينهما قاعدة واحدة: إذا أمكن تحليل الجزء بأكمله كائنَ JSON فهو إطار تحكم، وإلا فهو صوت. أما الفحص بالبايت الأول فلا يصلح: فقد يبدأ PCM بالقيمة 0x7b، أي بالرمز «{». لكن جزء PCM كاملاً لن يصبح JSON صالحاً تقريباً أبداً.

الوكيل بكامل مكوناته

في الإعداد الكامل يعمل الوكيل المبني على Strands، بنموذج من Amazon Bedrock، داخل Bedrock AgentCore runtime، ويستخدم ثلاث قنوات: SSE نحو المتصفح، واستدعاء Bedrock المتدفق للنص، وجلسة ثنائية الاتجاه مع Kokoro للتركيب الصوتي. وفي الطلب حقل mode بقيمة «bidi» أو «batch». وفي run_bidi تعمل مهمتان بالتوازي: الأولى تقرأ تدفق Bedrock وتمرر الجمل الجاهزة إلى المقطِّع، والثانية تقرأ ردود Kokoro وتُخرج الصوت. وتضع المهمتان نتيجتهما في طابور مشترك، ولذلك قد يصل صوت الجمل الأولى إلى المستدعي بينما لا يزال Bedrock يكتب الجمل اللاحقة. أما run_batch فينتظر النص كاملاً ويجري استدعاء تركيب واحداً، وهو أساس مقارنة عادل.

وفي المقطع بين الوكيل والمستدعي يبقى base64 قائماً على أي حال: فنقطة الدخول entrypoint في BedrockAgentCoreApp ملزمة بإعادة قواميس قابلة للتسلسل إلى JSON، وتحوّلها SDK بنفسها إلى إطارات `data: {json}`. ولا يمكن تمرير إطار ثنائي عبرها. وهذا الثمن واحد في الوضعين، لذا فإن الفرق في القياسات يعكس التوقيت الصرف لأول جزء صوتي.

ماذا نختار عملياً

إذا كانت الواجهة الأمامية تعمل أصلاً بـ SSE للنص، فإن base64 داخل الحدث خيار عملي: زيادة 33% في الحجم ومخزن مؤقت للتجميع، لكن البنية التحتية جاهزة. وإذا كانت القناة الصوتية تُبنى من الصفر أو كان زمن الاستجابة حرجاً، فإن المخطط الثنائي الموحد مع تحكم JSON يلغي التكلفتين معاً، ويستخدم اتصالاً يحمل النص أصلاً. وفي جميع الأحوال ينبغي اختبار الإطارات الثنائية تحديداً، والتحقق من التجميع عند حدود إطارات MP3، وعدم إغلاق الجلسة بعد كل جملة. وقبل الانتقال إلى الإنتاج، يجب تحديث إصدار PyTorch المثبّت.

القصة

أسئلة وأجوبة حول الخبر

ما أبرز التفاصيل حول نموذج صغير، وحدة GPU واحدة، ومساران؟

يضم Kokoro-82M ‏82 مليون معامل، وهذا يكفي لتشغيله على نسخة (instance) متوسطة الفئة ببطاقة رسومية واحدة. يعمل النموذج في حاوية مخصصة على نقطة نهاية real-time في SageMaker AI، ونوع النسخة ml.g5.xlarge. وتتيح الحاوية مسارين: POST /invocations للرد المتدفق على إدخال واحد مكتمل، وWS /invocations-bidirectional-stream حيث يبقى اتصال واحد مفتوحاً لرسائل كثيرة في الاتجاهين. ويجب أن تعلن الحاوية عن وسم Docker هو com.amazonaws.sagemaker.capabilities.bidirectional-streaming=true. يقتصر النشر على أمرين مبنيين على AWS CDK: الأمر `uv run speech-infra deploy kokoro-82m` يُنشئ نقطة النهاية

ما أبرز التفاصيل حول ثلاثة أفخاخ في البث ثنائي الاتجاه؟

الفخ الأول هو نوع إطار WebSocket. تسلّم SageMaker كل رسالة من العميل على هيئة إطار ثنائي. أما المعالج المعتاد على Starlette فيستدعي receive_text()، ويقرأ message["text"]، ثم يتعطل بالخطأ KeyError. وإذا أرسل معالج الاستثناءات العام str(e) إلى العميل، فسيرى الخطأ «'text'» الذي يبدو كحقل مفقود لا كمشكلة في تأطير الرسائل. ومع ذلك تعرض نقطة النهاية الحالة InService بصدق: فهي تقبل الاتصالات وترد على كل رسالة، لكن بخطأ. والحل أن يقبل المعالج النوعين من الإطارات، وأن يفك ترميز bytes بصيغة UTF-8 إذا غاب الحقل text. الفخ الثاني هو الإغلاق المبكر للجلسة. بعد إرسال الصوت وإطار التحكم s

ما أبرز التفاصيل حول SSE مع base64 مقابل التعدد الثنائي؟

لتسليم الصوت إلى المتصفح قارنت AWS بين نهجين. الأول هو SSE المألوف: يُرمَّز الصوت بصيغة base64 ويُغلَّف في حدث JSON. وعلى 24 مقطعاً قيس ثمن هذا الحل (ولم تتأثر الجودة المقيسة عبر Amazon Transcribe بطريقة الترميز): Raw PCM: ‏2 396 400 بايت، وبعد base64 ‏3 195 200، ومعدل البت 384 كيلوبت/ث MP3 48k: ‏308 304 بايت، وبعد base64 ‏411 072، و49,5 كيلوبت/ث AAC 32k fMP4: ‏258 039 بايت، وبعد base64 ‏344 084، و41,6 كيلوبت/ث يضيف Base64 نحو 33% المتوقعة تقريباً. وهناك مشكلة أقل وضوحاً: فحدود أحداث SSE (السطر الفارغ) لا تتطابق مع حدود إطارات MP3 ولا مع الطريقة التي يقطّع بها وسيط النقل ال

ZK
جمال حميدون — مدير المنتجات في Alpina Digital ومدير منتج AlpinaGPT. يبني منتجات الذكاء الاصطناعي منذ 2019. بدأ Hamidun News كمشروع شخصي ثم أصبح مُجمِّعًا إنتاجيًا بأكثر من 6000 قارئ.
✦ Autonomous AI Execution · Nomad Agent

Delegate Deep Research, Coding & Multi-Step Workflows to Nomad Agent

Deploy autonomous task execution with Nomad Agent (agent.nomadnet.ai) — automated web research, data synthesis, code generation, and MCP tool orchestration.

Launch Nomad Agent →Nomad AI Workspace
ما رأيك؟
جارٍ تحميل التعليقات…