Завантаження та індексація документів для RAG у мобільних додатках
Додаток вилітає при завантаженні PDF 50+ МБ — типова скарга на етапі QA. У 90% випадків пряме завантаження в пам'ять призводить до OutOfMemoryError на Android і низької продуктивності на iOS. Ми вирішили це потоковою передачею даних та асинхронним pipeline. Користувач прикріплює PDF із Files.app або галереї, натискає «Завантажити» — і через кілька секунд отримує можливість ставити запитання за документом. За цими секундами криється складний pipeline: завантаження файлу на сервер, парсинг, розбиття на чанки, генерація ембеддингів і запис у векторну БД. Кожен етап — потенційне джерело проблем: витік пам'яті на мобільному, обрив з'єднання, невірний чанкінг, втрата даних. Ми реалізували такий pipeline під ключ для 5 проєктів, включно з додатками з мільйонними базами документів. Наш досвід показує, що без правильної архітектури пайплайн може зруйнуватися під навантаженням. У цій статті детально розбираємо технічні рішення кожного етапу — від вибору API для завантаження до обробки помилок на сервері.
Як реалізувати завантаження файлів на мобільному пристрої?
Android: вибір правильного API
Для Android 13+ використовуємо ActivityResultContracts.GetContent() з MIME-типом application/pdf або */*. Отримуємо Uri типу content://. Щоб прочитати дані без OutOfMemoryError для великих файлів, не завантажуємо все в ByteArray — передаємо InputStream напряму через кастомний RequestBody в OkHttp. Приклад:
val uri: Uri = // з ActivityResult
val requestBody = object : RequestBody() {
override fun contentType() = "application/octet-stream".toMediaType()
override fun contentLength() = -1 // unknown
override fun writeTo(sink: BufferedSink) {
contentResolver.openInputStream(uri)?.use { input ->
sink.writeAll(input.source())
}
}
}
Для файлів понад 50 МБ — чанковане завантаження через MultipartBody з викликом writeTo по частинах. Це дозволяє уникнути перевантаження оперативної пам'яті та підвищити стабільність додатка.
iOS: UIDocumentPicker і потокове завантаження
На iOS використовуємо UIDocumentPickerViewController з UTType.pdf тощо. Отримуємо URL типу file://. Читання через Data(contentsOf:) для файлів >20 МБ небезпечне — краще використовувати URLSession.uploadTask(with:fromFile:), який читає файл напряму з файлової системи, не завантажуючи в пам'ять.
let request = URLRequest(url: uploadEndpoint)
let (_, response) = try await URLSession.shared.upload(for: request, fromFile: fileURL)
Детальніше про URLSession – стандартна документація Apple (Apple URLSession documentation).
Прогрес завантаження
Прогрес-бар обов'язковий: на Android — через CountingOutputStream в OkHttp, на iOS — через URLSession у властивості uploadProgress. Користувач повинен бачити, скільки залишилося. Без прогресу великі файли створюють враження «зависання». Типовий час завантаження 20-30 МБ файлу — близько 5 секунд на хорошому з'єднанні.
Серверний pipeline: від файлу до векторів
Процес включає наступні кроки: 1) завантаження файлу, 2) вилучення тексту, 3) розбиття на чанки, 4) генерація ембеддингів, 5) завантаження у векторну БД. Після завантаження файлу на сервер запускаємо асинхронний pipeline через Celery (або аналогічну чергу). Клієнту одразу повертаємо {"job_id": "abc", "status": "processing"}, а статус відстежується через polling або WebSocket.
@celery.task
def process_document(file_path: str, user_id: str, content_type: str):
text = extract_text(file_path, content_type)
chunks = split_into_chunks(text, chunk_size=500, overlap=50)
embeddings = create_embeddings_batch(chunks)
upsert_to_vector_store(chunks, embeddings, user_id)
update_document_status(file_path, "completed")
Парсинг документів — таблиця:
| Формат | Інструмент | Особливості |
|---|---|---|
| PDF (текст) | PyMuPDF (fitz) | Швидкий, зберігає структуру, в 10 разів швидше PyPDF2 |
| PDF (скан) | Tesseract + pdf2image | Повільно, потрібен OCR; скани до 2 хвилин |
| DOCX | python-docx | Без зображень |
| TXT / MD | Нативно | Тривіально |
| HTML | BeautifulSoup | Потрібне очищення від тегів |
| XLSX | openpyxl | Таблиці → текст рядково |
PyMuPDF — наш вибір для PDF: коректно обробляє кирилицю, зберігає інформацію про шрифти (корисно для визначення заголовків). Джерело: PyMuPDF. Детальніше — PyMuPDF.
Які чанки вибирати: фіксований розмір vs семантичний?
| Стратегія | Розмір | Перекриття | Застосування |
|---|---|---|---|
| Фіксований | 500 токенів | 50 токенів | Універсально, просто |
| Семантичний | За реченнями/абзацами | 1-2 речення | Краще для запитань, точніше |
Фіксований чанкінг швидший і передбачуваніший, але може розривати смислові блоки. Семантичний довший, але підвищує якість відповідей RAG на 15–20% за нашими вимірами. Розмір чанка залежить від моделі ембеддингів: для text-embedding-3-small оптимально 512 токенів. Якщо документ містить таблиці або код, збільшуйте перекриття до 100 токенів.
Чому важливий прогрес і статус індексації?
Поки документ обробляється, клієнт повинен бачити актуальний стан. Два підходи:
-
Polling — кожні 2–3 секунди запитуємо
/api/documents/{job_id}/status. Просто, працює скрізь, але створює зайві запити (до 20-30 за хвилину). -
WebSocket / SSE — клієнт підписується на події для
job_id. Бекенд надсилає оновлення:{"step": "chunking", "progress": 0.3}→{"step": "embedding", "progress": 0.7}→{"step": "completed"}. Найкращий UX, але складніше реалізувати при фоновій роботі.
Ми використовуємо SSE як баланс між складністю та досвідом користувача. Після завершення індексації — push-сповіщення через APNs/FCM. Час індексації одного документа скоротили на 40% за рахунок паралельної обробки чанків.
Управління документами користувача
Користувач може видаляти документи. При видаленні:
- Видаляємо файл зі сховища.
- Видаляємо всі чанки з векторної БД (по
user_id+document_id). - Оновлюємо запис у реляційній БД.
У Pinecone: index.delete(filter={"document_id": "xyz"}, namespace=user_id). У pgvector: DELETE FROM documents WHERE document_id = $1 AND user_id = $2.
Документи зберігаються з метаданими: ім'я, розмір, дата завантаження, кількість чанків, статус. Це дозволяє відновити індексацію при збої.
Що входить у нашу роботу з впровадження RAG-пайплайна
Ми надаємо:
- Проектування архітектури пайплайна під ваші сценарії
- Реалізацію завантаження з прогресом на iOS/Android
- Серверну частину: парсинг, чанкінг, ембеддинги, векторне зберігання
- Інтеграцію з Firebase Cloud Messaging для сповіщень
- Документацію за API та інструкції з деплою
- Навчання команди роботі з системою
- Гарантію стабільності: покриваємо код unit-тестами та проводимо навантажувальне тестування
Наша команда має понад 5 років досвіду в мобільній розробці та успішно впровадила RAG-пайплайни для 5+ проєктів. Отримайте консультацію з архітектури пайплайна.
Терміни та вартість
MVP з підтримкою PDF і TXT, базовим чанкінгом і pgvector — 3–4 тижні. Повний pipeline з OCR, кількома форматами, асинхронною чергою та WebSocket-статусом — 6–8 тижнів. Вартість розраховується індивідуально після аналізу вимог.
Зв'яжіться з нами для оцінки вашого проєкту. Замовте впровадження під ключ.







