ezrag-engine 0.1.0__tar.gz

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.
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: ezrag-engine
3
+ Version: 0.1.0
4
+ Summary: A 3-line, zero-boilerplate, cloud-ready RAG pipeline for Python and Hugging Face Spaces.
5
+ Author-email: Oguru Vinay Reddy <Vinayreddy.ace@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://huggingface.co/
8
+ Project-URL: Repository, https://github.com/<your-github-username>/ezrag
9
+ Project-URL: Documentation, https://github.com/<your-github-username>/ezrag#readme
10
+ Keywords: rag,llm,retrieval-augmented-generation,huggingface,embeddings,vector-search
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: sentence-transformers>=3.0.0
24
+ Requires-Dist: huggingface_hub>=0.23.0
25
+ Requires-Dist: numpy>=1.26.0
26
+ Requires-Dist: requests>=2.31.0
27
+ Requires-Dist: litellm>=1.40.0
28
+
29
+ <div align="center">
30
+
31
+ # **ezrag** — 3-Line, Zero-Boilerplate, Cloud-Ready RAG
32
+
33
+ **A production-grade Retrieval-Augmented Generation framework for Python and Hugging Face Spaces.**
34
+
35
+ ![PyPI](https://img.shields.io/pypi/v/ezrag.svg)
36
+ ![Python](https://img.shields.io/pypi/pyversions/ezrag.svg)
37
+ ![PyPI - License](https://img.shields.io/pypi/l/ezrag.svg)
38
+
39
+ </div>
40
+
41
+ ---
42
+
43
+ ## Why ezrag?
44
+
45
+ Most RAG stacks take hours to wire up: embedding models, vector stores, chunkers,
46
+ prompts, and LLM clients. ezrag hides all of that behind one class, so you go from
47
+ an empty folder to a **grounded, citation-aware answer** in exactly three lines of
48
+ code.
49
+
50
+ **What you get out of the box:**
51
+
52
+ - **Chunking** — sentence-aware splitting with a sliding overlap, zero configuration
53
+ - **Embeddings** — a lightweight `sentence-transformers` model, loaded lazily
54
+ - **Vector DB** — a self-contained in-memory store with fast NumPy cosine search
55
+ - **Context bundling** — evidence is ranked, tagged `[1] [2] [3] ...` and injected into the prompt
56
+ - **LLM execution** — unified calls through the Hugging Face Serverless API or LiteLLM
57
+ - **Weak-evidence guard** — answers truthfully when the documents do not cover the question
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ pip install ezrag
63
+ ```
64
+
65
+ ## Quick Start
66
+
67
+ That is the entire experience — import, load, ask:
68
+
69
+ ```python
70
+ from ezrag import EZRAG # 1. import
71
+
72
+ rag = EZRAG().load(SOURCE) # 2. initialise + load your source
73
+ print(rag.ask("Your question here")) # 3. ask — grounded, cited answer
74
+ ```
75
+
76
+ No environment variables to set up, no vector database to spin up, no prompting
77
+ prompts to craft. One object call chain produces a grounded, cited answer.
78
+
79
+ ## Configuration
80
+
81
+ | Parameter | Default | Purpose |
82
+ | --- | --- | --- |
83
+ | `llm` | `mistralai/Mistral-7B-Instruct-v0.2` | LLM identifier or endpoint |
84
+ | `embedding_model` | `all-MiniLM-L6-v2` | sentence-transformers model |
85
+ | `provider` | `"hf"` | `"hf"` (HF Serverless) or `"litellm"` |
86
+ | `hf_token` | env `HF_TOKEN` | token for protected / gated models |
87
+ | `top_k` | `4` | number of evidence chunks retrieved |
88
+ | `min_score` | `0.10` | retrieval similarity floor |
89
+ | `chunk_size` | `900` | target chunk length (characters) |
90
+ | `chunk_overlap` | `120` | sliding context overlap |
91
+
92
+ ## Providers
93
+
94
+ - **Hugging Face Serverless** (default) — no custom API wiring. Set the `HF_TOKEN`
95
+ environment variable only when the model is gated.
96
+ - **LiteLLM** — point `llm` at any OpenAI-compatible endpoint, for example
97
+ `openai/gpt-4o` or `azure/gpt-4o`.
98
+
99
+ ## How It Works
100
+
101
+ ```
102
+ source ──▶ chunk ──▶ embed ──▶ in-memory index
103
+
104
+ question ──▶ embed ──▶ top-k search ──┘
105
+
106
+ context bundle ──▶ LLM ──▶ cited answer
107
+ ```
108
+
109
+ ## Hugging Face Spaces
110
+
111
+ ezrag runs out of the box in a Hugging Face Space — pip pulls in every runtime
112
+ dependency automatically. Store a `HF_TOKEN` Space secret if your chosen model is
113
+ gated.
114
+
115
+ ## License
116
+
117
+ MIT © Oguru Vinay Reddy
@@ -0,0 +1,89 @@
1
+ <div align="center">
2
+
3
+ # **ezrag** — 3-Line, Zero-Boilerplate, Cloud-Ready RAG
4
+
5
+ **A production-grade Retrieval-Augmented Generation framework for Python and Hugging Face Spaces.**
6
+
7
+ ![PyPI](https://img.shields.io/pypi/v/ezrag.svg)
8
+ ![Python](https://img.shields.io/pypi/pyversions/ezrag.svg)
9
+ ![PyPI - License](https://img.shields.io/pypi/l/ezrag.svg)
10
+
11
+ </div>
12
+
13
+ ---
14
+
15
+ ## Why ezrag?
16
+
17
+ Most RAG stacks take hours to wire up: embedding models, vector stores, chunkers,
18
+ prompts, and LLM clients. ezrag hides all of that behind one class, so you go from
19
+ an empty folder to a **grounded, citation-aware answer** in exactly three lines of
20
+ code.
21
+
22
+ **What you get out of the box:**
23
+
24
+ - **Chunking** — sentence-aware splitting with a sliding overlap, zero configuration
25
+ - **Embeddings** — a lightweight `sentence-transformers` model, loaded lazily
26
+ - **Vector DB** — a self-contained in-memory store with fast NumPy cosine search
27
+ - **Context bundling** — evidence is ranked, tagged `[1] [2] [3] ...` and injected into the prompt
28
+ - **LLM execution** — unified calls through the Hugging Face Serverless API or LiteLLM
29
+ - **Weak-evidence guard** — answers truthfully when the documents do not cover the question
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ pip install ezrag
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ That is the entire experience — import, load, ask:
40
+
41
+ ```python
42
+ from ezrag import EZRAG # 1. import
43
+
44
+ rag = EZRAG().load(SOURCE) # 2. initialise + load your source
45
+ print(rag.ask("Your question here")) # 3. ask — grounded, cited answer
46
+ ```
47
+
48
+ No environment variables to set up, no vector database to spin up, no prompting
49
+ prompts to craft. One object call chain produces a grounded, cited answer.
50
+
51
+ ## Configuration
52
+
53
+ | Parameter | Default | Purpose |
54
+ | --- | --- | --- |
55
+ | `llm` | `mistralai/Mistral-7B-Instruct-v0.2` | LLM identifier or endpoint |
56
+ | `embedding_model` | `all-MiniLM-L6-v2` | sentence-transformers model |
57
+ | `provider` | `"hf"` | `"hf"` (HF Serverless) or `"litellm"` |
58
+ | `hf_token` | env `HF_TOKEN` | token for protected / gated models |
59
+ | `top_k` | `4` | number of evidence chunks retrieved |
60
+ | `min_score` | `0.10` | retrieval similarity floor |
61
+ | `chunk_size` | `900` | target chunk length (characters) |
62
+ | `chunk_overlap` | `120` | sliding context overlap |
63
+
64
+ ## Providers
65
+
66
+ - **Hugging Face Serverless** (default) — no custom API wiring. Set the `HF_TOKEN`
67
+ environment variable only when the model is gated.
68
+ - **LiteLLM** — point `llm` at any OpenAI-compatible endpoint, for example
69
+ `openai/gpt-4o` or `azure/gpt-4o`.
70
+
71
+ ## How It Works
72
+
73
+ ```
74
+ source ──▶ chunk ──▶ embed ──▶ in-memory index
75
+
76
+ question ──▶ embed ──▶ top-k search ──┘
77
+
78
+ context bundle ──▶ LLM ──▶ cited answer
79
+ ```
80
+
81
+ ## Hugging Face Spaces
82
+
83
+ ezrag runs out of the box in a Hugging Face Space — pip pulls in every runtime
84
+ dependency automatically. Store a `HF_TOKEN` Space secret if your chosen model is
85
+ gated.
86
+
87
+ ## License
88
+
89
+ MIT © Oguru Vinay Reddy
@@ -0,0 +1,7 @@
1
+ """ezrag — a 3-line RAG micro-framework for Hugging Face Spaces."""
2
+
3
+ from .engine import EZRAG, Chunk
4
+
5
+ __all__ = ["EZRAG", "Chunk"]
6
+ __version__ = "0.1.0"
7
+
@@ -0,0 +1,349 @@
1
+ """ezrag.engine — hidden orchestration of the EZRAG pipeline.
2
+
3
+ Everything heavy lives here and stays invisible to developers:
4
+ text chunking, embedding generation, in-memory similarity search,
5
+ context bundling and LLM query execution. Consumers only touch the
6
+ public surface exposed by ``ezrag.EZRAG``.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ import os as _os
13
+ import re
14
+ from dataclasses import dataclass, field
15
+ from pathlib import Path as _Path
16
+ from typing import Any, Dict, List, Optional, Tuple, Union
17
+
18
+ try:
19
+ import numpy as np
20
+ except ImportError: # pragma: no cover
21
+ np = None # type: ignore
22
+
23
+ try:
24
+ from huggingface_hub import InferenceClient
25
+ except ImportError: # pragma: no cover
26
+ InferenceClient = None # type: ignore
27
+
28
+ try:
29
+ import litellm
30
+ except ImportError: # pragma: no cover
31
+ litellm = None # type: ignore
32
+
33
+ logger = logging.getLogger("ezrag")
34
+
35
+ __all__ = ["EZRAG", "Chunk"]
36
+
37
+ DEFAULT_EMBEDDING_MODEL = "all-MiniLM-L6-v2"
38
+ DEFAULT_LLM = _os.getenv("EZRAG_LLM", "mistralai/Mistral-7B-Instruct-v0.2")
39
+
40
+ _SENTENCE_BOUNDARY = re.compile(r"(?<=[.!?。!?])\s+")
41
+ _WHITESPACE = re.compile(r"\s+")
42
+
43
+ _SYSTEM_PROMPT = (
44
+ "You are a precise, grounded assistant. Answer ONLY from the provided "
45
+ "CONTEXT delimited by <context></context>. Cite every fact with source "
46
+ "tags like [1]. If the CONTEXT is empty or clearly cannot answer the "
47
+ "question, reply exactly with 'I do not have enough information to "
48
+ "answer.' Never invent facts."
49
+ )
50
+
51
+
52
+ def _normalize_text(text: str) -> str:
53
+ return _WHITESPACE.sub(" ", text).strip()
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class Chunk:
58
+ """A retrievable unit of knowledge plus provenance and relevance score."""
59
+
60
+ text: str
61
+ score: float = 0.0
62
+ metadata: Dict[str, Any] = field(default_factory=dict, compare=False)
63
+
64
+
65
+ class EZRAG:
66
+ """Lazy, 3-line RAG: ``EZRAG().load(source).ask(question)``.
67
+
68
+ Chunking, embeddings, in-memory retrieval and LLM calls are handled
69
+ transparently. Nothing touches the network until data is loaded or a
70
+ question is asked.
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ llm: Optional[str] = None,
76
+ embedding_model: str = DEFAULT_EMBEDDING_MODEL,
77
+ provider: str = "hf",
78
+ hf_token: Optional[str] = None,
79
+ top_k: int = 4,
80
+ min_score: float = 0.10,
81
+ chunk_size: int = 900,
82
+ chunk_overlap: int = 120,
83
+ max_new_tokens: int = 400,
84
+ temperature: float = 0.3,
85
+ ) -> None:
86
+ self.llm = llm or _os.getenv("EZRAG_LLM") or DEFAULT_LLM
87
+ self.embedding_model = embedding_model
88
+ self.provider = provider
89
+ self.hf_token = hf_token or _os.getenv("HF_TOKEN")
90
+ self.top_k = top_k
91
+ self.min_score = min_score
92
+ self.chunk_size = chunk_size
93
+ self.chunk_overlap = chunk_overlap
94
+ self.max_new_tokens = max_new_tokens
95
+ self.temperature = temperature
96
+ self._encoder = None
97
+ self._vectors: List[np.ndarray] = []
98
+ self._chunks: List[Chunk] = []
99
+ self._cache: Dict[str, np.ndarray] = {}
100
+
101
+ # ------------------------------------------------------------------ #
102
+ # Public API
103
+ # ------------------------------------------------------------------ #
104
+
105
+ def load(self, source: Any) -> "EZRAG":
106
+ """Index a raw text, a file path, or a ``{name: text}`` mapping."""
107
+ if isinstance(source, dict):
108
+ for name, payload in source.items():
109
+ self._read(payload, {"source": str(name)})
110
+ elif isinstance(source, (list, tuple)):
111
+ for item in source:
112
+ self.load(item)
113
+ else:
114
+ self._read(source, {})
115
+ return self
116
+
117
+ def load_files(self, *paths: Union[str, _Path]) -> "EZRAG":
118
+ """Index one or more ``.txt``/``.md`` files from disk."""
119
+ for path in paths:
120
+ self.load(path)
121
+ return self
122
+
123
+ def search(self, question: str, top_k: Optional[int] = None) -> List[Chunk]:
124
+ """Return the most relevant chunks for a question, best first."""
125
+ if not self._vectors:
126
+ raise RuntimeError("no knowledge loaded yet - call load() first")
127
+ query_vec = self._encode([Chunk(text=question)])[0]
128
+ query_vec = query_vec / (float(np.linalg.norm(query_vec)) + 1e-9)
129
+ k = top_k or self.top_k
130
+ candidates: List[Chunk] = []
131
+ offset = 0
132
+ for block in self._vectors:
133
+ scores = block @ query_vec
134
+ for local in np.argsort(scores)[::-1][:k]:
135
+ score = float(scores[local])
136
+ if score < self.min_score:
137
+ continue
138
+ chunk = self._chunks[offset + int(local)]
139
+ candidates.append(
140
+ Chunk(text=chunk.text, score=score, metadata=dict(chunk.metadata))
141
+ )
142
+ offset += block.shape[0]
143
+ candidates.sort(key=lambda c: c.score, reverse=True)
144
+ return candidates[:k]
145
+
146
+ def ask(self, question: str, **overrides: Any) -> str:
147
+ """Retrieve evidence and answer using the configured LLM."""
148
+ top_k = overrides.pop("top_k", self.top_k)
149
+ hits = self.search(question, top_k=top_k)
150
+ if not hits:
151
+ return "I do not have enough information in the loaded documents to answer this."
152
+ context = self._bundle(hits)
153
+ prompt = self._build_prompt(question, context)
154
+ logger.info("asking %s with %d retrieved chunks", self.llm, len(hits))
155
+ return self._call_llm(prompt, **overrides)
156
+
157
+ def query(self, question: str, **overrides: Any) -> str:
158
+ """Alias for :meth:`ask`."""
159
+ return self.ask(question, **overrides)
160
+
161
+ def stats(self) -> Dict[str, Any]:
162
+ """Diagnostic summary of the indexed knowledge base."""
163
+ return {
164
+ "chunks": len(self._chunks),
165
+ "vectors": sum(int(b.shape[0]) for b in self._vectors),
166
+ "embedding_model": self.embedding_model,
167
+ "llm": self.llm,
168
+ "provider": self.provider,
169
+ }
170
+
171
+ def __len__(self) -> int:
172
+ return len(self._chunks)
173
+
174
+ def __repr__(self) -> str:
175
+ return (
176
+ f"EZRAG(embedding={self.embedding_model!r}, llm={self.llm!r}, "
177
+ f"provider={self.provider!r}, chunks={len(self._chunks)})"
178
+ )
179
+
180
+ # ------------------------------------------------------------------ #
181
+ # Ingestion
182
+ # ------------------------------------------------------------------ #
183
+
184
+ @staticmethod
185
+ def _is_path(value: str) -> bool:
186
+ try:
187
+ return _Path(value).is_file()
188
+ except (OSError, ValueError):
189
+ return False
190
+
191
+ def _read(self, source: Any, metadata: Dict[str, Any]) -> List[Chunk]:
192
+ try:
193
+ if hasattr(source, "read"):
194
+ payload = source.read()
195
+ elif isinstance(source, _Path):
196
+ payload = source.read_text(encoding="utf-8")
197
+ elif isinstance(source, str) and self._is_path(source):
198
+ payload = _Path(source).read_text(encoding="utf-8")
199
+ elif isinstance(source, str):
200
+ payload = source
201
+ else:
202
+ raise TypeError(f"unsupported source type: {type(source).__name__}")
203
+ except (UnicodeDecodeError, PermissionError) as exc:
204
+ raise ValueError(f"cannot read source {source!r}: {exc}") from exc
205
+ return self._ingest(str(payload), metadata)
206
+
207
+ def _ingest(self, payload: str, metadata: Dict[str, Any]) -> List[Chunk]:
208
+ chunks = self._chunk_text(payload)
209
+ logger.info("ingesting %d chunks (%d chars)", len(chunks), len(payload))
210
+ for start in range(0, len(chunks), 128):
211
+ batch = chunks[start:start + 128]
212
+ vectors = self._encode(batch)
213
+ self._vectors.append(vectors)
214
+ for i, chunk in enumerate(batch):
215
+ chunk_meta = dict(metadata)
216
+ chunk_meta["chunk_index"] = len(self._chunks) + i
217
+ self._chunks.append(
218
+ Chunk(text=chunk.text, metadata=chunk_meta)
219
+ )
220
+ return chunks
221
+
222
+ def _chunk_text(self, text: str) -> List[Chunk]:
223
+ """Split text into sentence-aware chunks with a sliding overlap."""
224
+ text = _normalize_text(text)
225
+ if not text:
226
+ return []
227
+ if len(text) <= self.chunk_size:
228
+ return [Chunk(text=text)]
229
+ chunks: List[Chunk] = []
230
+ current = ""
231
+ for sentence in _SENTENCE_BOUNDARY.split(text):
232
+ candidate = f"{current} {sentence}".strip() if current else sentence
233
+ if len(candidate) > self.chunk_size and current:
234
+ chunks.append(Chunk(text=current))
235
+ overlap = (
236
+ current[-(self.chunk_overlap or 0):] if self.chunk_overlap else ""
237
+ )
238
+ current = f"{overlap} {sentence}".strip()
239
+ else:
240
+ current = candidate
241
+ if current:
242
+ chunks.append(Chunk(text=current))
243
+ return chunks
244
+
245
+ # ------------------------------------------------------------------ #
246
+ # Embeddings
247
+ # ------------------------------------------------------------------ #
248
+
249
+ def _build_encoder(self) -> None:
250
+ if np is None:
251
+ raise RuntimeError("missing dependency: pip install numpy")
252
+ try:
253
+ from sentence_transformers import SentenceTransformer
254
+ except ImportError as exc:
255
+ raise RuntimeError(
256
+ "missing dependency: pip install sentence-transformers"
257
+ ) from exc
258
+ logger.info("loading embedding model %s", self.embedding_model)
259
+ self._encoder = SentenceTransformer(self.embedding_model)
260
+
261
+ def _encode(self, chunks: List[Chunk]) -> np.ndarray:
262
+ """Encode chunk texts into normalised float32 vectors (batched)."""
263
+ texts = [c.text for c in chunks]
264
+ vectors: Dict[int, np.ndarray] = {}
265
+ remaining: List[Tuple[int, str]] = []
266
+ for i, text in enumerate(texts):
267
+ hit = self._cache.get(text)
268
+ if hit is None:
269
+ remaining.append((i, text))
270
+ else:
271
+ vectors[i] = hit
272
+ if remaining:
273
+ if self._encoder is None:
274
+ self._build_encoder()
275
+ fresh = self._encoder.encode(
276
+ [text for _, text in remaining],
277
+ normalize_embeddings=True,
278
+ show_progress_bar=False,
279
+ )
280
+ for (i, text), vec in zip(remaining, np.asarray(fresh, dtype=np.float32)):
281
+ vectors[i] = vec
282
+ if len(self._cache) < 20_000:
283
+ self._cache[text] = vec
284
+ return np.stack([vectors[i] for i in range(len(texts))])
285
+
286
+ # ------------------------------------------------------------------ #
287
+ # Context bundling + LLM execution
288
+ # ------------------------------------------------------------------ #
289
+
290
+ @staticmethod
291
+ def _bundle(hits: List[Chunk]) -> str:
292
+ lines = ["<context>"]
293
+ for i, hit in enumerate(hits, start=1):
294
+ src = hit.metadata.get("source", "document")
295
+ idx = hit.metadata.get("chunk_index", i)
296
+ lines.append(f"[{i}] (source: {src}, chunk #{idx}) {hit.text}")
297
+ lines.append(f"</context>\nBest match score: {hits[0].score:.2f}")
298
+ return "\n".join(lines)
299
+
300
+ @staticmethod
301
+ def _build_prompt(question: str, context: str) -> str:
302
+ return (
303
+ f"{_SYSTEM_PROMPT}\n\n"
304
+ f"QUESTION: {question}\n\n"
305
+ f"{context}\n\n"
306
+ "ANSWER:"
307
+ )
308
+
309
+ def _call_llm(self, prompt: str, **overrides: Any) -> str:
310
+ model = overrides.pop("llm", None) or self.llm
311
+ provider = overrides.pop("provider", self.provider)
312
+ max_new_tokens = overrides.pop("max_new_tokens", self.max_new_tokens)
313
+ temperature = overrides.pop("temperature", self.temperature)
314
+
315
+ if provider == "hf":
316
+ if InferenceClient is None:
317
+ raise RuntimeError(
318
+ "the 'hf' provider requires huggingface_hub: pip install huggingface_hub"
319
+ )
320
+ client = InferenceClient(model=model, token=self.hf_token)
321
+ output = client.text_generation(
322
+ prompt,
323
+ max_new_tokens=max_new_tokens,
324
+ temperature=temperature,
325
+ top_p=0.9,
326
+ stop_sequences=["</s>", "<|endoftext|>"],
327
+ )
328
+ if hasattr(output, "generated_text"):
329
+ return str(output.generated_text).strip()
330
+ return str(output).strip()
331
+
332
+ if provider == "litellm":
333
+ if litellm is None:
334
+ raise RuntimeError(
335
+ "the 'litellm' provider requires litellm: pip install litellm"
336
+ )
337
+ response = litellm.completion(
338
+ model=model,
339
+ messages=[{"role": "user", "content": prompt}],
340
+ temperature=temperature,
341
+ max_tokens=max_new_tokens,
342
+ )
343
+ return str(response["choices"][0]["message"]["content"]).strip()
344
+
345
+ raise ValueError(
346
+ f"unknown provider {provider!r}; use 'hf' (Hugging Face Serverless) "
347
+ "or 'litellm' (any OpenAI-compatible gateway)"
348
+ )
349
+
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: ezrag-engine
3
+ Version: 0.1.0
4
+ Summary: A 3-line, zero-boilerplate, cloud-ready RAG pipeline for Python and Hugging Face Spaces.
5
+ Author-email: Oguru Vinay Reddy <Vinayreddy.ace@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://huggingface.co/
8
+ Project-URL: Repository, https://github.com/<your-github-username>/ezrag
9
+ Project-URL: Documentation, https://github.com/<your-github-username>/ezrag#readme
10
+ Keywords: rag,llm,retrieval-augmented-generation,huggingface,embeddings,vector-search
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: sentence-transformers>=3.0.0
24
+ Requires-Dist: huggingface_hub>=0.23.0
25
+ Requires-Dist: numpy>=1.26.0
26
+ Requires-Dist: requests>=2.31.0
27
+ Requires-Dist: litellm>=1.40.0
28
+
29
+ <div align="center">
30
+
31
+ # **ezrag** — 3-Line, Zero-Boilerplate, Cloud-Ready RAG
32
+
33
+ **A production-grade Retrieval-Augmented Generation framework for Python and Hugging Face Spaces.**
34
+
35
+ ![PyPI](https://img.shields.io/pypi/v/ezrag.svg)
36
+ ![Python](https://img.shields.io/pypi/pyversions/ezrag.svg)
37
+ ![PyPI - License](https://img.shields.io/pypi/l/ezrag.svg)
38
+
39
+ </div>
40
+
41
+ ---
42
+
43
+ ## Why ezrag?
44
+
45
+ Most RAG stacks take hours to wire up: embedding models, vector stores, chunkers,
46
+ prompts, and LLM clients. ezrag hides all of that behind one class, so you go from
47
+ an empty folder to a **grounded, citation-aware answer** in exactly three lines of
48
+ code.
49
+
50
+ **What you get out of the box:**
51
+
52
+ - **Chunking** — sentence-aware splitting with a sliding overlap, zero configuration
53
+ - **Embeddings** — a lightweight `sentence-transformers` model, loaded lazily
54
+ - **Vector DB** — a self-contained in-memory store with fast NumPy cosine search
55
+ - **Context bundling** — evidence is ranked, tagged `[1] [2] [3] ...` and injected into the prompt
56
+ - **LLM execution** — unified calls through the Hugging Face Serverless API or LiteLLM
57
+ - **Weak-evidence guard** — answers truthfully when the documents do not cover the question
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ pip install ezrag
63
+ ```
64
+
65
+ ## Quick Start
66
+
67
+ That is the entire experience — import, load, ask:
68
+
69
+ ```python
70
+ from ezrag import EZRAG # 1. import
71
+
72
+ rag = EZRAG().load(SOURCE) # 2. initialise + load your source
73
+ print(rag.ask("Your question here")) # 3. ask — grounded, cited answer
74
+ ```
75
+
76
+ No environment variables to set up, no vector database to spin up, no prompting
77
+ prompts to craft. One object call chain produces a grounded, cited answer.
78
+
79
+ ## Configuration
80
+
81
+ | Parameter | Default | Purpose |
82
+ | --- | --- | --- |
83
+ | `llm` | `mistralai/Mistral-7B-Instruct-v0.2` | LLM identifier or endpoint |
84
+ | `embedding_model` | `all-MiniLM-L6-v2` | sentence-transformers model |
85
+ | `provider` | `"hf"` | `"hf"` (HF Serverless) or `"litellm"` |
86
+ | `hf_token` | env `HF_TOKEN` | token for protected / gated models |
87
+ | `top_k` | `4` | number of evidence chunks retrieved |
88
+ | `min_score` | `0.10` | retrieval similarity floor |
89
+ | `chunk_size` | `900` | target chunk length (characters) |
90
+ | `chunk_overlap` | `120` | sliding context overlap |
91
+
92
+ ## Providers
93
+
94
+ - **Hugging Face Serverless** (default) — no custom API wiring. Set the `HF_TOKEN`
95
+ environment variable only when the model is gated.
96
+ - **LiteLLM** — point `llm` at any OpenAI-compatible endpoint, for example
97
+ `openai/gpt-4o` or `azure/gpt-4o`.
98
+
99
+ ## How It Works
100
+
101
+ ```
102
+ source ──▶ chunk ──▶ embed ──▶ in-memory index
103
+
104
+ question ──▶ embed ──▶ top-k search ──┘
105
+
106
+ context bundle ──▶ LLM ──▶ cited answer
107
+ ```
108
+
109
+ ## Hugging Face Spaces
110
+
111
+ ezrag runs out of the box in a Hugging Face Space — pip pulls in every runtime
112
+ dependency automatically. Store a `HF_TOKEN` Space secret if your chosen model is
113
+ gated.
114
+
115
+ ## License
116
+
117
+ MIT © Oguru Vinay Reddy
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ ezrag/__init__.py
4
+ ezrag/engine.py
5
+ ezrag_engine.egg-info/PKG-INFO
6
+ ezrag_engine.egg-info/SOURCES.txt
7
+ ezrag_engine.egg-info/dependency_links.txt
8
+ ezrag_engine.egg-info/requires.txt
9
+ ezrag_engine.egg-info/top_level.txt
@@ -0,0 +1,5 @@
1
+ sentence-transformers>=3.0.0
2
+ huggingface_hub>=0.23.0
3
+ numpy>=1.26.0
4
+ requests>=2.31.0
5
+ litellm>=1.40.0
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ezrag-engine"
7
+ version = "0.1.0"
8
+ description = "A 3-line, zero-boilerplate, cloud-ready RAG pipeline for Python and Hugging Face Spaces."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ keywords = [
13
+ "rag",
14
+ "llm",
15
+ "retrieval-augmented-generation",
16
+ "huggingface",
17
+ "embeddings",
18
+ "vector-search",
19
+ ]
20
+ authors = [
21
+ { name = "Oguru Vinay Reddy", email = "Vinayreddy.ace@gmail.com" },
22
+ ]
23
+ dependencies = [
24
+ "sentence-transformers>=3.0.0",
25
+ "huggingface_hub>=0.23.0",
26
+ "numpy>=1.26.0",
27
+ "requests>=2.31.0",
28
+ "litellm>=1.40.0",
29
+ ]
30
+ classifiers = [
31
+ "Development Status :: 4 - Beta",
32
+ "Intended Audience :: Developers",
33
+ #"License :: OSI Approved :: MIT License",
34
+ "Operating System :: OS Independent",
35
+ "Programming Language :: Python :: 3",
36
+ "Programming Language :: Python :: 3.9",
37
+ "Programming Language :: Python :: 3.10",
38
+ "Programming Language :: Python :: 3.11",
39
+ "Programming Language :: Python :: 3.12",
40
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
41
+ "Topic :: Software Development :: Libraries :: Python Modules",
42
+ ]
43
+
44
+ [project.urls]
45
+ Homepage = "https://huggingface.co/"
46
+ Repository = "https://github.com/<your-github-username>/ezrag"
47
+ Documentation = "https://github.com/<your-github-username>/ezrag#readme"
48
+
49
+ [tool.setuptools]
50
+ packages = ["ezrag"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+