RAG hyperparameter optimisation via Multi-Armed Bandits and question clustering
Project description
pluto
RAG hyperparameter optimisation with Multi-Armed Bandits — a Python library that uses UCB bandits and agglomerative question clustering to find the best Retrieval-Augmented Generation configuration for each question cluster, without expensive grid search.
Table of Contents
- Overview
- Key Concepts
- Repository Structure
- Installation
- Quick Start
- Module Reference
- Scripts
- Notebooks
- Development
- Version History
- Contributing
Overview
pluto solves a practical problem: which RAG configuration (chunk size, LLM temperature, …) gives the best answers for a given set of questions?
Instead of running an expensive brute-force grid search, it uses:
- Multi-Armed Bandits (UCB) to efficiently explore the configuration space and converge on the best
(chunk_size, llm_temperature)pair. - Agglomerative question clustering to group semantically similar questions, so each cluster gets its own optimal configuration rather than a single global one.
- A KNN predictor that maps new, unseen questions to the nearest cluster — and therefore to the right configuration — at inference time with a single call.
Key Concepts
| Concept | Description |
|---|---|
| RAGConfig | Dataclass holding every knob that controls the RAG pipeline (LLM provider, chunk size, temperature, …) |
| RagAction | A single point in the configuration search space (chunk_size, llm_temperature) |
| MABSelector | UCB bandit that evaluates actions on a question set and returns the best (action, score) |
| ClusterSelectionPipeline | Clusters questions → runs MABSelector per cluster → returns one optimal config per cluster |
| ClusterConfigPredictor | Routes a new question to the right cluster and retrieves the pre-computed optimal config |
| ExperimentRunner | Convenience wrapper for running brute-force / MAB experiments and accumulating heatmaps |
Repository Structure
pluto/
├── src/pluto/ # Installable Python package
│ ├── rag/ # RAG pipeline
│ │ ├── config.py # RAGConfig, LLMProvider
│ │ ├── rag.py # ChromaRAG end-to-end pipeline
│ │ ├── rag_interface.py # ChromaRAGInterface (low-level)
│ │ ├── llm_loader.py # LLM factory (Ollama / Groq)
│ │ └── utils.py # Prompt templates, helpers
│ ├── rl/ # Action space and reward scorers
│ │ └── rl.py # RagAction, DatasetRewardScorer, HumanRewardScorer
│ ├── clustering/ # Question clustering
│ │ ├── clusters.py # AgglomerativeQuestionClusterer
│ │ ├── embeders.py # QuestionEmbedder
│ │ ├── cluster_predictor.py # WeightedKNNClusterPredictor
│ │ └── viz.py # Cluster visualisation helpers
│ ├── eval/ # Evaluation
│ │ ├── evaluators.py # RAGBruteForceEvaluator, RAGMABEvaluator
│ │ ├── experiment.py # ExperimentRunner
│ │ ├── datasets.py # SquadV2Loader
│ │ └── viz.py # RewardHeatMap, plot_time_comparison
│ ├── selection/ # Configuration selection
│ │ ├── config_selector.py # BaseSelector, MABSelector, SelectionResult
│ │ ├── config_predictor.py # ClusterConfigPredictor, RoutedSelection
│ │ └── selection_pipeline.py # ClusterSelectionPipeline, PipelineResult
│ └── pipelines/ # End-to-end entry points
│ ├── training_pipeline.py # pluto-train (offline MAB training)
│ └── online_pipeline.py # pluto-ask (inference from artifact)
├── scripts/ # Runnable experiment scripts
├── notebooks/ # Jupyter notebooks
├── tests/ # pytest test suite
├── artifacts/ # Saved pipeline artifacts (.pkl)
└── docs/ # Documentation
Installation
Requirements: Python 3.10+, a running Ollama instance (or a Groq API key).
Editable install (recommended for development)
pip install -e ".[dev]"
Production install
pip install .
All runtime and development dependencies are declared in
pyproject.toml. There is norequirements.txt.
Quick Start
1 — RAG pipeline
from pluto.rag.config import RAGConfig
from pluto.rag.rag import ChromaRAG
from pluto.rag.utils import SHORT_ANSWER_PROMPT
config = RAGConfig(
texts=["Your long document goes here..."],
llm_model_name="llama3.1:8b",
llm_provider="ollama",
embedding_model_name="sentence-transformers/all-mpnet-base-v2",
custom_prompt_template=SHORT_ANSWER_PROMPT,
chunk_size=350,
llm_temperature=0.2,
)
rag = ChromaRAG(config)
answer = rag.query("What is the capital of France?")
print(answer)
2 — MAB configuration selector
Find the best RAG configuration for a set of questions using the UCB bandit:
from pluto.rag.config import RAGConfig
from pluto.rl.rl import DatasetRewardScorer, RagAction
from pluto.selection.config_selector import MABSelector
action_space = [
RagAction(chunk_size=k, llm_temperature=t)
for k in [100, 300, 500, 700, 1000]
for t in [0.0, 0.3, 0.7, 1.0]
]
selector = MABSelector(
config_template=config, # RAGConfig from step 1
reward_scorer=DatasetRewardScorer(similarity_threshold=0.5),
action_space=action_space,
alpha=1.0,
seed=42,
)
result = selector.select(questions=items, trials=100)
print("Best config:", result.best_action)
print("Best score: ", result.best_score)
3 — Cluster-aware selection pipeline
Group questions by topic and find the optimal configuration per cluster:
from pluto.clustering.embeders import QuestionEmbedder
from pluto.clustering.clusters import AgglomerativeQuestionClusterer
from pluto.clustering.cluster_predictor import WeightedKNNClusterPredictor
from pluto.selection.selection_pipeline import ClusterSelectionPipeline
embedder = QuestionEmbedder("sentence-transformers/all-mpnet-base-v2")
clusterer = AgglomerativeQuestionClusterer(embedder=embedder)
pipeline = ClusterSelectionPipeline(clusterer=clusterer, selector=selector)
pipeline_result = pipeline.run(
items=items, # list of {"question": ..., "answers": ...} dicts
min_clusters=2,
max_clusters=5,
trials=100,
)
# Predict optimal config for a new question
predictor = WeightedKNNClusterPredictor(
embedder=embedder,
train_embeddings=pipeline_result.cluster_result.embeddings,
train_labels=pipeline_result.cluster_result.labels,
k=3,
)
cluster_id = predictor.predict_cluster("What year was Python created?")
optimal = pipeline_result.cluster_selections[cluster_id].selection
print("Optimal action:", optimal.best_action)
Module Reference
pluto.rag — RAG pipeline
| Symbol | Description |
|---|---|
RAGConfig |
Dataclass with all RAG hyperparameters |
LLMProvider |
Enum: OLLAMA | GROQ |
ChromaRAG |
End-to-end RAG pipeline backed by ChromaDB |
ChromaRAGInterface |
Low-level index / retrieval interface |
SHORT_ANSWER_PROMPT |
Ready-made prompt template for short factual answers |
pluto.rl — Reward scoring
| Symbol | Description |
|---|---|
RagAction |
(chunk_size, llm_temperature) action dataclass — the MAB action space |
DatasetRewardScorer |
Cosine-similarity reward against gold answers (sentence-transformers) |
HumanRewardScorer |
Interactive human-in-the-loop reward scoring |
pluto.clustering — Question clustering
| Symbol | Description |
|---|---|
QuestionEmbedder |
Wraps a sentence-transformer to embed questions |
AgglomerativeQuestionClusterer |
Agglomerative clustering with optional silhouette auto-selection |
QuestionClusterResult |
Holds labels, embeddings, n_clusters |
WeightedKNNClusterPredictor |
KNN predictor that maps new questions to existing clusters |
pluto.eval — Evaluation
| Symbol | Description |
|---|---|
RAGBruteForceEvaluator |
Full grid-search over (chunk_sizes × temperatures) |
RAGMABEvaluator |
UCB MAB evaluator — returns MABEvaluationResult with heatmaps and snapshots |
MABEvaluationResult |
final_heatmap, snapshot_heatmaps, snapshot_counts |
ExperimentRunner |
High-level wrapper for multi-question brute-force / MAB experiments |
SquadV2Loader |
Loads random articles from the SQuAD v2 dataset |
RewardHeatMap |
Seaborn heatmap for reward visualisation |
plot_time_comparison |
Bar chart comparing brute-force vs MAB runtimes |
pluto.selection — Configuration selection
| Symbol | Description |
|---|---|
BaseSelector |
Abstract base class for all selectors |
MABSelector |
UCB-backed selector — evaluates each question independently and averages heatmaps |
SelectionResult |
best_action, best_score, heatmap, raw_results |
ClusterSelectionPipeline |
Clusters → per-cluster MABSelector → PipelineResult |
PipelineResult |
cluster_result + cluster_selections dict |
ClusterConfigPredictor |
Routes a new question to the pre-computed optimal config |
RoutedSelection |
question, cluster_id, best_action, selection_result |
Scripts
Pre-built runnable scripts are in scripts/. Run them from the repository root after installing the package.
| Script | Description |
|---|---|
scripts/single_question_analysis.py |
Brute-force + MAB on a single random question; saves heatmaps to output/ |
scripts/overall_analysis.py |
Brute-force + MAB over all questions of a random article; saves aggregate heatmaps |
scripts/test_selector.py |
Demonstrates MABSelector on a small question set |
scripts/test_predictor.py |
Demonstrates the full pipeline + ClusterConfigPredictor |
scripts/clustering_demo.py |
Standalone clustering visualisation demo |
# Example
python scripts/single_question_analysis.py
Notebooks
Interactive tutorials are in notebooks/:
| Notebook | Description |
|---|---|
mab_tutorial.ipynb |
Step-by-step Multi-Armed Bandit tutorial |
ucb_tutorial.ipynb |
Upper Confidence Bound deep-dive |
Development
# Install all dev dependencies
pip install -e ".[dev]"
# Lint & format
ruff check .
ruff format .
# Run tests
pytest tests/
Version History
[0.1.0] — 2026-06-14 · Initial Release
Core infrastructure
RAGConfigdataclass — unified control surface for all RAG hyperparameters (LLM provider, model, chunk size, temperature, embedding model, prompt template)ChromaRAGend-to-end pipeline andChromaRAGInterfacelow-level retrieval layer backed by ChromaDB- LLM factory supporting Ollama and Groq providers
Reward scoring
RagActionaction space (chunk size × LLM temperature)- UCB Multi-Armed Bandit selector (
MABSelector) with configurable exploration coefficient DatasetRewardScorer(cosine similarity) andHumanRewardScorerreward functions
Question clustering
QuestionEmbedderwrapping sentence-transformersAgglomerativeQuestionClustererwith silhouette-based automatic cluster count selectionWeightedKNNClusterPredictorfor routing unseen questions to the nearest cluster
Selection pipeline
ClusterSelectionPipeline— clusters questions and runs a per-cluster MAB selectorClusterConfigPredictor— single-call inference: question in, optimalRagActionout
Evaluation & tooling
- Brute-force and MAB evaluators with reward heatmaps and step-wise snapshots
ExperimentRunnerfor multi-question, multi-method comparisonsSquadV2Loaderdataset adapter- Runnable scripts and Jupyter notebooks
- Full pytest suite and GitHub Actions CI
Contributing
Want to contribute to pluto?
Open an issue or a pull request — all contributions are welcome.
Licensed under Apache 2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pluto_mab-0.1.1.tar.gz.
File metadata
- Download URL: pluto_mab-0.1.1.tar.gz
- Upload date:
- Size: 55.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e76df38e52e1ca9b500895190f5b381dc5b5e449ed7a2ea27728c9da8d3a08de
|
|
| MD5 |
01b0b27dccda0c2603f6cef53443e901
|
|
| BLAKE2b-256 |
ec1596f504974bed3fc85e97a439a224e3be5d3549dd746877b6012f2bd9e358
|
Provenance
The following attestation bundles were made for pluto_mab-0.1.1.tar.gz:
Publisher:
publish.yml on Ibinarriaga8/pluto
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pluto_mab-0.1.1.tar.gz -
Subject digest:
e76df38e52e1ca9b500895190f5b381dc5b5e449ed7a2ea27728c9da8d3a08de - Sigstore transparency entry: 1815105314
- Sigstore integration time:
-
Permalink:
Ibinarriaga8/pluto@69407f6df879841b232a9ff1f5208ef0b439ef53 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Ibinarriaga8
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@69407f6df879841b232a9ff1f5208ef0b439ef53 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pluto_mab-0.1.1-py3-none-any.whl.
File metadata
- Download URL: pluto_mab-0.1.1-py3-none-any.whl
- Upload date:
- Size: 42.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c7421e46520e66ed1cf5d21c598c9e1566fc5d4ca6bb2bd56a8dde99bde283b8
|
|
| MD5 |
e056edd1948423f4aa46884f7afad0cf
|
|
| BLAKE2b-256 |
1246c3c0a3ceb2425377547d9f29f31563868fb9e52b6d8dd94e219b3c05549d
|
Provenance
The following attestation bundles were made for pluto_mab-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on Ibinarriaga8/pluto
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pluto_mab-0.1.1-py3-none-any.whl -
Subject digest:
c7421e46520e66ed1cf5d21c598c9e1566fc5d4ca6bb2bd56a8dde99bde283b8 - Sigstore transparency entry: 1815105444
- Sigstore integration time:
-
Permalink:
Ibinarriaga8/pluto@69407f6df879841b232a9ff1f5208ef0b439ef53 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Ibinarriaga8
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@69407f6df879841b232a9ff1f5208ef0b439ef53 -
Trigger Event:
release
-
Statement type: