Вы точно помните, что видели на экране слово «дедлайн». Поиск по архиву скриншотов не находит ничего. Скриншот при этом есть, вы его нашли руками — и на нём действительно написано «дедлайн».
В индексе это слово записано как «депдайн». Распознавание перепутало «л» и «п», «л» и «д», и получившаяся строка не совпадает ни с чем, что вы наберёте.
Это не сбой. Это нормальный режим работы поиска по распознанному тексту, и строить его надо исходя из того, что индекс частично неверен.
#Короткий ответ
Поиск по OCR-тексту отличается от обычного тем, что в индексе лежат слова, которых никто не писал. Ошибки распознавания создают строки, отсутствующие в языке, и точное совпадение по ним не срабатывает.
Отсюда три следствия, которые определяют всю конструкцию:
- Точный поиск даёт ложные пропуски, а не ложные срабатывания. Вы не находите то, что есть.
- Ошибки не случайны, а систематичны: одни и те же пары символов путаются постоянно, и это можно использовать.
- Поиск по смыслу помогает меньше, чем ожидается: искажённое слово искажает и вектор.
#Типовые ошибки распознавания
| Класс | Пример | Почему возникает |
|---|---|---|
| Похожие буквы | «л» → «п», «и» → «н», «б» → «в» | близкие начертания в мелком кегле |
| Латиница вместо кириллицы | «сеть» → «cеть» (латинская c) | одинаковые глифы в разных алфавитах |
| Цифры и буквы | «О» → «0», «З» → «3», «l» → «1» | неотличимы во многих шрифтах |
| Разрыв слова | «дедлайн» → «дед лайн» | межбуквенный интервал принят за пробел |
| Склейка слов | «до конца» → «доконца» | узкий пробел не распознан |
| Пропуск строки | нет текста вовсе | низкий контраст, тёмная тема, антиалиасинг |
| Мусор из интерфейса | «⌘», «×», «▾» вперемешку | иконки и разделители распознаны как символы |
Третья строка — самая коварная в русском тексте на скриншотах, потому что даёт невидимую глазу ошибку: строка «сеть» с латинской «c» выглядит идентично правильной и не находится никогда.
Шестая строка объясняет, почему у некоторых архив пуст: тёмная тема со сглаженным шрифтом распознаётся заметно хуже светлой. Прежде чем чинить поиск, стоит убедиться, что текст вообще снимается — как это проверяется, разобрано в справке про OCR.
#Приёмы устойчивого поиска
Шесть приёмов, от самого дешёвого к самому дорогому.
1. Префиксный поиск по каждому токену. Запрос «дедлайн» превращается в «дедлайн*». Это ничего не стоит и заодно решает проблему морфологии. От ошибок внутри слова не спасает, но спасает от искажённых окончаний — а хвост слова распознаётся хуже начала, потому что там чаще стоят засечки и знаки препинания.
2. Поиск по началу слова, а не по целому. Введите «дедл» вместо «дедлайн». Чем короче запрос, тем меньше шансов попасть на искажённый символ. Это самый эффективный ручной приём, и его стоит знать пользователю.
3. Нормализация алфавитов при индексации. Латинские глифы, совпадающие по начертанию с кириллическими, приводятся к одному алфавиту перед записью в индекс. Закрывает третью строку таблицы, которую иначе не закрыть ничем.
4. Нормализация ё. В распознанном тексте ё и е смешиваются постоянно. Полнотекстовый индекс SQLite считает их разными токенами — это проверяется на трёх строках и разобрано в гайде по FTS5. Приведение к одной букве при записи и при поиске возвращает потерянные попадания.
5. Поиск по нескольким полям сразу. Заголовок окна и название приложения распознавать не нужно — они берутся из системы и потому точны. Искать стоит по ним тоже: «Figma» найдётся по имени приложения даже там, где текст на экране распознан плохо.
6. Нечёткое сравнение. Поиск с допуском на одну-две замены символов. Самый мощный приём и самый дорогой: в полнотекстовом индексе его нет, и придётся либо перебирать, либо строить дополнительный индекс по фрагментам слов. Прежде чем идти этим путём, выжмите первые пять.
#Почему векторный поиск здесь помогает слабее обычного
Кажется логичным: раз буквальный поиск ломается на опечатках, возьмём поиск по смыслу. На практике выигрыш меньше ожидаемого.
Модель-эмбеддер кодирует «депдайн» не как «дедлайн с ошибкой», а как незнакомую строку, разбитую токенизатором на бессмысленные куски. Вектор такого фрагмента смещается, и близость к нормальному запросу падает. Помогает контекст: если вокруг искажённого слова десять правильных, вектор фрагмента останется в нужной области. Не помогает, если искажено именно ключевое слово — а ищут обычно его.
Практический вывод: на OCR-тексте векторы работают на уровне фрагмента, а не слова. Они хорошо находят «тот экран, где обсуждали сроки», и плохо — «экран, где было написано конкретно это слово». Держать оба источника всё равно полезно, но ожидания от векторной половины здесь должны быть ниже, чем на чистом тексте.
Отдельная стоимость: на распознанном тексте есть смысл индексировать не всё подряд. Экран с одним диалоговым окном и экран с двадцатью строками кода дают одинаковую строку в базе, но совершенно разную пользу при поиске.
#Как это устроено в Persona
Поиск по распознанному тексту работает на том же полнотекстовом индексе, что и остальной поиск: виртуальная таблица над таблицей скриншотов, три поля — распознанный текст, заголовок окна, имя приложения. Запрос пользователя очищается от символов, способных сломать синтаксис поискового выражения, и каждое голое слово превращается в префиксный запрос — приём номер один из списка выше применяется по умолчанию.
Поверх этого есть необязательный семантический слой на локальных ONNX-моделях: он включается переменной окружения и требует отдельной группы зависимостей, а модель скачивается при первом обращении. Без него поиск работает полнотекстовый, и это нормальный режим.
Слияние двух источников здесь сделано проще, чем в памяти чата: к обратному рангу полнотекстовой выдачи прибавляется косинусная близость. На коротких списках это работает, но полагаться на такое сложение неправильно: шкалы у двух источников разные, и их совпадение по порядку величины — случайность, а не свойство.
Отдельно про приватность: распознанный текст перед записью в базу и в индекс прогоняется через правила маскирования — регулярные выражения, заменяющие совпадения на маску. Готовые наборы правил покрывают карты, пароли, ключи API, сетевые идентификаторы и контакты; на изображение это не влияет, маскируется только текстовое представление. Практическое следствие для поиска: замаскированное не находится, и это правильно. Как настраивать правила — в материале про правила редактирования OCR.
Захват экрана и всё производное от него, включая этот поиск, доступны только владельцу инстанса: данные захвата пока глобальные, одно-тенантные. Зачем вообще снимать экран в память ассистента, разобрано в статье про запись экрана как источник памяти ИИ.
#Где интуиция подводит
Кажется, что качество распознавания — вопрос движка. Оно в большей степени вопрос исходной картинки: масштаб интерфейса, тема оформления, сглаживание шрифтов и разрешение влияют сильнее, чем выбор движка. Проверить дёшево: снимите один и тот же экран в светлой и тёмной теме и сравните распознанный текст.
Кажется, что «ничего не находится» — проблема поиска. В половине случаев это проблема входа: текста в индексе нет вовсе. Проверяется не поиском, а просмотром распознанного текста конкретного скриншота.
Кажется, что модель зрения решает всё. Она читает изображение лучше классического распознавания, но стоит несопоставимо дороже: применять её ко всему потоку скриншотов нереалистично, а к отдельному — оправдано. Что умеют такие модели локально, разобрано в материале про локальные vision-модели.
#Частые вопросы
#Почему поиск не находит слово, которое я вижу на скриншоте?
Скорее всего, в индексе оно записано с ошибкой распознавания. Проверьте прямо: откройте распознанный текст этого скриншота и посмотрите, как слово там выглядит. Если оно искажено — ищите по короткому началу слова.
#Помогает ли увеличить разрешение съёмки?
Помогает до определённого предела, и упирается всё в размер шрифта в пикселях, а не в разрешение экрана. Увеличение масштаба интерфейса даёт заметно больше, чем увеличение разрешения при том же масштабе.
#Стоит ли исправлять ошибки распознавания вручную?
Для отдельных важных экранов — да, если инструмент это позволяет: одна правка возвращает документ в поиск навсегда. Массово — бессмысленно, объёмы не те.
#Почему нельзя просто искать с опечатками?
Нечёткий поиск требует либо перебора, либо дополнительного индекса по фрагментам слов, а во встроенном полнотекстовом поиске его нет. Это выполнимо, но это отдельная подсистема, и начинать стоит с более дешёвых приёмов.
#Как это связано с памятью ассистента?
Распознанный текст — один из источников, из которых память может пополняться. Важная оговорка: текст с экрана не атрибутирован говорящему, поэтому в аккуратной схеме он не может сам по себе создать факт о человеке — только поддержать факт, у которого есть другое доказательство. Общее устройство слоёв разобрано в статье о том, как работает память у ИИ.
#Вывод
Стройте поиск по распознанному тексту исходя из того, что индекс частично неверен: включайте префиксный поиск по умолчанию, нормализуйте алфавиты и ё при записи, ищите заодно по заголовку окна и имени приложения. Пользователю дайте простое правило: вводить начало слова, а не слово целиком. И не рассчитывайте, что векторы закроют ошибки распознавания, — они работают на уровне фрагмента, а ищут обычно конкретное слово, которое как раз и искажено.