Сборка RAG-агента для настольных игр: PocketFlow + BlackSheep + ObjectBox | AiManual
AiManual Logo Ai / Manual.
30 Дек 2025 Гайд

Как собрать RAG-агента для объяснения настолок без тяжёлых библиотек

Практический гайд по созданию лёгкого RAG-агента для объяснения правил настольных игр без сложных фреймворков. Работающий pet-проект на Python.

Проблема: Почему классические RAG-системы не подходят для настолок?

Представьте ситуацию: вы купили новую сложную настольную игру вроде "Терры Мистики" или "Глоссария". Правила занимают 50 страниц PDF, а друзья уже ждут начала игры. Классические RAG-системы типа LangChain или LlamaIndex — это монстры, которые требуют десятки зависимостей, много памяти и сложной настройки. Они созданы для enterprise-решений, а не для быстрого ответа на вопрос "Как проходит фаза действий в "Свинтусе"?"

Ключевая проблема: большинство RAG-фреймворков создают избыточную сложность. Они заточены под корпоративные сценарии с тысячами документов, а не под наш скромный кейс с парой PDF-файлов правил.

Решение: Минималистичный стек для конкретной задачи

Вместо того чтобы тащить в проект тяжеленные библиотеки, мы соберём систему из трёх лёгких компонентов:

  1. PocketFlow — для работы с векторными эмбеддингами и поиска
  2. BlackSheep — для создания быстрого веб-API
  3. ObjectBox — для локального хранения векторов и метаданных

Этот стек даёт нам всё необходимое без лишнего веса. Если вам интересны более сложные RAG-архитектуры, посмотрите мою статью про Production-ready AI-агент с нуля, но для нашего кейса достаточно простого подхода.

💡
Преимущество такого подхода — скорость разработки и лёгкость развёртывания. Весь проект умещается в 200 строк кода и работает даже на Raspberry Pi.

Пошаговый план сборки

1Подготовка окружения и данных

Сначала создадим виртуальное окружение и установим минимальные зависимости:

python -m venv venv
source venv/bin/activate  # или venv\Scripts\activate на Windows

pip install pocketflow blacksheep objectbox sentence-transformers pdfplumber

Теперь подготовим PDF с правилами игр. Я рекомендую начать с 2-3 популярных игр, чтобы протестировать систему. Для работы с PDF отлично подходит pdfplumber — он легче PyPDF2 и лучше сохраняет структуру текста.

import pdfplumber
import json

def extract_rules_from_pdf(pdf_path):
    """Извлекаем текст из PDF с правилами игры"""
    chunks = []
    with pdfplumber.open(pdf_path) as pdf:
        for page_num, page in enumerate(pdf.pages, 1):
            text = page.extract_text()
            if text:
                # Разбиваем на абзацы
                paragraphs = [p.strip() for p in text.split('\n') if p.strip()]
                for para in paragraphs:
                    chunks.append({
                        'text': para,
                        'page': page_num,
                        'game': pdf_path.stem
                    })
    return chunks

2Создание векторной базы знаний

Вместо тяжёлых векторных БД вроде Pinecone или Weaviate используем ObjectBox — лёгкую embedded базу данных. PocketFlow поможет с эмбеддингами:

from pocketflow import PocketFlow
from sentence_transformers import SentenceTransformer
import objectbox

class GameRulesDB:
    def __init__(self, db_path='game_rules.obx'):
        # Используем лёгкую модель для эмбеддингов
        self.embedder = SentenceTransformer('all-MiniLM-L6-v2')
        
        # Инициализируем PocketFlow для поиска
        self.flow = PocketFlow(dim=384)  # Размерность нашей модели
        
        # Настраиваем ObjectBox
        model = objectbox.Model()
        entity = model.entity('RuleChunk', 1)
        entity.property('id', objectbox.PropertyType.long, 1, True)
        entity.property('text', objectbox.PropertyType.string, 2)
        entity.property('embedding', objectbox.PropertyType.byteVector, 3)
        entity.property('game', objectbox.PropertyType.string, 4)
        entity.property('page', objectbox.PropertyType.int, 5)
        
        self.store = objectbox.Store(model=model, directory=db_path)
        self.box = self.store.box('RuleChunk')
    
    def add_rules(self, chunks):
        """Добавляем чанки с правилами в базу"""
        for chunk in chunks:
            embedding = self.embedder.encode(chunk['text'])
            
            # Сохраняем в ObjectBox
            rule = RuleChunk()
            rule.text = chunk['text']
            rule.embedding = embedding.tobytes()  # Сохраняем как bytes
            rule.game = chunk['game']
            rule.page = chunk['page']
            self.box.put(rule)
            
            # Добавляем в PocketFlow для быстрого поиска
            self.flow.add_item(rule.id, embedding)

Почему именно такая архитектура? ObjectBox хранит метаданные (название игры, страницу), а PocketFlow отвечает за быстрый поиск по векторам. Это разделение ответственности делает систему устойчивее.

3Построение поискового движка

Теперь реализуем логику поиска релевантных фрагментов правил:

class RulesSearchEngine:
    def __init__(self, db):
        self.db = db
        
    def search(self, query, game_filter=None, top_k=5):
        """Ищем релевантные фрагменты правил"""
        # Получаем эмбеддинг запроса
        query_embedding = self.db.embedder.encode(query)
        
        # Ищем похожие векторы через PocketFlow
        similar_ids = self.db.flow.search(query_embedding, top_k=top_k * 2)
        
        # Фильтруем по игре если нужно
        results = []
        for item_id, score in similar_ids:
            rule = self.db.box.get(item_id)
            if rule:
                if game_filter and rule.game != game_filter:
                    continue
                results.append({
                    'text': rule.text,
                    'game': rule.game,
                    'page': rule.page,
                    'score': float(score)
                })
                if len(results) >= top_k:
                    break
        
        return results

Если вам нужно работать с действительно большими объёмами данных (60 ГБ писем, например), посмотрите мою статью про локальный RAG на слабом железе. Но для правил настольных игр наш подход более чем достаточен.

4Создание веб-API с BlackSheep

BlackSheep — это быстрый async веб-фреймворк без лишних зависимостей. Создадим простой API:

from blacksheep import Application, json
from blacksheep.server.responses import text

app = Application()
db = GameRulesDB()
search_engine = RulesSearchEngine(db)

@app.route('/api/search')
async def search_rules(request):
    data = await request.json()
    query = data.get('query', '')
    game = data.get('game', None)
    
    if not query:
        return json({'error': 'Query is required'}, status=400)
    
    results = search_engine.search(query, game_filter=game)
    return json({'results': results})

@app.route('/api/games')
async def list_games(request):
    # Получаем список всех игр в базе
    games = set()
    for rule in db.box:
        games.add(rule.game)
    return json({'games': list(games)})

if __name__ == '__main__':
    import uvicorn
    uvicorn.run(app, host='0.0.0.0', port=8000)

5Интеграция с локальной LLM

Теперь добавим интеллект. Вместо облачных API используем локальную модель через Ollama или LM Studio:

import requests

class GameRulesAssistant:
    def __init__(self, search_engine, llm_url='http://localhost:11434/api/generate'):
        self.search_engine = search_engine
        self.llm_url = llm_url
    
    def answer_question(self, question, game=None):
        # 1. Ищем релевантные фрагменты
        context_chunks = self.search_engine.search(question, game_filter=game, top_k=3)
        
        # 2. Формируем промпт
        context_text = '\n\n'.join([f"[Игра: {c['game']}, стр. {c['page']}] {c['text']}" 
                                    for c in context_chunks])
        
        prompt = f"""Ты — помощник по настольным играм. Ответь на вопрос, используя предоставленные правила.

Контекст:
{context_text}

Вопрос: {question}

Ответ должен быть кратким и понятным. Если в контексте нет информации, скажи об этом.

Ответ:"""
        
        # 3. Отправляем в локальную LLM
        response = requests.post(self.llm_url, json={
            'model': 'llama3.2:3b',  # Или другая лёгкая модель
            'prompt': prompt,
            'stream': False
        })
        
        if response.status_code == 200:
            return response.json()['response']
        else:
            return "Ошибка при обращении к модели"
💡
Для улучшения качества ответов можно добавить цепочки промптов или ReAct-логику. Подробнее об этом в статье про упаковку знаний для LLM-агентов.

Сборка pet-проекта: GameRulesBot

Давайте соберём всё вместе в готовое приложение. Структура проекта:

game_rules_bot/
├── data/
│   ├── terra_mystica_rules.pdf
│   └── carcassonne_rules.pdf
├── src/
│   ├── __init__.py
│   ├── database.py      # GameRulesDB класс
│   ├── search.py        # RulesSearchEngine класс
│   ├── assistant.py     # GameRulesAssistant класс
│   └── api.py           # BlackSheep приложение
├── scripts/
│   └── populate_db.py   # Скрипт заполнения БД
├── requirements.txt
└── README.md

Запускаем всё одной командой:

# Устанавливаем зависимости
pip install -r requirements.txt

# Заполняем базу данных правилами
python scripts/populate_db.py

# Запускаем API сервер
python src/api.py

Нюансы и частые ошибки

ПроблемаРешениеПричина
Медленный поиск при большом количестве правилИспользуйте HNSW индекс в PocketFlowЛинейный поиск работает медленно при >1000 векторов
LLM игнорирует контекстДобавьте инструкцию "Отвечай ТОЛЬКО на основе предоставленного контекста"Модели склонны галлюцинировать без жёстких ограничений
PDF с таблицами плохо парситсяИспользуйте pdfplumber с extract_tables()Таблицы требуют специальной обработки
Не хватает памяти на слабом железеИспользуйте модель all-MiniLM-L6-v2 вместо большихБольшие модели эмбеддингов требуют много RAM

FAQ: Ответы на частые вопросы

Можно ли использовать этот подход для других типов документов?

Конечно! Эта архитектура подходит для любых structured документов: кулинарных книг, технических мануалов, юридических документов. Главное — адаптировать парсинг под формат ваших данных.

Что делать если правила игры содержат много изображений?

Для мультимодальных кейсов можно добавить обработку изображений через CLIP или аналогичные модели. Подробнее в статье про мультимодальный RAG.

Как масштабировать систему на сотни игр?

1. Добавьте шардирование по играм в ObjectBox
2. Используйте кэширование частых запросов
3. Рассмотрите переход на более производительную векторную БД для production

Можно ли добавить голосовой интерфейс?

Да! Подключите Whisper для транскрипции голоса в текст, а затем используйте наш API. Получится полноценный голосовой помощник для настольных игр.

Заключение: Почему этот подход работает?

Мы построили полностью локальную RAG-систему без тяжёлых зависимостей. Весь проект:

  • Занимает менее 300 строк кода
  • Работает на любом компьютере с Python 3.8+
  • Не требует облачных API или платных сервисов
  • Легко расширяется под новые форматы игр

Этот pet-проект отлично демонстрирует принцип "чем проще, тем лучше" в мире AI. Вместо того чтобы использовать переусложненные фреймворки, мы взяли минимальный набор инструментов и решили конкретную проблему.

Важный урок: Не всегда нужны сложные RAG-системы. Часто достаточно минимальной рабочей версии, которая решает 80% проблем с 20% усилий.

Если вы хотите углубиться в тему, рекомендую мои статьи про полностью локальные RAG-системы и работу с длинными PDF. Удачи в создании вашего игрового помощника!