Слепой программист — это грустно
Кодинг-агенты отлично пишут код. Они могут рефакторить, дебажить, генерировать целые файлы. Но есть одна проблема — они слепые. Буквально. Они не видят, что происходит на экране. Попроси агента исправить верстку — он может сгенерировать новый CSS, но не поймет, что кнопка наезжает на заголовок. Попроси разобраться с падением тестов — он прочитает логи, но не увидит скриншот ошибки. Это как чинить механизм, не глядя на него.
Решений на рынке два: либо вы вручную описываете агенту, что видите на экране („тут кнопка съехала вправо, а фон стал фиолетовым“), либо подключаете облачную vision-модель (GPT-4V, Claude 3.5 Vision) и молитесь на приватность и ценник. Первое — треш для продуктивности. Второе — треш для кошелька и безопасности.
Есть третий путь, и о нем я расскажу: поднять локальный MCP-сервер с моделью GLM-5.1 Vision 8B — легковесной, но достаточно умной, чтобы читать скриншоты, распознавать элементы интерфейса и описывать баги. Агент дергает MCP-тул, получает текстовое описание того, что видит, и действует. Всё локально, без утечек, без подписок, с латентностью 1-3 секунды на среднестатистической 3090.
Зачем это нужно? Агент перестает быть просто „писателем кода“. Он становится настоящим ревьювером, тестировщиком, дизайнером. Он видит результат своей работы так же, как человек. Это меняет подход к автономной разработке.
Почему именно GLM-5.1 Vision?
Моделей vision много: LLaVA, InternVL, Qwen-VL, CogVLM. Но GLM-5.1 (выпуск февраля 2026) — это свежая модель с архитектурой, адаптированной под 8B параметров. Она показывает лучшее соотношение качества и скорости среди моделей такого размера. В бенчмарках MMBench-V14 она выбивает 78.2% — выше, чем Qwen2-VL-7B (74.5%) и LLaVA-NeXT-8B (71.8%). При этом квантизированная версия (q4_k_m) весит всего 5.2 ГБ и влезает в одну 3090 с DDR5.
Но главное — она понимает экраны: кнопки, иконки, текст в капчах, расположение элементов. На тестах с UI-скриншотами (ScreenQA, VizWiz) GLM-5.1 Vision обгоняет многих гигантов. А еще модель поддерживает мульти-изображения в одном запросе — важно для сравнения скриншотов "было/стало".
| Модель | Размер | MMBench | ScreenQA | Latency (3090) |
|---|---|---|---|---|
| GLM-5.1 Vision 8B | 8B | 78.2% | 81.5% | 1.8 с |
| Qwen2-VL-7B | 7B | 74.5% | 77.2% | 2.1 с |
| LLaVA-NeXT-8B | 8B | 71.8% | 73.8% | 2.4 с |
Важно: GLM-5.1 Vision не имеет лицензионных ограничений на коммерческое использование (MIT), в отличие от некоторых аналогов. Но проверьте сами перед деплоем.
Анатомия MCP-сервера с глазами
MCP (Model Context Protocol) — это стандарт для подключения тулов и источников данных к LLM. Сегодня MCP-клиенты есть в llama.cpp, kilo-code, continue.dev и десятке других инструментов. Нам нужно написать свой MCP-сервер, который предоставляет тул analyze_screenshot. Агент вызывает этот тул, передавая путь к скриншоту или base64-изображение, а сервер возвращает JSON с текстовым описанием и списком найденных элементов.
Архитектура такова:
- MCP-сервер на Python (FastMCP или uvicorn)
- Инференс через
transformersс загрузкой модели GLM-5.1 Vision в half-precision - Кэширование запросов (скриншоты редко меняются)
- Поддержка batch и очередь по приоритетам
llama.cpp — она работает быстрее, чем полная модель на CPU. Мы еще вернемся к этому.1Настройка окружения
Создаем чистую среду с Python 3.12, ставим зависимости. Не забудьте про CUDA 12.4, если используете GPU. Я покажу минимальный набор.
# Создаем venv
python3.12 -m venv mcp_vision_env
source mcp_vision_env/bin/activate
# Ставим пакеты
pip install mcp transformers accelerate bitsandbytes pillow requests
# Для квантованной версии через llama.cpp отдельно ставим llama-cpp-python
pip install llama-cpp-python --force-reinstall --upgrade --no-cache-dir
# или с поддержкой CUDA: CMAKE_ARGS="-DLLAMA_CUDA=on" pip install llama-cpp-pythonНе ставьте bitsandbytes для 4-битной квантизации, если у вас RTX 30xx/40xx — есть конфликты с новыми ядрами. Лучше используйте родную квантизацию из transformers или сразу квантованный GGUF.
2Скачиваем и запускаем модель
Модель доступна на HuggingFace: THUDM/glm-5.1-vision-8b. Нам понадобится токен для доступа (git-lfs).
pip install huggingface_hub
huggingface-cli login
# введите токен
huggingface-cli download THUDM/glm-5.1-vision-8b --local-dir ./models/glm-5.1-vision-8b
Если места на диске меньше 16 ГБ, скачайте GGUF-версию (q4_k_m) через huggingface-cli download TheBloke/GLM-5.1-Vision-8B-GGUF glm-5.1-vision-8b-q4_k_m.gguf --local-dir ./models/. Она быстрее грузится и меньше жрет RAM.
Теперь создаем файл vision_server.py. Полный листинг дам в следующем шаге, но суть такая: загружаем модель, создаем MCP-приложение, регистрируем тул.
3Пишем MCP-сервер (полный код)
Я покажу базу, которую вы можете расширить. Используем библиотеку mcp от Anthropic (версия 1.2.6 на апрель 2026). Код — под спойлером.
import os
import json
import base64
from PIL import Image
from io import BytesIO
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
from mcp.types import Tool, TextContent, ImageContent
try:
from transformers import AutoModel, AutoTokenizer
except ImportError:
AutoModel = AutoTokenizer = None
MODEL_PATH = os.getenv("MODEL_PATH", "./models/glm-5.1-vision-8b")
USE_GGUF = os.getenv("USE_GGUF", "0") == "1"
# Загружаем модель (полная или GGUF)
if USE_GGUF:
from llama_cpp import Llama
llm = Llama(
model_path=MODEL_PATH,
n_gpu_layers=-1,
n_ctx=4096,
verbose=False
)
else:
if AutoModel is None:
raise ImportError("Установите transformers: pip install transformers")
model = AutoModel.from_pretrained(MODEL_PATH, trust_remote_code=True, device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True)
app = Server("vision-server")
@app.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="analyze_screenshot",
description="Анализирует скриншот: определяет элементы интерфейса, читает текст, описывает баги",
inputSchema={
"type": "object",
"properties": {
"image_path": {"type": "string", "description": "Путь к файлу изображения"},
"question": {"type": "string", "description": "Дополнительный вопрос (опционально)"}
},
"required": ["image_path"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list:
if name != "analyze_screenshot":
raise ValueError(f"Unknown tool: {name}")
image_path = arguments["image_path"]
question = arguments.get("question", "")
if not os.path.exists(image_path):
return [TextContent(type="text", text="Ошибка: файл не найден")]
# Открываем изображение
with Image.open(image_path) as img:
# Ресайз до 768x768 для скорости (GLM-5.1 принимает до 1344x1344)
img.thumbnail((768, 768), Image.LANCZOS)
if USE_GGUF:
# Для GGUF конвертим в base64 и передаем как текстовое вложение
buffer = BytesIO()
img.save(buffer, format="PNG")
b64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
prompt = f"<|image|>{b64}\nОпиши, что видишь на изображении. Ответь на русском. {question}"
output = llm(prompt, max_tokens=512, temperature=0.2)
response_text = output["choices"][0]["text"]
else:
# Отправляем напрямую через transformers
messages = [
{"role": "user", "content": [
{"type": "image", "image": img},
{"type": "text", "text": question or "Опиши, что видишь на скриншоте. Найди все интерактивные элементы, текст и ошибки."}
]}
]
inputs = tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_tensors="pt").to(model.device)
outputs = model.generate(**inputs, max_new_tokens=512, do_sample=False, temperature=0.2)
response_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
return [TextContent(type="text", text=response_text)]
if __name__ == "__main__":
import asyncio
asyncio.run(app.run(initialization_options=InitializationOptions(
server_name="vision-server",
server_version="1.0.0",
capabilities={"tools": {}}
)))Что тут важно: ресайз. Полный скриншот 1920x1080 модель сожрет, но время генерации уйдет в 5-7 секунд. Ресайз до 768 по большей стороне — оптимальный баланс. Модель все равно распознает текст на кнопках.
Совет: если нужно распознать мелкий текст, не ресайзите — скажите в question "увеличь область в центре" и подайте кроп.
4Подключаем сервер к агенту
Самый простой способ — использовать MCP-клиент из llama.cpp или kilo-code. Я покажу пример для llama-server (версия b4823+). В конфиг MCP добавляем строку:
{
"mcp_servers": [
{
"name": "vision-server",
"command": "python3",
"args": ["/path/to/vision_server.py"],
"env": {
"MODEL_PATH": "./models/glm-5.1-vision-8b",
"USE_GGUF": "1"
}
}
]
}Затем в промпте агента достаточно упомянуть тул: "сделай скриншот окна с ошибкой и проанализируй через vision-server". Если агент использует библиотеку pyautogui или selenium для создания скриншотов, он сам делает снимок и передает путь.
| Инструмент | Ссылка на гайд |
|---|---|
| llama-server Web UI + MCP | подробная инструкция |
| Kilo Code | настройка на 3x3090 |
Типичные грабли и как их избежать
Грабли №1. Модель выдает белиберду вместо текста.
Причина: изображение сжато до неприличия. Ресайз должен быть не менее 512 по меньшей стороне. Еще одна причина — модель не обучена читать русский язык на мелких кнопках. GLM-5.1 Vision поддерживает китайский и английский, но русский — не идеально. Если вам нужно распознавать русские интерфейсы, используйте дообученную версию или добавляйте в question: "Текст на русском, переведи на английский".
Грабли №2. Сервер падает с CUDA out of memory.
Причина: модель целиком загружена в GPU, а вы пытаетесь обрабатывать несколько скриншотов одновременно. Решение: используйте очередь (asyncio queue) или скиньте квантизацию до q4_k_m. На 24 ГБ 3090 можно держать полную 8B + запас для батча из 2 скриншотов.
Грабли №3. Агент не вызывает тул.
Причина: клиент не передает описание тула агенту. В MCP-протоколе нужно явно сообщить агенту список доступных инструментов. В промпт агента добавьте фразу: "У тебя есть доступ к тулу vision-server:analyze_screenshot. Используй его, если нужно увидеть экран".
Если у вас глубокая интеграция через llama.cpp с MCP-клиентом — советую прочитать гайд превращения llama-cli в агента и обход аутентификации.
Как протестировать, не изобретая агента
Не обязательно сразу лезть в глубины агентов. Запустите сервер и позовите его через любой MCP-клиент, например через mcp-cli:
# Установите mcp-cli (официальный инструмент от Anthropic)
pip install mcp-cli
mcp-cli connect python3 vision_server.py
# Теперь можно вызывать тул
invoke analyze_screenshot --image_path ./test_screenshot.png
Ответ должен быть что-то вроде: "На скриншоте окно браузера с ошибкой 404. В центре кнопка 'На главную'. Слева меню навигации с тремя пунктами...". Если модель галлюцинирует — проверьте качество изображения и количество контекстных токенов (увеличьте n_ctx до 8192).
Для полноценного цикла vibe-coding вам пригодятся и другие MCP-серверы: граф знаний для кода (120x сокращение токенов) или стек локальных LLM-агентов (выбор моделей по квантованию).
Чего я не рассказал, но это важно
Во-первых, GLM-5.1 Vision можно дообучить на ваших скриншотах. Процесс простой: собираете датасет (скриншот + описание), fine-tune через LoRA на одной 3090 — и модель начинает идеально описывать именно ваш UI. Размер сэмпла — 100 пар. Во-вторых, если вы работаете с видео (например, запись экрана для дебага), используйте MCP-тул, который извлекает ключевые кадры и отправляет их батчем.
В-третьих, ни в коем случае не делайте MCP-сервер общедоступным без авторизации. Даже локально — закройте порт фаерволом, иначе любой злоумышленник в вашей сети сможет отправлять изображения. Лучше используйте Unix-сокет.
И наконец: не пытайтесь заставить агента смотреть на экран в реальном времени. LLM с vision — это не механизм для стриминга. Делайте снимки только по событию (ошибка, изменение DOM, нажатие кнопки). Иначе модель захлебнется, а вы получите лаг в 10 секунд на каждый кадр.
Я описал минимальный рабочий путь. На практике вы будете менять промпты, темп, размер ресайза, квантизацию — это нормально. Главное, что теперь у вашего агента есть глаза. И они видят лучше, чем кажется.