Реалізація мультимодального AI-введення (текст + документ) у мобільному додатку
Користувач відкриває мобільний додаток, прикріплює PDF контракт та запитує: «На якому сроці розтергається договір?» Завдання виглядає простим, але між file_picker і осмисленим ответом моделі—десяток нетривіальних рішень.
Проблема номер один: як передати документ у LLM
Більшість LLM приймають текст, а не PDF. Значить потрібна конвертація. Варіанти:
Прямий upload через Files API. OpenAI Assistants API та Gemini Files API приймають PDF, DOCX, TXT прямо. Для мобільного додатку—найчистіший шлях: завантажити файл, отримати file_id, вставити в messages[]. Але обмеження існують—у OpenAI ліміт 512 МБ на файл та 100 файлів на assistant, та Files API прив'язана до Assistants/Batch, не до Chat Completions.
Вилучення тексту на клієнті. Для PDF на Android—PdfRenderer (вбудована з API 21) для рендеринга сторінок у Bitmap + OCR через ML Kit TextRecognizer, або Apache PDFBox port. На iOS—PDFKit + PDFPage.string для машинописного PDF; для сканів—Vision framework з VNRecognizeTextRequest. Текст йде в content[] як рядок.
Проблема сканованих документів. PDFKit.string повертає пусту рядок для PDF з відсканованих сторінок—там нема текстового шару. ML Kit TextRecognizer справляється, але потрібен рендеринг кожної сторінки у Bitmap/CGImage та прогін через OCR. Для 50-сторінкового документа—2–5 секунд на пристрої.
Вилучення тексту: підводні каміння
На Android PdfRenderer вимагає ParcelFileDescriptor з флагом MODE_READ_ONLY. Якщо файл прийшов через content:// URI від FileProvider, потрібен contentResolver.openFileDescriptor(). Прямий File() від content:// кидає FileNotFoundException—поширена помилка у тих, хто не працював з SAF (Storage Access Framework).
Багатосторінкові документи обробляють постранично, не завантажуючи все в пам'ять одразу. PdfRenderer.Page потрібно закривати після кожної сторінки—page.close() обов'язковий, інакше IllegalStateException на наступній ітерації.
На iOS PDFDocument(url:) може повернути nil для зашифрованих PDF. Обробляйте isEncrypted та запитуйте пароль через UI, а не крашиться мовчки.
Архітектурне рішення для великих документів
Повний текст 100-сторінкового договору не влізе у контекстне вікно більшості моделей—або влізе, але дорого. Правильний шлях для об'ємних документів—RAG: розбиваємо на чанки по 500–1000 токенів з перекриттям 50–100 токенів, індексуємо у векторну БД, при запиті шукаємо топ-5 релевантних чанків та тільки їх передаємо в context.
Для мобільного додатку це звичайно означає серверну обробку: клієнт завантажує файл на бекенд, бекенд займається чанкингом та еквалайзирами. На клієнті залишається тільки UI запиту та рендеринг ответу. Реалізовувати векторний пошук прямо на телефоні має смисл тільки для офлайн-сценаріїв.
Формати та ліміти
| Формат | Android | iOS | OpenAI Ліміт |
|---|---|---|---|
| PDF (текст) | PdfRenderer + PDFBox | PDFKit | 512 МБ |
| PDF (скан) | ML Kit OCR | Vision VNRecognizeTextRequest | — (потрібна передопрацювання) |
| DOCX | Apache POI (Java) | — | 512 МБ (через Files API) |
| TXT / MD | Нативно | Нативно | Без обмежень |
| XLSX | Apache POI | — | 512 МБ |
DOCX на iOS без сторонніх бібліотек—біль. Либо серверна конвертація (LibreOffice headless), либо обмежуєте підтримку форматів PDF + TXT для мобільного клієнта.
Процес роботи
Аудит форматів документів у вашому продукті → вибір стратегії (Files API vs клієнтська екстракція vs RAG) → реалізація завантаження файлів (file_picker, SAF, UIDocumentPickerViewController) → конвертація та очистка тексту → інтеграція з LLM → індикатори прогресу для довгих операцій → тестування на реальних документах різної якості.
Сроки: базова підтримка PDF + TXT з прямою передачею—1–2 тижні. Повнофункціональний pipeline з OCR, кількома форматами та RAG для великих документів—4–6 тижнів.







