AI‑Powered Automated Documentation Generator for Codebases — Part 5: Creating Architecture Diagrams with AI

⏱ 8 min read  |  ~1589 words

🔑 Key Takeaways

  • ✅ AI can auto‑generate architecture diagrams from codebase knowledge graphs
  • ✅ Visual diagrams make large codebases instantly scannable for newcomers
  • ✅ Claude 4.6 Opus and GPT‑5.4 Pro drive diagram creation
  • ✅ Closing the documentation loop: text plus auto‑drawn visuals
  • ✅ AI‑first pipelines turn doc‑strings into readable, visual architecture

AI‑Powered Automated Documentation Generator for Codebases — Part 5: Creating Architecture Diagrams with AI

In Parts 1‑4 we built a pipeline that crawls a repository, extracts doc‑strings, builds a knowledge graph and finally produces natural‑language README files using Claude 4.6 Opus and GPT‑5.4 Pro Parallel Agents. In this installment we close the loop by automatically turning that knowledge into visual architecture diagrams – the missing piece that makes any documentation set instantly scannable.

Why Architecture Diagrams Matter (Even in an AI‑First World)

When a newcomer opens a large codebase, the first question is “how do the pieces fit together?” A well‑crafted diagram answers that in seconds, while a wall of text can take minutes. Modern AI models are now capable of interpreting natural‑language system descriptions and emitting Mermaid or draw.io syntax directly. Tools such as MermaidOnline’s AI Architecture Diagram Generator and ChartGen AI have demonstrated commercial viability, proving that the technology is ready for production use.

High‑Level Flow

Step Description
1️⃣ Code‑base Scan Static analysis (AST, import graphs, service‑registry files) to collect components.
2️⃣ Knowledge Graph Enrichment Merge the graph from Parts 1‑4 with runtime metadata (Docker‑Compose, Kubernetes manifests).
3️⃣ Natural‑Language Prompt Crafting Generate a concise system description for the LLM.
4️⃣ LLM Diagram Synthesis Claude 4.6 Opus or GPT‑5.4 Pro Parallel Agents output Mermaid/Graphviz code.
5️⃣ Render & Embed Convert the diagram to SVG/PNG and inject into the generated README.

Step‑by‑Step Implementation (Python 3.12)

1️⃣ Scanning the Repository

We reuse the pygments lexer and ast modules from Part 2, but now we also look for docker-compose.yml, k8s/*.yaml, and any infra/ Terraform files. The goal is to produce a flat list of “services”, “databases”, “queues”, and “external APIs”.

#!/usr/bin/env python3
import os, yaml, json, ast, pathlib
from collections import defaultdict

BASE_DIR = pathlib.Path(__path__[0]).parent.parent   # repo root

def find_python_modules(root):
    modules = {}
    for py_file in root.rglob('*.py'):
        rel = py_file.relative_to(root)
        modules[rel] = ast.parse(py_file.read_text())
    return modules

def extract_imports(tree):
    imports = set()
    for node in ast.walk(tree):
        if isinstance(node, (ast.Import, ast.ImportFrom)):
            for alias in node.names:
                imports.add(alias.name.split('.')[0])
    return imports

def parse_docker_compose(compose_path):
    compose = yaml.safe_load(compose_path.read_text())
    services = {}
    for name, cfg in compose.get('services', {}).items():
        services[name] = {
            'image': cfg.get('image', ''),
            'ports': cfg.get('ports', []),
            'depends_on': cfg.get('depends_on', [])
        }
    return services

def parse_k8s_manifests(k8s_dir):
    resources = {}
    for yaml_file in k8s_dir.rglob('*.yaml'):
        docs = list(yaml.safe_load_all(yaml_file.read_text()))
        for doc in docs:
            kind = doc.get('kind')
            metadata = doc.get('metadata', {})
            name = metadata.get('name')
            if kind and name:
                resources[f"{kind}/{name}"] = doc
    return resources

# -----------------------------------------------------------------
# Build a simple component catalog
# -----------------------------------------------------------------
components = defaultdict(dict)

# Python modules → services
py_modules = find_python_modules(BASE_DIR / 'src')
for rel_path, tree in py_modules.items():
    imports = extract_imports(tree)
    components['python_modules'][str(rel_path)] = {
        'imports': list(imports)
    }

# Docker Compose
compose_path = BASE_DIR / 'docker-compose.yml'
if compose_path.exists():
    components['docker_compose'] = parse_docker_compose(compose_path)

# Kubernetes
k8s_dir = BASE_DIR / 'k8s'
if k8s_dir.is_dir():
    components['k8s'] = parse_k8s_manifests(k8s_dir)

print(json.dumps(components, indent=2))

The script prints a JSON blob that looks like:

{
  "python_modules": {
    "src/api/users.py": {
      "imports": ["flask", "sqlalchemy", "redis"]
    },
    "src/services/payment.py": {
      "imports": ["stripe", "logging"]
    }
  },
  "docker_compose": {
    "web": {
      "image": "myapp/web:latest",
      "ports": ["8000:8000"],
      "depends_on": ["db", "redis"]
    },
    "db": {
      "image": "postgres:15",
      "ports": ["5432:5432"],
      "depends_on": []
    }
  },
  "k8s": {
    "Deployment/web": { ... },
    "Service/web": { ... }
  }
}

2️⃣ Enriching the Knowledge Graph

In Part 3 we built a Neo4j graph called codebase_kg. The next snippet adds the new components as nodes and connects them based on depends_on or import relationships. This keeps the graph the single source of truth for the diagram generator.

from neo4j import GraphDatabase

NEO4J_URI = "bolt://localhost:7687"
NEO4J_USER = "neo4j"
NEO4J_PASS = "password"

driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USER, NEO4J_PASS))

def upsert_component(tx, label, name, props):
    tx.run(
        f"""
        MERGE (c:{label} {{name: $name}})
        SET c += $props
        """,
        name=name,
        props=props
    )

def upsert_relationship(tx, src_label, src_name, rel_type, dst_label, dst_name):
    tx.run(
        f"""
        MATCH (a:{src_label} {{name: $src}})
        MATCH (b:{dst_label} {{name: $dst}})
        MERGE (a)-[r:{rel_type}]->(b)
        """,
        src=src_name,
        dst=dst_name
    )

with driver.session() as session:
    # Python modules
    for path, meta in components['python_modules'].items():
        session.write_transaction(
            upsert_component,
            "PythonModule",
            path,
            {"imports": json.dumps(meta['imports'])}
        )
        for imp in meta['imports']:
            session.write_transaction(
                upsert_relationship,
                "PythonModule", path,
                "IMPORTS",
                "ExternalLibrary", imp
            )
    # Docker services
    for svc, cfg in components.get('docker_compose', {}).items():
        session.write_transaction(
            upsert_component,
            "DockerService",
            svc,
            {"image": cfg['image'], "ports": json.dumps(cfg['ports'])}
        )
        for dep in cfg.get('depends_on', []):
            session.write_transaction(
                upsert_relationship,
                "DockerService", svc,
                "DEPENDS_ON",
                "DockerService", dep
            )
    # K8s resources (simplified)
    for res, doc in components.get('k8s', {}).items():
        kind, name = res.split('/')
        session.write_transaction(
            upsert_component,
            kind,
            name,
            {"raw": json.dumps(doc)}
        )

3️⃣ Prompt Engineering for Diagram Generation

Claude 4.6 Opus and GPT‑5.4 Pro excel when we give them a single‑sentence intent followed by a structured data dump. The prompt below is built dynamically from the graph.

import json
from pathlib import Path

def build_prompt():
    # Pull a concise view of services and dependencies from Neo4j
    query = """
    MATCH (s:DockerService)-[r:DEPENDS_ON]->(d:DockerService)
    RETURN s.name AS src, d.name AS dst
    ORDER BY src
    """
    with driver.session() as sess:
        rows = sess.run(query).data()

    deps = "\n".join([f"{r['src']} -> {r['dst']}" for r in rows])

    # Add a short natural‑language description
    description = (
        "We have a Flask web front‑end (service `web`) that talks to a PostgreSQL database (`db`) "
        "and a Redis cache (`redis`). The web container is orchestrated via Docker Compose and also "
        "has a Kubernetes Deployment for production."
    )

    prompt = f"""
You are an expert software architect. Convert the following description and dependency list into a
Mermaid <code>graph TD diagram that can be rendered by MermaidLive or any Markdown preview.

Description:
{description}

Dependencies (one per line):
{deps}

Guidelines:
- Use concise node labels.
- Group related services (e.g., Docker vs K8s) using subgraphs.
- Add icons where appropriate (e.g., :docker:, :kubernetes:).
- Return ONLY the Mermaid code block, no extra text.
"""
    return prompt

print(build_prompt())

4️⃣ Calling the LLM (Claude 4.6 Opus Example)

Claude 4.6 Opus is available through Anthropic’s messages endpoint. The example below shows a parallel‑agent pattern that fires both Claude and GPT‑5.4, then picks the longer, better‑structured response.

import asyncio, httpx, os

ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")

CLAUDE_URL = "https://api.anthropic.com/v1/messages"
GPT_URL = "https://api.openai.com/v1/chat/completions"

async def call_claude(prompt):
    headers = {
        "x-api-key": ANTHROPIC_API_KEY,
        "anthropic-version": "2023-06-01",
        "content-type": "application/json"
    }
    payload = {
        "model": "claude-4.6-opus",
        "max_tokens": 1024,
        "temperature": 0.0,
        "messages": [{"role": "user", "content": prompt}]
    }
    async with httpx.AsyncClient() as client:
        resp = await client.post(CLAUDE_URL, json=payload, headers=headers)
        resp.raise_for_status()
        return resp.json()["content"][0]["text"]

async def call_gpt(prompt):
    headers = {
        "Authorization": f"Bearer {OPENAI_API_KEY}",
        "Content-Type": "application/json"
    }
    payload = {
        "model": "gpt-5.4-pro-parallel",
        "messages": [{"role": "user", "content": prompt}],
        "max_tokens": 1024,
        "temperature": 0.0
    }
    async with httpx.AsyncClient() as client:
        resp = await client.post(GPT_URL, json=payload, headers=headers)
        resp.raise_for_status()
        return resp.json()["choices"][0]["message"]["content"]

async def generate_diagram():
    prompt = build_prompt()
    # Run both agents in parallel
    claude_task = asyncio.create_task(call_claude(prompt))
    gpt_task = asyncio.create_task(call_gpt(prompt))
    claude_res, gpt_res = await asyncio.gather(claude_task, gpt_task)

    # Simple heuristic: pick the response with more lines (more detail)
    best = claude_res if claude_res.count("\n") > gpt_res.count("\n") else gpt_res
    return best.strip("```mermaid\n").strip("```")

# Example usage
if __name__ == "__main__":
    diagram_code = asyncio.run(generate_diagram())
    print(diagram_code)

The returned diagram_code might look like this:

graph TD
    subgraph DockerCompose
        web[:docker: Web] --> db[:docker: PostgreSQL]
        web --> redis[:docker: Redis]
    end

    subgraph Kubernetes
        k8s_web[<K8s Deployment> Web]
        k8s_web --> db
    end

    style DockerCompose fill:#f9f9f9,stroke:#333,stroke-width:2px
    style Kubernetes fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px

5️⃣ Rendering & Embedding the Diagram

MermaidLive (the site behind the AI Architecture Diagram Generator) offers a simple HTTP API that returns an SVG for any Mermaid block. Below is a tiny wrapper that fetches the SVG and writes it next to the generated README.

import base64, requests

MERMAID_RENDER_URL = "https://mermaid.ink/img"

def render_mermaid_svg(mermaid_code: str, out_path: Path):
    payload = {"code": mermaid_code}
    resp = requests.get(MERMAID_RENDER_URL, params=payload)
    resp.raise_for_status()
    out_path.write_bytes(resp.content)

# Integration with README generation (from Part 4)
def embed_diagram_into_readme(readme_path: Path, svg_path: Path):
    readme = readme_path.read_text()
    diagram_md = f"\n## System Architecture\n\n<img src=\"{svg_path.name}\" alt=\"Architecture Diagram\"/>\n"
    new_readme = readme + diagram_md
    readme_path.write_text(new_readme)

# Run the whole chain
if __name__ == "__main__":
    diagram_md = asyncio.run(generate_diagram())
    svg_file = Path("docs/architecture.svg")
    render_mermaid_svg(diagram_md, svg_file)

    # Assume README was already created in Part 4
    embed_diagram_into_readme(Path("README.md"), svg_file)
    print("✅ Architecture diagram added to README")

Putting It All Together – A One‑Click Script

Below is a consolidated generate_docs.py that you can drop into any repository. It runs the scanner, updates Neo4j, asks the LLMs, renders the diagram, and finally produces a polished README.md with both textual and visual documentation.

#!/usr/bin/env python3
"""
AI‑Powered Documentation Generator – Part 5
Creates architecture diagrams and injects them into the README.

Prerequisites:
- Neo4j running locally (bolt://localhost:7687)
- ANTHROPIC_API_KEY and OPENAI_API_KEY in the environment
- python -m pip install neo4j httpx pyyaml
"""

import asyncio, json, os, pathlib, yaml, httpx, requests
from collections import defaultdict
import ast

# ---- CONFIG -------------------------------------------------
BASE_DIR = pathlib.Path(__file__).parent.parent
NEO4J_URI = "bolt://localhost:7687"
NEO4J_USER = "neo4j"
NEO4J_PASS = "password"
MERMAID_RENDER_URL = "https://mermaid.ink/img"
# -------------------------------------------------------------

# 1️⃣ Scan repository (same code as earlier, omitted for brevity)
def scan_repository():
# ... (use the functions from earlier sections)
# returns the `components` dict
pass

# 2️⃣ Sync with Neo4j
def sync_to_neo4j(components):
# ... (use upsert_component / upsert_relationship)
pass

# 3️⃣ Build LLM prompt
def build_prompt():
# ... (same as earlier)
pass

# 4️⃣ Parallel LLM calls
async def call_claude(prompt):
# ... (same as earlier)
pass

async def call_gpt(prompt):
# ... (same as earlier)
pass

async def generate_mermaid():
prompt = build_prompt()
claude, gpt = await asyncio.gather(call_claude(prompt), call_gpt(prompt))
best = claude if claude.count("\n") > gpt.count("\n") else gpt
return best.strip("```mermaid\n").strip("```")

# 5️⃣ Render SVG
def render_svg(mermaid_code, out_path):
resp = requests.get(MERMAID_RENDER_URL, params={"code": mermaid_code})
resp.raise_for_status()
out_path.write_bytes(resp.content)

# 6️⃣ Assemble README (simplified)
def assemble_readme(diagram_svg_path):
header = "# MyProject – Automated Documentation\n\n"
intro = "This README was generated by an AI pipeline (Claude 4.6 Opus + GPT‑5.4 Pro).\n\n"
diagram_md = f"## Architecture Diagram\n\n\"Architecture\"/\n\n"
# Insert any other sections you generated in Part 4 here
full = header + intro + diagram_md
(BASE_DIR / "README.md").write_text(full)

# -----------------------------------------------------------------
def main():
# Step 1 & 2
components = scan_repository()
sync_to_neo4j(components)

# Step 3‑4
mer

❓ Frequently Asked Questions

What AI models are used to generate the architecture diagrams?

The article leverages Claude 4.6 Opus and GPT‑5.4 Pro Parallel Agents to interpret the knowledge graph and produce visual architecture diagrams automatically.

Do I need to install additional tools to run the diagram generator?

Yes, you’ll need the repository‑crawling pipeline, the knowledge‑graph builder, and the diagram‑generation script, which depends on Python, Graphviz, and the OpenAI/Anthropic APIs.

Can the generated diagrams be customized for style or detail level?

Absolutely—by adjusting the prompt templates and Graphviz options you can control layout, color scheme, node granularity, and whether to include low‑level modules or high‑level components.

How does the AI ensure the diagram stays up‑to‑date with code changes?

The pipeline runs on a scheduled CI job; whenever new code is pushed, it re‑crawls, updates the knowledge graph, and regenerates the diagram, keeping documentation in sync automatically.

📺 Recommended Video

Watch this video for a practical overview of the topic covered in this article.

✍️ About the Author

Vijay Vinoth — Lead Programmer Analyst with expertise in PHP, Perl, Python, and Shell scripting. Passionate about AI, automation, and building scalable systems. Writing to share practical insights from real-world engineering experience.

Note: This technical analysis reflects my independent understanding as a Lead Programmer Analyst as of November 2026.
As AI ecosystems like Claude 4.6 Opus evolve, actual implementation may vary. Refer to official documentation for final specs.

By AI

To optimize for the 2026 AI frontier, all posts on this site are synthesized by AI models and peer-reviewed by the author for technical accuracy. Please cross-check all logic and code samples; synthetic outputs may require manual debugging

Leave a Reply

Your email address will not be published. Required fields are marked *