forked from templates/template-go-orm
Go-сервис-прокси между Codex CLI и Ollama. Добавляет Bearer-авторизацию, LLM-маршрутизатор (deepseek классифицирует запросы: code/doc/general), поддержку OpenAI Responses API для Codex CLI, стриминг SSE, кеш модели. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
370 lines
17 KiB
Python
370 lines
17 KiB
Python
"""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 <token>')
|
||
|
||
# === 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}")
|