Завантаження та індексація документів для RAG в мобільних додатках
Користувач прикріпляє PDF з Files.app або галереї, натискає «Завантажити», і через кілька секунд може ставити питання до документа. За цими секундами — pipeline завантаження, парсингу, чанкування, створення вбудовувань та запису в векторну БД. Кожен етап має свої вузькі місця.
Завантаження файлів з мобільного: технічні деталі
Android. ActivityResultContracts.GetContent() з "application/pdf" або "*/*" — правильний спосіб для Android 13+. Отримуєте Uri типу content://. Для завантаження на сервер потрібен InputStream:
val uri: Uri = // з ActivityResult
val bytes = contentResolver.openInputStream(uri)?.use { it.readBytes() }
?: throw IOException("Не вдалося відкрити файл")
// Multipart upload через OkHttp
val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", filename,
bytes.toRequestBody("application/octet-stream".toMediaType()))
.build()
Для великих файлів (50+ МБ) — chunked upload. Не читайте весь файл в ByteArray одразу: на пристроях з 2 ГБ RAM це призведе до OutOfMemoryError. Використовуйте InputStream напрямо з OkHttp RequestBody через кастомний writeTo.
iOS. UIDocumentPickerViewController з UTType.pdf, UTType.plainText і т.д. URL типу file://. Для завантаження:
let data = try Data(contentsOf: fileURL)
// Для великих файлів — URLSession uploadTask зі стрімом
let request = URLRequest(url: uploadEndpoint)
let (_, response) = try await URLSession.shared.upload(for: request, from: data)
Data(contentsOf:) для файлів > 20 МБ — погана ідея на iOS. Використовуйте URLSession.shared.uploadTask(with:fromFile:) напрямо — він читає файл чанками, не завантажуючи в пам'ять.
Прогрес завантаження. URLSession надає uploadProgress (iOS), OkHttp — RequestBody.writeTo з CountingOutputStream (Android). Прогрес-бар при завантаженні документа — обов'язковий: користувач повинен розуміти, що відбувається з його 10-МБ файлом.
Серверний pipeline: від файла до векторів
Після отримання файла бекенд запускає асинхронний pipeline. Синхронна відповідь клієнту: {"job_id": "abc123", "status": "processing"}. Клієнт опитує статус або отримує push.
# FastAPI + Celery task
@app.post("/api/documents")
async def upload_document(file: UploadFile, user_id: str = Depends(get_user_id)):
# Зберігаємо файл
file_path = save_to_storage(await file.read(), file.filename)
# Запускаємо асинхронну обробку
job = process_document.delay(file_path, user_id, file.content_type)
return {"job_id": job.id, "status": "processing"}
@celery.task
def process_document(file_path: str, user_id: str, content_type: str):
# 1. Парсинг
text = extract_text(file_path, content_type)
# 2. Чанкування
chunks = split_into_chunks(text, chunk_size=500, overlap=50)
# 3. Вбудовування пакетом
embeddings = create_embeddings_batch(chunks)
# 4. Upsert у векторну БД
upsert_to_vector_store(chunks, embeddings, user_id)
# 5. Оновити статус
update_document_status(file_path, "completed")
Парсинг документів
| Формат | Інструмент | Особливості |
|---|---|---|
| PDF (текст) | PyMuPDF (fitz) | Швидкий, зберігає структуру |
| PDF (скан) | Tesseract + pdf2image | Повільно, потрібен OCR |
| DOCX | python-docx | Без зображень |
| TXT / MD | Нативно | Тривіально |
| HTML | BeautifulSoup | Потрібна очистка від тегів |
| XLSX | openpyxl | Таблиці → текст побудочно |
PyMuPDF — найкращий вибір для PDF: у 10 разів швидше PyPDF2, правильно обробляє кирилицю, зберігає інформацію про шрифти (корисно для визначення заголовків).
Відображення статусу індексації на мобільному
Поки документ обробляється — показуємо прогрес. Два варіанти:
Polling. Кожні 2–3 секунди запитуємо /api/documents/{job_id}/status. Просто, працює везде. Недолік — додаткові запити.
WebSocket / SSE. Клієнт підписується на події для job_id. Бекенд відправляє оновлення: {"step": "chunking", "progress": 0.3} → {"step": "embedding", "progress": 0.7} → {"step": "completed"}. Кращий UX, складніше реалізувати на клієнті у фоновому режимі.
Після завершення індексації — повідомлення користувачу та оновлення списку документів. Документи зберігаються з метаданими: ім'я файла, розмір, дата завантаження, кількість чанків, статус.
Управління документами користувача
Користувач повинен мати можливість видаляти документи. Видалення означає:
- Видалити файл зі сховища
- Видалити всі чанки з векторної БД (за
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.
Етапи та термін
Реалізація завантаження файлів з прогресом на мобільному → бекенд pipeline з черговою задач → парсинг форматів → чанкування та вбудовування → upsert у векторну БД → API статусу та push-повідомлення → управління документами користувача → тестування на реальних файлах.
MVP з PDF + TXT, базовим чанкуванням, pgvector — 3–4 тижні. Повний pipeline з OCR, кількома форматами, async чергою, WebSocket-статусом — 6–8 тижнів.







