Files
tf_docs/llm_project/PLAN_docs_ai_assistant_STEPS.md

12 KiB
Raw Permalink Blame History

Пошаговый план реализации 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).

Проверить и зафиксировать:

  1. response_format: {type: "json_object"} — поддерживается? (даёт ли валидный JSON)
  2. stream: true — какой формат чанков (data: SSE)? нужен ли нам вообще (v1 — нет).
  3. reasoning_effort: "low" — принимается? есть ли reasoning_content в ответе?
  4. Ответ на 429 — какие заголовки (Retry-After? X-RateLimit-*?), тело.
  5. Размер контекста — до скольки токенов принимает запрос (увеличивая промпт, найти предел/ошибку).
  6. Формат ошибки при пустом content / обрыве.
  7. Сколько длится этап 1-типа запрос (засечь 3 образца) — для дедлайна 40 с.

Критерий готово: RESULTS.md содержит таблицу ответов по всем 7 пунктам + пример сырого ответа (JSON) для этапов 1 и 2.

После шага 1: финализируем llmChat (структуру разбора ответа).


Шаг 2. Каркас assist.js

Задача Flash: создать assist.js (новый модуль), НЕ трогая server.js. Модули по брифу:

  • validateInput(req) — Content-Type application/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 финал, переменные, эксплуатация).


Порядок выполнения и зависимости

  1. Шаг 0 (утверждение) → блокер, ждать владельца.
  2. Шаг 1 и Шаг 2 — параллельно (каркас не зависит от результата шага 1; llmChat финализируется после шага 1).
  3. Шаг 3 → 4 → 5 → 6 (зависимы).
  4. Шаг 7 → 8 (после 6).
  5. Шаг 9 (оценка), Шаг 10 (деплой, по команде), Шаг 11 (документация).

Непроверенные допущения (снять на стенде)

  • proxy-read-timeout nginx-ingress (шаг 1/верификация).
  • Формат search_index.json (шаг 4).
  • Пропускает ли ingress POST (curl -X POST на стенде) — отдельная верификация.
  • Поддержка reasoning_effort/response_format/stream/429 у api.aillm.ru (шаг 1).