Впровадження AI-автодоповнення: від концепції до готового UX
Уявіть: користувач набирає відповідь у месенджері, і додаток пропонує закінчити речення фразою «Дякую за лист, я розгляну вашу пропозицію». Якщо підказка з'являється із затримкою 2 секунди або мигає при кожному символі — UX зруйновано. Ми вирішували цю проблему для фінтех-додатка з аудиторією 500k+ користувачів. Результат: 30% користувачів використовують автодоповнення щодня, час набору повідомлень скоротився на 40%. Розрив між концептом і робочою реалізацією — в деталях UX і продуктивності.
Коли пропонувати підказку
Найнедооціненіша частина — тригер. Підказка не повинна з'являтися при кожному символі. Робоча евристика: пропонуємо автодоповнення, якщо користувач набрав від 3 слів у поточному рядку і зробив паузу > 600 мс, або натиснув пробіл у кінці незавершеного речення. Нижче — порівняння поширених стратегій тригера.
| Евристика | Затримка | Точність | Приклад сценарію |
|---|---|---|---|
| Кожен символ | 0 мс | Низька (багато хибних спрацьовувань) | Набір кожного символу |
| Пауза >600 мс + мінімум 3 слова | ~600 мс | Висока | Користувач задумався |
| Пробіл після кінця речення | 0 мс | Середня (тільки контекстно) | «Я вважаю, що. » (пробіл) |
Докладніше про тригери
На практиці комбінуємо дві евристики: пауза понад 600 мс після введення не менше 15 символів і натискання пробілу після крапки, знаку питання або оклику. Це покриває 95% сценаріїв, де користувач очікує підказку.
// iOS - тригер автодоповнення private var autocompleteTask: Task<Void, Never>? func textDidChange(_ textView: UITextView) { autocompleteTask?.cancel() let text = textView.text ?? "" let cursorPosition = textView.selectedRange.location let textBeforeCursor = String(text.prefix(cursorPosition)) // Не пропонуємо в середині слова guard textBeforeCursor.last == " " || textBeforeCursor.last == "\n" else { hideAutocomplete() return } // Мінімум 15 символів контексту guard textBeforeCursor.trimmingCharacters(in: .whitespaces).count > 15 else { return } autocompleteTask = Task { try? await Task.sleep(nanoseconds: 600_000_000) // 600ms debounce guard !Task.isCancelled else { return } await fetchAutocomplete(context: textBeforeCursor) } } Запит до моделі та парсинг відповіді
Для автодоповнення використовуємо режим completion, не chat. gpt-4o-mini з max_tokens: 30 і temperature: 0.3 — швидко і передбачувано.
struct AutocompleteRequest: Encodable { let model = "gpt-4o-mini" let messages: [ChatMessage] let maxTokens = 30 let temperature = 0.3 let stop = ["\n", "."] // зупиняємося на кінці речення } func buildPrompt(context: String) -> [ChatMessage] { [ ChatMessage(role: "system", content: "Complete the text naturally. Continue from where it ends. Output only the continuation, no commentary."), ChatMessage(role: "user", content: context) ] } Стоп-токени \n і . важливі. Без них модель згенерує кілька речень, а нам потрібне одне продовження.
Чому on-device моделі не завжди підходять?
Альтернатива для on-device — CreateML Text Classifier не підходить, потрібен generative model. На iOS 18+ є Foundation Models framework з on-device LLM (Apple Intelligence). На Android — Gemini Nano через Google AI Edge SDK. Однак Gemini Nano доступний на Pixel 8+ і деяких Samsung — не універсальне рішення. Згідно з Apple Foundation Models, on-device LLM вимагає A17 Pro або M1+. Для широкої аудиторії потрібен серверний fallback.
// Android - Gemini Nano on-device (вимагає підтримки пристрою) val generativeModel = GenerativeModel( modelName = "gemini-nano", generationConfig = generationConfig { maxOutputTokens = 30 temperature = 0.3f stopSequences = listOf(".", "\n") } ) val response = generativeModel.generateContent( content { text("Complete naturally: $contextText") } ) val completion = response.text?.trim() ?: "" У таблиці нижче порівняємо підходи:
| Параметр | Серверне API (gpt-4o-mini) | On-device (Apple Intelligence/Gemini Nano) |
|---|---|---|
| Затримка | ~300-800 мс (залежить від мережі) | <100 мс (без мережі) |
| Якість | Висока (потужніша модель) | Середня (обмежені ресурси) |
| Доступність | Будь-який пристрій з інтернетом | Тільки флагманські пристрої |
| Приватність | Дані йдуть на сервер | Повна приватність на пристрої |
| Вартість | Оплата за токени | Безкоштовно для розробника |
| Підтримка офлайн | Ні | Так |
Гібридний підхід — on-device з серверним fallback — дає краще з обох світів.
Як уникнути мерехтіння підказки?
Suggestion мигає. Виникає, якщо новий запит повертається швидше ніж за 200 мс і одразу змінює попередній. Рішення — показувати новий suggestion лише якщо він відрізняється від попереднього більше ніж на 3 символи.
Модель продовжує віддалений текст. Якщо користувач видалив частину тексту — в контексті для промпта має бути актуальна версія, не попередня. Слідкуйте за синхронізацією textBeforeCursor з реальним станом TextStorage.
Tab перехоплюється системою. На Android Tab на м'якій клавіатурі недоступний. Використовуйте кастомну inline-клавішу або жест свайп-вправо через GestureDetector.
Відображення підказки
Стандартний патерн: сірий inline-текст після курсора. Користувач натискає Tab або свайп вправо — підказка приймається. Будь-яке інше введення — приховується.
// Android Compose - inline suggestion @Composable fun TextFieldWithSuggestion( value: String, suggestion: String, onValueChange: (String) -> Unit, onAcceptSuggestion: () -> Unit ) { val annotatedText = buildAnnotatedString { append(value) withStyle(SpanStyle(color = Color.Gray.copy(alpha = 0.6f))) { append(suggestion) } } BasicTextField( value = TextFieldValue( annotatedString = annotatedText, selection = TextRange(value.length) // курсор — після реального тексту ), onValueChange = { tfv -> val newText = tfv.text.take(value.length + suggestion.length) if (newText.startsWith(value + suggestion)) { onAcceptSuggestion() } else { onValueChange(tfv.text.take(value.length)) } }, keyboardActions = KeyboardActions( onDone = { onAcceptSuggestion() } ) ) } На iOS inline suggestion через UITextInput + drawText(in:) або простіше через overlay label, позиціонований через caretRect(for:).
Що входить у роботу
При замовленні реалізації AI-автодоповнення ви отримуєте:
- Архітектурну документацію: вибір підходу (сервер / on-device / гібрид), схему інтеграції.
- Реалізацію тригера та debounce з урахуванням UX.
- Інтеграцію з обраним API або on-device SDK.
- UI-компоненти inline-підказки під iOS та Android.
- Тестування на реальних пристроях: достовірність підказок, відсутність мерехтіння, коректна поведінка при видаленні.
- Інструкцію з експлуатації та рекомендації з донавчання моделі.
Спираючись на досвід реалізації понад 10 проектів у сфері NLP, ми гарантуємо стабільну роботу підказок. Зв'яжіться з нами, щоб оцінити ваш проект — ми підберемо оптимальне рішення за 1-2 дні.
Орієнтири за термінами
Базове автодоповнення з серверним API + inline UI — 5–8 днів. On-device через Apple Intelligence / Gemini Nano з серверним fallback — 2–3 тижні. Точні терміни залежать від кількості платформ, вимог до дизайну та необхідності підтримки офлайн-режиму. Отримайте консультацію — ми розрахуємо термін під ваш проект.
Часті проблеми та їх вирішення
Поширені помилки при реалізації
- Неврахування видалення тексту: контекст застаріває → модель дописує видалені символи. Рішення: синхронізувати
textBeforeCursorпісля кожної зміни. - Відсутність debounce: кожен символ → запит до API → 500+ запитів на хвилину → перевантаження та рахунок за токени. Рішення: поріг 600 мс та відміна попередньої задачі.
- Ігнорування стоп-токенів: модель генерує кілька речень → підказка займає пів екрану. Рішення:
stop: ["\n", "."]таmax_tokens: 30.







