→ `текст`
текст → [текст](url)
, ,
→ удали тег, замени переносами строк где нужно
Лишние пустые строки подряд → одна пустая строка
Сохраняй всё смысловое содержание. Не перефразируй, не сокращай.
═══════════════════════════════════════════════════════
ПРАВИЛА ДЛЯ ПРИМЕРОВ (_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
"""
def call_llm(prompt: str) -> str:
data = json.dumps({
"model": MODEL,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": prompt},
],
"temperature": 0.15,
"max_tokens": 8192,
}).encode()
req = Request(API_URL, data=data, headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
})
for attempt in range(2):
try:
socket.setdefaulttimeout(90)
with urlopen(req, timeout=90) as resp:
result = json.loads(resp.read())
content = result["choices"][0]["message"]["content"].strip()
if content.startswith("```"):
lines = content.split("\n")
if len(lines) > 2:
content = "\n".join(lines[1:-1])
return content
except Exception as e:
print(f" retry {attempt+1}/2: {e}", file=sys.stderr)
time.sleep(3)
raise RuntimeError("LLM failed after 2 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)
# Определяем выходную директорию
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"))
services = set()
for f in all_md:
name = f.stem
if name == "index":
continue
if "_" not in name:
services.add(name)
if not services:
print("No services found", file=sys.stderr)
sys.exit(1)
print(f"Services: {len(services)}")
failed = []
total = 0
for svc in sorted(services):
print(f"\n--- {svc} ---")
# Читаем MAN из главной страницы (raw docs)
main_file = docs_dir / f"{svc}.md"
man_text = ""
if main_file.exists():
man_text = extract_man(main_file.read_text())
# Обрабатываем все файлы сервиса
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
total += 1
dest = out_dir / f.name
# Skip already processed files
if dest.exists() and dest.stat().st_size > 100:
print(f" {f.name}... SKIP (already done)", flush=True)
continue
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"Failed: {', '.join(failed)}")
if __name__ == "__main__":
main()