AI-генерация архитектурных диаграмм
Архитектурные диаграммы устаревают почти сразу после создания: разработчики добавляют новые сервисы, меняют взаимодействия, а Confluence с диаграммами никто не обновляет. AI-генерация диаграмм из кода и инфраструктурных файлов решает проблему "живой документации" — диаграмма генерируется автоматически при каждом изменении.
Генерация из кодовой базы
from anthropic import Anthropic
from pathlib import Path
import ast
import re
import subprocess
client = Anthropic()
class ArchitectureDiagramGenerator:
def analyze_project_structure(self, project_root: str) -> dict:
"""Анализирует структуру Python проекта через AST"""
structure = {
"modules": [],
"imports": [],
"classes": [],
"http_clients": [],
"db_models": [],
}
for py_file in Path(project_root).rglob("*.py"):
if any(skip in str(py_file) for skip in ["migrations", "__pycache__", ".venv", "test_"]):
continue
try:
source = py_file.read_text()
tree = ast.parse(source)
rel_path = str(py_file.relative_to(project_root))
module_name = rel_path.replace("/", ".").replace(".py", "")
structure["modules"].append(module_name)
# Импорты
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom) and node.module:
structure["imports"].append({
"from": module_name,
"to": node.module,
})
# Классы
if isinstance(node, ast.ClassDef):
bases = [ast.unparse(b) for b in node.bases]
structure["classes"].append({
"module": module_name,
"name": node.name,
"bases": bases,
})
# HTTP клиенты (requests, httpx)
if "requests.get" in source or "httpx.get" in source or "AsyncClient" in source:
urls = re.findall(r'["\']https?://[^"\']+["\']', source)
structure["http_clients"].append({
"module": module_name,
"external_calls": urls[:5],
})
except (SyntaxError, UnicodeDecodeError):
pass
return structure
def generate_mermaid_diagram(
self,
analysis: dict,
diagram_type: str = "c4",
) -> str:
"""Генерирует Mermaid диаграмму"""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
system="""Ты — архитектор, генерирующий Mermaid диаграммы.
Создавай только валидный Mermaid синтаксис.
Для C4 Context/Container диаграмм:
- Группируй по слоям: Frontend, API, Services, Database, External
- Показывай основные взаимодействия стрелками
- Не перегружай — только ключевые компоненты
Для Flow диаграмм:
- Используй flowchart TD (top-down)
- Показывай бизнес-процесс понятно""",
messages=[{
"role": "user",
"content": f"""Создай {diagram_type} Mermaid диаграмму на основе анализа проекта.
Анализ:
{str(analysis)[:3000]}
Верни только Mermaid код (начиная с ```mermaid)."""
}]
)
return response.content[0].text
def generate_from_docker_compose(self, compose_file: str) -> str:
"""Генерирует диаграмму из docker-compose.yml"""
import yaml
with open(compose_file) as f:
compose = yaml.safe_load(f)
services = compose.get("services", {})
networks = compose.get("networks", {})
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
messages=[{
"role": "user",
"content": f"""Создай Mermaid архитектурную диаграмму из docker-compose.
Сервисы:
{str(services)[:2000]}
Сети:
{str(networks)}
Покажи:
- Каждый сервис как блок с именем и image
- Зависимости (depends_on) как стрелки
- Порты как пометки на блоках
- Общие сети как группы
Верни только Mermaid код."""
}]
)
return response.content[0].text
def generate_from_terraform(self, tf_dir: str) -> str:
"""Генерирует AWS/GCP архитектурную диаграмму из Terraform"""
# Читаем все .tf файлы
tf_content = ""
for tf_file in Path(tf_dir).glob("*.tf"):
tf_content += tf_file.read_text() + "\n\n"
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
messages=[{
"role": "user",
"content": f"""Создай Mermaid диаграмму инфраструктуры из Terraform кода.
Terraform:
```hcl
{tf_content[:4000]}
Покажи:
- VPC/сети как контейнеры
- EC2/ECS/Lambda как прямоугольники
- RDS/ElastiCache как цилиндры (или базы данных)
- ALB/API Gateway как ромбы
- Стрелки = трафик/взаимодействия
Формат: Mermaid flowchart LR.""" }] )
return response.content[0].text
### Генерация диаграмм последовательности
```python
def generate_sequence_diagram(endpoint_source: str, service_name: str) -> str:
"""Генерирует sequence diagram из кода обработчика endpoint"""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
messages=[{
"role": "user",
"content": f"""Создай Mermaid sequence diagram для этого API endpoint.
Код ({service_name}):
```python
{endpoint_source}
Покажи:
- Участников: Client, API, каждый внешний сервис/БД
- Все вызовы в правильном порядке
- Async вызовы (если есть) с параллельными стрелками
- Условные ветки через alt/opt
Верни только Mermaid код.""" }] )
return response.content[0].text
def generate_er_diagram(models_source: str) -> str: """Генерирует ER-диаграмму из SQLAlchemy моделей"""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
messages=[{
"role": "user",
"content": f"""Создай Mermaid ER диаграмму из SQLAlchemy моделей.
{models_source}
Покажи все таблицы с:
- Полями и типами
- PK/FK маркерами
- Связями (один-к-одному, один-ко-многим, многие-ко-многим)
Используй синтаксис: erDiagram""" }] )
return response.content[0].text
### Автоматическое обновление в CI/CD
```python
import subprocess
from pathlib import Path
def update_diagrams_on_push(project_root: str, docs_dir: str):
"""Обновляет диаграммы при каждом push"""
generator = ArchitectureDiagramGenerator()
# Анализируем проект
analysis = generator.analyze_project_structure(project_root)
# Генерируем диаграммы
diagrams = {
"architecture.md": generator.generate_mermaid_diagram(analysis, "c4"),
"database.md": generate_er_diagram(
(Path(project_root) / "models.py").read_text()
if (Path(project_root) / "models.py").exists() else ""
),
}
# docker-compose если есть
compose_file = Path(project_root) / "docker-compose.yml"
if compose_file.exists():
diagrams["infrastructure.md"] = generator.generate_from_docker_compose(str(compose_file))
# Сохраняем
docs_path = Path(docs_dir)
docs_path.mkdir(exist_ok=True)
for filename, content in diagrams.items():
(docs_path / filename).write_text(content)
# Рендерим PNG через mmdc (mermaid-cli)
for md_file in docs_path.glob("*.md"):
png_file = md_file.with_suffix(".png")
subprocess.run(
["mmdc", "-i", str(md_file), "-o", str(png_file)],
capture_output=True
)
Практический кейс: документирование микросервисной архитектуры
Контекст: финтех-стартап, 12 микросервисов, последняя архитектурная диаграмма нарисована 2 года назад. Онбординг новых разработчиков: "смотрите в код, других источников нет".
Внедрение:
- Анализ всех docker-compose.yml и terraform файлов
- Генерация 4 диаграмм: C4 Context, Container, Infrastructure, ER
- Интеграция в GitHub Actions: обновление при push в main
Результаты:
- Время онбординга нового разработчика (понимание архитектуры): 2 недели → 3 дня
- Диаграммы актуальны на 100% (генерируются при каждом PR)
- Выявлено 3 неочевидных циклических зависимости между сервисами
Типы генерируемых диаграмм:
| Диаграмма | Источник | Обновление |
|---|---|---|
| C4 Context | Весь проект | При изменении main services |
| ER Database | models.py / Prisma schema | При изменении моделей |
| Infrastructure | Terraform / docker-compose | При изменении IaC |
| Sequence | Конкретный endpoint | По запросу |
| Dependency Graph | package.json / requirements.txt | При PR |
Сроки
- Генерация одного типа диаграмм (docker-compose или models): 1–2 дня
- Полный набор из кодовой базы: 3–5 дней
- Интеграция в CI/CD с авто-обновлением: 1 неделя
- Confluence/Notion публикация: +2–3 дня







