Metadata-Version: 2.4
Name: ragotron
Version: 0.0.1
Summary: Automated search over RAG-pipeline configurations with reusable trial artifacts for training and inference
Home-page: https://github.com/Riter/RAGotron
Author: Riter
Author-email: morwes4@gmail.com
License: MIT
Project-URL: Source, https://github.com/Riter/RAGotron
Project-URL: Issues, https://github.com/Riter/RAGotron/issues
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy
Requires-Dist: pandas<3
Requires-Dist: pydantic>=2.0
Requires-Dist: faiss-cpu
Requires-Dist: loguru
Requires-Dist: tqdm
Requires-Dist: langchain>=0.3.14
Requires-Dist: langchain-core
Requires-Dist: langchain-openai>=0.2.0
Requires-Dist: langchain-text-splitters
Requires-Dist: langchain-community>=0.3.14
Requires-Dist: llama-index-core
Requires-Dist: fastapi
Requires-Dist: uvicorn
Requires-Dist: httpx
Requires-Dist: transformers[torch]<5.0.0,>=4.46.0
Requires-Dist: python-docx
Requires-Dist: python-dotenv
Requires-Dist: openrouter
Requires-Dist: datasets
Requires-Dist: torch
Requires-Dist: openpyxl
Requires-Dist: sentence_transformers
Requires-Dist: sentencepiece
Requires-Dist: protobuf
Requires-Dist: streamlit
Requires-Dist: tiktoken
Requires-Dist: chromadb
Requires-Dist: docx2txt
Requires-Dist: markdown
Requires-Dist: unstructured
Requires-Dist: bm25s
Requires-Dist: pyyaml
Requires-Dist: omegaconf
Requires-Dist: statsmodels
Requires-Dist: scikit-learn
Requires-Dist: optuna
Requires-Dist: FlagEmbedding
Requires-Dist: flashrank
Requires-Dist: einops
Provides-Extra: tests
Requires-Dist: pytest>=8.2.1; extra == "tests"
Requires-Dist: black>=24.4.2; extra == "tests"
Requires-Dist: notebook<7,>=6.5.7; extra == "tests"
Provides-Extra: experimental
Requires-Dist: pypandoc; extra == "experimental"
Requires-Dist: beautifulsoup4; extra == "experimental"
Requires-Dist: langchain-chroma; extra == "experimental"
Requires-Dist: razdel; extra == "experimental"
Requires-Dist: scipy; extra == "experimental"
Dynamic: license-file

# RAGotron

**RAGotron** – это инструмент, предназначенный для автоматического построения пайплайнов на основе технологии Retrieval-Augmented Generation (RAG). RAG-пайплайны используются для повышения качества ответов генеративных моделей путём дополнения генерации релевантной информацией из баз данных и документов.

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

- **Разбиение документов (Splitter)** — разбиение текста на фрагменты (чанки) для последующего индексирования.
- **Эмбеддер (Embedder)** — преобразование чанков текста в числовые векторные представления.
- **База данных (Data Base)** — хранение и поиск наиболее релевантных текстовых фрагментов по пользовательским запросам.
- **Генерация ответов (Generation)** — формирование ответа на основе извлечённых данных.
- **Валидация (Validation)** — оценка качества и точности ответов, полученных из пайплайна.

Каждая из перечисленных компонент может быть реализована различными способами, с использованием разных подходов и технологий (см. пример на изображении ниже). 
В RAGotron пока реализован ограниченный набор компонент, однако архитектура фреймворка позволяет легко добавлять и экспериментировать с новыми решениями. 
В будущем планируется расширять и улучшать RAGotron путём добавления новых видов компонент и реализации современных подходов к построению эффективных RAG-пайплайнов.

### **Текущие возможности RAGotron**
На данный момент RAGotron поддерживает:
- **Продвинутые методы Query Translation:** *Multi-query* и *RAG-fusion*  
- **Выбор сплиттера:** *RecursiveCharacterTextSplitter* и *tiktoken*  
- **Выбор векторной БД:** *FAISS* и *ChromaDB*  
- **Поддержка OpenAI-совместимых API** через OpenRouter для различных этапов пайплайна (генерация вопросов, генерация ответов, валидация)  
- **Выбор эмбеддера:** *кастомные SentenceTransformers эмбеддеры* (например, `intfloat/multilingual-e5-large`)

---

## Архитектура RAGotron

![Архитектура проекта](docs/pics/ragotron_main_arch.svg)

### **Общий процесс работы RAGotron**
1. **Настройка конфигурации (`AppConfig`)**  
   - Задаются параметры base стейджа и дополнительных стейджей.

2. **Циклический перебор (`Cyclic Run`)**  
   - Программа многократно запускает пайплайн для различных стейджей.

3. **Запуск основного пайплайна (`Run Pipeline`)**  
   - **Загрузка документов (`document loader`)** – распаковка zip-архива, обработка файлов `.txt`, `.docx`, `.pdf`  
   - **Разбиение (`splitter`)** – деление текста на чанки (`RecursiveCharacterTextSplitter`, `tiktoken`)  
   - **Преобразование в эмбеддинги (`embedder`)** – векторизация чанков с помощью `all-MiniLM-L6-v2` и других моделей  
   - **Retrieval (`retriever`)** – поиск по документам: dense (Faiss / ChromaDB), sparse (BM25S) или hybrid (dense + sparse с weighted fusion)
   - **Генерация вопросов** – создаются три типа вопросов:  
     - **Открытые (`Open`)** – свободные вопросы по содержимому документов  
     - **Закрытые (`Closed`)** – вопросы с единственно верным ответом  
     - **MMLU** – вопросы с несколькими вариантами ответов для оценки знаний модели  
   - **Генерация ответов (`question answering`)** – обработка вопросов через LLM (OpenAI-совместимые модели через OpenRouter)
   - **Валидация (`validation`)**
        - **Формирование отчетов** – создаются детализированные отчеты по каждому типу вопросов  
        - **Подсчет метрик**:  
            - **Метрики эмбеддера** – анализируются `retrieval metrics`, отражающие качество поиска
            - **Метрики токенайзера** – рассчитываются `ROUGE`, `BLEU`, `METEOR` на открытых вопросах  


---

## Как запустить RAGotron

Установим RAGotron:
```bash
git clone <repository-url>

pip install -e .
```

### Настройка API ключа

RAGotron использует OpenRouter для доступа к LLM. Создайте файл `.env` в корне проекта:
```bash
OPENROUTER_API_KEY=your_openrouter_api_key_here
```

Получить API ключ можно на сайте [OpenRouter](https://openrouter.ai/).

### Веб-интерфейс
Обращатся с RAGotron можно через UI.
Подробнее можно посмотреть в разделе [Запуск UI](#ui-интерфейс)

#### Запуск и использование конфига
Актуальный сценарий запуска — через `RagFactory`: задается `base_config`, пространство перебора `SearchSpace` и optimizer.

`base_config` хранится в YAML-файле и загружается через OmegaConf, а валидируется по-прежнему через Pydantic (`AppConfig`) — OmegaConf отвечает только за загрузку YAML, интерполяцию и резолв путей. Пример `configs/base.yaml`:

```yaml
splitter:
  model_type: semantic            # RecursiveCharacterTextSplitter | tiktoken | SentenceWindowNodeParser | semantic
  chunk_size: 512
  chunk_overlap: 0
  semantic_breakpoint_percentile: 90

retriever:
  mode: hybrid                    # dense | sparse | hybrid
  dense_backend: faiss
  top_n: 5
  dense_top_k: 10
  sparse_top_k: 10
  dense_weight: 0.5
  sparse_weight: 0.5

query_strategy:
  type: none                      # none | multi_query | rag_fusion

embedder:
  name: custom                    # custom | openrouter
  model_path: intfloat/multilingual-e5-large

reranker:
  model_type: bge_reranker_v2_m3  # none | flashrank | bge_reranker_v2_m3 | jina_reranker_v2_base_multilingual
  candidate_top_k: 15

llm:
  # модель задается один раз, остальные роли ссылаются на нее через интерполяцию
  question_answer_model: openai/gpt-4o-mini
  validation_model: ${llm.question_answer_model}
  question_generation_model: ${llm.question_answer_model}
  base_url: https://openrouter.ai/api/v1

validation:
  questions_per_index: 3

paths:
  # ${repo_root:...} резолвится в абсолютный путь от корня репозитория,
  # независимо от текущей рабочей директории
  documents: ${repo_root:data/corpus_debug.zip}

credentials:
  # секрет берется из .env (OPENROUTER_API_KEY), в YAML его не хранят;
  # null включает fallback в CredentialsConfig
  openrouter_api_key: null
```

Загрузка конфига и запуск (по мотивам `notebooks/run_rag_factory_work.ipynb`):

```python
from pathlib import Path

from dotenv import load_dotenv

from ragotron.config import load_base_config
from ragotron.orchestration import RagFactory
from ragotron.orchestration.optimizers import OptunaTPEOptimizer
from ragotron.orchestration.search_space import (
    CategoricalParam,
    FloatRangeParam,
    IntRangeParam,
    SearchSpace,
)

load_dotenv()  # подхватывает OPENROUTER_API_KEY из .env

repo_root = Path.cwd().resolve().parent  # из notebooks/ -> корень репозитория
base_config = load_base_config(repo_root / "configs" / "base.yaml")

# При необходимости можно переопределить поля без правки YAML:
# base_config = load_base_config(
#     repo_root / "configs" / "base.yaml",
#     overrides=["retriever.mode=dense", "splitter.chunk_size=256"],
# )

search_space = SearchSpace(
    {
        "retriever": {
            "mode": CategoricalParam(["dense", "hybrid"]),
            "top_n": IntRangeParam(3, 8, 1),
            "dense_top_k": IntRangeParam(5, 30, 5),
            "sparse_top_k": IntRangeParam(5, 30, 5),
            "dense_weight": FloatRangeParam(0.2, 0.8, 0.1),
        },
        "query_strategy": {
            "type": CategoricalParam(["none", "multi_query", "rag_fusion"]),
            "n_queries": IntRangeParam(2, 8, 1),
            "per_query_top_k": IntRangeParam(5, 20, 5),
            "rrf_k": CategoricalParam([20, 40, 60]),
        },
        "reranker": {
            "model_type": CategoricalParam(
                ["none", "flashrank", "bge_reranker_v2_m3", "jina_reranker_v2_base_multilingual"]
            ),
            "candidate_top_k": IntRangeParam(10, 40, 5),
        },
    }
)

optimizer = OptunaTPEOptimizer(
    n_trials=6,
    direction="maximize",
    n_startup_trials=2,
    multivariate=True,
    group=True,
    seed=42,
)

factory = RagFactory(base_config=base_config, search_space=search_space, optimizer=optimizer)
# result = await factory.run_async()
```

`load_base_config(...)` возвращает обычный `dict`, поэтому весь прежний код (deep-merge overrides в `RagFactory`, ручная сборка конфига) работает без изменений — YAML лишь удобнее для хранения, переиспользования и версионирования. Для inference-only сценариев есть `load_app_config(...)`, который сразу возвращает валидированный `AppConfig`.

`OptunaTPEOptimizer` — основной оптимизатор для широкого пространства параметров.
Для простого baseline-поиска также доступен `GreedyOptimizer`.

Для работы доступны следующие ноутбуки:
- **`RAGotron_run_rag_factory.ipynb`**  
  Запуск оптимизации через `RagFactory`, настройка `SearchSpace`, запуск trial и анализ результатов.

### Inference из сохраненного trial

После запуска оптимизации через `RagFactory` можно поднять inference runtime для любого trial по сохраненным артефактам (`trial_config.json`, индексы, bm25-данные), без пересборки пайплайна и без FastAPI.

```python
from ragotron.inference import RagPipelineInference, resolve_trial_path

# result = await factory.run_async()
best_trial_path = result.get_best_trial_path()
engine = RagPipelineInference.from_trial_path(best_trial_path)

answer = await engine.infer_async("Какие основные этапы RAG-пайплайна?")
print(answer.answer)
```

Выбор конкретного trial:

```python
from ragotron.inference import RagPipelineInference, resolve_trial_path

# results/experiment_17 + trial_id=1 -> results/experiment_17/trial_1
trial_path = resolve_trial_path("results/experiment_17", trial_id=1)
engine = RagPipelineInference.from_trial_path(trial_path)

df = engine.infer_batch(["Что такое RAG?", "Как работает reranker?"], top_n=3)
print(df[["query", "answer"]])
```

### RagFactory: Search Space и оптимизаторы

`RagFactory` строит несколько trial-конфигураций и сравнивает их по целевой метрике (например `open.correct_pct`).
Логика подбора задается двумя сущностями:

1. **`SearchSpace`** — какие параметры можно менять между trial.
2. **Optimizer** — как выбирать следующую комбинацию параметров.

Базовые типы параметров для `SearchSpace`:
- `CategoricalParam([...])` — дискретный выбор из набора значений.
- `IntRangeParam(start, stop, step)` — целочисленный диапазон.
- `FloatRangeParam(start, stop, step)` — вещественный диапазон.

В пространстве поиска можно задавать gate/conditional-поля: например, при `query_strategy.type="none"` параметры `n_queries`/`rrf_k` не влияют на рантайм, а для reranker логика может зависеть от `model_type`.

Сокращенный пример:

```python
search_space = SearchSpace(
    {
        "retriever": {
            "mode": CategoricalParam(["dense", "hybrid"]),
            "top_n": IntRangeParam(3, 8, 1),
            "dense_top_k": IntRangeParam(5, 30, 5),
            "sparse_top_k": IntRangeParam(5, 30, 5),
            "dense_weight": FloatRangeParam(0.2, 0.8, 0.1),
        },
        "query_strategy": {
            "type": CategoricalParam(["none", "multi_query", "rag_fusion"]),
            "n_queries": IntRangeParam(2, 8, 1),
            "per_query_top_k": IntRangeParam(5, 20, 5),
            "rrf_k": CategoricalParam([20, 40, 60]),
        },
        "reranker": {
            "model_type": CategoricalParam(["none", "flashrank", "bge_reranker_v2_m3"]),
            "candidate_top_k": IntRangeParam(10, 40, 5),
        },
    }
)

optimizer = OptunaTPEOptimizer(
    n_trials=6,
    direction="maximize",
    n_startup_trials=2,
    multivariate=True,
    group=True,
    seed=42,
)
```

По умолчанию основная стратегия оптимизации — `OptunaTPEOptimizer` (эффективен для широкого пространства поиска).
В проекте также доступен `GreedyOptimizer` как более простой baseline-вариант для ограниченного перебора и быстрых smoke-проверок.

### FastAPI + Streamlit inference (select + infer)

Можно поднять единый FastAPI-бэкенд и подключить к нему Streamlit-интерфейс.
Рекомендуемый порядок запуска:

1. Поднять FastAPI (`uvicorn ragotron.serve.app:app ...`).
2. Проверить `/health`.
3. Выполнить `POST /v1/pipelines/select` для выбора trial.
4. Выполнять `POST /v1/infer` с `pipeline_id` или без него (если настроен preload).
5. Поднять Streamlit UI для интерактивной работы.

Схема API:

- `POST /v1/pipelines/select` — выбрать/загрузить trial по `trial_path` или `experiment_path + trial_id`.
- `POST /v1/infer` — выполнить инференс по `pipeline_id` из `select`; если `pipeline_id` не передан, используется preloaded pipeline.
- `GET /v1/experiments` — discovery experiment/trial только внутри `RAGOTRON_SERVE_ALLOWED_ROOTS`.
- `GET /health` — liveness-check API.

Переменные окружения:

- `RAGOTRON_SERVE_ALLOWED_ROOTS` — список разрешенных корней для артефактов trial (через разделитель `os.pathsep`: `:` на macOS/Linux, `;` на Windows).  
  Если переменная не задана, доступ к путям не ограничивается allowlist-правилом.
- `RAGOTRON_SERVE_CACHE_SIZE` — размер LRU-кэша загруженных пайплайнов (по умолчанию `4`).
- `RAGOTRON_SERVE_DEFAULT_TRIAL_PATH` — явный путь к trial для preload на старте API.
- `RAGOTRON_SERVE_DEPLOY_MANIFEST` — путь к `deploy_manifest.json` для preload (используется, если `RAGOTRON_SERVE_DEFAULT_TRIAL_PATH` не задан).

Формат `deploy_manifest.json`:
- не хранит host absolute paths;
- содержит `best_trial_relative_path` (например `trial_2`), который резолвится относительно директории manifest-файла;
- это позволяет использовать один и тот же набор артефактов и на host, и в Docker mount-пути (`/artifacts/...`).

Запуск FastAPI:

```bash
uvicorn ragotron.serve.app:app --host 0.0.0.0 --port 8000 --workers 1
```

Проверка здоровья:

```bash
curl "http://127.0.0.1:8000/health"
```

Выбор trial:

```bash
curl -X POST "http://127.0.0.1:8000/v1/pipelines/select" \
  -H "Content-Type: application/json" \
  -d '{"experiment_path":"results/experiment_17","trial_id":1}'
```

Инференс:

```bash
curl -X POST "http://127.0.0.1:8000/v1/infer" \
  -H "Content-Type: application/json" \
  -d '{"pipeline_id":"<PIPELINE_ID_FROM_SELECT>","query":"Как работает RAG?"}'
```

Инференс с preloaded pipeline (без `pipeline_id`):

```bash
curl -X POST "http://127.0.0.1:8000/v1/infer" \
  -H "Content-Type: application/json" \
  -d '{"query":"Как работает RAG?"}'
```

Discovery experiments/trials:

```bash
curl "http://127.0.0.1:8000/v1/experiments"
```

Запуск Streamlit UI:

```bash
streamlit run ragotron/serve/streamlit_app.py
```

Ограничение текущей реализации: кэш пайплайнов хранится в памяти процесса. При нескольких воркерах/репликах без общего хранилища `pipeline_id` может отсутствовать в конкретном процессе, поэтому для предсказуемого поведения рекомендуется `--workers 1`.

### Docker deploy (FastAPI + Streamlit)

Для production-ориентированного запуска используйте `docker-compose.yml`:

1. Скопируйте `.env.example` в `.env` и заполните значения.
2. Убедитесь, что `RAGOTRON_RESULTS_HOST_PATH` указывает на папку, где лежат `experiment_*`.
3. Укажите `RAGOTRON_SERVE_DEPLOY_MANIFEST` (или `RAGOTRON_SERVE_DEFAULT_TRIAL_PATH`) для preload нужного trial.
4. Поднимите сервисы:

```bash
docker compose up -d --build
```

Особенности docker-потока:
- trial-артефакты не запекаются в образ, а читаются из внешнего read-only volume (`/artifacts`);
- добавление новых `experiment_*` на host не требует пересборки (`docker build`);
- новые эксперименты становятся доступны через `GET /v1/experiments` (и после рестарта контейнера);
- HuggingFace/Transformers cache вынесен в persistent volume (`ragotron-hf-cache`) через `HF_HOME`, `TRANSFORMERS_CACHE`, `SENTENCE_TRANSFORMERS_HOME`.

Быстрые команды через `Makefile`:

```bash
make up       # build + up
make health   # проверка /health
make logs     # логи сервисов
make refresh  # пересоздать контейнеры (подхватить новые experiment_*)
make down     # остановить и удалить контейнеры
```

### Как дообучить свой эмбеддер

Эмбеддер можно дообучить под свой корпус прямо в RAGotron — без ручной разметки. Весь сценарий собран в ноутбуке `notebooks/Embedder_trainer_pipeline.ipynb`:

1. конфиг загружается из `configs/finetune.yaml` через `load_app_config(...)` (модель из семейства E5, например `intfloat/multilingual-e5-small`, и путь к zip-корпусу), после чего `pipeline.build_indexes()` строит индексы из документов;
2. `generate_train_data(pipeline, n_samples=100)` синтезирует обучающую выборку пар «вопрос ↔ чанк» через LLM (поровну между открытыми и закрытыми вопросами; размер выборки задается параметром `n_samples`);
3. `E5RetrievalTrainer` дообучает эмбеддер на этих парах (`MultipleNegativesRankingLoss`) и сохраняет результат;
4. `autometrics_embedder` сравнивает retrieval-метрики (MRR / nDCG / MAP) базовой и дообученной модели на валидации.

Дообученную модель затем можно подставить в `embedder.model_path` обычного пайплайна. Реализация — в `ragotron/embedder_trainer/train_embedder.py`.


---

## Линтинг и форматирование

Перед коммитом стоит прогнать код через линтер и форматтер. RAGotron использует [Ruff](https://docs.astral.sh/ruff/) — он совмещает в себе линтер и форматтер (замена flake8 + isort + black). Настройки лежат в `pyproject.toml` (секция `[tool.ruff]`): включены правила `E`/`F` (pyflakes + pycodestyle) и `I` (сортировка импортов), длина строки контролируется форматтером.

В `Makefile` есть три команды:

```bash
make lint     # проверка кода без изменений (ruff check)
make format   # форматирование кода (ruff format)
make fix      # автоисправление проблем, включая порядок импортов (ruff check --fix)
```

Рекомендуемый порядок перед коммитом:

1. `make fix` — автоматически чинит то, что чинится (сортировка импортов, часть мелких замечаний);
2. `make format` — приводит форматирование к единому стилю;
3. `make lint` — финальная проверка; если выводит `All checks passed!`, можно коммитить.

Ruff не установлен по умолчанию вместе с пакетом, поставить его можно через `pip install ruff`.

---

## Файловая структура

Структура проекта:
```plaintext
RAGotron
├── README.md
├── ragotron/
│   ├── core/                  # пайплайн, конфиги, retrieval/generation/validation-компоненты
│   ├── orchestration/         # RagFactory, SearchSpace, optimizers (OptunaTPE, Greedy)
│   ├── inference/             # загрузка trial-артефактов и inference runtime
│   ├── serve/                 # FastAPI + Streamlit интерфейсы
│   ├── autovalidation/        # метрики и валидация ответов
│   ├── llm/                   # фабрики и structured-output утилиты
│   ├── config/                # prompt templates + OmegaConf YAML loader
│   └── utils/                 # workspace/saver/logging утилиты
├── configs/                   # YAML-конфиги (base.yaml, finetune.yaml), грузятся через ragotron.config
├── docs/
├── notebooks/
├── tests/
├── setup.cfg
└── setup.py
```

---

## Доступные параметры конфига 

**SplitterConfig**:
1. **`model_type`**  
   RecursiveCharacterTextSplitter - рекурсивное разбиение текста на чанки по символам  
   tiktoken - разбиение текста с использованием токенизатора tiktoken  

2. **`chunk_size`**  
   Размер одного текстового фрагмента (чанка) в токенах/символах  
   По умолчанию: 512

3. **`chunk_overlap`**  
   Размер перекрытия между соседними чанками в токенах/символах  
   По умолчанию: 0

4. **`window_size`**  
   Количество предложений в окне контекста при использовании SentenceWindowNodeParser  
   По умолчанию: 3

**EmbedderConfig**:
1. **`name`**  
   custom - SentenceTransformers эмбеддер, можно использовать название модели из HuggingFace Hub.

   openrouter - эмбеддер через OpenRouter API (`openrouter.embeddings.generate`).
   
   По умолчанию: "custom"

2. **`model_path`**
   Название модели эмбеддингов.
   Для `custom` - модель HuggingFace/локальный путь.
   Для `openrouter` - название модели OpenRouter.
   По умолчанию: `"intfloat/multilingual-e5-large"`

**RetrieverConfig**:
1. **`mode`**  
   `dense` — только векторный поиск (Faiss / Chroma)  
   `sparse` — только лексический поиск BM25S  
   `hybrid` — оба поиска + взвешенная fusion  
   По умолчанию: `"dense"`

2. **`dense_backend`**  
   `faiss` — FAISS, `chroma` — ChromaDB  
   По умолчанию: `"faiss"`

3. **`top_n`**  
   Количество финальных чанков, которые будут извлекаться  
   По умолчанию: `5`

4. **`dense_top_k`** / **`sparse_top_k`**  
   Количество кандидатов из каждого ретривера перед fusion (для hybrid)  
   По умолчанию: `10`

5. **`dense_weight`** / **`sparse_weight`**  
   Веса для взвешенной нормализованной fusion; автоматически нормализуются так, чтобы сумма = 1  
   По умолчанию: `0.5` / `0.5`

**QueryStrategyConfig**:
1. **`type`**  
   `none` — обычный single-query retrieval  
   `multi_query` — несколько rewrite-запросов, затем объединение и дедупликация документов  
   `rag_fusion` — несколько rewrite-запросов + Reciprocal Rank Fusion  
   По умолчанию: `"none"`

2. **`n_queries`**  
   Количество альтернативных запросов, генерируемых LLM  
   По умолчанию: `4`

3. **`include_original_query`**  
   Добавлять ли исходный вопрос в список запросов перед retrieval  
   По умолчанию: `True`

4. **`per_query_top_k`**  
   Сколько документов забирать на каждый rewrite-запрос  
   По умолчанию: `10`

5. **`rrf_k`**  
   Параметр формулы RRF `1 / (rrf_k + rank)` (используется только при `rag_fusion`)  
   По умолчанию: `60`

**LLMConfig**:
1. **`question_answer_model`**  
   Название модели для генерации ответов на вопросы (в формате OpenRouter)  
   Пример: `"openai/gpt-4o-mini"`

2. **`validation_model`**  
   Название модели для валидации ответов  
   Пример: `"openai/gpt-4o-mini"`

3. **`question_generation_model`**  
   Название модели для генерации вопросов  
   Пример: `"openai/gpt-4o-mini"`

4. **`base_url`**  
   URL API-эндпоинта для LLM  
   По умолчанию: `"https://openrouter.ai/api/v1"`

5. **`temperature`**  
   Температура генерации для LLM  
   По умолчанию: 0.0

6. **`max_tokens`**  
   Максимальное количество токенов в ответе  
   По умолчанию: 4096

7. **`system_prompt_template`**  
   Шаблон системного промпта для генерации ответов  
   По умолчанию **уже задан**

8. **`user_query_list`**  
   Список пользовательских вопросов на которые сгенерируются ответы во время сборки пайплайна 
   По умолчанию: пустой список

**ValidationConfig**:
1. **`compare_with_benchmark`**  
   Флаг для включения сравнения с ответами benchmark-модели
   По умолчанию: False

2. **`benchmark_model`**  
   Название модели для benchmark-сравнения  
   Пример: `"openai/gpt-4o"`

3. **`questions_per_index`**  
   Количество вопросов, генерируемых для каждого индекса. Если значение `None`, ограничение не задается и вопросы генерируются по всем чанкам в индеках
   По умолчанию: 3

**PathsConfig**:
1. **`documents`**  
   Путь к zip архиву в котором только docx файлы для создания поискового индекса.
    > **Важно!**  
        - Все файлы должны быть названы **на английском языке** для корректной работы создания индекса.
        - Архив должен содержать только документы (без вложенных папок).

2. **`results_path`**  
   Путь для сохранения результатов  
   По умолчанию: "./results"

3. **`experiment_folder_name`**  
   Название папки для текущего эксперимента.
   По умолчанию: None

**CredentialsConfig**:
1. **`openrouter_api_key`**  
   API ключ для OpenRouter. Если не указан, будет использована переменная окружения `OPENROUTER_API_KEY`

---
    
## Описание Артефактов

При запуске оптимизации через `RagFactory` результаты сохраняются в `results/<experiment_name>/`.
Актуальная структура ориентирована на **trial** (`trial_0`, `trial_1`, ...), а не на stage-папки.

### Артефакты уровня эксперимента

- `leaderboard.csv` — агрегированная таблица метрик по всем trial.
- `optimization_summary.json` — подробный список trial с метриками, overrides и статусами.
- `best_config.yaml` — лучшая конфигурация по целевой метрике.
- `pairwise_tournament_params.json` — параметры турнирного сравнения (если включено).
- `run.log` — общий лог запуска эксперимента.

### Артефакты уровня trial (`trial_<id>/`)

- `trial_config.json` — полный конфиг конкретного trial.
- `trial_result.json` — финальные метрики, latency/runtime и служебная информация.
- `trial.log` и `run.log` — логи выполнения trial.
- `<doc>.json` — document-level артефакты с чанками/метаданными для dense retrieval.
- `bm25_<doc>/` — sparse-индекс (`corpus.jsonl`, `corpus.mmindex.json`, `params.index.json`, `vocab.index.json`).
- `source_files/corpus_debug/` — копии исходных документов для отладки корпуса.

Пример актуальной структуры артефактов:
```plaintext
results/
└── experiment_1/
    ├── leaderboard.csv
    ├── pipeline_trials_detailed.xlsx
    ├── optimization_summary.json
    ├── best_config.yaml
    ├── pairwise_tournament_params.json
    ├── run.log
    ├── trial_0/
    │   ├── trial_config.json
    │   ├── trial_result.json
    │   ├── trial.log
    │   ├── run.log
    │   ├── fastapi__dependencies__index.json
    │   ├── bm25_fastapi__dependencies__index/
    │   │   ├── corpus.jsonl
    │   │   ├── corpus.mmindex.json
    │   │   ├── params.index.json
    │   │   └── vocab.index.json
    │   └── source_files/corpus_debug/
    ├── trial_1/
    └── ...
```
