Реализация загрузки и индексации документов для RAG в мобильном приложении
Пользователь прикрепляет PDF из Files.app или галереи, нажимает «Загрузить», и через несколько секунд может задавать вопросы по документу. За этими секундами — pipeline из загрузки, парсинга, chunking, создания эмбеддингов и записи в векторную БД. Каждый этап имеет свои узкие места.
Загрузка файлов с мобильного: технические детали
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. Chunking
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 с очередью задач → парсинг форматов → chunking и эмбеддинги → upsert в векторную БД → API статуса и push-уведомления → управление документами пользователя → тестирование на реальных файлах.
MVP с PDF + TXT, базовым чанкингом, pgvector — 3–4 недели. Полный pipeline с OCR, несколькими форматами, async очередью, WebSocket-статусом — 6–8 недель.







