12 KiB
Пошаговый план реализации AI-ассистента документации
Дата: 2026-10-07
Кому: субагент DeepSeek Flash (исполнитель механических шагов)
Кто отвечает за интеграцию/проверку: ассистент (я)
Контекст: см. BRIEF_docs_ai_assistant.md (решения), reviews/ (рецензия + диалог).
Рабочий каталог кода: ~/TF/tf_docs (Node.js, без внешних зависимостей, node >= 18).
КАЖДЫЙ шаг ниже — отдельная порция для субагента Flash. Flash делает механику, я (ассистент) принимаю/интегрирую/проверяю. После каждого шага — коммит осмысленным сообщением, push только по команде владельца.
Шаг 0. Блокеры и рекомендации (утвердить у владельца)
Не код. Нужны решения владельца до начала.
- Блокеры: 7.1 (путь
/<stand>/_api/assist), 7.2 (политика отправки данных в ЛЛМ), 7.3 (X-Forwarded-For/ASSIST_TRUSTED_PROXY_CIDR), 7.5 (не снят — уточнить), ротация ключаapi.aillm.ru. - Рекомендации одним списком: 7.4, 7.6, 7.7, 7.8, 7.9, 7.11.
- Приёмник:
llm_project/DECISIONS_v0.md(фиксируем принятое).
Шаг 1. Live-проверка api.aillm.ru
Задача Flash: написать одноразовый скрипт-пробник (НЕ часть assist.js) и вернуть факты.
Файл: llm_project/probe/probe_aillm.js (запуск вручную, результат — в llm_project/probe/RESULTS.md).
Проверить и зафиксировать:
response_format: {type: "json_object"}— поддерживается? (даёт ли валидный JSON)stream: true— какой формат чанков (data:SSE)? нужен ли нам вообще (v1 — нет).reasoning_effort: "low"— принимается? есть лиreasoning_contentв ответе?- Ответ на 429 — какие заголовки (
Retry-After?X-RateLimit-*?), тело. - Размер контекста — до скольки токенов принимает запрос (увеличивая промпт, найти предел/ошибку).
- Формат ошибки при пустом
content/ обрыве. - Сколько длится этап 1-типа запрос (засечь 3 образца) — для дедлайна 40 с.
Критерий готово: RESULTS.md содержит таблицу ответов по всем 7 пунктам + пример сырого ответа (JSON) для этапов 1 и 2.
После шага 1: финализируем llmChat (структуру разбора ответа).
Шаг 2. Каркас assist.js
Задача Flash: создать assist.js (новый модуль), НЕ трогая server.js.
Модули по брифу:
validateInput(req)— Content-Typeapplication/json, Content-Length > 2048 → 413, чтение потока со счётчиком байт (> 2 КБ → 413), разбор JSON, полеquestion(непустая строка).- rate-limit — локальная таблица ключ=IP (или IP/stand), периодическая чистка; лимит делится на число реплик (
ASSIST_REPLICAS). - семафор
ASSIST_MAX_CONCURRENT= 4; сверх — 503 +Retry-After: 5, тело{"error":"busy","message":"..."}. - объединение одинаковых запросов (ключ =
stand+ нормализованный запрос): два счётчика («ожидающие клиенты» / «держащие вызов»); обрыв клиента не отменяет общий вызов, пока есть держащие; отмена ЛЛМ только когда отключились все. llmChat(messages, {max_tokens, controller})— POST наapi.aillm.ru,AbortController, разбор толькоcontent.- общий дедлайн
ASSIST_TOTAL_DEADLINE_MS= 40000; повтор этапа 1 только если запас > 15 с;req.on('close')→ abort. - кэш ответов (ключ =
stand+ нормализованный запрос), TTL. - логирование: длина вопроса,
stand, время этапов, статус, кэш, токены. Текст запросов — НЕ логировать.
Критерий готово: модуль экспортирует обработчик, собирается без ошибок (node -c assist.js), покрыт беглыми юнит-пробами в test/ (validateInput, семафор, дедуп-ключ).
Шаг 3. Эталонный набор вопросов
Задача Flash: создать llm_project/golden/golden_questions.json — 20–30 вопросов.
Формат каждой записи:
{"question": "...", "expected_pages": ["path/на_страницу"], "resource_type": "nubes_..."}
Вопросы: по разным ресурсам (nubes_*), включая подресурсы (_user, _database, _topic), негативные (вопрос не про ресурс, вопрос с выдуманным ресурсом вроде nubes_instance).
Критерий готово: файл валиден, вопросы реалистичны (взяты из опубликованной документации стенда nubes-test).
Шаг 4. Индекс и поиск
Задача Flash: реализовать загрузку и лексический поиск по search_index.json.
- Источник:
VM_DOCS_BASE_URL/<stand>/search/search_index.json(только этот путь). - Снять формат индекса с реального стенда (см. шаг 7/8 верификации): структура записи, поля
location(видpage/#anchor),title,text(HTML → удалить). - Лекс. поиск: токенизация, BM25-подобный скор, top-K разделов, группировка по страницам.
- Собрать список реальных
nubes_*из имён страниц (для этапа 1). - Порог «не найдено» = «совпало >= N уникальных ключевых слов», N подобрать по эталону (шаг 3). Критерий готово: на эталонном наборе поиск выдаёт ожидаемые страницы с приемлемой точностью (записать метрику).
Шаг 5. Этап 1 (извлечение resource_type)
Задача Flash: реализовать этап 1.
- Системный промпт (рус.) со списком реальных
nubes_*из индекса. - Ответ:
resource_type; если не в списке — отбрасывается (нет ресурса). - Падение/таймаут этапа 1 — продолжить без него (только токен-поиск).
Критерий готово: на эталоне корректно извлекает/отбрасывает
resource_type; выдуманные (nubes_instance) — отбрасываются.
Шаг 6. Объединение + этап 2
Задача Flash: реализовать объединение (П.8) и этап 2. Объединение:
- Поиск по токенам исходного запроса — наравне с поиском после этапа 1.
- Страница ресурса — всегда первой в контекст (если
resource_typeв списке). - Кандидаты объединяются, повторы по
location, оценка = max из двух; контекст до K без повторов; страница ресурса входит в K. - Падение этапа 1 — только токен-поиск, без обязательной страницы. Этап 2:
- Промпт со строгим шаблоном «Роль/Задача/Контекст/Требования/Ограничения».
- Ответ без заголовков → 502
bad_llm_output. - Жёсткий
max_tokens. sources=/<stand>/+location; отбрасывать//,:,... Критерий готово: полный сквозной ответ на эталоне, корректныеsources.
Шаг 7. Маршрут в server.js + Dockerfile + README
Задача Flash (под контролем): в server.js добавить POST-ветку /<stand>/_api/assist до проверки 405 (строки 72–75).
standчерезSTAND_RE.assist.jsгрузить в try/catch; ошибки → 500 JSON.- Поведение статики и падение без
VM_DOCS_BASE_URL— НЕ менять. - Dockerfile: скопировать
assist.js(+ при необходимостиlib/). - README: описать эндпоинт и переменные окружения (
ASSIST_*, ключ). Критерий готово:node -c, статика работает как раньше, POST отвечает JSON (200/4xx/5xx).
Шаг 8. Виджет (фронтенд)
Задача Flash: написать виджет (встраиваемый JS/CSS/HTML или шаблон).
standиз первого сегментаlocation.pathname; нет сегмента — не показывать.- Плавающая кнопка на всех страницах.
- Под полем ввода: «не вводите ключи и пароли».
- Обращение к
/<stand>/_api/assist. - Обработка 503 (
Retry-After) — показ «занято, повторите». Критерий готово: виджет открывается, шлёт запрос, показывает ответ иsources-ссылки.
Шаг 9. Итоговая оценка качества
Задача Flash: прогнать эталонный набор (шаг 3) через полный пайплайн, собрать метрики.
- Точность
resource_type, точность страниц, корректностьsources, % 502/ошибок, задержки. Критерий готово: отчётllm_project/golden/EVAL_*.mdс таблицей.
Шаг 10. Деплой — ТОЛЬКО по команде владельца. Ассистент сам НЕ деплоит.
Шаг 11. Документация (README финал, переменные, эксплуатация).
Порядок выполнения и зависимости
- Шаг 0 (утверждение) → блокер, ждать владельца.
- Шаг 1 и Шаг 2 — параллельно (каркас не зависит от результата шага 1;
llmChatфинализируется после шага 1). - Шаг 3 → 4 → 5 → 6 (зависимы).
- Шаг 7 → 8 (после 6).
- Шаг 9 (оценка), Шаг 10 (деплой, по команде), Шаг 11 (документация).
Непроверенные допущения (снять на стенде)
proxy-read-timeoutnginx-ingress (шаг 1/верификация).- Формат
search_index.json(шаг 4). - Пропускает ли ingress POST (
curl -X POSTна стенде) — отдельная верификация. - Поддержка
reasoning_effort/response_format/stream/429 уapi.aillm.ru(шаг 1).