Files
Local_Perplexity/docs/generate_docx.py
fedos 8e74e53b3d feat: add Ollama proxy with LLM router and Codex CLI support
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>
2026-03-07 15:25:15 +03:00

370 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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}")