AI-генерація документації для коду
Документація застарівається на наступний день після написання — це константа розробки. AI-генерація документації розв'язує проблему інакше: не "писати раз і підтримувати", а "регенерувати при кожній змінці". Docstrings, API-документація, README-файли, архітектурні описи — все це можна автоматизувати з якістю, яка перевищує середнє по команді.
Генератор Docstring
from anthropic import Anthropic
import ast
from pathlib import Path
client = Anthropic()
DOCSTRING_SYSTEM = """You are a technical writer specializing in Python documentation.
Write docstrings in Google Style format:
```python
def function(param: type) -> type:
\"\"\"Brief description in one line (imperative mood).
Detailed description if needed. Explain WHAT the function does,
not HOW. Mention non-trivial algorithms or important side effects.
Args:
param: Parameter description. Don't specify type — it's in annotation.
Returns:
What it returns. Don't specify type.
Raises:
ValueError: When and why it arises.
Example:
>>> result = function(value)
>>> assert result == expected
\"\"\"
Rules:
- Brief line: imperative mood (Calculates/Returns/Creates, not Calculate/Return)
- Don't repeat function name and types from annotations
- Add Example only for non-trivial functions
- For simple getter/setter — minimal documentation"""
def generate_docstring(function_source: str) -> str: """Генерує docstring для функції"""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
system=DOCSTRING_SYSTEM,
messages=[{
"role": "user",
"content": f"Write docstring for this function. Return only docstring (triple quotes), no function code:\n\n```python\n{function_source}\n```"
}]
)
return response.content[0].text.strip()
def add_docstrings_to_file(file_path: str, overwrite: bool = False) -> str: """Додає docstrings до всіх функцій у файлі""" source = Path(file_path).read_text() tree = ast.parse(source) lines = source.splitlines()
# Збираємо функції без docstrings
functions_to_document = []
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
has_docstring = (
node.body and
isinstance(node.body[0], ast.Expr) and
isinstance(node.body[0].value, ast.Constant) and
isinstance(node.body[0].value.value, str)
)
if not has_docstring or overwrite:
func_source = ast.unparse(node)
functions_to_document.append({
"name": node.name,
"line": node.lineno,
"source": func_source,
"indent": len(lines[node.lineno - 1]) - len(lines[node.lineno - 1].lstrip()),
})
# Генеруємо docstrings
insertions = {} # line_number -> docstring_text
for func_info in functions_to_document:
docstring = generate_docstring(func_info["source"])
# Індентуємо docstring
indent = " " * (func_info["indent"] + 4)
docstring_lines = docstring.strip().splitlines()
indented = "\n".join(indent + line if line.strip() else line for line in docstring_lines)
# Рядок після def ... :
insertions[func_info["line"]] = indented
# Вставляємо docstrings у вихідний код
result_lines = []
for i, line in enumerate(lines, 1):
result_lines.append(line)
if i in insertions:
result_lines.append(insertions[i])
return "\n".join(result_lines)
### API документація (OpenAPI/Swagger)
```python
from pydantic import BaseModel
import json
def generate_api_docs(router_source: str, existing_models: str = "") -> str:
"""Генерує OpenAPI-сумісну документацію для FastAPI роутера"""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
messages=[{
"role": "user",
"content": f"""Analyze FastAPI router and add complete OpenAPI documentation:
- summary and description for each endpoint
- description for all parameters (Path, Query, Body)
- response_model with examples
- HTTPException with codes and descriptions
FastAPI router:
```python
{router_source}
{f"Existing models:{chr(10)}python{chr(10)}{existing_models}{chr(10)}" if existing_models else ""}
Return improved router with complete documentation.""" }] )
return response.content[0].text
def generate_readme_section(module_path: str) -> str: """Генерує README секцію для Python модуля""" source = Path(module_path).read_text()
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
messages=[{
"role": "user",
"content": f"""Write README section for this Python module.
Structure:
Module Name
Brief description (1-2 sentences).
Usage
# Example code
API
Table of public functions/classes with descriptions.
Requirements
Dependencies if any.
Module code:
{source[:3000]}
```"""
}]
)
return response.content[0].text
Архітектурна документація
def generate_architecture_doc(project_root: str) -> str:
"""Генерує архітектурне описання для проекту"""
from pathlib import Path
import os
# Збираємо структуру проекту
structure = []
for root, dirs, files in os.walk(project_root):
# Пропускаємо vendor, cache
dirs[:] = [d for d in dirs if d not in {
".git", "__pycache__", "node_modules", ".venv", "migrations"
}]
level = root.replace(project_root, "").count(os.sep)
indent = " " * 2 * level
structure.append(f"{indent}{os.path.basename(root)}/")
for file in files:
if file.endswith((".py", ".ts", ".tsx")):
structure.append(f"{indent} {file}")
# Читаємо ключові файли
key_files_content = {}
key_patterns = ["models.py", "urls.py", "routes.ts", "app.py", "main.py"]
for root, _, files in os.walk(project_root):
for file in files:
if file in key_patterns:
file_path = os.path.join(root, file)
try:
key_files_content[file_path] = Path(file_path).read_text()[:1500]
except Exception:
pass
if len(key_files_content) >= 5:
break
key_content = "\n\n".join([
f"### {path}\n```python\n{content}\n```"
for path, content in key_files_content.items()
])
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
messages=[{
"role": "user",
"content": f"""Write architecture documentation for project.
Project structure:
{chr(10).join(structure[:100])}
Key files:
{key_content}
Include:
1. Architecture overview (2-3 paragraphs)
2. Main components and their roles
3. Interaction diagram (text)
4. Key patterns used in code
5. Entry points and startup
Format: Markdown for README."""
}]
)
return response.content[0].text
Автооновлення документації в CI/CD
# .github/workflows/docs.yml
name: Update Documentation
on:
push:
branches: [main]
paths:
- "src/**/*.py"
- "app/**/*.py"
jobs:
update-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate docstrings
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
python scripts/generate_docs.py \
--source src/ \
--check-missing-only \
--output-report docs/coverage.json
- name: Update API docs
run: |
python scripts/generate_api_docs.py \
--routers app/api/ \
--output docs/api/
- name: Commit if changed
run: |
git config user.email "[email protected]"
git config user.name "Docs Bot"
git add docs/
git diff --staged --quiet || git commit -m "docs: auto-update documentation"
git push
Практичний кейс: мікросервіс з 0% документації
Задача: Python FastAPI сервіс, 4200 рядків, 67 endpoints, 0 docstrings. Онбординг нового розробника займав 3 тижні.
Автоматизація:
- Batch-генерація docstrings для всіх функцій (182 функції, 45 хв)
- Генерація OpenAPI descriptions для всіх endpoints
- Архітектурний README з описом компонентів
Результати:
- Docstring coverage: 0% → 91%
- Час онбординга нового розробника: 3 тижні → 1 тиждень
- Запитання в Slack "як працює X": -68%
- Оцінка якості документації командою: 4.1/5.0
Нюанс: для 8% функцій з нетривіальною бізнес-логікою AI-генерована документація була неточною — потребувала ручної правки. Система помічає такі функції (cyclomatic complexity > 10) для ревю.
Docstring coverage як метрика CI
def check_docstring_coverage(source_dir: str, threshold: float = 0.8) -> bool:
"""Перевіряє coverage docstrings, повертає False якщо нижче порога"""
total = 0
documented = 0
for py_file in Path(source_dir).rglob("*.py"):
source = py_file.read_text()
try:
tree = ast.parse(source)
except SyntaxError:
continue
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
if not node.name.startswith("_"): # Публічні функції
total += 1
if (node.body and isinstance(node.body[0], ast.Expr) and
isinstance(node.body[0].value, ast.Constant)):
documented += 1
coverage = documented / total if total > 0 else 1.0
print(f"Docstring coverage: {coverage:.1%} ({documented}/{total})")
return coverage >= threshold
Терміни
- Docstring генератор для існуючої кодової бази: 2–3 дні
- OpenAPI документація для FastAPI/Django REST: 3–5 днів
- Автоматичне оновлення в CI/CD: 1 тиждень
- Архітектурна документація + wiki: 1–2 тижні







