#!/usr/bin/env python3 """ LLM-улучшатель документации Nubes Terraform Provider. Прогоняет сгенерированные docs-generator'ом .md файлы через LLM, чтобы сделать описания читаемыми и логичными. Использование: python3 05_generate_docs_llm.py generated/test/docs """ import json, os, sys, time from pathlib import Path from urllib.request import Request, urlopen API_URL = "https://api.aillm.ru/v1/chat/completions" API_KEY = "sk-ucI5YvOticoOQ9Kuj5K9mQ" MODEL = "gpt-oss-120b" SYSTEM_PROMPT = """Ты — технический писатель. Улучши ОДИН .md файл документации Terraform-провайдера: сделай описания грамотными и логичными. ПРАВИЛА: 1. НЕ ВЫДУМЫВАЙ параметры, типы, значения. Только улучшай формулировки. 2. HTML-таблицы — меняй ТОЛЬКО текст внутри .... НЕ ломай теги. 3. HCL-блоки (```hcl ... ```) — НЕ ТРОГАТЬ вообще. 4. Navigation-строки — НЕ ТРОГАТЬ. 5. MAN-секцию с HTML — переведи в читаемый Markdown. 6. Верни ТОЛЬКО улучшенный Markdown. Без JSON, без пояснений, без ``` в начале/конце. Просто готовый текст файла.""" 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": 4096, }).encode() req = Request(API_URL, data=data, headers={ "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", }) for attempt in range(3): try: 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: content = "\n".join(lines[1:-1]) return content except Exception as e: print(f" retry {attempt+1}/3: {e}", file=sys.stderr) time.sleep(5) raise RuntimeError("LLM failed after 3 retries") 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) all_md = sorted(docs_dir.glob("*.md")) targets = [] 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) print(f"Processing {len(targets)} manual pages...") failed = [] for i, f in enumerate(targets): svc = f.stem print(f" [{i+1}/{len(targets)}] {svc}...", end=" ", flush=True) content = f.read_text() # Truncate very long files if len(content) > 12000: content = content[:12000] + "\n\n... (обрезано для LLM)\n" prompt = f"Улучши этот файл документации:\n\n=== {f.name} ===\n{content}" 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) time.sleep(1.5) if failed: print(f"\nFailed ({len(failed)}): {', '.join(failed)}") else: print(f"\nAll {len(targets)} OK") if __name__ == "__main__": main()