This commit is contained in:
Mikan
2026-06-20 19:13:05 +03:00
parent 32575e217e
commit 8514c63ec6
193 changed files with 22105 additions and 11660 deletions

382
README.md
View File

@@ -1,110 +1,330 @@
# AI RPG — гибкая ролевая игра с ИИ
# AI-RPG — Text RPG with an LLM Game Master
Веб-приложение для проведения ролевых игр с искусственным интеллектом.
FastAPI (backend) + React/Vite/TypeScript (frontend) + PostgreSQL + Redis + Qdrant.
Упаковано в Docker Compose.
> **Version:** 1.0.0
> **Stack:** Python 3.12 · FastAPI · SQLAlchemy 2.x async · PostgreSQL 15 · Qdrant 1.9 · React 18 · Vite 5 · TypeScript 5 · Tailwind CSS 3 · zustand 4 · react-i18next 14
## Возможности
AI-RPG — это веб-приложение, в котором игрок ведёт текстовую ролевую игру с ИИ-мастером (GM). Игрок создаёт мир (или выбирает готовый пресет), настраивает персонажа, и далее вступает в пошаговое взаимодействие: каждое действие игрока обрабатывается трёхфазным orchestrator-ом, который генерирует нарратив, обновляет состояние мира через tool calls, и предлагает 1-3 следующих действия.
- Регистрация / аутентификация пользователей
- Первичная инициализация администратора по одноразовому токену (выводится в консоль backend при первом запуске)
- Панель администратора: настройка OpenAI-совместимого endpoint'а, токена, модели, параметров генерации, лимитов контекста
- Создание мира: пресет (встроенный Fantasy) или с нуля через многошаговый диалог с ИИ
- ИИ генерирует: расширенный сеттинг, правила, JSON-схему состояния мира (характеристики, инвентарь, статы), начальную дату/время, общие рельсы сюжета
- Игрок правит и подтверждает → запускается первая итерация
- Сессия: чат-интерфейс со стримингом, глоссарий (RAG), лист персонажа, кнопки опций + свободный ввод
- Итерация: ИИ использует инструменты (dice, RAG, обновление состояния, sub-агенты, отложенные триггеры) и пишет сценарный шаг + технический "за-кадровый" шаг
- Контекстный менеджер: гарантированные последние N сообщений + динамическая граница с суммаризацией
- Отложенные триггеры, привязанные к дате/времени мира (background worker)
- Двуязычный UI (RU/EN)
- Логирование всех LLM-вызовов в БД
Полное ТЗ — в `docs/AI-RPG_TZ_TDD.md`.
## Быстрый старт
---
```bash
# 1. Скопировать .env и при необходимости отредактировать
cp .env.example .env
## Возможности (v1.0.0)
# 2. Поднять стек
docker compose up --build
-**JWT-аутентификация** — регистрация обычных пользователей и первого админа по токену.
-**Админ-панель** — настройки (LLM, embeddings, Qdrant, UI, game), логи LLM-вызовов с фильтрами, список пользователей, статистика, диагностические кнопки (test LLM / test LLM tools / test embeddings / probe dimension / recreate collections), загрузка favicon/logo/OG-image.
-**Пресеты миров** — 2 встроенных (Classic Fantasy, Deep Space Outpost) + создание/редактирование своих.
-**World Builder** — генерация нового мира из пресета или формы через SSE-стрим: schemas → environment → entities → intro scene.
-**World Editor** — чат-инструция для LLM, `propose_changes` с diff, `ask_user` для уточнений, optimistic locking.
-**Orchestrator (3 фазы)**:
- **Phase 1**: planner+executor — цикл tool-calls до `submit_plan`.
- **Phase 2**: writer — single LLM call с `submit_step`, стриминг `scene_chunk`.
- **Phase 3**: persist + deferred triggers + summary (если история длинная) + suggest actions.
-**RAG через Qdrant**`rag_query` / `rag_add`, двухстадийный retrieval (Qdrant → PostgreSQL), изоляция миров через payload-фильтр, фоновая индексация (через `embedding_status`).
-**Embeddings**`HashEmbedder` (offline, для dev) и `OpenAIEmbedder` (OpenAI-compatible API), авто-fallback `embeddings.api_url``llm.api_url`.
-**Игровые инструменты** (17 шт.): `entity_create/get/list/update/delete`, `env_update/get`, `update_plot_rails`, `advance_time`, `schedule_trigger`, `calc` (с кубиками), `random_choice` (детерминированный), `rag_query/add`, `run_subagent`, `submit_plan/step`, `suggest_actions`.
-**Schema tools**`schema_add_type/add_field/remove_field/modify_field` для world_editor.
-**Контекстный менеджер** — последние N сообщений + summary при превышении порога, деградация recent → rag → summary.
-**SSE-стриминг** — все долгие операции (world_builder, world_editor, orchestrator) отдают прогресс через SSE с `event:`/`data:`/`id:`, heartbeat, reconnect через `Last-Event-ID`.
-**Фронтенд** — React+TS+Vite+Tailwind, тёмная тема, i18n (en/ru), мобильный responsive, SSE-клиент с автопереподключением.
# 3. В логах backend найти строку:
# "ADMIN_SETUP_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxx"
# (или задать ADMIN_SETUP_TOKEN в .env вручную)
# 4. Открыть http://localhost:5173
# - Перейти на /admin/setup
# - Ввести токен → создать учётку администратора
# - Зайти в Admin Panel → настроить LLM endpoint
# 5. Зарегистрировать обычного пользователя и начать создавать мир
```
---
## Архитектура
```
┌────────────┐ SSE ┌────────────────────────────┐
Frontend │ <──────► │ FastAPI backend
│ React/Vite │ HTTP │ - auth (JWT) │
└────────────┘ - admin / settings │
│ - worlds / sessions │
│ - engine (orchestrator)
│ - tools (dice/rag/...)
│ - context manager
│ - trigger runner
└──┬──────────┬──────────┬───┘
┌────────▼─┐ ┌─────▼────┐ ┌───▼─────┐
│PostgreSQL│ Redis │ │ Qdrant
│ (data) │ │ (queues) │ │ (RAG) │
└──────────┘ └──────────┘ └─────────┘
┌──────────────────────────────────────────────────────────────┐
Браузер (React 18 + Vite + TS + Tailwind + zustand)
└──────────────────────────┬───────────────────────────────────┘
│ HTTP / SSE
┌──────────────────────────▼───────────────────────────────────┐
│ FastAPI Backend (uvicorn)
├─ app/api/ — роутеры (auth, worlds, sessions, ...) │
├─ app/engine/ — game_master, world_builder, editor
└─ tools/ — ToolRegistry + 17 game tools
│ ├─ app/core/ — llm, rag, embeddings, security, ... │
├─ app/models/ — SQLAlchemy ORM
├─ app/prompts/ — системные промпты (en) │
└─ app/schemas/ — Pydantic request/response
└──────┬─────────────────────────────────┬─────────────────────┘
│ async SQLAlchemy │ httpx + qdrant-client
┌──────▼──────────────┐ ┌───────▼──────────────────────┐
│ PostgreSQL 15 │ │ Qdrant 1.9 (векторный индекс)│
│ (users, worlds, │ │ collections: entities, │
│ entities, steps, │ │ story_entries │
│ logs, ...) │ └──────────────────────────────┘
└─────────────────────┘ ▲
│ httpx (embeddings API)
┌────────┴─────────────┐
│ LLM Provider (any │
│ OpenAI-compatible) │
└──────────────────────┘
```
## Локальная разработка
---
## Быстрый старт
### Опция 1: docker-compose (рекомендуется)
```bash
# Backend hot reload
docker compose up backend postgres redis qdrant
# 1. Скопировать .env.example в .env и отредактировать
cp .env.example .env
# Отредактируйте SECRET_KEY, ADMIN_SETUP_TOKEN, LLM_API_URL, LLM_API_KEY
# Frontend dev
docker compose up frontend
# 2. Поднять всё
docker compose up -d
# Миграции (автоматически при старте backend, но можно вручную)
docker compose exec backend python -m app.migrations.init_db
# 3. Зайти на http://localhost (frontend через nginx)
# Или http://localhost:5173 (frontend dev) / http://localhost:8000/api/docs (backend)
```
При первом старте сервер напечатает в лог:
```
=== AI-RPG Admin Setup ===
No admin user yet. Open this URL in your browser:
/register/admin?token=<ADMIN_SETUP_TOKEN>
===========================
```
Откройте `http://localhost/register/admin?token=<...>` и создайте первого админа.
### Опция 2: локальный dev (backend + frontend раздельно)
```bash
# Backend
cd /path/to/ai-rpg
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Запустить PostgreSQL и Qdrant (через docker compose up db qdrant)
docker compose up -d db qdrant
# Применить миграции (создаёт все таблицы)
python -c "import asyncio; from app.db import get_engine; from app.migrations.versions.001_initial_schema import create_all_tables; asyncio.run(create_all_tables(get_engine()))"
# Запустить backend
uvicorn app.main:app --reload --port 8000
# В другом терминале — frontend
cd frontend
npm install
npm run dev
# Откроется http://localhost:5173
```
---
## Конфигурация LLM
AI-RPG работает с **любым OpenAI-compatible API**:
| Provider | `LLM_API_URL` | Пример `LLM_MODEL` |
|-----------------|-------------------------------------|------------------------------|
| OpenAI | `https://api.openai.com/v1` | `gpt-4o-mini` |
| Ollama (local) | `http://localhost:11434/v1` | `qwen2.5:7b-instruct` |
| LM Studio | `http://localhost:1234/v1` | `local-model` |
| vLLM | `http://localhost:8000/v1` | `Qwen/Qwen2.5-7B-Instruct` |
| OpenRouter | `https://openrouter.ai/api/v1` | `qwen/qwen-2.5-7b-instruct` |
Если `LLM_API_URL` пуст — backend использует `MockLlmClient` (возвращает записанные replay-ответы). Это удобно для dev и тестов.
### Embeddings
Два провайдера:
- `offline_hash` (по умолчанию) — `HashEmbedder`, детерминированный bag-of-words + hash projection. Не делает HTTP-запросов, работает offline. Размерность 256.
- `openai` — OpenAI-compatible embeddings API. URL/key fallback на `llm.api_url`/`llm.api_key`, если `embeddings.api_url`/`embeddings.api_key` пустые.
Кнопка «Авто-проба размерности» в админке (`POST /api/admin/test/embeddings/probe-dimension`) определяет реальную размерность модели и предлагает сохранить её в `embeddings.dimension`.
---
## Структура проекта
```
ai-rpg/
├── app/ # Backend (Python 3.12)
│ ├── api/ # FastAPI роутеры
│ │ ├── auth.py # /api/register, /api/auth/*
│ │ ├── worlds.py # /api/worlds
│ │ ├── sessions.py # /api/sessions/* (SSE streams)
│ │ ├── presets.py # /api/presets
│ │ ├── admin.py # /api/admin/* (settings, logs, test, upload)
│ │ └── misc.py # /api/health, /api/i18n
│ ├── core/ # Сквозные сервисы
│ │ ├── llm.py # LlmClient + MockLlmClient
│ │ ├── rag.py # RAG через Qdrant + PostgreSQL
│ │ ├── embeddings.py # HashEmbedder, OpenAIEmbedder
│ │ ├── qdrant_client.py # singleton AsyncQdrantClient
│ │ ├── security.py # JWT + bcrypt
│ │ ├── settings_service.py # settings table CRUD + seed
│ │ ├── state_validator.py # validate_state, apply_patch
│ │ ├── time_utils.py # GameTime, parse_delta, advance_time
│ │ └── logging.py # structlog setup
│ ├── engine/ # Игровой движок
│ │ ├── game_master.py # orchestrator (3 фазы)
│ │ ├── world_builder.py # flow создания мира
│ │ ├── world_editor.py # flow редактирования мира
│ │ ├── context.py # контекстный менеджер
│ │ ├── sse.py # SseEmitter
│ │ └── tools/
│ │ ├── base.py # Tool, ToolRegistry, ToolContext, ToolResult
│ │ ├── game.py # 17 игровых инструментов
│ │ ├── schema_tools.py # 4 schema-инструмента
│ │ └── register_all.py # build_default_registry()
│ ├── models/ # SQLAlchemy ORM (10 таблиц)
│ ├── prompts/
│ │ ├── registry.py # get_prompt(stage, language)
│ │ └── stages/ # 11 stage-промптов (en)
│ ├── schemas/ # Pydantic request/response
│ ├── migrations/
│ │ ├── init_db.py # init_db(session)
│ │ ├── init_qdrant.py # re-export init_qdrant_collections
│ │ ├── seed.py # builtin presets (fantasy, sci-fi)
│ │ └── versions/001_initial_schema.py
│ ├── config.py # Settings (pydantic-settings)
│ ├── db.py # async engine + session factory
│ └── main.py # FastAPI app + lifespan
├── frontend/ # Frontend (React 18 + Vite + TS)
│ ├── src/
│ │ ├── pages/ # 8 pages
│ │ ├── components/
│ │ │ ├── ui/ # 9 primitives (Button, Card, Modal, ...)
│ │ │ ├── sessions/ # Chat, ToolCallBubble, ActionInput, ...
│ │ │ ├── worlds/ # WorldCard, WorldBuilder, WorldEditor
│ │ │ └── admin/ # SettingsPanel, LlmLogsTable, ...
│ │ ├── stores/ # zustand (auth, ui, worlds, session, toast)
│ │ ├── lib/ # api.ts, sse.ts, cn.ts
│ │ ├── i18n/ # en.json, ru.json
│ │ └── types/index.ts
│ ├── package.json
│ ├── vite.config.ts
│ ├── tsconfig.json
│ └── tailwind.config.js
├── tests/ # pytest
│ ├── unit/ # 65+ unit-тестов
│ └── integration/ # (placeholder)
├── deploy/
│ ├── Dockerfile.backend
│ ├── Dockerfile.frontend
│ └── nginx.conf
├── docs/
│ └── AI-RPG_TZ_TDD.md # оригинальное ТЗ
├── docker-compose.yml
├── requirements.txt
├── pytest.ini
├── .env.example
── backend/
│ ├── Dockerfile
│ ├── requirements.txt
│ └── app/
│ ├── main.py # FastAPI entrypoint
│ ├── config.py # Settings
│ ├── api/ # Routers
│ ├── core/ # LLM client, security, rag
│ ├── engine/ # Game orchestrator
│ │ └── tools/ # LLM tool-call handlers
│ ├── models/ # SQLAlchemy models
│ ├── schemas/ # Pydantic schemas
│ ├── prompts/ # Bilingual prompt templates
│ └── workers/ # Background workers (triggers)
└── frontend/
├── Dockerfile
├── package.json
└── src/
├── api/ # API client
├── components/ # UI components
├── pages/ # Page components
├── store/ # Zustand stores
├── hooks/ # React hooks
└── i18n/ # RU/EN translations
── README.md
```
---
## API обзор
Полная OpenAPI-схема — на `http://localhost:8000/api/docs` (Swagger UI).
### Ключевые эндпоинты
| Метод | Путь | Назначение |
|---------|---------------------------------------------------|-----------------------------------------|
| `POST` | `/api/register` | Регистрация пользователя |
| `POST` | `/api/register/admin?token=` | Регистрация первого админа |
| `POST` | `/api/auth/login` | Логин по email/username → JWT |
| `GET` | `/api/auth/me` | Текущий профиль |
| `GET` | `/api/worlds` | Список миров пользователя |
| `POST` | `/api/worlds` | Создать мир → SSE URL для world_builder |
| `GET` | `/api/sessions/worlds/{id}/state` | Текущее состояние для play-страницы |
| `POST` | `/api/sessions/worlds/{id}/iterate` | Запустить orchestrator → SSE URL |
| `GET` | `/api/sessions/worlds/{id}/iterate/stream` | SSE-стрим итерации |
| `POST` | `/api/sessions/worlds/{id}/retry` | Повторить последний шаг |
| `POST` | `/api/sessions/worlds/{id}/rollback` | Откатить последний шаг |
| `GET` | `/api/admin/settings` | Все настройки (секреты замаскированы) |
| `PATCH` | `/api/admin/settings` | Обновить настройки |
| `GET` | `/api/admin/llm-logs?stage=&status_filter=&...` | Логи LLM-вызовов с фильтрами |
| `POST` | `/api/admin/test/llm?api_url=&api_key=&model=` | Проверка связности LLM |
| `POST` | `/api/admin/test/embeddings/probe-dimension` | Авто-проба размерности эмбеддингов |
| `POST` | `/api/admin/upload-icon` | Загрузить favicon/logo/og_image |
| `GET` | `/api/health` | Health-check (db, qdrant, llm, emb) |
### SSE-события (orchestrator)
| Event | Когда |
|----------------------|--------------------------------------------------|
| `phase_start` | Начало Phase 1/2/3 |
| `phase_end` | Конец фазы |
| `tool_call` | LLM вызвала инструмент |
| `llm_call_start/end` | Начало/конец LLM-вызова |
| `scene_chunk` | Streaming-чанк текста из Phase 2 |
| `scene_complete` | Полный текст сцены + delta_time |
| `suggested_actions` | 1-3 следующих действия |
| `trigger_fired` | Сработал отложенный триггер |
| `summary_generated` | Сгенерирован summary |
| `iteration_complete` | Полное завершение итерации |
| `done` | Успешное завершение стрима |
| `error` | Фатальная ошибка, стрим закрывается |
---
## Тестирование
```bash
# Backend unit-тесты
pytest tests/unit/ -v
# С покрытием
pytest --cov=app --cov-report=term-missing tests/
# Frontend
cd frontend
npm run typecheck # tsc --noEmit
npm run lint # ESLint
npm run test # vitest
npm run build # production build
```
Покрытие unit-тестами:
- `app.core.time_utils` — парсинг/advance времени, дельты
- `app.core.security` — JWT, bcrypt, валидация пароля
- `app.core.state_validator` — валидация state/world, apply_patch (set/inc/dec/append/remove)
- `app.core.embeddings.HashEmbedder` — детерминизм, нормализация, размерность
- `app.core.llm.MockLlmClient` — replay, исчерпание, запись вызовов, стриминг
- `app.engine.tools.base.ToolRegistry` — регистрация, диспетч, unknown tool, исключения
- `app.engine.tools.game.CalcTool` — арифметика, кубики, переменные, ошибки
- `app.engine.tools.game.RandomChoiceTool` — детерминизм, веса
- `app.engine.sse.SseEmitter` — emit/done/error, ID, сериализация
- `app.prompts.registry` — все 11 stage-промптов валидны
---
## Production-деплой
См. `docs/AI-RPG_TZ_TDD.md` §13 (DevOps / Deployment) и §18.3 (Pre-deploy checklist).
Ключевые моменты:
1. **SECRET_KEY** — сгенерировать через `python -c "import secrets; print(secrets.token_urlsafe(48))"`.
2. **ADMIN_SETUP_TOKEN** — задать в `.env` ИЛИ оставить пустым (тогда сгенерируется случайно при первом старте, напечатается в логах).
3. **HTTPS** — terminate TLS на nginx или на внешнем reverse-proxy.
4. **Backups** — ежедневно `pg_dump` + Qdrant snapshot в S3.
5. **Health-check**`GET /api/health` должен вернуть 200 с `db:true, qdrant:true`.
6. **Мониторинг** — structlog пишет JSON в stdout, забирается любой log-агрегатор.
---
## Лицензия
MIT
MIT — см. `LICENSE` (если отсутствует, предполагается MIT).
---
## Changelog
### v1.0.0 (2026-06-20)
- Первый release. Реализованы все 8 спринтов из ТЗ:
- Sprint 1: Foundation (docker-compose, БД, Qdrant, миграции, seed, health-check)
- Sprint 2: Auth + Admin (JWT, регистрация, админ-панель настроек, тесты LLM/embeddings)
- Sprint 3: World Builder (LLM-клиент, промпты, SSE-стрим генерации мира)
- Sprint 4: World Editor (чат-редактор, propose_changes, ask_user, optimistic lock)
- Sprint 5: Orchestrator (3 фазы, retry/rollback)
- Sprint 6: Frontend polish (i18n en/ru, тёмная тема, responsive, SSE reconnect)
- Sprint 7: RAG + Triggers + Summary + Context Manager
- Sprint 8: Production (README, метрики, тесты, сборка ZIP)