Skip to content

Commit cfcd6b6

Browse files
committed
fix: Lint Errors
1 parent b0d08e3 commit cfcd6b6

11 files changed

Lines changed: 333 additions & 95 deletions

File tree

src/minirag/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,23 @@
1414
from .client import RAG, RAGAnswer
1515
from .rag_pipeline import ingest, query, RAGResponse
1616
from .providers import make_llm_provider, make_embedding_provider
17+
from .options import (
18+
ChunkingConfig,
19+
DocumentConfig,
20+
QueryConfig,
21+
RAGConfig,
22+
SearchConfig,
23+
)
1724

1825
__all__ = [
1926
"__version__",
2027
"RAG",
2128
"RAGAnswer",
29+
"RAGConfig",
30+
"ChunkingConfig",
31+
"SearchConfig",
32+
"DocumentConfig",
33+
"QueryConfig",
2234
"ingest",
2335
"query",
2436
"RAGResponse",

src/minirag/cleaning.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def is_likely_header_footer(line: str) -> bool:
3838

3939
def deduplicate_sentences(text: str) -> str:
4040
"""Remove consecutive duplicate sentences (and near-duplicates by line)."""
41-
lines = [normalize_whitespace(l) for l in text.splitlines() if l.strip()]
41+
lines = [normalize_whitespace(line) for line in text.splitlines() if line.strip()]
4242
seen = set()
4343
out = []
4444
for line in lines:

src/minirag/client.py

Lines changed: 105 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,36 @@
22
33
This wraps the full RAG pipeline behind a simple interface:
44
5-
from minirag import RAG
5+
from minirag import RAG, RAGConfig, ChunkingConfig, SearchConfig
66
7-
rag = RAG(llm_model="gpt-4o-mini", embedding_model="text-embedding-3-small")
8-
rag.ingest(["./docs", "README.md"])
7+
rag = RAG(
8+
llm_model="gpt-4o-mini",
9+
embedding_provider="openai",
10+
embedding_model="text-embedding-3-small",
11+
config=RAGConfig(
12+
chunking=ChunkingConfig(strategy="recursive", chunk_size=512),
13+
search=SearchConfig(retriever="multi_query", top_k_retrieve=20),
14+
),
15+
)
16+
rag.ingest(["./docs", "./policies.pdf", "README.md"])
917
answer = rag.query("What is our leave policy?")
1018
print(answer.text)
1119
"""
12-
from dataclasses import dataclass
20+
from dataclasses import dataclass, replace
1321
from pathlib import Path
1422
from typing import Iterable
1523

16-
from .config import DATA_DIR, LLM_MODEL, EMBEDDING_MODEL
24+
from .config import CHROMA_PERSIST_DIR, DATA_DIR, EMBEDDING_MODEL, LLM_MODEL
25+
from .options import (
26+
ChunkingConfig,
27+
DocumentConfig,
28+
QueryConfig,
29+
RAGConfig,
30+
SearchConfig,
31+
)
1732
from .providers import make_llm_provider, make_embedding_provider
1833
from .rag_pipeline import ingest as _ingest, query as _query, RAGResponse
34+
from .vector_store import set_persist_dir
1935

2036

2137
@dataclass
@@ -27,7 +43,7 @@ class RAGAnswer:
2743

2844

2945
class RAG:
30-
"""User-facing RAG client."""
46+
"""User-facing RAG client with configurable chunking, retrieval, and embeddings."""
3147

3248
def __init__(
3349
self,
@@ -37,6 +53,18 @@ def __init__(
3753
embedding_provider: str = "openai",
3854
embedding_model: str | None = None,
3955
data_dir: str | Path | None = None,
56+
chroma_dir: str | Path | None = None,
57+
config: RAGConfig | None = None,
58+
# Shorthand overrides (merged into ``config`` when provided)
59+
chunk_strategy: str | None = None,
60+
chunk_size: int | None = None,
61+
chunk_overlap: int | None = None,
62+
retriever: str | None = None,
63+
top_k_retrieve: int | None = None,
64+
top_k_rerank: int | None = None,
65+
multi_query_n: int | None = None,
66+
rerank_enabled: bool | None = None,
67+
document_extensions: tuple[str, ...] | None = None,
4068
# Provider kwargs (optional)
4169
openai_api_key: str | None = None,
4270
azure_endpoint: str | None = None,
@@ -52,7 +80,31 @@ def __init__(
5280
self.embedding_model = embedding_model or EMBEDDING_MODEL
5381
self.data_dir = Path(data_dir) if data_dir else Path(DATA_DIR)
5482

55-
# Providers
83+
if chroma_dir:
84+
set_persist_dir(chroma_dir)
85+
elif CHROMA_PERSIST_DIR:
86+
set_persist_dir(CHROMA_PERSIST_DIR)
87+
88+
self.config = config or RAGConfig()
89+
if chunk_strategy is not None:
90+
self.config.chunking = replace(self.config.chunking, strategy=chunk_strategy) # type: ignore[arg-type]
91+
if chunk_size is not None:
92+
self.config.chunking = replace(self.config.chunking, chunk_size=chunk_size)
93+
if chunk_overlap is not None:
94+
self.config.chunking = replace(self.config.chunking, chunk_overlap=chunk_overlap)
95+
if retriever is not None:
96+
self.config.search = replace(self.config.search, retriever=retriever) # type: ignore[arg-type]
97+
if top_k_retrieve is not None:
98+
self.config.search = replace(self.config.search, top_k_retrieve=top_k_retrieve)
99+
if top_k_rerank is not None:
100+
self.config.search = replace(self.config.search, top_k_rerank=top_k_rerank)
101+
if multi_query_n is not None:
102+
self.config.search = replace(self.config.search, multi_query_n=multi_query_n)
103+
if rerank_enabled is not None:
104+
self.config.search = replace(self.config.search, rerank_enabled=rerank_enabled)
105+
if document_extensions is not None:
106+
self.config.documents = replace(self.config.documents, extensions=document_extensions)
107+
56108
self.llm = make_llm_provider(
57109
llm_provider, # type: ignore[arg-type]
58110
api_key=openai_api_key or anthropic_api_key or gemini_api_key or azure_api_key,
@@ -69,39 +121,62 @@ def __init__(
69121
)
70122

71123
def ingest(self, paths: Iterable[str | Path], *, reindex: bool = False) -> int:
72-
"""Ingest the given files/directories into the vector store."""
73-
self.data_dir.mkdir(parents=True, exist_ok=True)
74-
75-
for p in paths:
76-
p = Path(p)
77-
if p.is_file():
78-
target = self.data_dir / p.name
79-
if str(p.resolve()) != str(target.resolve()):
80-
target.write_bytes(p.read_bytes())
81-
elif p.is_dir():
82-
for f in p.rglob("*"):
83-
if f.is_file():
84-
rel = f.relative_to(p)
85-
target = self.data_dir / rel
86-
target.parent.mkdir(parents=True, exist_ok=True)
87-
if str(f.resolve()) != str(target.resolve()):
88-
target.write_bytes(f.read_bytes())
124+
"""Ingest one or more files/directories into the vector store."""
125+
path_list = [Path(p) for p in paths]
126+
doc_cfg: DocumentConfig = self.config.documents
127+
chunk_cfg: ChunkingConfig = self.config.chunking
128+
129+
if doc_cfg.copy_to_data_dir:
130+
self.data_dir.mkdir(parents=True, exist_ok=True)
131+
for p in path_list:
132+
if p.is_file():
133+
target = self.data_dir / p.name
134+
if str(p.resolve()) != str(target.resolve()):
135+
target.write_bytes(p.read_bytes())
136+
elif p.is_dir():
137+
for f in p.rglob("*"):
138+
if f.is_file():
139+
rel = f.relative_to(p)
140+
target = self.data_dir / rel
141+
target.parent.mkdir(parents=True, exist_ok=True)
142+
if str(f.resolve()) != str(target.resolve()):
143+
target.write_bytes(f.read_bytes())
144+
return _ingest(
145+
data_path=self.data_dir,
146+
clean=doc_cfg.clean,
147+
chunk_strategy=chunk_cfg.strategy,
148+
chunk_size=chunk_cfg.chunk_size,
149+
chunk_overlap=chunk_cfg.chunk_overlap,
150+
extensions=doc_cfg.extensions,
151+
reindex=reindex,
152+
embedding_model=self.embedding_model,
153+
embedder=self.embedder,
154+
)
89155

90156
return _ingest(
91-
data_path=self.data_dir,
92-
clean=True,
157+
paths=path_list,
158+
clean=doc_cfg.clean,
159+
chunk_strategy=chunk_cfg.strategy,
160+
chunk_size=chunk_cfg.chunk_size,
161+
chunk_overlap=chunk_cfg.chunk_overlap,
162+
extensions=doc_cfg.extensions,
93163
reindex=reindex,
94164
embedding_model=self.embedding_model,
95165
embedder=self.embedder,
96166
)
97167

98-
def query(self, question: str, *, multi_query: bool = True) -> RAGAnswer:
168+
def query(
169+
self,
170+
question: str,
171+
*,
172+
search: SearchConfig | None = None,
173+
query_config: QueryConfig | None = None,
174+
) -> RAGAnswer:
99175
"""Run a full RAG query and return a friendly answer object."""
100176
resp: RAGResponse = _query(
101177
question,
102-
multi_query=multi_query,
103-
use_guardrails=True,
104-
use_retry=True,
178+
search=search or self.config.search,
179+
query_config=query_config or self.config.query,
105180
llm_model=self.llm_model,
106181
embedding_model=self.embedding_model,
107182
llm=self.llm,
@@ -113,4 +188,3 @@ def query(self, question: str, *, multi_query: bool = True) -> RAGAnswer:
113188
evaluation=resp.evaluation,
114189
retried=resp.retried,
115190
)
116-

src/minirag/evaluation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""RAG evaluation: faithfulness, relevance; retry & self-correction loop."""
2-
from .config import LLM_MODEL, MAX_RETRIES
2+
from .config import LLM_MODEL
33
from .providers import LLMProvider, make_llm_provider
44

55

src/minirag/options.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""User-configurable options for RAG ingest, retrieval, and generation."""
2+
from __future__ import annotations
3+
4+
from dataclasses import dataclass, field
5+
from typing import Literal
6+
7+
from .config import (
8+
CHUNK_OVERLAP,
9+
CHUNK_SIZE,
10+
GUARDRAILS_ENABLED,
11+
MAX_RETRIES,
12+
MULTI_QUERY_N,
13+
RERANK_ENABLED,
14+
TOP_K_RERANK,
15+
TOP_K_RETRIEVE,
16+
)
17+
18+
ChunkStrategy = Literal["recursive", "structure_aware", "semantic"]
19+
RetrieverStrategy = Literal["vector", "multi_query"]
20+
21+
22+
@dataclass
23+
class ChunkingConfig:
24+
"""How documents are split before embedding."""
25+
26+
strategy: ChunkStrategy = "recursive"
27+
chunk_size: int = CHUNK_SIZE
28+
chunk_overlap: int = CHUNK_OVERLAP
29+
30+
31+
@dataclass
32+
class SearchConfig:
33+
"""Retrieval and reranking behaviour at query time."""
34+
35+
retriever: RetrieverStrategy = "multi_query"
36+
top_k_retrieve: int = TOP_K_RETRIEVE
37+
top_k_rerank: int = TOP_K_RERANK
38+
multi_query_n: int = MULTI_QUERY_N
39+
rerank_enabled: bool = RERANK_ENABLED
40+
41+
42+
@dataclass
43+
class DocumentConfig:
44+
"""Which files to load and how to preprocess them."""
45+
46+
extensions: tuple[str, ...] = (".txt", ".md", ".pdf", ".docx")
47+
clean: bool = True
48+
copy_to_data_dir: bool = True
49+
50+
51+
@dataclass
52+
class QueryConfig:
53+
"""Guardrails, evaluation, and retry settings."""
54+
55+
use_guardrails: bool = GUARDRAILS_ENABLED
56+
use_retry: bool = True
57+
max_retries: int = MAX_RETRIES
58+
eval_threshold: float = 0.6
59+
60+
61+
@dataclass
62+
class RAGConfig:
63+
"""All tunable pipeline options (pass to ``RAG(config=...)``)."""
64+
65+
chunking: ChunkingConfig = field(default_factory=ChunkingConfig)
66+
search: SearchConfig = field(default_factory=SearchConfig)
67+
documents: DocumentConfig = field(default_factory=DocumentConfig)
68+
query: QueryConfig = field(default_factory=QueryConfig)

src/minirag/providers/ollama_provider.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import json
43
from typing import Any
54

65

src/minirag/query_rewriting.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Query understanding & rewriting: expand/rewrite user query for better retrieval."""
2-
from .config import LLM_MODEL, MULTI_QUERY_N
2+
from .config import LLM_MODEL
33
from .providers import LLMProvider, make_llm_provider
44

55

@@ -27,7 +27,7 @@ def rewrite_for_retrieval(
2727
temperature=0.3,
2828
max_tokens=200,
2929
)
30-
lines = [l.strip() for l in text.splitlines() if l.strip()]
30+
lines = [line.strip() for line in text.splitlines() if line.strip()]
3131
cleaned = []
3232
for line in lines[:n_queries]:
3333
for sep in (". ", ") ", "- ", " ", ""):

0 commit comments

Comments
 (0)