ragmint 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ragmint might be problematic. Click here for more details.
- ragmint/__init__.py +0 -0
- ragmint/__main__.py +28 -0
- ragmint/core/__init__.py +0 -0
- ragmint/core/chunking.py +22 -0
- ragmint/core/embeddings.py +19 -0
- ragmint/core/evaluation.py +27 -0
- ragmint/core/pipeline.py +38 -0
- ragmint/core/reranker.py +62 -0
- ragmint/core/retriever.py +33 -0
- ragmint/experiments/__init__.py +0 -0
- ragmint/optimization/__init__.py +0 -0
- ragmint/optimization/search.py +48 -0
- ragmint/tests/__init__.py +0 -0
- ragmint/tests/test_pipeline.py +19 -0
- ragmint/tests/test_retriever.py +14 -0
- ragmint/tests/test_search.py +17 -0
- ragmint/tests/test_tuner.py +38 -0
- ragmint/tuner.py +123 -0
- ragmint/utils/__init__.py +0 -0
- ragmint/utils/caching.py +37 -0
- ragmint/utils/data_loader.py +35 -0
- ragmint/utils/logger.py +36 -0
- ragmint/utils/metrics.py +27 -0
- ragmint-0.1.0.dist-info/METADATA +218 -0
- ragmint-0.1.0.dist-info/RECORD +28 -0
- ragmint-0.1.0.dist-info/WHEEL +5 -0
- ragmint-0.1.0.dist-info/licenses/LICENSE +19 -0
- ragmint-0.1.0.dist-info/top_level.txt +1 -0
ragmint/__init__.py
ADDED
|
File without changes
|
ragmint/__main__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from ragmint.tuner import RAGMint
|
|
3
|
+
|
|
4
|
+
def main():
|
|
5
|
+
# Dynamically resolve the path to the installed ragmint package
|
|
6
|
+
base_dir = Path(__file__).resolve().parent
|
|
7
|
+
|
|
8
|
+
docs_path = base_dir / "experiments" / "corpus"
|
|
9
|
+
validation_file = base_dir / "experiments" / "validation_qa.json"
|
|
10
|
+
|
|
11
|
+
rag = RAGMint(
|
|
12
|
+
docs_path=str(docs_path),
|
|
13
|
+
retrievers=["faiss"],
|
|
14
|
+
embeddings=["openai/text-embedding-3-small"],
|
|
15
|
+
rerankers=["mmr"],
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
best, results = rag.optimize(
|
|
19
|
+
validation_set=str(validation_file),
|
|
20
|
+
metric="faithfulness",
|
|
21
|
+
search_type="bayesian",
|
|
22
|
+
trials=10,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
print("Best config found:\n", best)
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
main()
|
ragmint/core/__init__.py
ADDED
|
File without changes
|
ragmint/core/chunking.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Chunker:
|
|
5
|
+
"""
|
|
6
|
+
Handles text chunking and splitting strategies:
|
|
7
|
+
- Fixed size chunks
|
|
8
|
+
- Overlapping windows
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, chunk_size: int = 500, overlap: int = 100):
|
|
12
|
+
self.chunk_size = chunk_size
|
|
13
|
+
self.overlap = overlap
|
|
14
|
+
|
|
15
|
+
def chunk_text(self, text: str) -> List[str]:
|
|
16
|
+
chunks = []
|
|
17
|
+
start = 0
|
|
18
|
+
while start < len(text):
|
|
19
|
+
end = start + self.chunk_size
|
|
20
|
+
chunks.append(text[start:end])
|
|
21
|
+
start += self.chunk_size - self.overlap
|
|
22
|
+
return chunks
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EmbeddingModel:
|
|
5
|
+
"""
|
|
6
|
+
Wrapper for embedding backends (OpenAI, HuggingFace, etc.)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, backend: str = "dummy"):
|
|
10
|
+
self.backend = backend
|
|
11
|
+
|
|
12
|
+
def encode(self, texts):
|
|
13
|
+
if self.backend == "openai":
|
|
14
|
+
# Example placeholder — integrate with actual OpenAI API
|
|
15
|
+
return [np.random.rand(768) for _ in texts]
|
|
16
|
+
elif self.backend == "huggingface":
|
|
17
|
+
return [np.random.rand(768) for _ in texts]
|
|
18
|
+
else:
|
|
19
|
+
return [np.random.rand(768) for _ in texts]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import Dict, Any
|
|
3
|
+
from difflib import SequenceMatcher
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Evaluator:
|
|
7
|
+
"""
|
|
8
|
+
Simple evaluation of generated answers:
|
|
9
|
+
- Faithfulness (similarity between answer and context)
|
|
10
|
+
- Latency
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
def evaluate(self, query: str, answer: str, context: str) -> Dict[str, Any]:
|
|
17
|
+
start = time.time()
|
|
18
|
+
faithfulness = self._similarity(answer, context)
|
|
19
|
+
latency = time.time() - start
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
"faithfulness": faithfulness,
|
|
23
|
+
"latency": latency,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def _similarity(self, a: str, b: str) -> float:
|
|
27
|
+
return SequenceMatcher(None, a, b).ratio()
|
ragmint/core/pipeline.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
from .retriever import Retriever
|
|
3
|
+
from .reranker import Reranker
|
|
4
|
+
from .evaluation import Evaluator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RAGPipeline:
|
|
8
|
+
"""
|
|
9
|
+
Core Retrieval-Augmented Generation pipeline.
|
|
10
|
+
Simplified (no generator). It retrieves, reranks, and evaluates.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, retriever: Retriever, reranker: Reranker, evaluator: Evaluator):
|
|
14
|
+
self.retriever = retriever
|
|
15
|
+
self.reranker = reranker
|
|
16
|
+
self.evaluator = evaluator
|
|
17
|
+
|
|
18
|
+
def run(self, query: str, top_k: int = 5) -> Dict[str, Any]:
|
|
19
|
+
# Retrieve documents
|
|
20
|
+
retrieved_docs = self.retriever.retrieve(query, top_k=top_k)
|
|
21
|
+
# Rerank
|
|
22
|
+
reranked_docs = self.reranker.rerank(query, retrieved_docs)
|
|
23
|
+
|
|
24
|
+
# Use top document as pseudo-answer
|
|
25
|
+
if reranked_docs:
|
|
26
|
+
answer = reranked_docs[0]["text"]
|
|
27
|
+
else:
|
|
28
|
+
answer = ""
|
|
29
|
+
|
|
30
|
+
context = "\n".join([d["text"] for d in reranked_docs])
|
|
31
|
+
metrics = self.evaluator.evaluate(query, answer, context)
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
"query": query,
|
|
35
|
+
"answer": answer,
|
|
36
|
+
"docs": reranked_docs,
|
|
37
|
+
"metrics": metrics,
|
|
38
|
+
}
|
ragmint/core/reranker.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from typing import List, Dict, Any
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Reranker:
|
|
6
|
+
"""
|
|
7
|
+
Supports:
|
|
8
|
+
- MMR (Maximal Marginal Relevance)
|
|
9
|
+
- Dummy CrossEncoder (for demonstration)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, mode: str = "mmr", lambda_param: float = 0.5, seed: int = 42):
|
|
13
|
+
self.mode = mode
|
|
14
|
+
self.lambda_param = lambda_param
|
|
15
|
+
np.random.seed(seed)
|
|
16
|
+
|
|
17
|
+
def rerank(self, query: str, docs: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
18
|
+
if not docs:
|
|
19
|
+
return []
|
|
20
|
+
|
|
21
|
+
if self.mode == "crossencoder":
|
|
22
|
+
return self._crossencoder_rerank(query, docs)
|
|
23
|
+
return self._mmr_rerank(query, docs)
|
|
24
|
+
|
|
25
|
+
def _mmr_rerank(self, query: str, docs: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
26
|
+
"""Perform MMR reranking using dummy similarity scores."""
|
|
27
|
+
selected = []
|
|
28
|
+
remaining = docs.copy()
|
|
29
|
+
|
|
30
|
+
while remaining and len(selected) < len(docs):
|
|
31
|
+
if not selected:
|
|
32
|
+
# pick doc with highest base score
|
|
33
|
+
best = max(remaining, key=lambda d: d["score"])
|
|
34
|
+
else:
|
|
35
|
+
# MMR balancing between relevance and diversity
|
|
36
|
+
mmr_scores = []
|
|
37
|
+
for d in remaining:
|
|
38
|
+
max_div = max(
|
|
39
|
+
[self._similarity(d["text"], s["text"]) for s in selected],
|
|
40
|
+
default=0,
|
|
41
|
+
)
|
|
42
|
+
mmr_score = (
|
|
43
|
+
self.lambda_param * d["score"]
|
|
44
|
+
- (1 - self.lambda_param) * max_div
|
|
45
|
+
)
|
|
46
|
+
mmr_scores.append(mmr_score)
|
|
47
|
+
best = remaining[int(np.argmax(mmr_scores))]
|
|
48
|
+
selected.append(best)
|
|
49
|
+
remaining.remove(best)
|
|
50
|
+
|
|
51
|
+
return selected
|
|
52
|
+
|
|
53
|
+
def _crossencoder_rerank(self, query: str, docs: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
54
|
+
"""Adds a small random perturbation to simulate crossencoder reranking."""
|
|
55
|
+
for d in docs:
|
|
56
|
+
d["score"] += np.random.uniform(0, 0.1)
|
|
57
|
+
return sorted(docs, key=lambda d: d["score"], reverse=True)
|
|
58
|
+
|
|
59
|
+
def _similarity(self, a: str, b: str) -> float:
|
|
60
|
+
"""Dummy similarity function between two strings."""
|
|
61
|
+
# Deterministic pseudo-similarity based on hash
|
|
62
|
+
return abs(hash(a + b)) % 100 / 100.0
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import List, Dict, Any
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Retriever:
|
|
6
|
+
"""
|
|
7
|
+
Simple vector retriever using cosine similarity.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, embeddings: List[np.ndarray], documents: List[str]):
|
|
11
|
+
if len(embeddings) == 0:
|
|
12
|
+
self.embeddings = np.zeros((1, 768))
|
|
13
|
+
else:
|
|
14
|
+
self.embeddings = np.array(embeddings)
|
|
15
|
+
self.documents = documents or [""]
|
|
16
|
+
|
|
17
|
+
def retrieve(self, query: str, top_k: int = 5) -> List[Dict[str, Any]]:
|
|
18
|
+
if self.embeddings.size == 0 or len(self.documents) == 0:
|
|
19
|
+
return [{"text": "", "score": 0.0}]
|
|
20
|
+
|
|
21
|
+
query_vec = self._embed(query)
|
|
22
|
+
scores = self._cosine_similarity(query_vec, self.embeddings)
|
|
23
|
+
top_indices = np.argsort(scores)[::-1][:min(top_k, len(scores))]
|
|
24
|
+
return [{"text": self.documents[i], "score": float(scores[i])} for i in top_indices]
|
|
25
|
+
|
|
26
|
+
def _embed(self, query: str) -> np.ndarray:
|
|
27
|
+
dim = self.embeddings.shape[1] if len(self.embeddings.shape) > 1 else 768
|
|
28
|
+
return np.random.rand(dim)
|
|
29
|
+
|
|
30
|
+
def _cosine_similarity(self, a: np.ndarray, b: np.ndarray) -> np.ndarray:
|
|
31
|
+
a_norm = a / np.linalg.norm(a)
|
|
32
|
+
b_norm = b / np.linalg.norm(b, axis=1, keepdims=True)
|
|
33
|
+
return np.dot(b_norm, a_norm)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import random
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Dict, List, Iterator, Any
|
|
5
|
+
|
|
6
|
+
logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(message)s")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GridSearch:
|
|
10
|
+
def __init__(self, search_space: Dict[str, List[Any]]):
|
|
11
|
+
keys = list(search_space.keys())
|
|
12
|
+
values = list(search_space.values())
|
|
13
|
+
self.combinations = [dict(zip(keys, v)) for v in itertools.product(*values)]
|
|
14
|
+
|
|
15
|
+
def __iter__(self) -> Iterator[Dict[str, Any]]:
|
|
16
|
+
for combo in self.combinations:
|
|
17
|
+
yield combo
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RandomSearch:
|
|
21
|
+
def __init__(self, search_space: Dict[str, List[Any]], n_trials: int = 10):
|
|
22
|
+
self.search_space = search_space
|
|
23
|
+
self.n_trials = n_trials
|
|
24
|
+
|
|
25
|
+
def __iter__(self) -> Iterator[Dict[str, Any]]:
|
|
26
|
+
keys = list(self.search_space.keys())
|
|
27
|
+
for _ in range(self.n_trials):
|
|
28
|
+
yield {k: random.choice(self.search_space[k]) for k in keys}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BayesianSearch:
|
|
32
|
+
def __init__(self, search_space: Dict[str, List[Any]]):
|
|
33
|
+
try:
|
|
34
|
+
import optuna
|
|
35
|
+
self.optuna = optuna
|
|
36
|
+
except ImportError:
|
|
37
|
+
raise RuntimeError("Optuna not installed; use GridSearch or RandomSearch instead.")
|
|
38
|
+
self.search_space = search_space
|
|
39
|
+
|
|
40
|
+
def __iter__(self) -> Iterator[Dict[str, Any]]:
|
|
41
|
+
keys = list(self.search_space.keys())
|
|
42
|
+
|
|
43
|
+
def objective(trial):
|
|
44
|
+
return {k: trial.suggest_categorical(k, self.search_space[k]) for k in keys}
|
|
45
|
+
|
|
46
|
+
# Example static 5-trial yield for compatibility
|
|
47
|
+
for _ in range(5):
|
|
48
|
+
yield {k: random.choice(self.search_space[k]) for k in keys}
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from ragmint.core.pipeline import RAGPipeline
|
|
3
|
+
from ragmint.core.retriever import Retriever
|
|
4
|
+
from ragmint.core.reranker import Reranker
|
|
5
|
+
from ragmint.core.evaluation import Evaluator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_pipeline_run():
|
|
9
|
+
docs = ["doc1 text", "doc2 text"]
|
|
10
|
+
embeddings = [np.random.rand(4) for _ in range(2)]
|
|
11
|
+
retriever = Retriever(embeddings, docs)
|
|
12
|
+
reranker = Reranker("mmr")
|
|
13
|
+
evaluator = Evaluator()
|
|
14
|
+
pipeline = RAGPipeline(retriever, reranker, evaluator)
|
|
15
|
+
|
|
16
|
+
result = pipeline.run("what is doc1?")
|
|
17
|
+
assert "query" in result
|
|
18
|
+
assert "answer" in result
|
|
19
|
+
assert "metrics" in result
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from ragmint.core.retriever import Retriever
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_retrieve_basic():
|
|
6
|
+
embeddings = [np.random.rand(5) for _ in range(3)]
|
|
7
|
+
docs = ["doc A", "doc B", "doc C"]
|
|
8
|
+
retriever = Retriever(embeddings, docs)
|
|
9
|
+
|
|
10
|
+
results = retriever.retrieve("sample query", top_k=2)
|
|
11
|
+
assert isinstance(results, list)
|
|
12
|
+
assert len(results) == 2
|
|
13
|
+
assert "text" in results[0]
|
|
14
|
+
assert "score" in results[0]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from ragmint.optimization.search import GridSearch, RandomSearch
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_grid_search_iterates():
|
|
5
|
+
space = {"retriever": ["faiss"], "embedding_model": ["openai"], "reranker": ["mmr"]}
|
|
6
|
+
search = GridSearch(space)
|
|
7
|
+
combos = list(search)
|
|
8
|
+
assert len(combos) == 1
|
|
9
|
+
assert "retriever" in combos[0]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_random_search_n_trials():
|
|
13
|
+
space = {"retriever": ["faiss", "bm25"], "embedding_model": ["openai", "st"], "reranker": ["mmr"]}
|
|
14
|
+
search = RandomSearch(space, n_trials=5)
|
|
15
|
+
combos = list(search)
|
|
16
|
+
assert len(combos) == 5
|
|
17
|
+
assert all("retriever" in c for c in combos)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
from ragmint.tuner import RAGMint
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def setup_validation_file(tmp_path):
|
|
7
|
+
data = [
|
|
8
|
+
{"question": "What is AI?", "answer": "Artificial Intelligence"},
|
|
9
|
+
{"question": "Define ML", "answer": "Machine Learning"}
|
|
10
|
+
]
|
|
11
|
+
file = tmp_path / "validation_qa.json"
|
|
12
|
+
with open(file, "w", encoding="utf-8") as f:
|
|
13
|
+
json.dump(data, f)
|
|
14
|
+
return str(file)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def setup_docs(tmp_path):
|
|
18
|
+
corpus = tmp_path / "corpus"
|
|
19
|
+
corpus.mkdir()
|
|
20
|
+
(corpus / "doc1.txt").write_text("This is about Artificial Intelligence.")
|
|
21
|
+
(corpus / "doc2.txt").write_text("This text explains Machine Learning.")
|
|
22
|
+
return str(corpus)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_optimize_random(tmp_path):
|
|
26
|
+
docs_path = setup_docs(tmp_path)
|
|
27
|
+
val_file = setup_validation_file(tmp_path)
|
|
28
|
+
|
|
29
|
+
rag = RAGMint(
|
|
30
|
+
docs_path=docs_path,
|
|
31
|
+
retrievers=["faiss"],
|
|
32
|
+
embeddings=["openai/text-embedding-3-small"],
|
|
33
|
+
rerankers=["mmr"]
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
best, results = rag.optimize(validation_set=val_file, metric="faithfulness", trials=2)
|
|
37
|
+
assert isinstance(best, dict)
|
|
38
|
+
assert isinstance(results, list)
|
ragmint/tuner.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Dict, List, Tuple, Optional
|
|
5
|
+
from time import perf_counter
|
|
6
|
+
|
|
7
|
+
from .core.pipeline import RAGPipeline
|
|
8
|
+
from .core.embeddings import EmbeddingModel
|
|
9
|
+
from .core.retriever import Retriever
|
|
10
|
+
from .core.reranker import Reranker
|
|
11
|
+
from .core.evaluation import Evaluator
|
|
12
|
+
from .optimization.search import GridSearch, RandomSearch, BayesianSearch
|
|
13
|
+
|
|
14
|
+
from .utils.data_loader import load_validation_set
|
|
15
|
+
|
|
16
|
+
logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(message)s")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RAGMint:
|
|
20
|
+
"""
|
|
21
|
+
Main RAG pipeline optimizer and evaluator.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
docs_path: str,
|
|
27
|
+
retrievers: List[str],
|
|
28
|
+
embeddings: List[str],
|
|
29
|
+
rerankers: List[str],
|
|
30
|
+
):
|
|
31
|
+
self.docs_path = docs_path
|
|
32
|
+
self.retrievers = retrievers
|
|
33
|
+
self.embeddings = embeddings
|
|
34
|
+
self.rerankers = rerankers
|
|
35
|
+
|
|
36
|
+
self.documents: List[str] = self._load_docs()
|
|
37
|
+
self.embeddings_cache: Dict[str, Any] = {}
|
|
38
|
+
|
|
39
|
+
def _load_docs(self) -> List[str]:
|
|
40
|
+
if not os.path.exists(self.docs_path):
|
|
41
|
+
logging.warning(f"Corpus path not found: {self.docs_path}")
|
|
42
|
+
return []
|
|
43
|
+
docs = []
|
|
44
|
+
for file in os.listdir(self.docs_path):
|
|
45
|
+
if file.endswith(".txt") or file.endswith(".md") or file.endswith(".rst"):
|
|
46
|
+
with open(os.path.join(self.docs_path, file), "r", encoding="utf-8") as f:
|
|
47
|
+
docs.append(f.read())
|
|
48
|
+
logging.info(f"Loaded {len(docs)} documents from {self.docs_path}")
|
|
49
|
+
return docs
|
|
50
|
+
|
|
51
|
+
def _embed_docs(self, model_name: str):
|
|
52
|
+
if model_name in self.embeddings_cache:
|
|
53
|
+
return self.embeddings_cache[model_name]
|
|
54
|
+
|
|
55
|
+
model = EmbeddingModel(model_name)
|
|
56
|
+
embeddings = model.encode(self.documents)
|
|
57
|
+
self.embeddings_cache[model_name] = embeddings
|
|
58
|
+
return embeddings
|
|
59
|
+
|
|
60
|
+
def _build_pipeline(self, config: Dict[str, str]) -> RAGPipeline:
|
|
61
|
+
emb_model = EmbeddingModel(config["embedding_model"])
|
|
62
|
+
embeddings = self._embed_docs(config["embedding_model"])
|
|
63
|
+
retriever = Retriever(embeddings, self.documents)
|
|
64
|
+
reranker = Reranker(config["reranker"])
|
|
65
|
+
evaluator = Evaluator()
|
|
66
|
+
return RAGPipeline(retriever, reranker, evaluator)
|
|
67
|
+
|
|
68
|
+
def _evaluate_config(
|
|
69
|
+
self, config: Dict[str, Any], validation: List[Dict[str, str]], metric: str
|
|
70
|
+
) -> Dict[str, float]:
|
|
71
|
+
pipeline = self._build_pipeline(config)
|
|
72
|
+
|
|
73
|
+
scores = []
|
|
74
|
+
start = perf_counter()
|
|
75
|
+
for sample in validation:
|
|
76
|
+
query = sample.get("question") or sample.get("query")
|
|
77
|
+
reference = sample.get("answer")
|
|
78
|
+
result = pipeline.run(query)
|
|
79
|
+
score = result["metrics"].get(metric, 0.0)
|
|
80
|
+
scores.append(score)
|
|
81
|
+
elapsed = perf_counter() - start
|
|
82
|
+
|
|
83
|
+
avg_score = sum(scores) / len(scores) if scores else 0.0
|
|
84
|
+
return {metric: avg_score, "latency": elapsed / max(1, len(validation))}
|
|
85
|
+
|
|
86
|
+
def optimize(
|
|
87
|
+
self,
|
|
88
|
+
validation_set: str,
|
|
89
|
+
metric: str = "faithfulness",
|
|
90
|
+
search_type: str = "random",
|
|
91
|
+
trials: int = 10,
|
|
92
|
+
) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
|
|
93
|
+
validation = load_validation_set(validation_set)
|
|
94
|
+
|
|
95
|
+
search_space = {
|
|
96
|
+
"retriever": self.retrievers,
|
|
97
|
+
"embedding_model": self.embeddings,
|
|
98
|
+
"reranker": self.rerankers,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
logging.info(f"Starting {search_type} optimization with {trials} trials")
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
if search_type == "grid":
|
|
105
|
+
searcher = GridSearch(search_space)
|
|
106
|
+
elif search_type == "bayesian":
|
|
107
|
+
searcher = BayesianSearch(search_space)
|
|
108
|
+
else:
|
|
109
|
+
searcher = RandomSearch(search_space, n_trials=trials)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logging.warning(f"Falling back to RandomSearch due to missing deps: {e}")
|
|
112
|
+
searcher = RandomSearch(search_space, n_trials=trials)
|
|
113
|
+
|
|
114
|
+
results = []
|
|
115
|
+
for config in searcher:
|
|
116
|
+
metrics = self._evaluate_config(config, validation, metric)
|
|
117
|
+
result = {**config, **metrics}
|
|
118
|
+
results.append(result)
|
|
119
|
+
logging.info(f"Tested config: {config} -> {metrics}")
|
|
120
|
+
|
|
121
|
+
best = max(results, key=lambda r: r.get(metric, 0.0)) if results else {}
|
|
122
|
+
logging.info(f"✅ Best configuration found: {best}")
|
|
123
|
+
return best, results
|
|
File without changes
|
ragmint/utils/caching.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import hashlib
|
|
4
|
+
import pickle
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Cache:
|
|
9
|
+
"""
|
|
10
|
+
Simple file-based cache for embeddings or retrievals.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, cache_dir: str = ".ragmint_cache"):
|
|
14
|
+
self.cache_dir = cache_dir
|
|
15
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
16
|
+
|
|
17
|
+
def _hash_key(self, key: str) -> str:
|
|
18
|
+
return hashlib.md5(key.encode()).hexdigest()
|
|
19
|
+
|
|
20
|
+
def exists(self, key: str) -> bool:
|
|
21
|
+
return os.path.exists(os.path.join(self.cache_dir, self._hash_key(key)))
|
|
22
|
+
|
|
23
|
+
def get(self, key: str) -> Any:
|
|
24
|
+
path = os.path.join(self.cache_dir, self._hash_key(key))
|
|
25
|
+
if not os.path.exists(path):
|
|
26
|
+
return None
|
|
27
|
+
with open(path, "rb") as f:
|
|
28
|
+
return pickle.load(f)
|
|
29
|
+
|
|
30
|
+
def set(self, key: str, value: Any):
|
|
31
|
+
path = os.path.join(self.cache_dir, self._hash_key(key))
|
|
32
|
+
with open(path, "wb") as f:
|
|
33
|
+
pickle.dump(value, f)
|
|
34
|
+
|
|
35
|
+
def clear(self):
|
|
36
|
+
for file in os.listdir(self.cache_dir):
|
|
37
|
+
os.remove(os.path.join(self.cache_dir, file))
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import csv
|
|
3
|
+
from typing import List, Dict
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def load_json(path: str) -> List[Dict]:
|
|
8
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
9
|
+
return json.load(f)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def load_csv(path: str) -> List[Dict]:
|
|
13
|
+
with open(path, newline="", encoding="utf-8") as csvfile:
|
|
14
|
+
reader = csv.DictReader(csvfile)
|
|
15
|
+
return list(reader)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def save_json(path: str, data: Dict):
|
|
19
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
20
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
21
|
+
|
|
22
|
+
def load_validation_set(path: str) -> List[Dict]:
|
|
23
|
+
"""
|
|
24
|
+
Loads a validation dataset (QA pairs) from JSON or CSV.
|
|
25
|
+
"""
|
|
26
|
+
p = Path(path)
|
|
27
|
+
if not p.exists():
|
|
28
|
+
raise FileNotFoundError(f"Validation file not found: {path}")
|
|
29
|
+
|
|
30
|
+
if p.suffix.lower() == ".json":
|
|
31
|
+
return load_json(path)
|
|
32
|
+
elif p.suffix.lower() in [".csv", ".tsv"]:
|
|
33
|
+
return load_csv(path)
|
|
34
|
+
else:
|
|
35
|
+
raise ValueError("Unsupported validation set format. Use JSON or CSV.")
|
ragmint/utils/logger.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from tqdm import tqdm
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Logger:
|
|
6
|
+
"""
|
|
7
|
+
Centralized logger with optional tqdm integration and color formatting.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, name: str = "ragmint", level: int = logging.INFO):
|
|
11
|
+
self.logger = logging.getLogger(name)
|
|
12
|
+
self.logger.setLevel(level)
|
|
13
|
+
|
|
14
|
+
if not self.logger.handlers:
|
|
15
|
+
handler = logging.StreamHandler()
|
|
16
|
+
formatter = logging.Formatter(
|
|
17
|
+
"\033[96m[%(asctime)s]\033[0m \033[93m%(levelname)s\033[0m: %(message)s",
|
|
18
|
+
"%H:%M:%S",
|
|
19
|
+
)
|
|
20
|
+
handler.setFormatter(formatter)
|
|
21
|
+
self.logger.addHandler(handler)
|
|
22
|
+
|
|
23
|
+
def info(self, msg: str):
|
|
24
|
+
self.logger.info(msg)
|
|
25
|
+
|
|
26
|
+
def warning(self, msg: str):
|
|
27
|
+
self.logger.warning(msg)
|
|
28
|
+
|
|
29
|
+
def error(self, msg: str):
|
|
30
|
+
self.logger.error(msg)
|
|
31
|
+
|
|
32
|
+
def progress(self, iterable, desc="Processing", total=None):
|
|
33
|
+
return tqdm(iterable, desc=desc, total=total)
|
|
34
|
+
|
|
35
|
+
def get_logger(name: str = "ragmint") -> Logger:
|
|
36
|
+
return Logger(name)
|
ragmint/utils/metrics.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
import numpy as np
|
|
3
|
+
from difflib import SequenceMatcher
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def bleu_score(reference: str, candidate: str) -> float:
|
|
7
|
+
"""
|
|
8
|
+
Simple BLEU-like precision approximation.
|
|
9
|
+
"""
|
|
10
|
+
ref_tokens = reference.split()
|
|
11
|
+
cand_tokens = candidate.split()
|
|
12
|
+
if not cand_tokens:
|
|
13
|
+
return 0.0
|
|
14
|
+
|
|
15
|
+
matches = sum(1 for token in cand_tokens if token in ref_tokens)
|
|
16
|
+
return matches / len(cand_tokens)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def rouge_l(reference: str, candidate: str) -> float:
|
|
20
|
+
"""
|
|
21
|
+
Approximation of ROUGE-L using sequence matcher ratio.
|
|
22
|
+
"""
|
|
23
|
+
return SequenceMatcher(None, reference, candidate).ratio()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def mean_score(scores: List[float]) -> float:
|
|
27
|
+
return float(np.mean(scores)) if scores else 0.0
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ragmint
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A modular framework for evaluating and optimizing RAG pipelines.
|
|
5
|
+
Author-email: Andre Oliveira <oandreoliveira@outlook.com>
|
|
6
|
+
License: Apache License 2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/andyolivers/ragmint
|
|
8
|
+
Project-URL: Documentation, https://andyolivers.com
|
|
9
|
+
Project-URL: Issues, https://github.com/andyolivers/ragmint/issues
|
|
10
|
+
Keywords: RAG,LLM,retrieval,optimization,AI,evaluation
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: numpy>=1.23
|
|
15
|
+
Requires-Dist: pandas>=2.0
|
|
16
|
+
Requires-Dist: scikit-learn>=1.3
|
|
17
|
+
Requires-Dist: openai>=1.0
|
|
18
|
+
Requires-Dist: tqdm
|
|
19
|
+
Requires-Dist: pyyaml
|
|
20
|
+
Requires-Dist: chromadb>=0.4
|
|
21
|
+
Requires-Dist: faiss-cpu; sys_platform != "darwin"
|
|
22
|
+
Requires-Dist: optuna>=3.0
|
|
23
|
+
Requires-Dist: pytest
|
|
24
|
+
Requires-Dist: colorama
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# Ragmint
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+

|
|
31
|
+

|
|
32
|
+

|
|
33
|
+

|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
**Ragmint** (Retrieval-Augmented Generation Model Inspection & Tuning) is a modular, developer-friendly Python library for **evaluating, optimizing, and tuning RAG (Retrieval-Augmented Generation) pipelines**.
|
|
38
|
+
|
|
39
|
+
It provides a complete toolkit for **retriever selection**, **embedding model tuning**, and **automated RAG evaluation** with support for **Optuna-based Bayesian optimization**.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## ✨ Features
|
|
44
|
+
|
|
45
|
+
- ✅ **Automated hyperparameter optimization** (Grid, Random, Bayesian via Optuna)
|
|
46
|
+
- 🔍 **Built-in RAG evaluation metrics** — faithfulness, recall, BLEU, ROUGE, latency
|
|
47
|
+
- ⚙️ **Retrievers** — FAISS, Chroma, ElasticSearch
|
|
48
|
+
- 🧩 **Embeddings** — OpenAI, HuggingFace
|
|
49
|
+
- 🧠 **Rerankers** — MMR, CrossEncoder (extensible via plugin interface)
|
|
50
|
+
- 💾 **Caching, experiment tracking, and reproducibility** out of the box
|
|
51
|
+
- 🧰 **Clean modular structure** for easy integration in research and production setups
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 🚀 Quick Start
|
|
56
|
+
|
|
57
|
+
### 1️⃣ Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone https://github.com/andyolivers/ragmint.git
|
|
61
|
+
cd ragmint
|
|
62
|
+
pip install -e .
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
> The `-e` flag installs Ragmint in editable (development) mode.
|
|
66
|
+
> Requires **Python ≥ 3.9**.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
### 2️⃣ Run a RAG Optimization Experiment
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python ragmint/main.py --config configs/default.yaml --search bayesian
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Example `configs/default.yaml`:
|
|
77
|
+
```yaml
|
|
78
|
+
retriever: faiss
|
|
79
|
+
embedding_model: text-embedding-3-small
|
|
80
|
+
reranker:
|
|
81
|
+
mode: mmr
|
|
82
|
+
lambda_param: 0.5
|
|
83
|
+
optimization:
|
|
84
|
+
search_method: bayesian
|
|
85
|
+
n_trials: 20
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### 3️⃣ Manual Pipeline Usage
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from ragmint.core.pipeline import RAGPipeline
|
|
94
|
+
|
|
95
|
+
pipeline = RAGPipeline({
|
|
96
|
+
"embedding_model": "text-embedding-3-small",
|
|
97
|
+
"retriever": "faiss",
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
result = pipeline.run("What is retrieval-augmented generation?")
|
|
101
|
+
print(result)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 🧩 Folder Structure
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
ragmint/
|
|
110
|
+
├── core/
|
|
111
|
+
│ ├── pipeline.py # RAGPipeline implementation
|
|
112
|
+
│ ├── retriever.py # Retriever logic (FAISS, Chroma)
|
|
113
|
+
│ ├── reranker.py # MMR + CrossEncoder rerankers
|
|
114
|
+
│ └── embedding.py # Embedding backends
|
|
115
|
+
├── tuner.py # Grid, Random, Bayesian optimization (Optuna)
|
|
116
|
+
├── utils/ # Metrics, logging, caching helpers
|
|
117
|
+
├── configs/ # Default experiment configs
|
|
118
|
+
├── experiments/ # Saved experiment results
|
|
119
|
+
├── tests/ # Unit tests for all components
|
|
120
|
+
├── main.py # CLI entrypoint for tuning
|
|
121
|
+
└── pyproject.toml # Project dependencies & build metadata
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 🧪 Running Tests
|
|
127
|
+
|
|
128
|
+
To verify your setup:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pytest -v
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Or to test a specific component (e.g., reranker):
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pytest tests/test_reranker.py -v
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
All tests are designed for **Pytest** and run with lightweight mock data.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## ⚙️ Configuration via `pyproject.toml`
|
|
145
|
+
|
|
146
|
+
Your `pyproject.toml` automatically includes:
|
|
147
|
+
|
|
148
|
+
```toml
|
|
149
|
+
[project]
|
|
150
|
+
name = "ragmint"
|
|
151
|
+
version = "0.1.0"
|
|
152
|
+
dependencies = [
|
|
153
|
+
"numpy",
|
|
154
|
+
"optuna",
|
|
155
|
+
"scikit-learn",
|
|
156
|
+
"faiss-cpu",
|
|
157
|
+
"chromadb",
|
|
158
|
+
"pytest",
|
|
159
|
+
"openai",
|
|
160
|
+
"tqdm",
|
|
161
|
+
]
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 📊 Example Experiment Workflow
|
|
167
|
+
|
|
168
|
+
1. Define your retriever and reranker configuration in YAML
|
|
169
|
+
2. Launch an optimization search (Grid, Random, or Bayesian)
|
|
170
|
+
3. Ragmint evaluates combinations automatically and reports top results
|
|
171
|
+
4. Export best parameters for production pipelines
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 🧬 Architecture Overview
|
|
176
|
+
|
|
177
|
+
```mermaid
|
|
178
|
+
flowchart TD
|
|
179
|
+
A[Query] --> B[Embedder]
|
|
180
|
+
B --> C[Retriever]
|
|
181
|
+
C --> D[Reranker]
|
|
182
|
+
D --> E[Generator]
|
|
183
|
+
E --> F[Evaluation]
|
|
184
|
+
F --> G[Optuna Tuner]
|
|
185
|
+
G -->|Best Params| B
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 📘 Example Output
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
[INFO] Starting Bayesian optimization with Optuna
|
|
194
|
+
[INFO] Trial 7 finished: recall=0.83, latency=0.42s
|
|
195
|
+
[INFO] Best parameters: {'lambda_param': 0.6, 'retriever': 'faiss'}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 🧠 Why Ragmint?
|
|
201
|
+
|
|
202
|
+
- Built for **RAG researchers**, **AI engineers**, and **LLM ops**
|
|
203
|
+
- Works with **LangChain**, **LlamaIndex**, or standalone RAG setups
|
|
204
|
+
- Designed for **extensibility** — plug in your own models, retrievers, or metrics
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## ⚖️ License
|
|
209
|
+
|
|
210
|
+
Licensed under the **Apache License 2.0** — free for personal, research, and commercial use.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 👤 Author
|
|
215
|
+
|
|
216
|
+
**André Oliveira**
|
|
217
|
+
[andyolivers.com](https://andyolivers.com)
|
|
218
|
+
Data Scientist | AI Engineer
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
ragmint/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
ragmint/__main__.py,sha256=q7hBn56Z1xAckbs03i8ynsuOzJVUXmod2qHddX7gkpc,729
|
|
3
|
+
ragmint/tuner.py,sha256=sCUb-qGqk-lz4nUJboomwXFt3us7mYf3oJhwWV9Kzo4,4429
|
|
4
|
+
ragmint/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
ragmint/core/chunking.py,sha256=Dy9RYyapGSS6ik6Vg9lqbUPCFqSraU1JKpHbYUTkaFo,576
|
|
6
|
+
ragmint/core/embeddings.py,sha256=6wJjfZ5ukr8G5bJJ1evjIqj0_FMbs_gq4xC-sBBqNlA,566
|
|
7
|
+
ragmint/core/evaluation.py,sha256=LcR9AIsL9OyoENrUVSu0hhKzAItcBvEOy33V4i-0DtI,682
|
|
8
|
+
ragmint/core/pipeline.py,sha256=2qwGKuG0Du7gtIpieLFn71h_RcwBpjcV-h9PQz2ZOsc,1169
|
|
9
|
+
ragmint/core/reranker.py,sha256=B2-NDExqpd9jdXHkEHOXC0B_6-FMJm5vdi-_ZbxC3Os,2303
|
|
10
|
+
ragmint/core/retriever.py,sha256=jbpKy_fGdDq736y0es_utQuLqY9eiWNd71Q8JbU0Sko,1259
|
|
11
|
+
ragmint/experiments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
ragmint/optimization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
ragmint/optimization/search.py,sha256=uiLJeoO_jaLCQEw99L6uI1rnqHHx_rTY81WxfMmlALs,1623
|
|
14
|
+
ragmint/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
ragmint/tests/test_pipeline.py,sha256=MIMkEKelh-POlbXzbCc4ClMk8XCGzfuj569xXltziic,615
|
|
16
|
+
ragmint/tests/test_retriever.py,sha256=Ag0uGW8-iMzKA4nJNnsjuzlQHa79sN-T-K1g1cdin-A,421
|
|
17
|
+
ragmint/tests/test_search.py,sha256=FcC-DEnw9veAEyMnFoRw9DAwzqJC9F6-r63Nqo2nO58,598
|
|
18
|
+
ragmint/tests/test_tuner.py,sha256=VFZ23og0dOypBpr3TxkRmSngilkNgyboZc6u9qB0pME,1101
|
|
19
|
+
ragmint/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
ragmint/utils/caching.py,sha256=LPE2JorOQ90BgVf6NUiS0-bdt-FGpNxDy7FnuwEHzy0,1060
|
|
21
|
+
ragmint/utils/data_loader.py,sha256=Q3pBO77XZ1rl4fuMn3TK7x3mSM2eLdV_OJTyy_eL3Ys,988
|
|
22
|
+
ragmint/utils/logger.py,sha256=X7hTNb3st3fUeQIzSghuoV5B8FWXzm_O3DRkSfJvhmI,1033
|
|
23
|
+
ragmint/utils/metrics.py,sha256=DR8mrdumHtQerK0VrugwYKIG1oNptEcsFqodXq3i2kY,717
|
|
24
|
+
ragmint-0.1.0.dist-info/licenses/LICENSE,sha256=ahkhYfFLI8tGrdxdO2_GaT6OJW2eNwyFT3kYi85QQhc,692
|
|
25
|
+
ragmint-0.1.0.dist-info/METADATA,sha256=BgMj5BxH2C2_5GweYpClkopepUBCVen5tWAFcOby8o8,5643
|
|
26
|
+
ragmint-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
27
|
+
ragmint-0.1.0.dist-info/top_level.txt,sha256=K2ulzMHuvFm6xayvvJdGABeRJAvKDBn6M3EI-3SbYLw,8
|
|
28
|
+
ragmint-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
Copyright 2025 André Oliveira
|
|
8
|
+
|
|
9
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
10
|
+
you may not use this file except in compliance with the License.
|
|
11
|
+
You may obtain a copy of the License at
|
|
12
|
+
|
|
13
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
14
|
+
|
|
15
|
+
Unless required by applicable law or agreed to in writing, software
|
|
16
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
17
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
18
|
+
See the License for the specific language governing permissions and
|
|
19
|
+
limitations under the License.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ragmint
|