chore: форматирование модулей + план по шагам
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
# Пошаговый план реализации 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 вопросов.
|
||||
Формат каждой записи:
|
||||
```json
|
||||
{"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).
|
||||
Reference in New Issue
Block a user