בעת פיתוח עוזר AI קולי לאפליקציית מובייל, צוותים רבים נתקלים בעיכובים העולים על 3 שניות, טריגרים שגויים של VAD וקפיאות. אנחנו פותרים בעיות אלה: לעוזרים שלנו יש זמן השהיה של כ-1.2 שניות, שיעור VAD שגוי מתחת ל-0.5%, וצריכת טוקנים מופחתת ב-40% באמצעות ניהול קונטקסט מתקדם. למהנדסים שלנו יש ניסיון של למעלה מ-10 שנים בפיתוח מובייל ו-20+ פרויקטים עם ממשקי קול. אנו מפתחים עוזרי AI קוליים עם מצב שיחה עבור אפליקציות מובייל. זה לא רק שרשרת של STT, GPT ו-TTS — זה ניהול מצב שיחה, הפרעות, חלון קונטקסט וסשן אודיו שאינו מתנגש עם אפליקציות מערכת. קבלו ייעוץ לתרחיש שלכם — אנו נבחר את הסטאק והארכיטקטורה האופטימליים.
כיצד מכונת מצבים פותרת תנאי מרוץ
enum AssistantState {
case idle
case listening
case transcribing
case thinking(history: [Message])
case speaking(text: String)
case error(Error)
}
class AssistantViewModel: ObservableObject {
@Published private(set) var state: AssistantState = .idle
func startListening() {
guard case .idle = state else { return }
state = .listening
audioCapture.start { [weak self] audioData in
self?.handleAudioChunk(audioData)
}
}
func onSilenceDetected() {
guard case .listening = state else { return }
state = .transcribing
audioCapture.stop()
Task {
await transcribeAndRespond()
}
}
private func transcribeAndRespond() async {
do {
let text = try await stt.transcribe(audioCapture.buffer)
state = .thinking(history: conversationHistory)
let response = try await llm.chat(messages: conversationHistory + [.user(text)])
conversationHistory.append(.user(text))
conversationHistory.append(.assistant(response))
state = .speaking(text: response)
await tts.speak(response)
state = .idle
} catch {
state = .error(error)
}
}
}המפתח הוא מעבר למצב הבא רק מהמצב הקודם הצפוי (enum AssistantState { case idle case listening case transcribing case thinking(history: [Message]) case speaking(text: String) case error(Error) } class AssistantViewModel: ObservableObject { @Published private(set) var state: AssistantState = .idle func startListening() { guard case .idle = state else { return } state = .listening audioCapture.start { [weak self] audioData in self?.handleAudioChunk(audioData) } } func onSilenceDetected() { guard case .listening = state else { return } state = .transcribing audioCapture.stop() Task { await transcribeAndRespond() } } private func transcribeAndRespond() async { do { let text = try await stt.transcribe(audioCapture.buffer) state = .thinking(history: conversationHistory) let response = try await llm.chat(messages: conversationHistory + [.user(text)]) conversationHistory.append(.user(text)) conversationHistory.append(.assistant(response)) state = .speaking(text: response) await tts.speak(response) state = .idle } catch { state = .error(error) } } } ). זה מבטל תנאי מרוץ עם אירועים מקבילים. למידע נוסף על מכונות מצבים סופיות.
כיצד ליישם Barge-in?
המשתמש מדבר מעל תגובת העוזר. צריך: לעצור TTS, לבטל את בקשת ה-LLM הנוכחית, ולהתחיל להאזין שוב.
ב-iOS:
func handleBargeIn() {
tts.stopSpeaking(at: .immediate)
currentLLMTask?.cancel()
audioCapture.reset()
state = .listening
audioCapture.start { ... }
}VAD חייב לעבוד במקביל במהלך השמעה. אם guard case במצב func handleBargeIn() { tts.stopSpeaking(at: .immediate) currentLLMTask?.cancel() audioCapture.reset() state = .listening audioCapture.start { ... } } , המיקרופון זמין במקביל לרמקול. סף ה-VAD במהלך דיבור צריך להיות מוגבה ב-30%, אחרת הד מהרמקול יפעיל Barge-in. ראו כיצד VAD עובד.
מה לבחור: Push-to-Talk או מילת הפעלה?
| קריטריון | Push-to-Talk | מילת הפעלה |
|---|---|---|
| תחילת הקלטה | בלחיצת כפתור | פקודה קולית |
| הפעלות שגויות | אין | אפשריות |
| צריכת חשמל | נמוכה | גבוהה פי 5 |
| השהיה | מינימלית | קטנה (זיהוי מילה) |
| מורכבות אינטגרציה | נמוכה | בינונית |
| מצב רקע | אופציונלי | נדרש (ForegroundService) |
Push-to-Talk צורך פי 5 פחות חשמל מאשר מילת הפעלה ויש לו אפס הפעלות שגויות. מתאים לכלים מקצועיים. מילת הפעלה באמצעות Picovoice Porcupine פעילה תמיד, רצה על המכשיר (< 1% CPU), ותומכת במילים מותאמות אישית.
דוגמת אינטגרציה באנדרואיד:
val porcupine = Porcupine.Builder()
.setAccessKey(accessKey)
.setKeyword(Porcupine.BuiltInKeyword.HEY_GOOGLE)
.build(context)
porcupineManager = PorcupineManager.Builder()
.setAccessKey(accessKey)
.setKeyword(Porcupine.BuiltInKeyword.HEY_GOOGLE)
.build(context) { keywordIndex ->
runOnUiThread {
viewModel.onWakeWordDetected()
}
}
porcupineManager.start()מילת הפעלה במצב רקע באנדרואיד דורשת AVAudioSession עם התראה. בלעדיה, המערכת תהרוג את התהליך.
ניהול חלון הקונטקסט
GPT-4o תומך ב-128K טוקנים, אבל שליחת כל היסטוריית השיחה בכל בקשה עולה כסף ומגדילה את ההשהיה. החיסכון האופייני עם תצורה נכונה מגיע ל-40% בעלויות API, שעם נפח ממוצע של 50,000 בקשות בחודש מניב חיסכון משמעותי.
שיטות ניהול קונטקסט
| שיטה | תיאור | חיסכון בטוקנים |
|---|---|---|
| חלון מתגלגל | שמירת N ההודעות האחרונות (15–20) | 40% |
| סיכום | סיכום הודעות ישנות לאחת | 60% |
| סינון רלוונטיות | בחירת קטעים רלוונטיים באמצעות embeddings | 50% |
עבור רוב העוזרים הניידים, חלון מתגלגל מספיק. כך מגדירים אותו שלב אחר שלב:
- הגדירו את גודל החלון (בדרך כלל 15–20 הודעות).
- שמרו היסטוריה במערך
.playAndRecord. - בכל בקשה, העבירו את N ההודעות האחרונות.
- כשהמגבלה נחצית, הסירו את ההודעות הישנות ביותר.
כיצד להפחית השהיית TTS?
TTS בסטרימינג הוא המפתח להשהיה נמוכה (מתחת ל-300 אלפיות השנייה). OpenAI TTS תומך בסטרימינג: התגובה מגיעה בנתחי val porcupine = Porcupine.Builder() .setAccessKey(accessKey) .setKeyword(Porcupine.BuiltInKeyword.HEY_GOOGLE) .build(context) porcupineManager = PorcupineManager.Builder() .setAccessKey(accessKey) .setKeyword(Porcupine.BuiltInKeyword.HEY_GOOGLE) .build(context) { keywordIndex -> runOnUiThread { viewModel.onWakeWordDetected() } } porcupineManager.start() , והלקוח מתחיל בהשמעה לפני קבלת כל האודיו.
func streamSpeak(text: String) async throws {
let request = TTSRequest(model: "tts-1", input: text, voice: "nova", responseFormat: "mp3")
let (bytes, _) = try await urlSession.bytes(for: ttsURLRequest(request))
var audioData = Data()
for try await byte in bytes {
audioData.append(byte)
if audioData.count > 8192 {
try audioPlayer.enqueueChunk(audioData)
audioData = Data()
}
}
}עבור ביטויים שחוזרים על עצמם תדיר ("אני מקשיב", "נא להמתין", "לא הבנתי"), סנתזו אודיו מראש מקומית. זה מבטל השהיה לתגובות טיפוסיות.
כיצד פועל זיהוי הפסקות בזמן אמת (VAD)?
VAD עובד על בסיס אנרגיית אות ומאפיינים ספקטרליים. עבור מכשירים ניידים, אנו משתמשים ב-WebRTC VAD — הוא קל משקל ונותן השהיה מתחת ל-30 אלפיות השנייה. פרמטר ForegroundService נע בין 0 (האגרסיבי ביותר) ל-3 (שמרני). עבור חללים פתוחים, אנו ממליצים על mode=1, שנותן <0.5% הפעלות שגויות.
טעויות אופייניות וכיצד להימנע מהן
- חוסר במכונת מצבים — מוביל לתנאי מרוץ ב-90% מהמקרים.
- התעלמות מ-Barge-in — המשתמש לא יכול להפריע לתגובה, חוויית המשתמש נפגעת.
- שליחת כל ההיסטוריה ל-LLM — השהיה של עד 6 שניות ובזבוז של 40% טוקנים.
- ערבוב VAD ו-TTS ללא עדיפויות — הד גורם לזיהויים שגויים ב-30% מהמקרים.
- אין מטמון TTS — כל ביטוי מסונתז שוב, מה שמגדיל את ההשהיה.
מה כלול בעבודה
- תיעוד ארכיטקטוני: דיאגרמות מצבים, דיאגרמות זרימת אודיו, בחירת סטאק.
- קוד מקור עם הערות, בדיקות (יחידה ואינטגרציה).
- אינטגרציה עם השרת שלכם: REST/GraphQL, WebSocket, התראות push (APNs/FCM).
- הגדרת CI/CD עבור App Store ו-Google Play.
- הדרכת צוות: סדנה לתמיכה ושיפור העוזר.
- תמיכה טכנית: שבועיים לאחר השחרור לתיקון באגים.
תהליך
- אנליטיקה: ביקורת על הפתרון הנוכחי (אם קיים), הגדרת תרחישים.
- עיצוב: פיתוח מכונת מצבים, בחירת סטאק (STT, LLM, TTS).
- יישום: אינטגרציה של VAD, Barge-in, ניהול קונטקסט, מצב רקע.
- בדיקות: בדיקות עומס, בדיקות השהיה והפעלות שגויות.
- פריסה: פרסום ל-App Store / Google Play, הגדרת מפתחות API.
לוחות זמנים
MVP עם Push-to-Talk, Whisper STT, GPT-4o, OpenAI TTS — בין 2 ל-3 שבועות לפלטפורמה. עוזר מלא עם מילת הפעלה, Barge-in, TTS בסטרימינג, ניהול קונטקסט ומצב רקע — בין 6 ל-10 שבועות.
אנו מבטיחים פעולה יציבה של העוזר בזכות מהנדסים מוסמכים וניסיון בפריסה לייצור. צרו קשר להערכת פרויקט. הזמינו ביקורת על הפתרון הנוכחי שלכם — אנו נזהה צווארי בקבוק ונציע תוכנית אופטימיזציה.







