feat(P1): новый LLM-промпт для документации — правила A-E

- value_list → читаемый текст (Допустимые значения)
- regex → описание формата
- пустые описания → заполнять из MAN
- группы map-fixed → 1 предложение о содержимом
- операции без описания → шаблоны
- MAN-контекст для params/ops файлов
- max_tokens 4096 → 8192
- вывод в docs_llm/ вместо перезаписи docs/
This commit is contained in:
“Naeel”
2026-08-09 21:11:03 +04:00
parent ac2ce0ea53
commit c9a881aa84
8 changed files with 696 additions and 48 deletions
+36
View File
@@ -0,0 +1,36 @@
# Sonnet: ответ по улучшению документации
## Принцип: «User Journey First» — 4 сценария
A. «Хочу задеплоить» → описание (30s) → минимальный пример (1m) → apply
B. «Хочу настроить параметр» → справка с читаемыми constraints
C. «Хочу использовать output» → outputs + HCL-примеры
D. «Хочу modify/restart/suspend» → страница операций
## Изменения по 3 слоям
### Слой 1: LLM-промпт — P1 (макс. польза, 0 компиляции)
- Раскрывать `value_list` → «Допустимые значения: 1, 3, 5, 7»
- Раскрывать `regex` → «Формат: cron»
- Заполнять пустые описания из MAN
- Группы (clusterConfiguration) — 1 предложение из MAN
- Операции — заполнять «—» из MAN
- Заменять TODO в примерах на реальные значения
### Слой 2: docs-generator (Go) — P2
- Убрать колонку ID
- value_list → читаемый текст
- Двойной пример: минимальный (15 строк) + полный в <details>
- Секция «Быстрый старт» перед MAN
### Слой 3: mkdocs — P3
- Sidebar по категориям (Базы данных / K8s / Хранилище / ...)
- Хлебные крошки
- Убрать inline-навигацию (заменяет sidebar)
- Back/Next кнопки
## Порядок реализации
1. LLM промпт — мгновенный эффект на все 34 сервиса
2. renderParamTable — убрать ID, раскрыть constraints
3. buildExamplePage — двойной пример
4. mkdocs nav — категории в sidebar
+130
View File
@@ -0,0 +1,130 @@
# Sonnet Briefing: анализ и улучшение документации провайдера
Цель: изучить КАЖДЫЙ шаг генерации документации и предложить конкретные улучшения,
чтобы пользователю было понятно и удобно работать с каждым ресурсом.
---
## ⛔ Файлы — ПРОЧИТАТЬ ОБЯЗАТЕЛЬНО ВСЕ
### Генераторы
| # | Файл | Что смотреть |
|---|------|-------------|
| 1 | `TOOLS/docs-generator/main.go` | весь main — как вызывается, какие флаги |
| 2 | `TOOLS/docs-generator/internal/writers/writers.go` | ВСЕ функции. Особенно: `buildCreateParamsPage`, `buildModifyParamsPage`, `buildOutputsPage`, `buildExamplePage`, `buildManualPage`, `renderParamTable`, `renderNestedParams`, `htmlToMarkdown`, `formatParamOrBlock` |
| 3 | `TOOLS/docs-generator/internal/types/` | структуры YAML-спеки |
### LLM-обработка
| # | Файл | Что смотреть |
|---|------|-------------|
| 4 | `TOOLS/scripts/05_generate_docs_llm.py` | весь скрипт — как вызывается LLM, промпт, как парсится ответ |
| 5 | `docs/LLM_DOCS_GENERATION.md` | архитектура, правила для LLM |
### Результаты генерации (примеры)
| # | Файл | Что смотреть |
|---|------|-------------|
| 6 | `generated/test/docs/postgres_params_create.md` | RAW-вывод docs-generator (без LLM) |
| 7 | `generated/test/docs_llm/90_postgres.md` | ПОСЛЕ LLM-обработки |
| 8 | `generated/test/docs/postgres.md` | главная страница (MAN) |
| 9 | `generated/test/docs/postgres_example.md` | HCL пример |
| 10 | `generated/test/docs/postgres_outputs.md` | выходные параметры |
| 11 | `generated/test/docs/postgres_ops.md` | список операций |
---
## Текущий пайплайн (3 шага)
```
YAML-спеки
→ docs-generator (Go) → raw .md с HTML-таблицами
→ LLM (gpt-oss-120b, по одному файлу) → улучшенные .md
→ mkdocs-material → статический сайт → S3
```
---
## Что видит пользователь СЕЙЧАС (пример: postgres_params_create.md)
### До LLM (raw):
```html
<table><thead><tr><th>ID</th><th>Code</th><th>Type</th><th>Description</th><th>Constraints</th></tr></thead>
<tr><td>788</td><td><strong><code>cluster_configuration</code></strong></td><td><code>map-fixed</code></td><td></td><td></td></tr>
```
- Колонка ID (техническая, пользователю не нужна)
- Английские заголовки (Code, Description, Constraints)
- Пустые ячейки Description
- Raw `value_list=` в Constraints
### После LLM:
```
| clusterConfiguration | map-fixed | да | — | — |
```
- Русские заголовки ✅
- ID убран ✅
- Но: пустые Description (—), value_list не раскрыт
---
## Проблемы (что нужно улучшить)
### 1. Пустые описания параметров
Многие параметры в выводе имеют `—` в колонке «Описание». Если сервис предоставил `descr` или `man` в YAML — он должен быть в документации.
### 2. Технические колонки
- Колонка «Constraints» показывает `value_list=1, 3, 5, 7` вместо читаемого «Допустимые значения: 1, 3, 5, 7»
- Колонка «Default» показывает пустую строку вместо «нет» или «—»
- ID параметров виден в raw-версии, но нужен ли он вообще?
### 3. MAN-секция (service_man)
Это HTML-строка с полным руководством от облачного провайдера. Она обрабатывается `htmlToMarkdown()` — regex-заменами. Часто результат нечитаемый: сломанные списки, потерянные ссылки, HTML-мусор.
### 4. HCL-примеры
`buildExamplePage` генерирует пример с ВСЕМИ параметрами (required + default). Это гигантский манифест на 100+ строк. Может, показывать сначала минимальный working example, а полный — отдельно?
### 5. Навигация
На каждой странице — строка навигации из 6 ссылок. Занимает место, дублируется. Может, сделать сайдбар или хлебные крошки?
### 6. LLM-промпт (05_generate_docs_llm.py)
Промпт просит «улучшить формулировки», но:
- Не просит раскрывать value_list в читаемый вид
- Не просит добавлять «почему» и «зачем» к параметрам
- Не использует `service_man` как дополнительный контекст для обогащения описаний
- Обрабатывает по одному файлу — теряет контекст между страницами
---
## Вопросы
### Q1: Структура страниц
Текущая: Manual | Create params | Modify params | Outputs | Ops | Example.
Удобно ли это? Что переставить/добавить/убрать? Может, всё на одной странице с якорями?
### Q2: HTML-таблицы vs Markdown-таблицы
Сейчас raw — HTML, LLM конвертирует в Markdown-таблицы. Оставить Markdown? Или HTML-таблицы лучше (CSS, выравнивание)?
### Q3: LLM-промпт
Как улучшить промпт чтобы:
- description параметров наполнялся из `service_man` где возможно
- value_list показывался читаемо
- empty cells говорили «не указано» а не «—»
### Q4: HCL-примеры
Минимальный пример + полный? Или только минимальный? Или только полный?
### Q5: Постраничная vs одностраничная документация
7 .md файлов на сервис. Это норм или перебор? Может, генерировать один README.md на сервис со всем внутри?
### Q6: Что ещё можно улучшить для UX?
Посмотри на любые 2-3 страницы из `generated/test/docs_llm/` и скажи: что непонятно, что раздражает, чего не хватает.
---
## Ожидаемый ответ
1. Анализ текущего состояния: что хорошо, что плохо (с конкретными примерами из файлов)
2. Конкретные предложения по каждому из 6 вопросов
3. Unified diff предлагаемых изменений в writers.go и 05_generate_docs_llm.py
4. Пример одной страницы «как должно быть» для postgres (хотя бы params_create)
+50
View File
@@ -0,0 +1,50 @@
# Документация — полный диалог и финальный план
## Ответы на 3 вопроса Соннета
### 1. Inline-навигацию убирать?
**Убирать, но сначала добавить страницы в sidebar.**
Сейчас ресурсные страницы не в `mkdocs.yml nav:` — они сироты. Inline nav — единственная навигация.
Порядок: сначала P3 (добавить в sidebar через `_nav_fragment.yml`), потом убрать inline из writers.go.
### 2. Минимальный пример — только required без default?
**Да.** Параметр с дефолтом и так сработает без указания. Минимальный пример = required=true И default пустой.
15 строк вместо 100.
### 3. Категории для sidebar
Группировка по 7 категориям:
| Категория | Сервисы |
|---|---|
| Базы данных | postgres, redis, mongodb, mariadb, clickhouse |
| Очереди | rabbitmq, kafka |
| Хранилище | s3, s3bucket, nextcloud |
| K8s | k8s_velero, k8s_sthutrval_cluster, k8s_openbao, vc_mgmt_sthutrval_cluster |
| VMware | vc_org, vc_vdc, vc_nsxt, vcexternalip, vapp, vc_vm_v2, vc_vm_v3, vc_vdc_group |
| Приложения | flask, nodejs, lucee, http, gitea, superset, pgadmin, harbor, akhq, llm_ai |
| Сеть | zones_v2, dnsrecord |
---
## Финальный план (3 слоя)
### P1: LLM-промпт (05_generate_docs_llm.py) — 0 компиляции, 34 сервиса
6 инструкций:
- value_list → «Допустимые значения: X, Y, Z»
- regex → «Формат: cron / UUID / IP»
- Описания групп из MAN
- Пустые описания заполнять
- Операции без «—»
- TODO в примерах → реальные значения
### P2: docs-generator (writers.go)
- renderParamTable: убрать ID, value_list → читаемый текст
- buildExamplePage: минимальный пример + полный в <details>
### P3: mkdocs навигация
- docs-generator генерирует _nav_fragment.yml с категориями
- 04_build_and_publish_docs.sh вставляет его в mkdocs.yml
- writers.go: убрать inline nav
- mkdocs.yml: breadcrumbs + prev/next
### Порядок: P1 → P2 → P3
+32
View File
@@ -0,0 +1,32 @@
# Документация — финальный план P1 (утверждён)
Дата: 2026-08-09
Источник: Sonnet, после серии брифов и уточнений
## Что меняется в 05_generate_docs_llm.py
### 1. SYSTEM_PROMPT — замена
Новый промпт с правилами A-E (см. HISTORY/SONNET/docs_prompt_full_response.md)
### 2. max_tokens: 4096 → 8192
### 3. Новая функция extract_man(text) → str
Вырезает блок ## MAN из Name.md. Используется как контекст для params/ops.
### 4. Новая функция build_prompt(file_type, filename, content, man) → str
Формирует сообщение для LLM: тип файла + MAN-контекст + содержимое.
### 5. Обработка ВСЕХ типов файлов
Было: только Name.md
Стало: Name.md, _params_create.md, _params_modify.md, _ops.md, _example.md
MAN-контекст: для params и ops, без MAN для главной и примеров.
### 6. Копирование в docs_llm/
- outputs, params-landing, subresource — копировать as-is
- 30_registry/, guides/, index.md — копировать из docs/
### Решения
- Subresource: копировать as-is (не через LLM)
- max_tokens: 8192
- Вывод: docs_llm/
- 30_registry копировать в самом скрипте
+35
View File
@@ -0,0 +1,35 @@
# Sonnet: финальный план P1 — 05_generate_docs_llm.py
## Пайплайн на один сервис
```
docs/Name.md ─┐
docs/Name_params_create.md ─┤ LLM → docs_llm/Name.md
docs/Name_params_modify.md ─┤ docs_llm/Name_params_create.md
docs/Name_ops.md ─┤ docs_llm/Name_params_modify.md
docs/Name_example.md ─┘ docs_llm/Name_ops.md
docs_llm/Name_example.md
docs/Name_outputs.md ─── copy → docs_llm/Name_outputs.md
docs/Name_params.md ─── copy → docs_llm/Name_params.md
docs/Name_subresource*.md ─── copy → docs_llm/Name_subresource*.md
```
## Ключевые детали
### MAN-контекст
- Извлекается из `docs/Name.md` (raw HTML) функцией `extract_man()`
- Передаётся в том же сообщении что и params/ops файлы
- Для главной страницы (Name.md) и примеров (_example.md) — без MAN
### Параметры LLM
- `max_tokens`: 4096 → **8192**
- `temperature`: 0.15 (без изменений)
- `model`: gpt-oss-120b (без изменений)
### Интеграция с 04_build_and_publish_docs.sh
Вариант A: DOCS_GEN_DIR → `docs_llm/`. Скрипт копирует guides/, 30_registry/, index.md в docs_llm/.
## Вопрос: subresource-страницы обрабатывать LLM?
postgres_user.md, postgres_database.md, postgres_backup.md — их структура как у _params_create.md.
Пока копировать as-is или тоже через LLM?
+38
View File
@@ -0,0 +1,38 @@
# Sonnet: ответы — готовый SYSTEM_PROMPT и механизм MAN→группы
## Вопрос 2: MAN → группы
Явный маппинг НЕ нужен. LLM делает семантический матч:
- Видит группу `clusterConfiguration` с sub-params `cpu, memory, disk, replicas`
- Видит в MAN: «Квота (millicore) ядра пода... Квота памяти... Размер диска... Количество узлов»
- Сопоставляет по смыслу → «Ресурсы пода кластера: CPU, RAM, диск, реплики»
Условие: MAN в том же сообщении, что и params-файл.
## Готовый SYSTEM_PROMPT
См. полный текст с правилами A-E:
- A: удалить колонку ID
- B: value_list → «Допустимые значения: X, Y, Z»
- C: regex → читаемый формат
- D: пустые описания → заполнить из MAN
- E: группы (map-fixed) → 1 предложение о содержимом
+ правила для _ops.md, _example.md, главной страницы (MAN)
## Новая логика вызова LLM
MAN передаётся как контекст в том же сообщении что и params-файл:
```
Тип файла: _params_create
Сервис: postgres
=== MAN СЕРВИСА ===
{текст MAN из Name.md}
=== Файл ===
{содержимое}
```
## 2 вопроса
1. max_tokens 4096 → 8192? (params для postgres ~4KB HTML)
2. Писать в docs_llm/ или сразу на место?
+162
View File
@@ -0,0 +1,162 @@
# Sonnet: ПОЛНЫЙ ответ — готовый SYSTEM_PROMPT + механизм MAN→группы
## Вопрос 2: механизм маппинга MAN → группы
**Явный маппинг не нужен.** LLM делает его сам через семантику.
Как это работает для `clusterConfiguration`:
```
LLM видит в _params_create.md:
группа: clusterConfiguration (map-fixed)
sub-params: cpu, memory, disk, replicas
LLM видит в MAN (в том же сообщении):
«Квота (millicore) ядра пода... Квота (megabyte) памяти...
Размер диска... Количество узлов (реплик)...»
LLM выводит:
→ «Ресурсы пода кластера: CPU (milicores), RAM (MB), диск (GB), реплики»
```
**Страховка**: группы без секции в MAN (`autoscaleConfiguration`) — LLM работает только по именам sub-params: `enabled`, `percent`, `quota`, `schedule` → «Автомасштабирование PV: расширяет диск на заданный процент при заполнении».
**Условие**: MAN передаётся в **том же сообщении**, что и params-файл, а не отдельно.
---
## Полный SYSTEM_PROMPT
```python
SYSTEM_PROMPT = """Ты — технический писатель Nubes Terraform Provider.
Улучшаешь автогенерированные Markdown-файлы документации.
═══════════════════════════════════════════════════════
АБСОЛЮТНЫЕ ЗАПРЕТЫ
═══════════════════════════════════════════════════════
- НЕ выдумывай имена параметров, типы, значения по умолчанию
- НЕ трогай HCL-блоки (всё внутри ```hcl ... ```)
- НЕ трогай имена параметров в таблицах (snake_case / camelCase из API)
- НЕ трогай навигационные строки вида [Manual](x.md) · [Create params](y.md) ...
- НЕ добавляй и не удаляй строки/колонки в таблицах
- Верни ТОЛЬКО готовый текст файла. Без объяснений, без``` вокруг всего текста
═══════════════════════════════════════════════════════
ПРАВИЛА ДЛЯ ТАБЛИЦ ПАРАМЕТРОВ (_params_create.md, _params_modify.md)
═══════════════════════════════════════════════════════
Таблицы содержат столбцы: ID | Code | Type | Required | Default | Description | Constraints
Можно менять ТОЛЬКО текст в <td>Description</td> и <td>Constraints</td>.
ПРАВИЛО A — удали колонку ID:
Удали <th>ID</th> из заголовка и соответствующий первый <td>число</td> из каждой строки.
ПРАВИЛО B — value_list в Constraints:
value_list=1, 3, 5, 7 → очисти ячейку Constraints до пустой.
В ячейку Description добавь строку: «Допустимые значения: **1, 3, 5, 7**»
Если в Description уже был текст — добавь после него, через пробел или перевод строки (<br/>).
ПРАВИЛО C — regex в Constraints:
Замени regex-строку на читаемое описание формата:
- cron-подобный regex → «Формат: cron-выражение. Пример: `0 0 * * *`»
- UUID regex → «Формат: UUID»
- IP-адрес regex → «Формат: IP-адрес»
- Прочее → кратко опиши формат своими словами
ПРАВИЛО D — пустое Description (пустая ячейка или —):
Напиши краткое описание параметра (1–2 предложения). Приоритет источников:
1. MAN — ищи текст, связанный с параметром по смыслу и по именам sub-params
2. Имя параметра snake_case → понятный русский
3. Тип и контекст соседних параметров в группе
ПРАВИЛО E — верхнеуровневые группы (строки с map-fixed или array-map-fixed):
Эти строки — контейнеры, в них вложены sub-params.
Если Description пустое — напиши 1 предложение: что содержит группа и зачем.
Смотри на имена sub-params (они идут в следующих строках) + MAN.
Пример: clusterConfiguration с sub-params cpu/memory/disk/replicas
→ «Ресурсы пода кластера: CPU (milicores), RAM (MB), диск (GB) и количество реплик»
Пример: backupConfiguration с sub-params s3_uid/retain/schedule
→ «Параметры резервного копирования: S3-хранилище, расписание и глубина хранения»
═══════════════════════════════════════════════════════
ПРАВИЛА ДЛЯ ОПЕРАЦИЙ (_ops.md)
═══════════════════════════════════════════════════════
Операции без описания (пустая строка, нет текста после —):
Напиши 1 предложение о том, что делает операция с ресурсом.
Используй MAN если передан. Не придумывай параметров.
Универсальные шаблоны (если MAN не помогает):
suspend → «Приостановка ресурса (поды остановлены, данные сохранены)»
resume → «Запуск ранее остановленного ресурса»
restart → «Перезапуск подов ресурса. ⚠️ Возможна кратковременная недоступность»
reconcile → «Принудительная синхронизация состояния с API»
recovery → «Восстановление из резервной копии»
═══════════════════════════════════════════════════════
ПРАВИЛА ДЛЯ ГЛАВНОЙ СТРАНИЦЫ (Name.md — секция ## MAN)
═══════════════════════════════════════════════════════
Блок ## MAN содержит HTML внутри <div class="man-content">.
Преобразуй HTML → читаемый Markdown:
<h2>/<h3> → ## / ###
<ul><li> → - элемент списка
<strong> → **текст**
<code> → `текст`
<a href="url">текст</a> → [текст](url)
<br/>, <p>, <div> → удали тег, замени переносами строк где нужно
Лишние пустые строки подряд → одна пустая строка
Сохраняй всё смысловое содержание. Не перефразируй, не сокращай.
═══════════════════════════════════════════════════════
ПРАВИЛА ДЛЯ ПРИМЕРОВ (_example.md)
═══════════════════════════════════════════════════════
Строки с TODO — замени на типичный реальный пример если он предсказуем:
resource_name = "TODO""my-postgres"
resource_realm = "TODO""k8s-3-sandbox-nubes-ru" # укажите ваш кластер
master_ip_space = "TODO""internet-no-antiddos-v1" # из вашей организации
slave_ip_space = "TODO""internet-no-antiddos-v1" # из вашей организации
Оставь TODO если значение непредсказуемо (UUID чужого ресурса):
s3_uid = "TODO" → s3_uid = "TODO" # UUID ресурса nubes_s3 из state: nubes_s3.backup_store.id
"""
```
---
## Новая логика USER-сообщения
```python
# При обработке _params_create / _params_modify / _ops / _example:
man_text = extract_man_section(service_main_md) # берём ## MAN из Name.md
prompt = f"""Тип файла: {file_type}
Сервис: {service_name}
=== MAN СЕРВИСА (контекст для описаний групп и параметров) ===
{man_text}
=== Файл для улучшения: {filename} ===
{file_content}
"""
# При обработке Name.md (главная):
prompt = f"""Тип файла: ГЛАВНАЯ СТРАНИЦА
Сервис: {service_name}
=== Файл для улучшения: {filename} ===
{file_content}
"""
```
---
## 2 вопроса от Соннета
1. `max_tokens` сейчас `4096` — но `_params_create.md` для postgres ~4KB HTML. Поднять до `8192`?
2. Писать в `docs_llm/` или сразу на место?
---
## Ответы
1. **max_tokens = 8192** — да. После обогащения описаниями файл станет больше.
2. **Писать в `docs_llm/`** — не затирать сырой вывод docs-generator, нужен для отладки.
+213 -48
View File
@@ -5,9 +5,9 @@ LLM-улучшатель документации Nubes Terraform Provider.
чтобы сделать описания читаемыми и логичными.
Использование:
python3 05_generate_docs_llm.py generated/test/docs
python3 05_generate_docs_llm.py generated/test/docs [--out generated/test/docs_llm]
"""
import json, os, sys, time
import json, os, re, shutil, sys, time
from pathlib import Path
from urllib.request import Request, urlopen
@@ -15,16 +15,94 @@ API_URL = "https://api.aillm.ru/v1/chat/completions"
API_KEY = "sk-ucI5YvOticoOQ9Kuj5K9mQ"
MODEL = "gpt-oss-120b"
SYSTEM_PROMPT = """Ты — технический писатель. Улучши ОДИН .md файл документации Terraform-провайдера: сделай описания грамотными и логичными.
SYSTEM_PROMPT = """Ты — технический писатель Nubes Terraform Provider.
Улучшаешь автогенерированные Markdown-файлы документации.
═══════════════════════════════════════════════════════
АБСОЛЮТНЫЕ ЗАПРЕТЫ
═══════════════════════════════════════════════════════
- НЕ выдумывай имена параметров, типы, значения по умолчанию
- НЕ трогай HCL-блоки (всё внутри ```hcl ... ```)
- НЕ трогай имена параметров в таблицах (snake_case / camelCase из API)
- НЕ трогай навигационные строки вида [Manual](x.md) · [Create params](y.md) ...
- НЕ добавляй и не удаляй строки/колонки в таблицах
- Верни ТОЛЬКО готовый текст файла. Без объяснений, без``` вокруг всего текста
═══════════════════════════════════════════════════════
ПРАВИЛА ДЛЯ ТАБЛИЦ ПАРАМЕТРОВ (_params_create.md, _params_modify.md)
═══════════════════════════════════════════════════════
Таблицы содержат столбцы: ID | Code | Type | Required | Default | Description | Constraints
Можно менять ТОЛЬКО текст в <td>Description</td> и <td>Constraints</td>.
ПРАВИЛО A — value_list в Constraints:
value_list=1, 3, 5, 7 → очисти ячейку Constraints до пустой.
В ячейку Description добавь строку: «Допустимые значения: **1, 3, 5, 7**»
Если в Description уже был текст — добавь после него, через пробел или перевод строки (<br/>).
ПРАВИЛО B — regex в Constraints:
Замени regex-строку на читаемое описание формата:
- cron-подобный regex → «Формат: cron-выражение. Пример: `0 0 * * *`»
- UUID regex → «Формат: UUID»
- IP-адрес regex → «Формат: IP-адрес»
- Прочее → кратко опиши формат своими словами
ПРАВИЛО C — пустое Description (пустая ячейка или —):
Напиши краткое описание параметра (1–2 предложения). Приоритет источников:
1. MAN — ищи текст, связанный с параметром по смыслу и по именам sub-params
2. Имя параметра snake_case → понятный русский
3. Тип и контекст соседних параметров в группе
ПРАВИЛО D — верхнеуровневые группы (строки с map-fixed или array-map-fixed):
Эти строки — контейнеры, в них вложены sub-params.
Если Description пустое — напиши 1 предложение: что содержит группа и зачем.
Смотри на имена sub-params (они идут в следующих строках) + MAN.
Пример: clusterConfiguration с sub-params cpu/memory/disk/replicas
→ «Ресурсы пода кластера: CPU (milicores), RAM (MB), диск (GB) и количество реплик»
Пример: backupConfiguration с sub-params s3_uid/retain/schedule
→ «Параметры резервного копирования: S3-хранилище, расписание и глубина хранения»
═══════════════════════════════════════════════════════
ПРАВИЛА ДЛЯ ОПЕРАЦИЙ (_ops.md)
═══════════════════════════════════════════════════════
Операции без описания (пустая строка, нет текста после —):
Напиши 1 предложение о том, что делает операция с ресурсом.
Используй MAN если передан. Не придумывай параметров.
Универсальные шаблоны (если MAN не помогает):
suspend → «Приостановка ресурса (поды остановлены, данные сохранены)»
resume → «Запуск ранее остановленного ресурса»
restart → «Перезапуск подов ресурса. ⚠️ Возможна кратковременная недоступность»
reconcile → «Принудительная синхронизация состояния с API»
recovery → «Восстановление из резервной копии»
═══════════════════════════════════════════════════════
ПРАВИЛА ДЛЯ ГЛАВНОЙ СТРАНИЦЫ (Name.md — секция ## MAN)
═══════════════════════════════════════════════════════
Блок ## MAN содержит HTML внутри <div class="man-content">.
Преобразуй HTML → читаемый Markdown:
<h2>/<h3> → ## / ###
<ul><li> → - элемент списка
<strong> → **текст**
<code> → `текст`
<a href="url">текст</a> → [текст](url)
<br/>, <p>, <div> → удали тег, замени переносами строк где нужно
Лишние пустые строки подряд → одна пустая строка
Сохраняй всё смысловое содержание. Не перефразируй, не сокращай.
═══════════════════════════════════════════════════════
ПРАВИЛА ДЛЯ ПРИМЕРОВ (_example.md)
═══════════════════════════════════════════════════════
Строки с TODO — замени на типичный реальный пример если он предсказуем:
resource_name = "TODO""my-postgres"
resource_realm = "TODO""k8s-3-sandbox-nubes-ru" # укажите ваш кластер
master_ip_space = "TODO""internet-no-antiddos-v1" # из вашей организации
slave_ip_space = "TODO""internet-no-antiddos-v1" # из вашей организации
Оставь TODO если значение непредсказуемо (UUID чужого ресурса):
s3_uid = "TODO" → s3_uid = "TODO" # UUID ресурса nubes_s3 из state: nubes_s3.backup_store.id
"""
ПРАВИЛА:
1. НЕ ВЫДУМЫВАЙ параметры, типы, значения. Только улучшай формулировки.
2. HTML-таблицы — меняй ТОЛЬКО текст внутри <td>...</td>. НЕ ломай теги.
3. HCL-блоки (```hcl ... ```) — НЕ ТРОГАТЬ вообще.
4. Navigation-строки — НЕ ТРОГАТЬ.
5. MAN-секцию с HTML — переведи в читаемый Markdown.
6. Верни ТОЛЬКО улучшенный Markdown. Без JSON, без пояснений, без ``` в начале/конце.
Просто готовый текст файла."""
def call_llm(prompt: str) -> str:
data = json.dumps({
@@ -34,7 +112,7 @@ def call_llm(prompt: str) -> str:
{"role": "user", "content": prompt},
],
"temperature": 0.15,
"max_tokens": 4096,
"max_tokens": 8192,
}).encode()
req = Request(API_URL, data=data, headers={
"Authorization": f"Bearer {API_KEY}",
@@ -45,7 +123,6 @@ def call_llm(prompt: str) -> str:
with urlopen(req, timeout=120) as resp:
result = json.loads(resp.read())
content = result["choices"][0]["message"]["content"].strip()
# Strip markdown fences if present
if content.startswith("```"):
lines = content.split("\n")
if len(lines) > 2:
@@ -56,61 +133,149 @@ def call_llm(prompt: str) -> str:
time.sleep(5)
raise RuntimeError("LLM failed after 3 retries")
def extract_man(text: str) -> str:
"""Вырезает блок ## MAN из главной страницы."""
m = re.search(r'## MAN\s*\n(.*?)(?=\n## |\Z)', text, re.DOTALL)
return m.group(1).strip() if m else ""
def build_prompt(file_type: str, filename: str, content: str, man: str = "") -> str:
"""Формирует промпт для LLM с типом файла и опциональным MAN-контекстом."""
parts = [f"Тип файла: {file_type}"]
if man:
parts.append(f"\n=== MAN СЕРВИСА (контекст для описаний групп и параметров) ===\n{man}")
parts.append(f"\n=== {filename} ===\n{content}")
return "\n".join(parts)
def file_type(name: str) -> str:
"""Определяет тип файла по имени."""
if name.endswith("_params_create"):
return "ПАРАМЕТРЫ СОЗДАНИЯ"
if name.endswith("_params_modify"):
return "ПАРАМЕТРЫ ИЗМЕНЕНИЯ"
if name.endswith("_ops"):
return "ОПЕРАЦИИ"
if name.endswith("_example"):
return "ПРИМЕР"
return "ГЛАВНАЯ СТРАНИЦА"
def needs_man_context(name: str) -> bool:
"""Нужен ли MAN-контекст для этого типа файла."""
return any(name.endswith(s) for s in ("_params_create", "_params_modify", "_ops"))
def is_llm_target(name: str) -> bool:
"""Файлы, которые обрабатываются через LLM."""
return any(name.endswith(s) for s in ("_params_create", "_params_modify", "_ops", "_example")) or (
"_" not in name and name != "index"
)
def main():
docs_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("generated/test/docs")
if not docs_dir.exists():
print(f"ERROR: {docs_dir} not found", file=sys.stderr)
sys.exit(1)
# Process only main manual pages: Name.md (not _example, _params_*, _outputs, _ops)
# Определяем выходную директорию
out_dir = docs_dir
for i, arg in enumerate(sys.argv):
if arg == "--out" and i + 1 < len(sys.argv):
out_dir = Path(sys.argv[i + 1])
break
out_dir.mkdir(parents=True, exist_ok=True)
# Находим все сервисы (по главным страницам без суффиксов и без subresource)
all_md = sorted(docs_dir.glob("*.md"))
targets = []
services = set()
for f in all_md:
name = f.stem
if name == "index":
continue
# Skip known non-manual suffixes
if any(name.endswith(sfx) for sfx in ["_example", "_params_create", "_params_modify", "_outputs", "_ops", "_params"]):
continue
# Also skip second-level (subresource) example files
if "_" in name:
# Could be subresource manual like "postgres_database"
# Check if there's a matching _example file
targets.append(f)
else:
targets.append(f)
if "_" not in name:
services.add(name)
print(f"Processing {len(targets)} manual pages...")
if not services:
print("No services found", file=sys.stderr)
sys.exit(1)
print(f"Services: {len(services)}")
failed = []
for i, f in enumerate(targets):
svc = f.stem
print(f" [{i+1}/{len(targets)}] {svc}...", end=" ", flush=True)
total = 0
content = f.read_text()
# Truncate very long files
if len(content) > 12000:
content = content[:12000] + "\n\n... (обрезано для LLM)\n"
for svc in sorted(services):
print(f"\n--- {svc} ---")
prompt = f"Улучши этот файл документации:\n\n=== {f.name} ===\n{content}"
# Читаем MAN из главной страницы (raw docs)
main_file = docs_dir / f"{svc}.md"
man_text = ""
if main_file.exists():
man_text = extract_man(main_file.read_text())
try:
improved = call_llm(prompt)
if improved and len(improved) > 100:
f.write_text(improved)
print("OK")
else:
print("SKIP (empty response)")
except Exception as e:
print(f"FAILED: {e}")
failed.append(svc)
# Обрабатываем все файлы сервиса
svc_files = sorted(docs_dir.glob(f"{svc}*.md"))
for f in svc_files:
name = f.stem
if not is_llm_target(name):
# Копируем как есть: outputs, params (landing), subresource
dest = out_dir / f.name
shutil.copy2(f, dest)
continue
time.sleep(1.5)
total += 1
print(f" {f.name}...", end=" ", flush=True)
content = f.read_text()
if len(content) > 12000:
content = content[:12000] + "\n\n... (обрезано)\n"
ft = file_type(name)
man = man_text if needs_man_context(name) else ""
prompt = build_prompt(ft, f.name, content, man)
try:
improved = call_llm(prompt)
if improved and len(improved) > 100:
dest = out_dir / f.name
dest.write_text(improved)
print("OK")
else:
# fallback: копируем оригинал
dest = out_dir / f.name
shutil.copy2(f, dest)
print("SKIP (empty response, copied original)")
except Exception as e:
dest = out_dir / f.name
shutil.copy2(f, dest)
print(f"FAILED: {e} (copied original)")
failed.append(f.name)
time.sleep(1.5)
# Копируем index.md
index_src = docs_dir / "index.md"
if index_src.exists():
shutil.copy2(index_src, out_dir / "index.md")
# Копируем 30_registry если есть
for sub in ["30_registry", "guides"]:
src = docs_dir.parent / sub
if not src.exists():
src = docs_dir / sub
if src.exists():
dest = out_dir / sub
if dest.exists():
shutil.rmtree(dest)
shutil.copytree(src, dest)
print(f" copied {sub}/")
print(f"\nProcessed {total} files. Failed: {len(failed)}")
if failed:
print(f"\nFailed ({len(failed)}): {', '.join(failed)}")
else:
print(f"\nAll {len(targets)} OK")
print(f"Failed: {', '.join(failed)}")
if __name__ == "__main__":
main()