Раздел: AI & Agents
Идентификатор:vision· Экран:ui/vision/VisionScreen.kt(473) +VisionViewModel.kt(206)
Снимок: 2026-10-11
1. Зачем и что это
Ключевая архитектурная особенность: конфиг цепочки живёт на бэкенде (~/.y83-ws/vision.json), и его читают три потребителя:
- экран Vision (выбор и проверка),
- MCP-инструменты
ai_vision,browser_look,screen_look, - композер чата при обработке вложения.
Поэтому «кто отвечает» совпадает везде: настроили один раз — одинаково работает на телефоне и в чате агента.
Пиксели в контекст модели не попадают: снимок сохраняется файлом, а в тред уходит путь и текстовый ответ. Поэтому картинка не платит vision-токенами на каждом ходе.
2. Как попасть
Хаб инструментов → секция AI & Agents → карточка «Vision» (hub_vision_title). Также экран открывается из хаба инструментов Codex: Vision идёт в общий список «кто отвечает», а не в настройки конкретного агента.
| Откуда ещё | Путь |
|---|---|
| Мост | app_navigate с screen: "vision" |
Маршрут: Screen.kt:130 ("vision" -> Screen.Vision) → MainScreen.kt:176 (Screen.Vision -> "vision").
3. Экран: элементы и действия
| Элемент | Что делает | Где в коде |
|---|---|---|
Секция // chain | Два слота: «основной» и «страховщик» | ChainLine (слот 0/1) |
| Кнопка «Убрать» в слоте | Снимает выбор источника | onClear → setSource(slot, null) |
| Список источников | SelectChip на каждый доступный источник | SourceRow |
| Метка источника | Подпись + «основной»/«страховщик», если он в цепочке | SourceRow:413 |
| Секция «как это работает» | Пояснения: пиксели не уходят, источник один для всех | media_reading_config, media_pixels_are_not_sent_to_the_model |
Секция // test | Кнопка «Проверить цепочку» + результат | viewModel::test |
| Результат проверки | Текст, источник, задержка, попытки по каждому звену | testText, testSource, testLatencyMs, testAttempts |
meta шапки | «N src · цепочка» или «доступные/видимые» | visionMetaLabel |
Пустая цепочка подписывается как «авто»: источники выбираются автоматически.
4. Возможности подробно
Слоты цепочки
Константы в VisionViewModel.kt: VISION_PRIMARY = 0 (основной), VISION_FALLBACK = 1 (страховщик). Функции:
| Функция | Что делает |
|---|---|
visionSourceId(source) | id источника в виде, который ждёт бэкенд: webai:deepseek, provider:<id>/<model>, local |
visionSlotOf(chain, id) | в каком слоте стоит источник, null если не выбран |
visionChainLabel(chain, sources) | подпись цепочки для шапки: «WebAI → провайдер» |
visionChainWith(chain, sources, slot, id) | чистая перестановка источника (есть тест) |
Правило visionChainWith:
- один источник не может занимать оба слота — повторный выбор просто переносит его (
next = current.filter { visionSourceId(it) != target }, затем вставка в слот); id = nullснимает выбор в слоте;- источники с пустым
kind, а такжеlocalбезbaseUrlотбрасываются: легаси-конфиги без baseUrl бэкенд всё равно выбросит при сохранении; - вставка в слот 1 при цепочке длины 1 делается через
slot.coerceAtMost(next.size)— то есть страховщик встаёт в конец.
Сохранение
setSource(slot, id) вызывает save(VisionConfigDto(chain, native), key = "slot:$slot"). Сохранение идёт на backend, и в состоянии есть множество busy — по нему видно, какой слот пишется. Бэкенд нормализует конфиг (SetConfig в backend/internal/vision/vision.go) и обрезает цепочку до maxChain = 4.
native — отдельный флаг конфигурации (Config.Native); в экране он не переключается, но сохраняется при каждой записи, чтобы не затереть его.
Какие источники доступны
Список источников приходит с сервера (repo.config() → config, sources). На клиенте видны источники, которые либо available, либо уже стоят в цепочке (visibleVisionSources) — то есть выбранный, но ставший недоступным источник не исчезает из экрана молча, и его можно снять.
В meta шапки видно, все ли видимые источники доступны: «3 src» (все) или «2/3 src» (часть недоступна).
Кто может быть источником
По коду backend:
- WebAI DeepSeek — источник
webai:deepseek; - vision-модели провайдеров —
provider:<id>/<model>, отбираются поproviders.VisionCapable(model): ручной флагmodel.VisionИЛИ автоопределение по имени (LooksVisionID); local— вид источника, который поддерживается в коде и на клиенте, но локального сервера в списке нет (по AGENTS.md), поэтому на практике не используется.
LooksVisionID смотрит на маркеры vision, -vl, vl-, /vl, vl2, vl3, multimodal в id модели и на префиксы gpt-4o, gpt-4.1, gpt-5, claude-, gemini-, qwen-vl, qwen2-vl, qwen2.5-vl, qwen3-vl, glm-4v, internvl, llava, pixtral, grok-2, grok-3. Так модель с картинками попадает в цепочку без ручной настройки; модель без подсказки в имени добавляется кнопкой «+ модель» — пикер ставит ручной флаг model.Vision и перечитывает источники.
Проверка цепочки
Кнопка «Проверить цепочку» отправляет на backend встроенную картинку (BridgeState.TEST_IMAGE_B64 — base64 PNG 512×512 прямо в коде) с вопросом «Describe this test image briefly: what is shown and what text is visible».
Ответ показывает:
| Поле | Что значит |
|---|---|
testText | что ответил источник |
testSource | кто именно ответил (visionSourceLabel) |
testLatencyMs | задержка, форматируется как 830ms или 1.2s (visionLatencyLabel) |
testAttempts | попытки по каждому звену: ok/fail · источник · задержка · ошибка (visionAttemptLabel) |
testError | ошибка, если res.ok == false |
Повторное нажатие во время проверки игнорируется (if (_ui.value.testing) return) — это защита от двойного запроса.
5. Данные: БД, файлы, настройки
| Что | Где лежит | Как меняется | Кто читает |
|---|---|---|---|
| Конфиг цепочки | ~/.y83-ws/vision.json (backend) | PUT /api/v1/vision/config | ai_vision, browser_look, screen_look, композер |
| Снимки | ~/.y83-ws/downloads/screenshots/ | browser_shot, app_screenshot | Затем ai_vision по пути |
| Тестовая картинка | Константа BridgeState.TEST_IMAGE_B64 | Не меняется | Кнопка «Проверить цепочку» |
| Локальные настройки | GlobalVisionConfig prefs (инициализируется в App.init) | Клиентский кэш | Приложение |
БД backend (ws-backend.db) цепочку vision не хранит — только файл vision.json. Это сделано, чтобы конфиг можно было прочитать и положить в backup рядом с остальными json-файлами .y83-ws.
6. Backend: REST/роуты
| Метод и путь | Назначение | Обработчик |
|---|---|---|
GET /api/v1/vision/config | текущий конфиг + список источников | vh.Config |
PUT /api/v1/vision/config | сохранить конфиг (нормализация, обрезка до 4) | vh.Config |
POST /api/v1/vision/describe | распознать картинку (используется кнопкой проверки и MCP) | vh.Describe |
Роуты: backend/internal/api/v1/routes.go:147–149. Логика — backend/internal/vision/vision.go, отбор моделей — backend/internal/providers/catalog.go.
7. Мост и MCP
| Инструмент | Что делает | Вызывается откуда |
|---|---|---|
ai_vision {imagePath, question, region?} | распознать файл по пути | скилл y83-imagegen/y83-bridge, разработка |
browser_look {region?} | снимок страницы встроенного браузера → описание | скиллы y83-browser-product, y83-browser-testing |
screen_look {region?} | снимок экрана приложения → описание | скилл y83-bridge |
app_navigate screen: "vision" | открыть экран | MainScreen.kt:176 |
Все три возвращают {path, description, source} — одинаковый контракт, поэтому агенту не важно, смотрит он страницу или экран. region: {x,y,w,h} кропается до отправки в координатах исходного кадра.
Отдельной bridge-команды «прочитать vision-конфиг» в коде моста не найдено: агент управляет цепочкой через MCP-вызовы, а этот экран — для человека.
8. Настройки, конфиг, квоты и лимиты
| Параметр | Значение | Где задаётся |
|---|---|---|
| Максимум источников в цепочке | 4 (maxChain) | backend/internal/vision/vision.go:204 |
| Слотов в UI | 2 (основной, страховщик) | VISION_PRIMARY, VISION_FALLBACK |
| Тестовая картинка | PNG 512×512, base64 в коде | BridgeState.TEST_IMAGE_B64 |
| Формат результата проверки | N src или N/M src | visionMetaLabel |
| Формат задержки | 830ms / 1.2s | visionLatencyLabel |
9. Ловушки и ограничения
- Цепочка обрезается до 4 источников на сервере. Экран показывает только два слота, но в конфиге их может быть больше после ручной правки файла — лишнее молча исчезнет при следующем сохранении.
- В UI два слота, в конфиге — до четырёх. Это разные вещи:
chainна бэкенде упорядоченный список,slotв приложении — только первые две позиции. nativeне переключается в UI, но сохраняется при каждой записи. Если его сбросить вручную вvision.json, экран при первом же сохранении перезапишет конфиг текущим значением из загруженного состояния.- Выбранный, но недоступный источник остаётся видимым — специально, чтобы его можно было снять. Поэтому «N/M src» в шапке — это не баг.
- Пустая цепочка означает «авто», а не « vision выключен»: источники выбираются автоматически, и подпись «авто» это прямо показывает.
localбезbaseUrlотбрасывается и на клиенте, и на сервере — легаси-конфиг с таким источником не восстановить через экран.- Проверка тратит реальный запрос к модели (встроенная картинка) — её не стоит жать часто при лимитах.
- Модель с картинками определяется по имени. Если провайдер не даёт модели id с маркером
vision/vl/multimodalи не выставил ручной флагVision, она не появится в списке источников.
10. Источники правды
app/src/main/java/com/y83/ws/ui/vision/VisionScreen.kt— экран, фильтр видимых источников, форматирование.app/src/main/java/com/y83/ws/ui/vision/VisionViewModel.kt— слоты, чистая функция цепочки, сохранение, проверка.app/src/main/java/com/y83/ws/bridge/BridgeState.kt— встроенная тестовая картинка.app/src/main/java/com/y83/ws/data/repo/VisionRepository.kt,data/api/— клиент REST.backend/internal/vision/vision.go— конфиг,maxChain, нормализация,Describe.backend/internal/handlers/vision/— HTTP-обработчики.backend/internal/providers/catalog.go—VisionCapable,LooksVisionID.backend/internal/api/v1/routes.go:147–149— роуты.app/src/main/res/values/strings.xml(media_*) — подписи экрана.