rag-python 0.3.0__py3-none-any.whl → 0.3.1__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.
- rag_python/__init__.py +19 -4
- rag_python/cli.py +163 -24
- rag_python/client.py +19 -1
- rag_python/generation.py +32 -2
- rag_python/help_text.py +229 -0
- rag_python/log.py +21 -0
- rag_python/providers/anthropic_provider.py +23 -0
- rag_python/providers/azure_openai_provider.py +26 -0
- rag_python/providers/base.py +11 -0
- rag_python/providers/ollama_provider.py +37 -0
- rag_python/providers/openai_provider.py +26 -0
- rag_python/providers/streaming.py +35 -0
- rag_python/rag_pipeline.py +262 -49
- rag_python-0.3.1.dist-info/METADATA +205 -0
- {rag_python-0.3.0.dist-info → rag_python-0.3.1.dist-info}/RECORD +19 -16
- rag_python-0.3.0.dist-info/METADATA +0 -180
- {rag_python-0.3.0.dist-info → rag_python-0.3.1.dist-info}/LICENSE +0 -0
- {rag_python-0.3.0.dist-info → rag_python-0.3.1.dist-info}/WHEEL +0 -0
- {rag_python-0.3.0.dist-info → rag_python-0.3.1.dist-info}/entry_points.txt +0 -0
- {rag_python-0.3.0.dist-info → rag_python-0.3.1.dist-info}/top_level.txt +0 -0
rag_python/__init__.py
CHANGED
|
@@ -2,18 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
Quick start::
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
pip install rag-python
|
|
6
|
+
export OPENAI_API_KEY=sk-...
|
|
7
|
+
|
|
8
|
+
# CLI
|
|
9
|
+
rag-python ingest ./docs --reindex
|
|
10
|
+
rag-python query "What is our leave policy?"
|
|
11
|
+
rag-python docs quickstart
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
# Python
|
|
14
|
+
from rag_python import RAG
|
|
15
|
+
rag = RAG()
|
|
8
16
|
rag.ingest(["./docs"], reindex=True)
|
|
9
17
|
print(rag.query("What is our leave policy?").text)
|
|
18
|
+
|
|
19
|
+
Documentation: https://github.com/RaghavOG/rag-python/tree/main/docs
|
|
10
20
|
"""
|
|
11
21
|
|
|
12
|
-
__version__ = "0.3.
|
|
22
|
+
__version__ = "0.3.1"
|
|
13
23
|
|
|
14
24
|
from .client import RAG, RAGAnswer
|
|
15
|
-
from .rag_pipeline import ingest, query, RAGResponse
|
|
25
|
+
from .rag_pipeline import ingest, query, query_stream, RAGResponse, RAGStream
|
|
16
26
|
from .providers import make_llm_provider, make_embedding_provider
|
|
27
|
+
from .log import configure_logging, get_logger
|
|
17
28
|
from .options import (
|
|
18
29
|
ChunkingConfig,
|
|
19
30
|
DocumentConfig,
|
|
@@ -33,7 +44,11 @@ __all__ = [
|
|
|
33
44
|
"QueryConfig",
|
|
34
45
|
"ingest",
|
|
35
46
|
"query",
|
|
47
|
+
"query_stream",
|
|
48
|
+
"RAGStream",
|
|
36
49
|
"RAGResponse",
|
|
50
|
+
"configure_logging",
|
|
51
|
+
"get_logger",
|
|
37
52
|
"make_llm_provider",
|
|
38
53
|
"make_embedding_provider",
|
|
39
54
|
]
|
rag_python/cli.py
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"""rag-python command-line interface."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
2
4
|
import argparse
|
|
3
5
|
import json
|
|
6
|
+
import sys
|
|
4
7
|
from dataclasses import replace
|
|
5
8
|
|
|
6
9
|
from . import __version__
|
|
7
10
|
from .client import RAG
|
|
11
|
+
from .help_text import CLI_EPILOG, list_topics, print_topic, print_topic_list
|
|
8
12
|
|
|
9
13
|
|
|
10
14
|
def _build_rag(args: argparse.Namespace) -> RAG:
|
|
@@ -42,21 +46,44 @@ def _add_provider_args(parser: argparse.ArgumentParser) -> None:
|
|
|
42
46
|
"--llm-provider",
|
|
43
47
|
default="openai",
|
|
44
48
|
choices=["openai", "azure_openai", "anthropic", "gemini", "ollama"],
|
|
49
|
+
metavar="PROVIDER",
|
|
50
|
+
help="LLM backend (default: openai). See: rag-python docs providers",
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"--llm-model",
|
|
54
|
+
default=None,
|
|
55
|
+
metavar="MODEL",
|
|
56
|
+
help="LLM model or Azure deployment name (default: from env LLM_MODEL)",
|
|
45
57
|
)
|
|
46
|
-
parser.add_argument("--llm-model", default=None)
|
|
47
58
|
parser.add_argument(
|
|
48
59
|
"--embedding-provider",
|
|
49
60
|
default="openai",
|
|
50
61
|
choices=["openai", "azure_openai", "ollama", "local"],
|
|
62
|
+
metavar="PROVIDER",
|
|
63
|
+
help="Embedding backend (default: openai). Use local for offline embeddings",
|
|
51
64
|
)
|
|
52
|
-
parser.add_argument(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
parser.add_argument(
|
|
59
|
-
|
|
65
|
+
parser.add_argument(
|
|
66
|
+
"--embedding-model",
|
|
67
|
+
default=None,
|
|
68
|
+
metavar="MODEL",
|
|
69
|
+
help="Embedding model name (default: from env EMBEDDING_MODEL)",
|
|
70
|
+
)
|
|
71
|
+
parser.add_argument(
|
|
72
|
+
"--ollama-base-url",
|
|
73
|
+
default=None,
|
|
74
|
+
metavar="URL",
|
|
75
|
+
help="Ollama server URL (default: http://localhost:11434 or OLLAMA_BASE_URL)",
|
|
76
|
+
)
|
|
77
|
+
parser.add_argument("--azure-endpoint", default=None, help="Azure OpenAI endpoint URL")
|
|
78
|
+
parser.add_argument("--azure-api-key", default=None, help="Azure OpenAI API key")
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"--azure-api-version",
|
|
81
|
+
default=None,
|
|
82
|
+
help="Azure API version (default: 2023-09-01-preview)",
|
|
83
|
+
)
|
|
84
|
+
parser.add_argument("--openai-api-key", default=None, help="OpenAI API key (overrides env)")
|
|
85
|
+
parser.add_argument("--anthropic-api-key", default=None, help="Anthropic API key")
|
|
86
|
+
parser.add_argument("--gemini-api-key", default=None, help="Gemini API key")
|
|
60
87
|
|
|
61
88
|
|
|
62
89
|
def _add_search_args(parser: argparse.ArgumentParser) -> None:
|
|
@@ -64,37 +91,135 @@ def _add_search_args(parser: argparse.ArgumentParser) -> None:
|
|
|
64
91
|
"--retriever",
|
|
65
92
|
choices=["vector", "multi_query", "hybrid"],
|
|
66
93
|
default=None,
|
|
67
|
-
|
|
94
|
+
metavar="MODE",
|
|
95
|
+
help=(
|
|
96
|
+
"Retrieval mode: vector (single query), multi_query (default, with rewriting), "
|
|
97
|
+
"or hybrid (BM25+vector; requires pip install rag-python[hybrid])"
|
|
98
|
+
),
|
|
68
99
|
)
|
|
69
100
|
parser.add_argument(
|
|
70
101
|
"--metadata-filter",
|
|
71
102
|
type=_parse_metadata_filter,
|
|
72
103
|
default=None,
|
|
73
|
-
|
|
104
|
+
metavar="JSON",
|
|
105
|
+
help='Filter chunks by metadata, e.g. \'{"filename": "policy.pdf"}\'',
|
|
74
106
|
)
|
|
75
107
|
|
|
76
108
|
|
|
77
|
-
def
|
|
109
|
+
def _make_parser() -> argparse.ArgumentParser:
|
|
78
110
|
parser = argparse.ArgumentParser(
|
|
79
111
|
prog="rag-python",
|
|
80
|
-
description=
|
|
112
|
+
description=(
|
|
113
|
+
"Production-grade RAG for Python — ingest documents, ask questions, "
|
|
114
|
+
"get grounded answers with multi-LLM support."
|
|
115
|
+
),
|
|
116
|
+
epilog=CLI_EPILOG,
|
|
117
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
81
118
|
)
|
|
82
|
-
parser.add_argument("--version", action="version", version=f"
|
|
83
|
-
sub = parser.add_subparsers(dest="command", required=True)
|
|
119
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
120
|
+
sub = parser.add_subparsers(dest="command", required=True, metavar="COMMAND")
|
|
84
121
|
|
|
85
|
-
ing = sub.add_parser(
|
|
86
|
-
|
|
87
|
-
|
|
122
|
+
ing = sub.add_parser(
|
|
123
|
+
"ingest",
|
|
124
|
+
help="Load files into the vector store (chunk + embed)",
|
|
125
|
+
description=(
|
|
126
|
+
"Ingest one or more files or directories into the ChromaDB vector store.\n"
|
|
127
|
+
"Supported formats: .txt .md .pdf .docx .csv .json .html"
|
|
128
|
+
),
|
|
129
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
130
|
+
epilog=(
|
|
131
|
+
"examples:\n"
|
|
132
|
+
" rag-python ingest ./data --reindex\n"
|
|
133
|
+
" rag-python ingest policy.pdf handbook/ --embedding-provider local"
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
ing.add_argument(
|
|
137
|
+
"paths",
|
|
138
|
+
nargs="+",
|
|
139
|
+
metavar="PATH",
|
|
140
|
+
help="File or directory paths to ingest",
|
|
141
|
+
)
|
|
142
|
+
ing.add_argument(
|
|
143
|
+
"--reindex",
|
|
144
|
+
action="store_true",
|
|
145
|
+
help="Delete existing vectors before ingesting (fresh index)",
|
|
146
|
+
)
|
|
88
147
|
_add_provider_args(ing)
|
|
89
148
|
|
|
90
|
-
q = sub.add_parser(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
149
|
+
q = sub.add_parser(
|
|
150
|
+
"query",
|
|
151
|
+
help="Ask a question against ingested documents",
|
|
152
|
+
description=(
|
|
153
|
+
"Run the full RAG pipeline: retrieve relevant chunks, generate an answer, "
|
|
154
|
+
"optionally stream tokens and show sources."
|
|
155
|
+
),
|
|
156
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
157
|
+
epilog=(
|
|
158
|
+
"examples:\n"
|
|
159
|
+
' rag-python query "How many days of annual leave?"\n'
|
|
160
|
+
" rag-python query \"PTO policy\" --stream -v\n"
|
|
161
|
+
' rag-python query "benefits" --retriever hybrid --metadata-filter \'{"filename": "hr.pdf"}\''
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
q.add_argument(
|
|
165
|
+
"question",
|
|
166
|
+
nargs="+",
|
|
167
|
+
metavar="QUESTION",
|
|
168
|
+
help="Question text (multiple words are joined)",
|
|
169
|
+
)
|
|
170
|
+
q.add_argument(
|
|
171
|
+
"--no-multi-query",
|
|
172
|
+
action="store_true",
|
|
173
|
+
help="Use single-query vector retrieval (same as --retriever vector)",
|
|
174
|
+
)
|
|
175
|
+
q.add_argument(
|
|
176
|
+
"--stream",
|
|
177
|
+
action="store_true",
|
|
178
|
+
help="Stream answer tokens to stdout as they are generated",
|
|
179
|
+
)
|
|
180
|
+
q.add_argument(
|
|
181
|
+
"-v",
|
|
182
|
+
"--verbose",
|
|
183
|
+
action="store_true",
|
|
184
|
+
help="After the answer, print evaluation scores and top source paths",
|
|
185
|
+
)
|
|
94
186
|
_add_provider_args(q)
|
|
95
187
|
_add_search_args(q)
|
|
96
188
|
|
|
97
|
-
|
|
189
|
+
docs = sub.add_parser(
|
|
190
|
+
"docs",
|
|
191
|
+
help="Show user documentation in the terminal",
|
|
192
|
+
description="Print built-in help topics. Full docs: https://github.com/RaghavOG/rag-python/tree/main/docs",
|
|
193
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
194
|
+
epilog="topics: " + ", ".join(list_topics()),
|
|
195
|
+
)
|
|
196
|
+
docs.add_argument(
|
|
197
|
+
"topic",
|
|
198
|
+
nargs="?",
|
|
199
|
+
default="quickstart",
|
|
200
|
+
choices=list_topics(),
|
|
201
|
+
metavar="TOPIC",
|
|
202
|
+
help="Documentation topic (default: quickstart)",
|
|
203
|
+
)
|
|
204
|
+
docs.add_argument(
|
|
205
|
+
"--list",
|
|
206
|
+
action="store_true",
|
|
207
|
+
help="List all available documentation topics",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return parser
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def main(argv: list[str] | None = None) -> None:
|
|
214
|
+
parser = _make_parser()
|
|
215
|
+
args = parser.parse_args(argv)
|
|
216
|
+
|
|
217
|
+
if args.command == "docs":
|
|
218
|
+
if args.list:
|
|
219
|
+
print_topic_list()
|
|
220
|
+
else:
|
|
221
|
+
print_topic(args.topic)
|
|
222
|
+
return
|
|
98
223
|
|
|
99
224
|
if args.command == "ingest":
|
|
100
225
|
rag = _build_rag(args)
|
|
@@ -113,6 +238,20 @@ def main() -> None:
|
|
|
113
238
|
retriever=retriever or rag.config.search.retriever,
|
|
114
239
|
metadata_filter=args.metadata_filter or rag.config.search.metadata_filter,
|
|
115
240
|
)
|
|
241
|
+
if args.stream:
|
|
242
|
+
stream = rag.query_stream(question, search=search)
|
|
243
|
+
for token in stream:
|
|
244
|
+
print(token, end="", flush=True)
|
|
245
|
+
print()
|
|
246
|
+
result = stream.result
|
|
247
|
+
if args.verbose:
|
|
248
|
+
print("\n--- evaluation ---")
|
|
249
|
+
print(result.evaluation)
|
|
250
|
+
print("\n--- sources ---")
|
|
251
|
+
for s in result.sources[:5]:
|
|
252
|
+
print(s.get("metadata", {}).get("source", ""), "score:", s.get("score"))
|
|
253
|
+
return
|
|
254
|
+
|
|
116
255
|
ans = rag.query(question, search=search)
|
|
117
256
|
print(ans.text)
|
|
118
257
|
if args.verbose:
|
|
@@ -124,4 +263,4 @@ def main() -> None:
|
|
|
124
263
|
|
|
125
264
|
|
|
126
265
|
if __name__ == "__main__":
|
|
127
|
-
main()
|
|
266
|
+
main(sys.argv[1:])
|
rag_python/client.py
CHANGED
|
@@ -30,7 +30,7 @@ from .options import (
|
|
|
30
30
|
SearchConfig,
|
|
31
31
|
)
|
|
32
32
|
from .providers import make_llm_provider, make_embedding_provider
|
|
33
|
-
from .rag_pipeline import ingest as _ingest, query as _query, RAGResponse
|
|
33
|
+
from .rag_pipeline import ingest as _ingest, query as _query, query_stream as _query_stream, RAGResponse, RAGStream
|
|
34
34
|
from .vector_store import set_persist_dir
|
|
35
35
|
|
|
36
36
|
|
|
@@ -191,3 +191,21 @@ class RAG:
|
|
|
191
191
|
evaluation=resp.evaluation,
|
|
192
192
|
retried=resp.retried,
|
|
193
193
|
)
|
|
194
|
+
|
|
195
|
+
def query_stream(
|
|
196
|
+
self,
|
|
197
|
+
question: str,
|
|
198
|
+
*,
|
|
199
|
+
search: SearchConfig | None = None,
|
|
200
|
+
query_config: QueryConfig | None = None,
|
|
201
|
+
) -> RAGStream:
|
|
202
|
+
"""Stream answer tokens; call ``stream.result`` after iterating."""
|
|
203
|
+
return _query_stream(
|
|
204
|
+
question,
|
|
205
|
+
search=search or self.config.search,
|
|
206
|
+
query_config=query_config or self.config.query,
|
|
207
|
+
llm_model=self.llm_model,
|
|
208
|
+
embedding_model=self.embedding_model,
|
|
209
|
+
llm=self.llm,
|
|
210
|
+
embedder=self.embedder,
|
|
211
|
+
)
|
rag_python/generation.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"""LLM generation with context (RAG)."""
|
|
2
|
+
from collections.abc import Iterator
|
|
3
|
+
|
|
2
4
|
from .config import LLM_MODEL
|
|
3
5
|
from .providers import LLMProvider, make_llm_provider
|
|
6
|
+
from .providers.streaming import stream_generate
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
RAG_SYSTEM = (
|
|
@@ -10,6 +13,11 @@ RAG_SYSTEM = (
|
|
|
10
13
|
)
|
|
11
14
|
|
|
12
15
|
|
|
16
|
+
def _build_user_prompt(query: str, context_chunks: list[str]) -> str:
|
|
17
|
+
context = "\n\n---\n\n".join(context_chunks)
|
|
18
|
+
return f"Context:\n{context}\n\nQuestion: {query}\n\nAnswer:"
|
|
19
|
+
|
|
20
|
+
|
|
13
21
|
def generate(
|
|
14
22
|
query: str,
|
|
15
23
|
context_chunks: list[str],
|
|
@@ -20,12 +28,11 @@ def generate(
|
|
|
20
28
|
) -> str:
|
|
21
29
|
"""Generate answer from query and retrieved context."""
|
|
22
30
|
llm = llm or make_llm_provider("openai")
|
|
23
|
-
context = "\n\n---\n\n".join(context_chunks)
|
|
24
31
|
sys = system_prompt or RAG_SYSTEM
|
|
25
32
|
try:
|
|
26
33
|
return llm.generate(
|
|
27
34
|
system=sys,
|
|
28
|
-
user=
|
|
35
|
+
user=_build_user_prompt(query, context_chunks),
|
|
29
36
|
model=model or LLM_MODEL,
|
|
30
37
|
temperature=0.2,
|
|
31
38
|
max_tokens=1024,
|
|
@@ -33,3 +40,26 @@ def generate(
|
|
|
33
40
|
except Exception as e:
|
|
34
41
|
return f"[Generation error: {e}]"
|
|
35
42
|
|
|
43
|
+
|
|
44
|
+
def generate_stream(
|
|
45
|
+
query: str,
|
|
46
|
+
context_chunks: list[str],
|
|
47
|
+
*,
|
|
48
|
+
model: str | None = None,
|
|
49
|
+
system_prompt: str | None = None,
|
|
50
|
+
llm: LLMProvider | None = None,
|
|
51
|
+
) -> Iterator[str]:
|
|
52
|
+
"""Stream answer tokens from query and retrieved context."""
|
|
53
|
+
llm = llm or make_llm_provider("openai")
|
|
54
|
+
sys = system_prompt or RAG_SYSTEM
|
|
55
|
+
try:
|
|
56
|
+
yield from stream_generate(
|
|
57
|
+
llm,
|
|
58
|
+
system=sys,
|
|
59
|
+
user=_build_user_prompt(query, context_chunks),
|
|
60
|
+
model=model or LLM_MODEL,
|
|
61
|
+
temperature=0.2,
|
|
62
|
+
max_tokens=1024,
|
|
63
|
+
)
|
|
64
|
+
except Exception as e:
|
|
65
|
+
yield f"[Generation error: {e}]"
|
rag_python/help_text.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""User-facing documentation printed by ``rag-python docs``."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
DOCS_BASE_URL = "https://github.com/RaghavOG/rag-python/tree/main/docs"
|
|
5
|
+
|
|
6
|
+
CLI_EPILOG = """
|
|
7
|
+
examples:
|
|
8
|
+
rag-python ingest ./data --reindex
|
|
9
|
+
rag-python query "How many days of annual leave?"
|
|
10
|
+
rag-python query "leave policy" --stream -v
|
|
11
|
+
rag-python query "benefits" --retriever hybrid
|
|
12
|
+
rag-python docs quickstart
|
|
13
|
+
rag-python docs --list
|
|
14
|
+
|
|
15
|
+
online docs:
|
|
16
|
+
{base_url}
|
|
17
|
+
""".format(base_url=DOCS_BASE_URL).strip()
|
|
18
|
+
|
|
19
|
+
TOPICS: dict[str, str] = {
|
|
20
|
+
"quickstart": """
|
|
21
|
+
rag-python — Quick start
|
|
22
|
+
========================
|
|
23
|
+
|
|
24
|
+
1. Install
|
|
25
|
+
pip install rag-python
|
|
26
|
+
|
|
27
|
+
2. Set your API key (OpenAI default)
|
|
28
|
+
export OPENAI_API_KEY=sk-...
|
|
29
|
+
|
|
30
|
+
3. Ingest documents (TXT, MD, PDF, DOCX, CSV, JSON, HTML)
|
|
31
|
+
rag-python ingest ./my-docs --reindex
|
|
32
|
+
|
|
33
|
+
4. Ask a question
|
|
34
|
+
rag-python query "What is our leave policy?"
|
|
35
|
+
rag-python query "annual leave" --stream -v
|
|
36
|
+
|
|
37
|
+
Python API
|
|
38
|
+
----------
|
|
39
|
+
from rag_python import RAG
|
|
40
|
+
|
|
41
|
+
rag = RAG(llm_model="gpt-4o-mini")
|
|
42
|
+
rag.ingest(["./my-docs"], reindex=True)
|
|
43
|
+
print(rag.query("What is our leave policy?").text)
|
|
44
|
+
|
|
45
|
+
More: rag-python docs install | cli | config | providers | features
|
|
46
|
+
Online: {base_url}
|
|
47
|
+
""".format(base_url=DOCS_BASE_URL).strip(),
|
|
48
|
+
"install": """
|
|
49
|
+
rag-python — Install & optional extras
|
|
50
|
+
======================================
|
|
51
|
+
|
|
52
|
+
Base install (OpenAI + Chroma + document loaders):
|
|
53
|
+
pip install rag-python
|
|
54
|
+
|
|
55
|
+
Optional extras:
|
|
56
|
+
pip install rag-python[local] Offline embeddings (sentence-transformers)
|
|
57
|
+
pip install rag-python[hybrid] BM25 + vector hybrid search
|
|
58
|
+
pip install rag-python[rerank] Cross-encoder reranking
|
|
59
|
+
pip install rag-python[anthropic] Claude LLM
|
|
60
|
+
pip install rag-python[gemini] Gemini LLM
|
|
61
|
+
pip install rag-python[all] All optional features
|
|
62
|
+
|
|
63
|
+
From source:
|
|
64
|
+
git clone https://github.com/RaghavOG/rag-python.git
|
|
65
|
+
cd rag-python
|
|
66
|
+
pip install -e ".[dev,all]"
|
|
67
|
+
|
|
68
|
+
Verify:
|
|
69
|
+
rag-python --version
|
|
70
|
+
pytest
|
|
71
|
+
""".strip(),
|
|
72
|
+
"cli": """
|
|
73
|
+
rag-python — CLI reference
|
|
74
|
+
==========================
|
|
75
|
+
|
|
76
|
+
USAGE
|
|
77
|
+
rag-python ingest PATH [PATH ...] [options]
|
|
78
|
+
rag-python query QUESTION [options]
|
|
79
|
+
rag-python docs [TOPIC]
|
|
80
|
+
rag-python --version
|
|
81
|
+
|
|
82
|
+
INGEST
|
|
83
|
+
paths Files or directories to ingest
|
|
84
|
+
--reindex Clear vector store before ingesting
|
|
85
|
+
--llm-provider openai | azure_openai | anthropic | gemini | ollama
|
|
86
|
+
--embedding-provider openai | azure_openai | ollama | local
|
|
87
|
+
--llm-model Model or deployment name
|
|
88
|
+
--embedding-model Embedding model name
|
|
89
|
+
--openai-api-key Override OPENAI_API_KEY
|
|
90
|
+
--ollama-base-url Ollama URL (default http://localhost:11434)
|
|
91
|
+
|
|
92
|
+
QUERY
|
|
93
|
+
question Natural-language question (words joined if multiple)
|
|
94
|
+
--retriever vector | multi_query | hybrid (default: multi_query)
|
|
95
|
+
--no-multi-query Shortcut for --retriever vector
|
|
96
|
+
--metadata-filter JSON Chroma filter, e.g. '{"filename": "policy.pdf"}'
|
|
97
|
+
--stream Stream answer tokens to stdout
|
|
98
|
+
-v, --verbose Show evaluation scores and source paths
|
|
99
|
+
|
|
100
|
+
ENVIRONMENT
|
|
101
|
+
OPENAI_API_KEY Required for default OpenAI provider
|
|
102
|
+
RAG_PYTHON_DATA_DIR Document storage (default ./data)
|
|
103
|
+
RAG_PYTHON_CHROMA_DIR Vector DB path (default ./chroma_db)
|
|
104
|
+
|
|
105
|
+
Examples:
|
|
106
|
+
rag-python ingest ./policies ./handbook.pdf --reindex
|
|
107
|
+
rag-python query "How many vacation days?" -v
|
|
108
|
+
rag-python query "PTO policy" --retriever hybrid --stream
|
|
109
|
+
rag-python query "benefits" --metadata-filter '{"filename": "hr.pdf"}'
|
|
110
|
+
""".strip(),
|
|
111
|
+
"config": """
|
|
112
|
+
rag-python — Configuration
|
|
113
|
+
==========================
|
|
114
|
+
|
|
115
|
+
Environment variables (.env supported via python-dotenv):
|
|
116
|
+
|
|
117
|
+
OPENAI_API_KEY OpenAI LLM + embeddings
|
|
118
|
+
ANTHROPIC_API_KEY Claude (LLM only)
|
|
119
|
+
GEMINI_API_KEY Gemini (LLM only)
|
|
120
|
+
AZURE_OPENAI_ENDPOINT Azure OpenAI
|
|
121
|
+
AZURE_OPENAI_API_KEY Azure OpenAI
|
|
122
|
+
OPENAI_API_VERSION Azure API version
|
|
123
|
+
OLLAMA_BASE_URL Local Ollama server
|
|
124
|
+
LOCAL_EMBEDDING_MODEL Model for embedding_provider=local
|
|
125
|
+
RAG_PYTHON_DATA_DIR Default ./data
|
|
126
|
+
RAG_PYTHON_CHROMA_DIR Default ./chroma_db
|
|
127
|
+
|
|
128
|
+
CHUNK_SIZE, CHUNK_OVERLAP, CHUNK_STRATEGY
|
|
129
|
+
TOP_K_RETRIEVE, TOP_K_RERANK, MULTI_QUERY_N
|
|
130
|
+
GUARDRAILS_ENABLED, MAX_RETRIES, RERANKER_MODEL
|
|
131
|
+
|
|
132
|
+
Python RAGConfig:
|
|
133
|
+
from rag_python import RAG, RAGConfig, ChunkingConfig, SearchConfig
|
|
134
|
+
|
|
135
|
+
rag = RAG(
|
|
136
|
+
config=RAGConfig(
|
|
137
|
+
chunking=ChunkingConfig(strategy="recursive", chunk_size=512),
|
|
138
|
+
search=SearchConfig(
|
|
139
|
+
retriever="hybrid",
|
|
140
|
+
top_k_retrieve=20,
|
|
141
|
+
metadata_filter={"filename": "policy.pdf"},
|
|
142
|
+
),
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
Shorthand on RAG():
|
|
147
|
+
chunk_strategy, chunk_size, retriever, metadata_filter,
|
|
148
|
+
top_k_retrieve, document_extensions, ...
|
|
149
|
+
|
|
150
|
+
Logging:
|
|
151
|
+
import rag_python
|
|
152
|
+
rag_python.configure_logging()
|
|
153
|
+
""".strip(),
|
|
154
|
+
"providers": """
|
|
155
|
+
rag-python — Providers
|
|
156
|
+
======================
|
|
157
|
+
|
|
158
|
+
LLM (generation, rewriting, guardrails):
|
|
159
|
+
openai OPENAI_API_KEY (default)
|
|
160
|
+
azure_openai AZURE_OPENAI_ENDPOINT + AZURE_OPENAI_API_KEY
|
|
161
|
+
anthropic ANTHROPIC_API_KEY + pip install rag-python[anthropic]
|
|
162
|
+
gemini GEMINI_API_KEY + pip install rag-python[gemini]
|
|
163
|
+
ollama Local Ollama — set --llm-model to your model name
|
|
164
|
+
|
|
165
|
+
Embeddings (retrieval):
|
|
166
|
+
openai OPENAI_API_KEY (default)
|
|
167
|
+
azure_openai Azure deployment for embeddings
|
|
168
|
+
ollama Local embedding model via Ollama
|
|
169
|
+
local Offline sentence-transformers + pip install rag-python[local]
|
|
170
|
+
|
|
171
|
+
Common combos:
|
|
172
|
+
OpenAI end-to-end:
|
|
173
|
+
RAG(llm_provider="openai", embedding_provider="openai")
|
|
174
|
+
|
|
175
|
+
Claude + OpenAI embeddings:
|
|
176
|
+
RAG(llm_provider="anthropic", llm_model="claude-opus-4-6",
|
|
177
|
+
embedding_provider="openai")
|
|
178
|
+
|
|
179
|
+
Fully local embeddings:
|
|
180
|
+
RAG(llm_provider="ollama", llm_model="llama3.1",
|
|
181
|
+
embedding_provider="local", embedding_model="all-MiniLM-L6-v2")
|
|
182
|
+
""".strip(),
|
|
183
|
+
"features": """
|
|
184
|
+
rag-python — Features
|
|
185
|
+
=====================
|
|
186
|
+
|
|
187
|
+
Ingest pipeline
|
|
188
|
+
Loaders: .txt .md .pdf .docx .csv .json .html
|
|
189
|
+
Cleaning, chunking (recursive | structure_aware | semantic)
|
|
190
|
+
Embeddings → ChromaDB vector store
|
|
191
|
+
|
|
192
|
+
Query pipeline
|
|
193
|
+
Query rewriting + multi-query retrieval
|
|
194
|
+
Hybrid search: BM25 + vector (pip install rag-python[hybrid])
|
|
195
|
+
Cross-encoder reranking (pip install rag-python[rerank])
|
|
196
|
+
Metadata filters on retrieval (source, filename, ...)
|
|
197
|
+
Streaming answers: rag.query_stream()
|
|
198
|
+
Guardrails: prompt injection + hallucination checks
|
|
199
|
+
Evaluation + self-correction retry loop
|
|
200
|
+
|
|
201
|
+
CLI
|
|
202
|
+
rag-python ingest | query | docs
|
|
203
|
+
rag-python --version
|
|
204
|
+
|
|
205
|
+
Docs
|
|
206
|
+
rag-python docs [topic]
|
|
207
|
+
https://github.com/RaghavOG/rag-python/tree/main/docs
|
|
208
|
+
""".strip(),
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def list_topics() -> list[str]:
|
|
213
|
+
return sorted(TOPICS.keys())
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def print_topic(topic: str) -> None:
|
|
217
|
+
key = topic.lower().strip()
|
|
218
|
+
if key not in TOPICS:
|
|
219
|
+
available = ", ".join(list_topics())
|
|
220
|
+
raise SystemExit(f"Unknown topic '{topic}'. Available: {available}\nUse: rag-python docs --list")
|
|
221
|
+
print(TOPICS[key])
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def print_topic_list() -> None:
|
|
225
|
+
print("rag-python documentation topics:\n")
|
|
226
|
+
for name in list_topics():
|
|
227
|
+
print(f" {name}")
|
|
228
|
+
print("\nUsage: rag-python docs <topic>")
|
|
229
|
+
print(f"Online: {DOCS_BASE_URL}")
|
rag_python/log.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Structured logging helpers for rag-python."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
PACKAGE_LOGGER = "rag_python"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_logger(name: str | None = None) -> logging.Logger:
|
|
10
|
+
"""Return a namespaced logger (default: ``rag_python``)."""
|
|
11
|
+
if name:
|
|
12
|
+
return logging.getLogger(f"{PACKAGE_LOGGER}.{name}")
|
|
13
|
+
return logging.getLogger(PACKAGE_LOGGER)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def configure_logging(level: int = logging.INFO) -> None:
|
|
17
|
+
"""Enable default console logging for the package (optional convenience)."""
|
|
18
|
+
logging.basicConfig(
|
|
19
|
+
level=level,
|
|
20
|
+
format="%(asctime)s %(levelname)s [%(name)s] %(message)s",
|
|
21
|
+
)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
class AnthropicProvider:
|
|
5
7
|
def __init__(self, *, api_key: str | None = None) -> None:
|
|
@@ -36,6 +38,27 @@ class AnthropicProvider:
|
|
|
36
38
|
parts.append(text)
|
|
37
39
|
return ("\n".join(parts)).strip()
|
|
38
40
|
|
|
41
|
+
def generate_stream(
|
|
42
|
+
self,
|
|
43
|
+
*,
|
|
44
|
+
user: str,
|
|
45
|
+
system: str | None = None,
|
|
46
|
+
model: str | None = None,
|
|
47
|
+
temperature: float = 0.2,
|
|
48
|
+
max_tokens: int = 1024,
|
|
49
|
+
) -> Iterator[str]:
|
|
50
|
+
if not model:
|
|
51
|
+
raise RuntimeError("AnthropicProvider requires `model=...` (e.g. claude-...)")
|
|
52
|
+
with self._client.messages.stream(
|
|
53
|
+
model=model,
|
|
54
|
+
max_tokens=max_tokens,
|
|
55
|
+
temperature=temperature,
|
|
56
|
+
system=system or "",
|
|
57
|
+
messages=[{"role": "user", "content": user}],
|
|
58
|
+
) as stream:
|
|
59
|
+
for text in stream.text_stream:
|
|
60
|
+
yield text
|
|
61
|
+
|
|
39
62
|
def embed(self, texts: list[str], *, model: str | None = None) -> list[list[float]]:
|
|
40
63
|
raise RuntimeError("Anthropic does not provide embeddings in this package. Use OpenAI/Ollama or local embeddings.")
|
|
41
64
|
|