"""Generate architecture.docx from architecture content.""" from docx import Document from docx.shared import Pt, Inches, RGBColor from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.enum.table import WD_TABLE_ALIGNMENT import os doc = Document() style = doc.styles['Normal'] font = style.font font.name = 'Calibri' font.size = Pt(11) def add_heading(text, level=1): h = doc.add_heading(text, level=level) for run in h.runs: run.font.color.rgb = RGBColor(0x1A, 0x1A, 0x2E) def add_para(text, bold=False): p = doc.add_paragraph() run = p.add_run(text) run.bold = bold return p def add_bullet(text, bold_prefix=None): p = doc.add_paragraph(style='List Bullet') if bold_prefix: run = p.add_run(bold_prefix) run.bold = True p.add_run(text) else: p.add_run(text) def add_table(headers, rows): table = doc.add_table(rows=1, cols=len(headers)) table.style = 'Light Grid Accent 1' table.alignment = WD_TABLE_ALIGNMENT.LEFT hdr = table.rows[0] for i, h in enumerate(headers): hdr.cells[i].text = h for p in hdr.cells[i].paragraphs: for run in p.runs: run.bold = True for row_data in rows: row = table.add_row() for i, val in enumerate(row_data): row.cells[i].text = val doc.add_paragraph() def add_code(text): p = doc.add_paragraph() run = p.add_run(text) run.font.name = 'Consolas' run.font.size = Pt(9) run.font.color.rgb = RGBColor(0x33, 0x33, 0x33) pf = p.paragraph_format pf.space_before = Pt(4) pf.space_after = Pt(4) # === TITLE === title = doc.add_heading('Архитектура AI-платформы', level=0) title.alignment = WD_ALIGN_PARAGRAPH.CENTER doc.add_paragraph() # === 1. ОБЩЕЕ ОПИСАНИЕ === add_heading('1. Общее описание') add_para('AI-платформа для интеллектуальной маршрутизации запросов пользователей по различным LLM-моделям. Система состоит из двух основных компонентов:') add_bullet(' — авторизация, управление чатами и сообщениями, REST API', 'Go Backend') add_bullet(' — классификация запросов и маршрутизация к нужной модели', 'Python LLM Orchestrator') # === 2. АРХИТЕКТУРНАЯ СХЕМА === add_heading('2. Архитектурная схема') add_para('Общий поток данных:', bold=True) add_code( 'Пользователь → Go Backend (:8080) → Python Orchestrator (:8000) → LLM (Groq API)\n' ' ↕ ↕\n' ' PostgreSQL Router LLM → Target LLM' ) doc.add_paragraph() add_para('Компоненты Go Backend:', bold=True) add_bullet(' — /api/auth/register, /api/auth/login', 'Auth Handler') add_bullet(' — CRUD /api/chats', 'Chat Handler') add_bullet(' — POST/GET /api/chats/{id}/messages', 'Message Handler') add_bullet(' — bcrypt + JWT генерация/валидация', 'Auth Service') add_bullet(' — CRUD операции с чатами', 'Chat Service') add_bullet(' — sliding window контекст + вызов оркестратора', 'Message Service') add_bullet(' — HTTP-клиент к Python FastAPI', 'Orchestrator Client') doc.add_paragraph() add_para('Компоненты Python Orchestrator:', bold=True) add_bullet(' — маленькая быстрая модель (llama-3.1-8b-instant) классифицирует запрос', 'Router LLM') add_bullet(' — Qwen 2.5 Coder 32B для программирования', 'Code Model') add_bullet(' — DeepSeek R1 70B для анализа документов', 'Document Model') add_bullet(' — Llama 3.3 70B для общих вопросов', 'General Model') # === 3. СТРУКТУРА ПРОЕКТА === add_heading('3. Структура проекта') add_code( '.\n' '├── cmd/server/main.go # Точка входа Go backend\n' '├── internal/\n' '│ ├── config/config.go # Конфигурация (env-переменные)\n' '│ ├── handler/\n' '│ │ ├── auth.go # Хендлеры регистрации и логина\n' '│ │ ├── chat.go # CRUD чатов\n' '│ │ ├── message.go # Отправка/получение сообщений\n' '│ │ └── middleware.go # JWT middleware\n' '│ ├── service/\n' '│ │ ├── auth.go # bcrypt, JWT генерация\n' '│ │ ├── chat.go # Бизнес-логика чатов\n' '│ │ ├── message.go # Контекст + вызов оркестратора\n' '│ │ └── orchestrator.go # HTTP-клиент к Python\n' '│ └── router/router.go # chi router, маршруты\n' '├── ent/schema/\n' '│ ├── common.go # Миксины (PkMixin, RegisteredMixin)\n' '│ ├── user.go # Модель User\n' '│ ├── chat.go # Модель Chat\n' '│ └── message.go # Модель Message\n' '├── python/\n' '│ ├── pyproject.toml\n' '│ ├── app/\n' '│ │ ├── main.py # FastAPI приложение\n' '│ │ ├── config.py # Настройки\n' '│ │ ├── routers/completions.py # Эндпоинт чат-комплишенов\n' '│ │ └── services/\n' '│ │ ├── router_llm.py # Классификатор запросов\n' '│ │ ├── groq_client.py # Клиент Groq API\n' '│ │ └── vllm_client.py # Клиент vLLM (заглушка)\n' '│ └── tests/\n' '├── atlas/migrations/ # SQL-миграции\n' '├── Makefile\n' '└── go.mod / go.sum' ) # === 4. МОДЕЛЬ ДАННЫХ === add_heading('4. Модель данных (PostgreSQL)') add_para('Таблица USERS:', bold=True) add_table( ['Поле', 'Тип', 'Ограничения'], [ ['id', 'UUID', 'PRIMARY KEY, DEFAULT uuid_generate_v4()'], ['login', 'VARCHAR(128)', 'UNIQUE, NOT NULL'], ['password_hash', 'VARCHAR(256)', 'NOT NULL (bcrypt)'], ['role', 'VARCHAR(64)', "DEFAULT 'user'"], ['is_active', 'BOOLEAN', 'DEFAULT true'], ['created_at', 'TIMESTAMP', 'IMMUTABLE, DEFAULT now()'], ] ) add_para('Таблица CHATS:', bold=True) add_table( ['Поле', 'Тип', 'Ограничения'], [ ['id', 'UUID', 'PRIMARY KEY'], ['title', 'VARCHAR(256)', 'OPTIONAL'], ['active_model', 'VARCHAR(128)', 'OPTIONAL'], ['created_at', 'TIMESTAMP', 'IMMUTABLE, DEFAULT now()'], ['user_chats', 'UUID', 'FK → users.id, NOT NULL'], ] ) add_para('Таблица MESSAGES:', bold=True) add_table( ['Поле', 'Тип', 'Ограничения'], [ ['id', 'UUID', 'PRIMARY KEY'], ['role', 'ENUM', 'user | assistant | system'], ['content', 'TEXT', 'NOT NULL'], ['model_used', 'VARCHAR(128)', 'OPTIONAL'], ['token_count', 'INTEGER', 'OPTIONAL'], ['created_at', 'TIMESTAMP', 'IMMUTABLE, DEFAULT now()'], ['chat_messages', 'UUID', 'FK → chats.id, NOT NULL'], ] ) add_para('Правило изоляции: каждый чат изолирован. История одного чата не передаётся в другой. Новый чат = чистый контекст.') # === 5. API ЭНДПОИНТЫ === add_heading('5. API эндпоинты') add_para('Публичные (без авторизации):', bold=True) add_table( ['Метод', 'URL', 'Описание'], [ ['GET', '/health', 'Проверка работоспособности'], ['POST', '/api/auth/register', 'Регистрация {login, password} → {token}'], ['POST', '/api/auth/login', 'Вход {login, password} → {token}'], ] ) add_para('Защищённые (Bearer JWT):', bold=True) add_table( ['Метод', 'URL', 'Описание'], [ ['POST', '/api/chats', 'Создать чат {title?, active_model?}'], ['GET', '/api/chats', 'Список чатов пользователя'], ['GET', '/api/chats/{id}', 'Получить чат'], ['DELETE', '/api/chats/{id}', 'Удалить чат (каскадно с сообщениями)'], ['POST', '/api/chats/{id}/messages', 'Отправить сообщение → получить ответ LLM'], ['GET', '/api/chats/{id}/messages', 'История сообщений (пагинация)'], ] ) add_para('Python Orchestrator (внутренний):', bold=True) add_table( ['Метод', 'URL', 'Описание'], [ ['POST', '/api/v1/chat/completions', 'Получить ответ от LLM'], ] ) # === 6. ПОТОК ОБРАБОТКИ === add_heading('6. Поток обработки сообщения') add_para('1. Пользователь отправляет POST /api/chats/{id}/messages с содержимым сообщения.') add_para('2. Go Backend:') add_bullet('JWT middleware — проверка токена, извлечение user_id') add_bullet('Проверка: чат принадлежит пользователю') add_bullet('Сохранить user-сообщение в БД') add_bullet('Собрать контекст: последние N сообщений из чата (sliding window)') add_bullet('HTTP POST → Python Orchestrator') add_para('3. Python Orchestrator:') add_bullet('Router LLM классифицирует запрос (code / document / general)') add_bullet('Выбирает целевую модель') add_bullet('Отправляет контекст + запрос в модель (через Groq API)') add_bullet('Возвращает: {content, model_used, tokens}') add_para('4. Go Backend:') add_bullet('Сохранить assistant-сообщение в БД (с model_used и token_count)') add_bullet('Вернуть ответ пользователю') # === 7. МАРШРУТИЗАЦИЯ === add_heading('7. Маршрутизация запросов (Router LLM)') add_para('Router LLM — маленькая быстрая модель (llama-3.1-8b-instant), которая анализирует запрос и определяет категорию:') add_table( ['Категория', 'Целевая модель (Groq)', 'Когда'], [ ['code', 'qwen-2.5-coder-32b', 'Программирование, код, отладка'], ['document', 'deepseek-r1-distill-llama-70b', 'Анализ документов, резюме, тексты'], ['general', 'llama-3.3-70b-versatile', 'Всё остальное'], ] ) add_para('Если пользователь явно выбрал модель в чате (active_model), маршрутизация пропускается.') # === 8. КОНТЕКСТ === add_heading('8. Управление контекстом') add_bullet('Sliding Window: передаются последние N сообщений (по умолчанию 20)', '') add_bullet('Лимит токенов: максимум MAX_TOKENS (по умолчанию 4096)', '') add_bullet('Приблизительный подсчёт: len(content) / 4 ≈ 1 токен', '') add_bullet('Новый чат: пользователь создаёт новый чат для сброса контекста', '') # === 9. АУТЕНТИФИКАЦИЯ === add_heading('9. Аутентификация') add_bullet('Пароли хешируются bcrypt (golang.org/x/crypto)') add_bullet('JWT токен (HS256): sub (user_id), role, exp, iat') add_bullet('Срок действия: 24 часа (настраивается через JWT_EXPIRE_HOURS)') add_bullet('Заголовок: Authorization: Bearer ') # === 10. СТЕК === add_heading('10. Технологический стек') add_para('Go Backend:', bold=True) add_table( ['Компонент', 'Технология'], [ ['HTTP-фреймворк', 'chi/v5'], ['ORM', 'Ent (entgo.io)'], ['Миграции', 'Atlas'], ['JWT', 'golang-jwt/jwt/v5'], ['Хеширование', 'golang.org/x/crypto (bcrypt)'], ['БД-драйвер', 'pgx/v5'], ['Конфигурация', 'caarlos0/env/v11'], ] ) add_para('Python Orchestrator:', bold=True) add_table( ['Компонент', 'Технология'], [ ['Web-фреймворк', 'FastAPI'], ['ASGI-сервер', 'Uvicorn'], ['LLM API', 'Groq SDK'], ['HTTP-клиент', 'httpx'], ['Конфигурация', 'pydantic-settings'], ] ) # === 11. ENV === add_heading('11. Переменные окружения') add_para('Go Backend:', bold=True) add_table( ['Переменная', 'Описание', 'По умолчанию'], [ ['DB_URL', 'PostgreSQL connection string', '— (обязательна)'], ['JWT_SECRET', 'Секрет для подписи JWT', '— (обязательна)'], ['PORT', 'Порт сервера', '8080'], ['JWT_EXPIRE_HOURS', 'Время жизни токена (часы)', '24'], ['ORCHESTRATOR_URL', 'URL Python-оркестратора', 'http://localhost:8000'], ['CONTEXT_WINDOW', 'Кол-во сообщений в контексте', '20'], ['MAX_TOKENS', 'Максимум токенов контекста', '4096'], ] ) add_para('Python Orchestrator:', bold=True) add_table( ['Переменная', 'Описание', 'По умолчанию'], [ ['GROQ_API_KEY', 'Ключ Groq API', '— (обязательна)'], ['ROUTER_MODEL', 'Модель-классификатор', 'llama-3.1-8b-instant'], ['PORT', 'Порт оркестратора', '8000'], ] ) # === 12. JSON КОНТРАКТ === add_heading('12. JSON-контракт Go ↔ Python') add_para('Запрос (Go → Python):', bold=True) add_code( '{\n' ' "messages": [\n' ' {"role": "user", "content": "Напиши функцию..."},\n' ' {"role": "assistant", "content": "Вот функция..."},\n' ' {"role": "user", "content": "А теперь добавь тесты"}\n' ' ],\n' ' "active_model": null\n' '}' ) add_para('Ответ (Python → Go):', bold=True) add_code( '{\n' ' "content": "Вот тесты для функции...",\n' ' "model_used": "qwen-2.5-coder-32b",\n' ' "tokens": 523\n' '}' ) # === 13. ЭТАПЫ === add_heading('13. Этапы разработки') add_para('Спринт 1: Go-скелет ✓', bold=True) add_bullet('go.mod, зависимости, Ent-схемы (User, Chat, Message), main.go + /health') add_para('Спринт 2: Аутентификация', bold=True) add_bullet('Сервис регистрации/логина (bcrypt + JWT), middleware, хендлеры') add_para('Спринт 3: CRUD чатов', bold=True) add_bullet('Сервис и REST-хендлеры создания/получения/удаления чатов') add_para('Спринт 4: Python-оркестратор', bold=True) add_bullet('FastAPI, Router LLM, Groq API клиент, эндпоинт /api/v1/chat/completions') add_para('Спринт 5: Сквозной поток сообщений', bold=True) add_bullet('Go HTTP-клиент к Python, sliding window, хендлеры сообщений, полный цикл') # === 14. PRODUCTION === add_heading('14. Production-сценарий (будущее)') add_para('После проверки маршрутизации через Groq API система переводится на локальные модели:') add_bullet('Каждая модель запускается через vLLM на отдельной GPU') add_bullet('Каждая модель — отдельный сервис (отдельный порт)') add_bullet('Python Orchestrator переключается с Groq API на локальные vLLM-эндпоинты') add_bullet('Внешние API не используются') # Save output_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'architecture.docx') doc.save(output_path) print(f"Saved to {output_path}")