From 72ef2f6a18ac51e269419174a6c5766e36b1b6d5 Mon Sep 17 00:00:00 2001 From: Repinoid Date: Mon, 12 Oct 2026 08:13:36 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20=D0=BA=D0=BE=D0=B4-=D1=80=D0=B5=D0=B2?= =?UTF-8?q?=D1=8C=D1=8E=20=D0=B0=D1=81=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=20+=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20(?= =?UTF-8?q?=D0=BF=D0=BE=D0=B8=D1=81=D0=BA=20*=5Fsub=5Fuser,=20per-call=20?= =?UTF-8?q?=D1=82=D0=B0=D0=B9=D0=BC=D0=B0=D1=83=D1=82)=20=D0=B2=20HISTORY?= =?UTF-8?q?=20=D0=B8=20ASSISTANT=5FARCHITECTURE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HISTORY/2026-10-12_code_review_and_fixes.md | 73 +++++++++++++++++++++ llm_project/docs/ASSISTANT_ARCHITECTURE.md | 13 +++- 2 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 HISTORY/2026-10-12_code_review_and_fixes.md diff --git a/HISTORY/2026-10-12_code_review_and_fixes.md b/HISTORY/2026-10-12_code_review_and_fixes.md new file mode 100644 index 0000000..8635dfa --- /dev/null +++ b/HISTORY/2026-10-12_code_review_and_fixes.md @@ -0,0 +1,73 @@ +# tf_docs: код-ревью AI-ассистента и правки (поиск подресурсов + таймаут ЛЛМ) + +Дата: 2026-10-12 +Коммит: `12d29ee` (репозиторий `tf_docs`, ветка `master`, запушен) +Контекст: по запросу — фактическое ревью кода/архитектуры ассистента с упором на ЛЛМ. + +## Что проверялось + +Прочитан backend целиком: `server.js`, `assist.js`, `search.js`, `stage1.js`, +`stage2.js`, `orchestrator.js`; сверено с `llm_project/docs/ASSISTANT_ARCHITECTURE.md` +и результатами прогонов (`llm_project/tests/RESULTS_*.json`). + +## Найдено (факты, `файл:строка` на момент ревью) + +1. **🔴 `search.js:206-215`** — `resourceBaseSegment` перебирал суффиксы-подресурсы + в исходном порядке, где `_user` стоит раньше `_sub_user`. Для + `nubes_s3_sub_user` (rest=`s3_sub_user`) отрезался `_user` → base `s3_sub` + вместо `s3`. Следствие: `isResourcePage` не находила страницу ресурса + (`"s3_sub_user" !== "s3"`), поиск по `resource_type` для `*_sub_user` был + неэффективен. Реальный ресурс (есть в `golden_questions_300.json`); эффект + маскировался лексическим поиском по исходному вопросу. +2. **🔴 `assist.js:25`** — `ASSIST_LLM_TIMEOUT_MS` (25 000) был объявлен в `ENV` и + задокументирован (README, `ASSISTANT_ARCHITECTURE.md:§5`), но **нигде не + использовался**: `orchestrator.js` не передавал `timeoutMs` в `stage1`, а + `stage2Generate` его не принимал. Фактически работал только общий дедлайн 40 с. +3. **🟡 `orchestrator.js:205`** — `clientIp` брал **последний** элемент + `X-Forwarded-For` (ближайший хоп) вместо первого (исходный клиент). Влияет + только при заданном `ASSIST_TRUSTED_PROXY_CIDR` (по умолчанию пусто). +4. **🟡 `orchestrator.js:317,328`** — мёртвая ветка дедупа (`handleAssistAgain` — + заглушка); в однопоточном JS недостижима. +5. **🟢** кэш ответов без TTL/лимита (`orchestrator.js:306`); `npm test` покрывает + только прокси/`health` (LLM-пайплайн без юнит-тестов); `fetchIndex` без лимита + размера; `FORWARD_HEADERS` без `etag/last-modified`; при `aborted` отдаётся + нестандартный HTTP 499. + +## Исправлено (2 пункта) + +### 1. `search.js` — порядок суффиксов подресурсов + +Массив `knownSubs` вынесен в `KNOWN_RESOURCE_SUBS`, отсортирован по убыванию длины +при загрузке модуля. Проверено изолированно: +`nubes_s3_sub_user → s3` (было `s3_sub`), `nubes_mariadb_sub_user → mariadb`; +`nubes_postgres_database → postgres`, `nubes_postgres → postgres` — без изменений. + +### 2. `assist.js` + `stage1.js` + `orchestrator.js` — реальный per-call таймаут + +- `llmChat` получил параметр `timeoutMs` (дефолт `ENV.ASSIST_LLM_TIMEOUT_MS`) и + составной сигнал: дочерний `AbortController`, абортящийся по таймауту ИЛИ по + аборту вызывающего. Таймаут покрывает вызов целиком (в т.ч. чтение тела). +- Таймаут отдаётся отдельным кодом **`llm_timeout`** (отмена клиентом/дедлайн — + по-прежнему `aborted`), чтобы `stage2` различал причины и корректно решал о повторе. +- `stage1.js` освобождён от дублирующего `AbortController`. +- `orchestrator.js`: `llm_timeout` → HTTP **504**. + +Проверено на локальном «зависшем» сервере: таймаут → `llm_timeout` (314 мс при +лимите 300), abort родителя → `aborted` (152 мс). `node -c` всех модулей OK, +`node --test` 4/4. + +## Проверка на живом стенде + +- `redeploy` инстанса `tf-docs` (`7a48a863-…`) через `llm_project/tests/redeploy.js` + → успешно (`finish=2026-10-12T07:56:44+03:00`, `ok=true`); `/health` → 200. +- Прогон 62 вопросов на `nubes-test` (`--out RESULTS_AFTER_FIX`): + **62/62 — 200 OK**, шаблон **62/62**, ошибок 0; задержки p50 5.9 с, + p95 10.8 с, max 12.4 с; `llm_timeout` не срабатывал ни разу. +- ⚠️ Сравнение с прежним `RESULTS_LIVE.json` **некорректно**: тот от 2026-10-07, + за 5 дней индекс документации изменился (у 45 из 62 вопросов другой набор + источников) → это дрейф индекса, а не эффект правки. + +## Что НЕ менялось (осознанно) + +Пункты 3–5 из «Найдено» (XFF, мёртвая ветка дедупа, кэш без TTL, юнит-тесты) — +оставлены как есть: не мешают текущей работе; вернуться при масштабировании/переработке. diff --git a/llm_project/docs/ASSISTANT_ARCHITECTURE.md b/llm_project/docs/ASSISTANT_ARCHITECTURE.md index 7464631..daef3a8 100644 --- a/llm_project/docs/ASSISTANT_ARCHITECTURE.md +++ b/llm_project/docs/ASSISTANT_ARCHITECTURE.md @@ -1,6 +1,7 @@ # AI-ассистент документации Nubes — полная документация Дата: 2026-10-07 +Обновлено: 2026-10-12 (правки: поиск `*_sub_user`, per-call таймаут ЛЛМ; коммит `12d29ee`). Статус: **работает на проде** (`https://tf-docs.nodejsk8s.dev.nubes.ru/nubes-test/`). Репозитории: `tf_docs` (backend), `tf_provider` (виджет + сборка сайта). @@ -117,6 +118,7 @@ tf_docs (Node.js, кластер nodejsk8s) [orchestrator.js] | `rate_limited` | 429 | превышен лимит (заголовок `Retry-After`) | | `busy` | 503 | семафор исчерпан (`Retry-After: 5`) | | `bad_llm_output` | 502 | LLM не выдал обязательные заголовки | +| `llm_timeout` | 504 | вызов ЛЛМ превысил `ASSIST_LLM_TIMEOUT_MS` (с 2026-10-12) | | `index_unavailable` | 503 | не удалось загрузить индекс | --- @@ -131,7 +133,7 @@ tf_docs (Node.js, кластер nodejsk8s) [orchestrator.js] | `LLM_MODEL` | `gpt-oss-120b` | модель | | `ASSIST_MAX_CONCURRENT` | `4` | семафор одновременных вызовов ЛЛМ | | `ASSIST_TOTAL_DEADLINE_MS` | `40000` | общий бюджет запроса | -| `ASSIST_LLM_TIMEOUT_MS` | `25000` | таймаут одного вызова ЛЛМ | +| `ASSIST_LLM_TIMEOUT_MS` | `25000` | таймаут одного вызова ЛЛМ; применяется с 2026-10-12 (код `llm_timeout` → 504) | | `ASSIST_RATE_PER_WINDOW` | `6` | лимит запросов на IP | | `ASSIST_RATE_WINDOW_MS` | `60000` | окно rate-limit | | `ASSIST_REPLICAS` | `1` | делитель лимита на реплики | @@ -176,7 +178,9 @@ tf_docs (Node.js, кластер nodejsk8s) [orchestrator.js] - руководство (`_manual`) — ×0.6; - служебная (`30_registry`) — ×0.4. - **Подресурс → родитель**: `nubes_postgres_database` при поиске дополняется - токеном `postgres` (`resourceBaseSegment`). + токеном `postgres` (`resourceBaseSegment`). Суффиксы-подресурсы перебираются по + убыванию длины (`KNOWN_RESOURCE_SUBS`); иначе короткий `_user` затенял `_sub_user` + (`nubes_s3_sub_user` → `s3_sub` вместо `s3`) — исправлено 2026-10-12. - **Предтокенизация**: токены всех 823 документов считаются один раз при загрузке индекса (поиск ~6 мс вместо ~1.2 с). - **Списки**: `listResourceTypes()` (56 `nubes_*`) и `resourcePages()` (35 страниц) @@ -189,6 +193,9 @@ tf_docs (Node.js, кластер nodejsk8s) [orchestrator.js] - **Повторы этапа 2**: до 2 попыток, при обрезке ответа и транзиентных ошибках ЛЛМ (кроме отмены клиентом) — с удвоением бюджета. - **Дедлайн 40 с**: `AbortController` прерывает вызовы ЛЛМ. +- **Таймаут одного вызова ЛЛМ**: `ASSIST_LLM_TIMEOUT_MS` (25 с, с 2026-10-12) — + реализован в `llmChat`; покрывает вызов целиком, включая чтение тела. Отдаётся + кодом `llm_timeout` (504) и отличается от отмены клиентом (`aborted`, 499). - **Отмена при закрытии соединения**: `req.on('close')` → abort (если нет других держащих). - **Кэш индекса**: TTL 10 мин; при сбое загрузки — работаем на свежем кэше. @@ -241,9 +248,11 @@ tf_docs (Node.js, кластер nodejsk8s) [orchestrator.js] | 3 | 61/62 | 1 | + шаблон для вопросов вне документации | | 4 | **62/62** | **0** | — | | 5 | 61/62 | 1 | клиентский сбой сети (не бэкенд) | +| 2026-10-12 | **62/62** | **0** | после правок (поиск `_sub_user` + per-call таймаут); файл `tests/RESULTS_AFTER_FIX.{json,md}` | Задержки (5-й прогон): p50 7.7 с, p95 23 с, max 40.8 с — доминирует LLM (`gpt-oss-120b` под нагрузкой), наш код ≈ 6 мс поиск + кэш. +Задержки (2026-10-12): p50 5.9 с, p95 10.8 с, max 12.4 с. Разбор: `llm_project/tests/ANALYSIS_bad_llm_output.md`.