22
33This 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
1321from pathlib import Path
1422from 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+ )
1732from .providers import make_llm_provider , make_embedding_provider
1833from .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
2945class 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-
0 commit comments