أضف وكيلًا صوتيًا إلى Flutter عبر WebSocket مع Wixzel Voice
في النهاية يتيح تطبيق Flutter لديك للمستخدم التحدث إلى وكيل صوتي بنيته، عبر WebSocket، دون SIP trunk ودون رقم هاتف. يُنشئ خادمك جلسة تُستخدم مرة واحدة، فلا يصل مفتاح API إلى التطبيق أبدًا، وتتولى حزمة wixzel_voice لـ Dart البروتوكول. توقّع نحو ساعة، معظمها لتوصيل الميكروفون ومكبّر الصوت. تُحتسب الجلسة مثل مكالمة هاتفية على المحرك نفسه: $0.0491 لكل دقيقة اتصال على محرك Gemini Live، أي نحو $0.20 لجلسة مدتها أربع دقائق، بالثانية من رصيد مسبق الدفع، دون أي تكلفة اتصالات هاتفية.
بقلم Aqeel Shamsudheen · نُشر في · حُدّث في
- المدة
- نحو ساعة
- المحرك
- gemini-live
- وقت المحرك
- $0.0491 لكل دقيقة متصلة
ما الذي تحتاجه قبل أن تبدأ؟
- مفتاح API من Wixzel Voice مع رصيد، محفوظ على خادمك.
- خادم تتحكم فيه لإنشاء الجلسات. يستخدم المثال Node.js وExpress؛ وتصلح أي لغة قادرة على إرسال طلب HTTPS.
- تطبيق Flutter مع إضافة تسجيل تبث PCM بدقة 16 بت، ويُفضَّل بتردد 8 kHz أحادي القناة مع إلغاء الصدى، ومشغّل يقبل PCM خامًا.
كيف تبني هذا على Wixzel Voice، خطوة بخطوة؟
01أنشئ الوكيل مرة واحدة
يستخدم هذا الوكيل Gemini Live، وهو نموذج آني واحد يستمع ويفكر ويتكلم:
shellcurl https://api.voice.wixzel.com/v1/agents \ -H "Authorization: Bearer $WIXZEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "In-app assistant", "system_prompt": "You are the voice assistant inside the Acme app. Answer in one or two short sentences.", "opening_message": "Hi, what can I help you with?", "voice": { "realtime": { "model": "google/gemini-live", "voice": "Kore" } } }'لا شيء في هذا الوكيل خاص بالتطبيق: يمكن للوكيل نفسه أن يجيب على رقم هاتف. احتفظ بقيمة
idالخاصة به على خادمك باسمAGENT_ID.02أنشئ جلسة على خادمك
لا يحمل التطبيق مفتاح API أبدًا. يطلب من خادمك، ويطلب خادمك من الـ API جلسة عبر
POST /v1/realtime/sessions، هنا من خلال TypeScript SDK:server.mjsimport express from 'express'; import { WixzelVoice } from 'wixzel-voice'; const wixzel = new WixzelVoice({ apiKey: process.env.WIXZEL_API_KEY }); const app = express(); // Replace with your own authentication. It must set req.userId. function requireUser(req, res, next) { req.userId = 'demo-user'; next(); } app.post('/voice/session', requireUser, async (req, res) => { const session = await wixzel.realtime.createSession({ agent_id: process.env.AGENT_ID, max_duration_seconds: 300, metadata: { user_id: req.userId }, }); res.json(session); }); app.listen(3000);تحمل الاستجابة عنوان المقبس
url، وclient_secretيعمل مرة واحدة، لمدة دقيقة، لهذا الوكيل، وaudio_format: "mulaw_8000". إنشاء الجلسة مجاني؛ ويبدأ الاحتساب عند اتصال التطبيق. يحددmax_duration_secondsالحد الأقصى للمحادثة، من 10 إلى 3600 ثانية، و600 افتراضيًا. تُعادmetadataفي سجل المكالمة ولا تُرسل إلى النموذج أبدًا. لا تهمallowed_originsإلا للمتصفحات، لذا يتركها التطبيق الأصلي (native).03اتصل من التطبيق
أضف الـ SDK عبر
flutter pub add wixzel_voice http، ثم افتح الجلسة التي أعادها خادمك:voice_call.dartimport 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; import 'package:http/http.dart' as http; import 'package:wixzel_voice/wixzel_voice.dart'; /// Whatever your playback plugin offers, behind a small interface. abstract class PcmPlayer { void add(Int16List samples); void clear(); } class VoiceCall { VoiceCall({required this.player, required this.onLine}); final PcmPlayer player; final void Function(String role, String text) onLine; RealtimeConnection? _conn; StreamSubscription<Uint8List>? _mic; final List<int> _pending = []; /// [micBytes] is 16-bit little-endian PCM, 8 kHz mono, from your recording plugin. Future<void> start(Stream<Uint8List> micBytes) async { final res = await http.post(Uri.parse('https://api.example.com/voice/session')); final status = res.statusCode; if (status != 200) throw Exception('Could not start a voice session: HTTP $status'); final session = RealtimeSession.fromJson(jsonDecode(res.body) as Map<String, dynamic>); final conn = RealtimeConnection.forSession(session); _conn = conn; conn.events.listen((event) { switch (event) { case RealtimeAudio(:final mulaw): player.add(mulawToPcm16(mulaw)); case RealtimeAudioClear(): player.clear(); // the user interrupted: drop queued audio case RealtimeTranscript(:final role, :final text): onLine(role, text); case RealtimeError(:final code, :final message): onLine('error', '$code: $message'); case RealtimeEnded(:final reason): onLine('system', 'ended: $reason'); _mic?.cancel(); default: } }); await conn.ready; _mic = micBytes.listen(_send); } void _send(Uint8List bytes) { final data = ByteData.sublistView(bytes); for (var i = 0; i + 1 < bytes.length; i += 2) { _pending.add(data.getInt16(i, Endian.little)); } // 160 samples is 20 ms at 8 kHz, the frame size the server expects. while (_pending.length >= 160) { final frame = Int16List.fromList(_pending.sublist(0, 160)); _pending.removeRange(0, 160); _conn?.sendAudio(pcm16ToMulaw(frame)); } } Future<void> end() async { await _mic?.cancel(); await _conn?.end(); } }يتولى
RealtimeConnectionالبروتوكول فقط: لا يفتح الميكروفون ولا مكبّر الصوت، مما يبقي الـ SDK خاليًا من الاعتماد على الإضافات ويتيح لك استخدام إضافات الصوت التي لديك بالفعل. تحوّلpcm16ToMulawوmulawToPcm16بين PCM بدقة 16 بت وما يُرسل عبر الاتصال.اطلب إذن الميكروفون أولًا:
RECORD_AUDIOعلى Android وNSMicrophoneUsageDescriptionعلى iOS.04فعّل إلغاء الصدى، وتوقّع جودة صوت بمستوى الهاتف
فعّل إلغاء الصدى الخاص بالمنصة في إضافة التسجيل. بدونه يسمع الوكيل نفسه عبر مكبّر الصوت ويقاطع نفسه.
الصوت بصيغة G.711 µ-law بتردد 8 kHz في الاتجاهين، مثل خط الهاتف، لأن الجلسات تمر عبر مسار الوسائط نفسه الذي تمر به المكالمات. هذا مناسب للكلام وأضيق من الصوت عريض النطاق. سجّل بتردد 8 kHz إن سمحت الإضافة بذلك، أو أعد أخذ العينات قبل
pcm16ToMulaw، وشغّل صوت الوكيل بتردد 8 kHz أحادي القناة.05اقرأ الجلسة بعد انتهائها
كل جلسة هي سجل مكالمة مع
channel: "web":shellcurl "https://api.voice.wixzel.com/v1/calls?channel=web&limit=5" \ -H "Authorization: Bearer $WIXZEL_API_KEY"جلسة الويب واردة، وليس لها
fromأوto، وتحمل مدتها وcost_microsوmetadataالخاصة بك؛ ويضيفGET /v1/calls/{id}النص المكتوب. تنتهي بالـ webhook نفسه الذي ترسله المكالمة الهاتفية،callCompleted. لا تُسجَّل جلسات الويب.
كم يكلّف هذا البناء على Wixzel Voice؟
وقت المحرك لجلسات مدة كل منها 4 دقيقة على محرك gemini-live بسعر $0.0491 لكل دقيقة متصلة: نحو $0.20 للجلسة. لا توجد تكلفة اتصالات هاتفية، لأنه لا يوجد خط هاتفي.
| الجلسات شهريًا | الدقائق المتصلة | تكلفة المحرك شهريًا |
|---|---|---|
| 500 | 2,000 | $98.20 |
| 2,500 | 10,000 | $491 |
| 10,000 | 40,000 | $1,964 |
تكلف الجلسة ما تكلفه مكالمة هاتفية على المحرك نفسه، بالثانية، من الرصيد المسبق الدفع نفسه. إنشاء الجلسة مجاني، لذا فإن المستخدم الذي يفتح الشاشة ثم يرفض إذن الميكروفون لا يكلف شيئًا.
مسبق الدفع، دون اشتراك ودون خطة مجانية: أضِف رصيدًا بدءًا من $5 ويُستهلك مع الاستخدام. قدّر تكلفة استخدامك.
ما حدود Wixzel Voice التي تنطبق على هذا البناء؟
- صوت بجودة الهاتف: G.711 µ-law بتردد 8 kHz في الاتجاهين، وليس عريض النطاق.
- لا تلتقط Dart SDK الصوت ولا تشغّله. يوفّر تطبيقك الميكروفون ومكبّر الصوت عبر الإضافات التي يختارها.
- تُحتسب الجلسات ضمن حد المكالمات المتزامنة للحساب، خمس افتراضيًا، إلى جانب المكالمات الهاتفية. يُرفع الحد عند الطلب.
- تستمر الجلسة 10 دقائق افتراضيًا وساعة كحد أقصى، وينهيها صمت لمدة دقيقتين.
- لا تُسجَّل جلسات الويب، ولا يوجد خط ثانٍ لتحويل المستخدم إلى شخص.
الأسئلة الشائعة
هل توجد واجهة API لوكيل صوتي في Flutter؟
wixzel_voice على pub.dev، الصنف RealtimeConnection الذي يربط تطبيق Flutter أو Dart بوكيل صوتي عبر WebSocket. يُنشئ خادمك الجلسة بمفتاح API الخاص بك؛ ولا يحمل التطبيق إلا سرًا صالحًا لدقيقة واحدة ولمرة واحدة.هل أحتاج إلى رقم هاتف أو SIP trunk لوكيل صوتي داخل التطبيق؟
لماذا يستمر الوكيل في مقاطعة نفسه؟
هل يمكنني استخدام محرك مختلف في التطبيق؟
ماذا يحدث عندما ينفد رصيد الحساب أثناء الجلسة؟
session.warning بالرمز low_balance وعددًا تقريبيًا للثواني المتبقية، ثم تنتهي الجلسة بالسبب insufficient_credits. الجلسة التي لا يمكن تمويلها إطلاقًا تُرفض برمز الإغلاق 4402.للمزيد من القراءة
- الوقت الفعلي: الويب والهاتف المحمولالتوثيق
إنشاء الجلسات، والبروتوكول، ورموز الرفض، والاحتساب.
- حزم SDKالتوثيق
حزمتا Dart وTypeScript SDK، بما في ذلك عميل المتصفح.
- محركات الصوتالتوثيق
الأصوات الآنية والمركّبة، وقوائم الأصوات.
- Webhooksالتوثيق
callCompleted لجلسات الويب، مع الـ metadata الخاصة بك.
عن Wixzel Voice
Wixzel Voice هو API للذكاء الاصطناعي الصوتي لبناء وكلاء ذكاء اصطناعي يجرون مكالمات هاتفية حقيقية ويردّون عليها عبر SIP trunk الخاص بك، أو يتحدثون إلى الناس في تطبيقات الويب والجوال الخاصة بك. مفتاح API واحد ورصيد مدفوع مسبقًا واحد يغطيان كل محركات الصوت، مع فوترة بالثانية حسب الاستخدام الفعلي.
المزيد من الأدلة
Claude، في Claude Code أو claude.ai، يجري مكالمة حقيقية عبر SIP trunk الخاص بك ويقرأ لك النص المكتوب، مع طلب تأكيد قبل أي اتصال.
رقم Twilio الحالي لديك يجيب عليه موظف استقبال بالذكاء الاصطناعي يحجز المواعيد عبر الـ API، مبني بـ Node.js.
وكيلان هاتفيان، أحدهما يتحدث الهندية والآخر المالايالامية، على نماذج Sarvam المخصصة للغات الهند، يجيبان على المكالمات أو يجريانها عبر SIP trunk الخاص بك.
وكيل Gemini Live يتصل بهاتف حقيقي عبر SIP trunk الخاص بك من أربعة طلبات cURL، وسجل المكالمة مع النص المكتوب والتكلفة.
قائمة عملاء محتملين اتصل بهم وكيلك عبر SIP trunk الخاص بك، على دفعات بحجم حد التزامن لديك، مع قراءة حالة كل مكالمة ونصها المكتوب.
شخصية تعريفية ناطقة في زاوية موقعك يتصل بها الزوار من المتصفح، مقيّدة بنطاقاتك وحدودك اليومية، مع قائمة بكل متصل.
شاشة Flutter فيها الشخصية التعريفية لوكيلك تحرّك فمها مع صوت الوكيل أثناء مكالمة آنية، مع بقاء مفتاح API على خادمك.
ابنِ أول وكيل صوتي لك

