בעיה: חלון הקשר של LLM ונתוני סטרימינג
תרחיש טיפוסי: משתמש מכתיב הרצאה או מדביק קובץ PDF בן 100 עמודים. ה-LLM מקבל את הטקסט אך חלון ההקשר עולה על גדותיו — התגובה נקטעת או מתעלמת מהאמצע. או שתמליל פגישה זורם פנימה, ויש צורך בסיכום מיד לאחר מכן. אנו פותרים בעיות אלו ב-iOS וב-Android עם סיכום map-reduce, תמלול חי ופלט מובנה. אנו משתמשים ב-Swift 5.9, Kotlin, Flutter 3.x, gpt-4o, Core Data ו-Room. הפתרונות שלנו כוללים חלוקה אדפטיבית, עיבוד מקבילי ושמירה מקומית של סיכומים לגישה לא מקוונת. ניסיון — מעל 50 פרויקטים עם שילוב AI.
לדוגמה, עבור לקוח EdTech אחד, יישמנו סיכום הרצאות חי: קטעי אודיו של 30 שניות מעובדים על ידי SpeechRecognizer, והתמליל המצטבר מסוכם עם חלון נע כל 2000 מילים. התוצאה — הערות מובנות זמינות 5 דקות לאחר ההרצאה. הטכניקה המרכזית היא map-reduce: חלוקת המסמך לנתחים של 2500 טוקנים עם חפיפה של 200 טוקנים, סיכום במקביל, ולאחר מכן צמצום לסיכום סופי. זה מאפשר עיבוד מסמכים עד 500 עמודים ללא אובדן קוהרנטיות.
כיצד סיכום Map-Reduce פותר את בעיית המסמכים הארוכים
gpt-4o תומך ב-128k טוקנים של הקשר, אך העברת כל המסמך בכל פעם היא יקרה ואיטית. הדפוס הסטנדרטי — MapReduce:
- חלוקת המסמך לנתחים של 2000–3000 טוקנים עם חפיפה של ~200 טוקנים
- סיכום כל נתח באופן עצמאי (map)
- סיכום רשימת הסיכומים לסיכום סופי (reduce)
מידע נוסף על פרמטרי חלוקה
בפועל, גודל הנתח תלוי במודל: עבור gpt-4o-mini, 3000 טוקנים הוא אופטימלי, עבור gpt-3.5-turbo — 2000. החפיפה של 200 טוקנים מבטיחה שאף משפט לא נשבר בגבולות הנתח.
// iOS func summarizeDocument(_ text: String) async throws -> String {
let chunks = chunkText(text, maxTokens: 2500, overlap: 200)
// Параллельная суммаризация чанков
let partialSummaries = try await withThrowingTaskGroup(of: String.self) { group in
for chunk in chunks {
group.addTask { try await self.summarizeChunk(chunk) }
}
var results = [String]()
for try await result in group {
results.append(result)
}
return results
}
// Финальный reduce
let combined = partialSummaries.joined(separator: "\n\n")
return try await summarizeChunk(combined, isFinal: true)
}
func chunkText(_ text: String, maxTokens: Int, overlap: Int) -> [String] {
// ~4 символа = 1 токен для русского текста (приблизительно)
let chunkSize = maxTokens * 3
let overlapSize = overlap * 3
var chunks = [String]()
var start = text.startIndex
while start < text.endIndex {
let end = text.index(start, offsetBy: chunkSize, limitedBy: text.endIndex) ?? text.endIndex
chunks.append(String(text[start..<end]))
guard let nextStart = text.index(start, offsetBy: chunkSize - overlapSize, limitedBy: text.endIndex) else { break }
start = nextStart
}
return chunks
}// iOS func summarizeDocument(_ text: String) async throws -> String { let chunks = chunkText(text, maxTokens: 2500, overlap: 200) // Параллельная суммаризация чанков let partialSummaries = try await withThrowingTaskGroup(of: String.self) { group in for chunk in chunks { group.addTask { try await self.summarizeChunk(chunk) } } var results = [String]() for try await result in group { results.append(result) } return results } // Финальный reduce let combined = partialSummaries.joined(separator: "\n\n") return try await summarizeChunk(combined, isFinal: true) } func chunkText(_ text: String, maxTokens: Int, overlap: Int) -> [String] { // ~4 символа = 1 токен для русского текста (приблизительно) let chunkSize = maxTokens * 3 let overlapSize = overlap * 3 var chunks = [String]() var start = text.startIndex while start < text.endIndex { let end = text.index(start, offsetBy: chunkSize, limitedBy: text.endIndex) ?? text.endIndex chunks.append(String(text[start..<end])) guard let nextStart = text.index(start, offsetBy: chunkSize - overlapSize, limitedBy: text.endIndex) else { break } start = nextStart } return chunks } מאפשר ביצוע מקבילי של משימות עבור כל נתח. עבור 10 נתחים, זה מהיר פי 5–7 מעיבוד רציף.
מדוע פלט מובנה משפר את חוויית המשתמש
סיכומים יכולים להיות מכמה סוגים. הנחיות לכל אחד:
| סוג | הוראת הנחיה |
|---|---|
| סיכום קצר | «סכם ב-3-5 משפטים. רק נקודות מפתח.» |
| נקודות | «חלץ 5-8 נקודות מפתח כרשימת תבליטים. כל נקודה = רעיון אחד.» |
| מפת חשיבה JSON | «החזר JSON: {title, branches: [{topic, subtopics: []}]}» |
| שאלות ותשובות | «צור 5 שאלות ותשובות על בסיס הטקסט.» |
| פריטי פעולה | «חלץ רק פריטי פעולה ומועדים. פורמט: - [משימה]: [מועד/אחראי]» |
עבור פלט מובנה, אנו משתמשים ב-withThrowingTaskGroup ב-OpenAI API — המודל חייב להחזיר JSON תקין ללא עטיפת markdown.
let requestBody: [String: Any] = [
"model": "gpt-4o-mini",
"messages": messages,
"response_format": ["type": "json_object"],
"temperature": 0.2
] תמלול חי: עיבוד אודיו בזמן אמת
אם המקור הוא מיקרופון (הקלטת הרצאה, פגישה), הסיכום נבנה על גבי התמלול. הזרימה:
response_format: { type: "json_object" } → קטעים של 30 שניות → let requestBody: [String: Any] = [ "model": "gpt-4o-mini", "messages": messages, "response_format": ["type": "json_object"], "temperature": 0.2 ] (Whisper API או AVAudioEngine מקורי) → תמליל מצטבר → סיכום עם חלון נע.
// Суммаризация каждые 5 минут транскрипта с перекрытием
class LiveSummaryEngine {
private var transcript = ""
private var lastSummaryLength = 0
func onNewTranscript(_ chunk: String) {
transcript += " " + chunk
// Суммаризируем новый блок при накоплении ~2000 слов
let wordCount = transcript.split(separator: " ").count
if wordCount - lastSummaryLength > 2000 {
Task {
await summarizeNewBlock()
}
lastSummaryLength = wordCount
}
}
private func summarizeNewBlock() async {
let newContent = transcript.components(separatedBy: " ")
.dropFirst(max(0, lastSummaryLength - 200)) // перекрытие 200 слов
.joined(separator: " ")
let summary = try? await llmService.summarize(newContent)
await MainActor.run {
appendToNotes(summary ?? "")
}
}
}ב-Android, השתמש ב-SpeechRecognizer + SFSpeechRecognizer עם חלוקה לפי // Суммаризация каждые 5 минут транскрипта с перекрытием class LiveSummaryEngine { private var transcript = "" private var lastSummaryLength = 0 func onNewTranscript(_ chunk: String) { transcript += " " + chunk // Суммаризируем новый блок при накоплении ~2000 слов let wordCount = transcript.split(separator: " ").count if wordCount - lastSummaryLength > 2000 { Task { await summarizeNewBlock() } lastSummaryLength = wordCount } } private func summarizeNewBlock() async { let newContent = transcript.components(separatedBy: " ") .dropFirst(max(0, lastSummaryLength - 200)) // перекрытие 200 слов .joined(separator: " ") let summary = try? await llmService.summarize(newContent) await MainActor.run { appendToNotes(summary ?? "") } } } .
היכן לאחסן סיכומים?
סיכומים חייבים להיות זמינים לא מקוונים ולתמוך בחיפוש. ב-iOS — Core Data או SpeechRecognizer עם אינדקס טקסט מלא דרך MediaRecorder עם SQLite FTS5. לפי תיעוד Apple, אינדקס טקסט מלא FTS5 מאיץ חיפוש פי 10. ב-Android — RECOGNIZER_RESULT_STABILITY עם SwiftData או הערת NSPersistentStoreDescription.
חיפוש סמנטי (לפי משמעות, לא מילים) — דרך embeddings וקטוריים המאוחסנים מקומית ב-Room או בשרת דרך pgvector. עבור אפליקציות מובייל, חיפוש embeddings בצד השרת עם תוצאות שמורות במטמון מספיק.
תוכנית יישום שלב אחר שלב
- ניתוח דרישות: קביעת סוג התוכן (טקסט/אודיו), תדירות, צורך בגישה לא מקוונת.
- בחירת מודל:
@Fts4למהירות,@Fts5למקרים מורכבים. - יישום חלוקה וסיכום: שימוש ב-map-reduce עם משימות מקבילות.
- שילוב תמלול: חיבור AVAudioEngine/SpeechRecognizer ב-iOS או SpeechRecognizer ב-Android.
- הגדרת אחסון: בחירת Core Data או Room עם FTS לחיפוש.
- בדיקות: הרצה על נתונים אמיתיים, אימות איכות הסיכום.
כל שלב מלווה בתיעוד ארכיטקטוני וקוד מקור. אנו גם מאמנים את הצוות שלך בתכונות AI ומספקים אחריות עד 6 חודשים לאחר המסירה.
מה כלול
- תיעוד ארכיטקטורה ושילוב API
- קוד מקור עם הערות ובדיקות
- גישה לריפוזיטורי (Git) וצינור CI/CD
- הכשרת צוות (2 מפגשים)
- תמיכת אחריות ל-6 חודשים
לוחות זמנים משוערים
| משימה | מסגרת זמן |
|---|---|
| סיכום API בסיסי | 2–3 ימים |
| Map-reduce + פורמטי פלט מרובים | 1.5 שבועות |
| סיכום חי עם תמלול | 3–4 שבועות |
העלות מחושבת באופן אישי לאחר ניתוח הפרויקט. חיסכון בזמן בהכנת חומרים לפרויקט אחד הגיע ל-80%.
הניסיון והאחריות שלנו
אנו מתמחים בפיתוח מובייל עם AI למעלה מ-6 שנים. השלמנו 50+ פרויקטים, כולל אפליקציות עם סיכום מסמכים, דיבור לטקסט ותמלול חי. אנו משתמשים בטכנולוגיות מודרניות: Swift 5.9, Kotlin, Flutter 3.x, OpenAI API, Firebase, Core Data, Room. אנו מבטיחים עמידה בהנחיות App Store Review וב-Google Play Policy.
צור קשר — נבחן את הפרויקט שלך. הזמן פיתוח מקצה לקצה וקבל ייעוץ למקרה השימוש שלך.







