ragit 0.7.5__tar.gz → 0.8.1__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.
- ragit-0.8.1/PKG-INFO +166 -0
- ragit-0.8.1/README.md +117 -0
- {ragit-0.7.5 → ragit-0.8.1}/pyproject.toml +3 -1
- {ragit-0.7.5 → ragit-0.8.1}/ragit/__init__.py +36 -9
- {ragit-0.7.5 → ragit-0.8.1}/ragit/assistant.py +106 -23
- {ragit-0.7.5 → ragit-0.8.1}/ragit/config.py +15 -6
- {ragit-0.7.5 → ragit-0.8.1}/ragit/core/experiment/experiment.py +85 -20
- ragit-0.8.1/ragit/providers/__init__.py +47 -0
- ragit-0.8.1/ragit/providers/function_adapter.py +237 -0
- {ragit-0.7.5 → ragit-0.8.1}/ragit/providers/ollama.py +1 -1
- ragit-0.8.1/ragit/providers/sentence_transformers.py +225 -0
- {ragit-0.7.5 → ragit-0.8.1}/ragit/version.py +1 -1
- ragit-0.8.1/ragit.egg-info/PKG-INFO +166 -0
- {ragit-0.7.5 → ragit-0.8.1}/ragit.egg-info/SOURCES.txt +2 -0
- {ragit-0.7.5 → ragit-0.8.1}/ragit.egg-info/requires.txt +3 -0
- ragit-0.7.5/PKG-INFO +0 -553
- ragit-0.7.5/README.md +0 -506
- ragit-0.7.5/ragit/providers/__init__.py +0 -20
- ragit-0.7.5/ragit.egg-info/PKG-INFO +0 -553
- {ragit-0.7.5 → ragit-0.8.1}/LICENSE +0 -0
- {ragit-0.7.5 → ragit-0.8.1}/ragit/core/__init__.py +0 -0
- {ragit-0.7.5 → ragit-0.8.1}/ragit/core/experiment/__init__.py +0 -0
- {ragit-0.7.5 → ragit-0.8.1}/ragit/core/experiment/results.py +0 -0
- {ragit-0.7.5 → ragit-0.8.1}/ragit/loaders.py +0 -0
- {ragit-0.7.5 → ragit-0.8.1}/ragit/providers/base.py +0 -0
- {ragit-0.7.5 → ragit-0.8.1}/ragit/utils/__init__.py +0 -0
- {ragit-0.7.5 → ragit-0.8.1}/ragit.egg-info/dependency_links.txt +0 -0
- {ragit-0.7.5 → ragit-0.8.1}/ragit.egg-info/top_level.txt +0 -0
- {ragit-0.7.5 → ragit-0.8.1}/setup.cfg +0 -0
ragit-0.8.1/PKG-INFO
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ragit
|
|
3
|
+
Version: 0.8.1
|
|
4
|
+
Summary: Automatic RAG Pattern Optimization Engine
|
|
5
|
+
Author: RODMENA LIMITED
|
|
6
|
+
Maintainer-email: RODMENA LIMITED <info@rodmena.co.uk>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Project-URL: Homepage, https://github.com/rodmena-limited/ragit
|
|
9
|
+
Project-URL: Repository, https://github.com/rodmena-limited/ragit
|
|
10
|
+
Project-URL: Issues, https://github.com/rodmena-limited/ragit/issues
|
|
11
|
+
Keywords: AI,RAG,LLM,GenAI,Optimization,Ollama
|
|
12
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Requires-Python: <3.14,>=3.12
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: requests>=2.31.0
|
|
23
|
+
Requires-Dist: numpy>=1.26.0
|
|
24
|
+
Requires-Dist: pandas>=2.2.0
|
|
25
|
+
Requires-Dist: pydantic>=2.0.0
|
|
26
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
27
|
+
Requires-Dist: scikit-learn>=1.5.0
|
|
28
|
+
Requires-Dist: tqdm>=4.66.0
|
|
29
|
+
Requires-Dist: trio>=0.24.0
|
|
30
|
+
Requires-Dist: httpx>=0.27.0
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: ragit[test]; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
35
|
+
Requires-Dist: issuedb[web]; extra == "dev"
|
|
36
|
+
Requires-Dist: ruff; extra == "dev"
|
|
37
|
+
Requires-Dist: mypy; extra == "dev"
|
|
38
|
+
Provides-Extra: test
|
|
39
|
+
Requires-Dist: pytest; extra == "test"
|
|
40
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
41
|
+
Requires-Dist: pytest-mock; extra == "test"
|
|
42
|
+
Provides-Extra: transformers
|
|
43
|
+
Requires-Dist: sentence-transformers>=2.2.0; extra == "transformers"
|
|
44
|
+
Provides-Extra: docs
|
|
45
|
+
Requires-Dist: sphinx>=7.0; extra == "docs"
|
|
46
|
+
Requires-Dist: sphinx-rtd-theme>=2.0; extra == "docs"
|
|
47
|
+
Requires-Dist: sphinx-copybutton>=0.5; extra == "docs"
|
|
48
|
+
Dynamic: license-file
|
|
49
|
+
|
|
50
|
+
# ragit
|
|
51
|
+
|
|
52
|
+
RAG toolkit for Python. Document loading, chunking, vector search, LLM integration.
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install ragit
|
|
58
|
+
|
|
59
|
+
# For offline embedding
|
|
60
|
+
pip install ragit[transformers]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
You must provide an embedding source: custom function, SentenceTransformers, or any provider.
|
|
66
|
+
|
|
67
|
+
### Custom Embedding Function
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from ragit import RAGAssistant
|
|
71
|
+
|
|
72
|
+
def my_embed(text: str) -> list[float]:
|
|
73
|
+
# Use any embedding API: OpenAI, Cohere, HuggingFace, etc.
|
|
74
|
+
return embedding_vector
|
|
75
|
+
|
|
76
|
+
assistant = RAGAssistant("docs/", embed_fn=my_embed)
|
|
77
|
+
results = assistant.retrieve("search query")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### With LLM for Q&A
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
def my_embed(text: str) -> list[float]:
|
|
84
|
+
return embedding_vector
|
|
85
|
+
|
|
86
|
+
def my_generate(prompt: str, system_prompt: str = "") -> str:
|
|
87
|
+
return llm_response
|
|
88
|
+
|
|
89
|
+
assistant = RAGAssistant("docs/", embed_fn=my_embed, generate_fn=my_generate)
|
|
90
|
+
answer = assistant.ask("How does authentication work?")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Offline Embedding (SentenceTransformers)
|
|
94
|
+
|
|
95
|
+
Models are downloaded automatically on first use (~90MB for default model).
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from ragit import RAGAssistant
|
|
99
|
+
from ragit.providers import SentenceTransformersProvider
|
|
100
|
+
|
|
101
|
+
# Uses all-MiniLM-L6-v2 by default
|
|
102
|
+
assistant = RAGAssistant("docs/", provider=SentenceTransformersProvider())
|
|
103
|
+
|
|
104
|
+
# Or specify a model
|
|
105
|
+
assistant = RAGAssistant(
|
|
106
|
+
"docs/",
|
|
107
|
+
provider=SentenceTransformersProvider(model_name="all-mpnet-base-v2")
|
|
108
|
+
)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Available models: `all-MiniLM-L6-v2` (384d), `all-mpnet-base-v2` (768d), `paraphrase-MiniLM-L6-v2` (384d)
|
|
112
|
+
|
|
113
|
+
## Core API
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
assistant = RAGAssistant(
|
|
117
|
+
documents, # Path, list of Documents, or list of Chunks
|
|
118
|
+
embed_fn=..., # Embedding function: (str) -> list[float]
|
|
119
|
+
generate_fn=..., # LLM function: (prompt, system_prompt) -> str
|
|
120
|
+
provider=..., # Or use a provider instead of functions
|
|
121
|
+
chunk_size=512,
|
|
122
|
+
chunk_overlap=50
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
results = assistant.retrieve(query, top_k=3) # [(Chunk, score), ...]
|
|
126
|
+
context = assistant.get_context(query, top_k=3) # Formatted string
|
|
127
|
+
answer = assistant.ask(question, top_k=3) # Requires generate_fn/LLM
|
|
128
|
+
code = assistant.generate_code(request) # Requires generate_fn/LLM
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Document Loading
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from ragit import load_text, load_directory, chunk_text
|
|
135
|
+
|
|
136
|
+
doc = load_text("file.md")
|
|
137
|
+
docs = load_directory("docs/", "*.md")
|
|
138
|
+
chunks = chunk_text(text, chunk_size=512, chunk_overlap=50, doc_id="id")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Hyperparameter Optimization
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from ragit import RagitExperiment, Document, BenchmarkQuestion
|
|
145
|
+
|
|
146
|
+
def my_embed(text: str) -> list[float]:
|
|
147
|
+
return embedding_vector
|
|
148
|
+
|
|
149
|
+
def my_generate(prompt: str, system_prompt: str = "") -> str:
|
|
150
|
+
return llm_response
|
|
151
|
+
|
|
152
|
+
docs = [Document(id="1", content="...")]
|
|
153
|
+
benchmark = [BenchmarkQuestion(question="...", ground_truth="...")]
|
|
154
|
+
|
|
155
|
+
experiment = RagitExperiment(
|
|
156
|
+
docs, benchmark,
|
|
157
|
+
embed_fn=my_embed,
|
|
158
|
+
generate_fn=my_generate
|
|
159
|
+
)
|
|
160
|
+
results = experiment.run(max_configs=20)
|
|
161
|
+
print(results[0]) # Best config
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
Apache-2.0 - RODMENA LIMITED
|
ragit-0.8.1/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# ragit
|
|
2
|
+
|
|
3
|
+
RAG toolkit for Python. Document loading, chunking, vector search, LLM integration.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install ragit
|
|
9
|
+
|
|
10
|
+
# For offline embedding
|
|
11
|
+
pip install ragit[transformers]
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
You must provide an embedding source: custom function, SentenceTransformers, or any provider.
|
|
17
|
+
|
|
18
|
+
### Custom Embedding Function
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from ragit import RAGAssistant
|
|
22
|
+
|
|
23
|
+
def my_embed(text: str) -> list[float]:
|
|
24
|
+
# Use any embedding API: OpenAI, Cohere, HuggingFace, etc.
|
|
25
|
+
return embedding_vector
|
|
26
|
+
|
|
27
|
+
assistant = RAGAssistant("docs/", embed_fn=my_embed)
|
|
28
|
+
results = assistant.retrieve("search query")
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### With LLM for Q&A
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
def my_embed(text: str) -> list[float]:
|
|
35
|
+
return embedding_vector
|
|
36
|
+
|
|
37
|
+
def my_generate(prompt: str, system_prompt: str = "") -> str:
|
|
38
|
+
return llm_response
|
|
39
|
+
|
|
40
|
+
assistant = RAGAssistant("docs/", embed_fn=my_embed, generate_fn=my_generate)
|
|
41
|
+
answer = assistant.ask("How does authentication work?")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Offline Embedding (SentenceTransformers)
|
|
45
|
+
|
|
46
|
+
Models are downloaded automatically on first use (~90MB for default model).
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from ragit import RAGAssistant
|
|
50
|
+
from ragit.providers import SentenceTransformersProvider
|
|
51
|
+
|
|
52
|
+
# Uses all-MiniLM-L6-v2 by default
|
|
53
|
+
assistant = RAGAssistant("docs/", provider=SentenceTransformersProvider())
|
|
54
|
+
|
|
55
|
+
# Or specify a model
|
|
56
|
+
assistant = RAGAssistant(
|
|
57
|
+
"docs/",
|
|
58
|
+
provider=SentenceTransformersProvider(model_name="all-mpnet-base-v2")
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Available models: `all-MiniLM-L6-v2` (384d), `all-mpnet-base-v2` (768d), `paraphrase-MiniLM-L6-v2` (384d)
|
|
63
|
+
|
|
64
|
+
## Core API
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
assistant = RAGAssistant(
|
|
68
|
+
documents, # Path, list of Documents, or list of Chunks
|
|
69
|
+
embed_fn=..., # Embedding function: (str) -> list[float]
|
|
70
|
+
generate_fn=..., # LLM function: (prompt, system_prompt) -> str
|
|
71
|
+
provider=..., # Or use a provider instead of functions
|
|
72
|
+
chunk_size=512,
|
|
73
|
+
chunk_overlap=50
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
results = assistant.retrieve(query, top_k=3) # [(Chunk, score), ...]
|
|
77
|
+
context = assistant.get_context(query, top_k=3) # Formatted string
|
|
78
|
+
answer = assistant.ask(question, top_k=3) # Requires generate_fn/LLM
|
|
79
|
+
code = assistant.generate_code(request) # Requires generate_fn/LLM
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Document Loading
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from ragit import load_text, load_directory, chunk_text
|
|
86
|
+
|
|
87
|
+
doc = load_text("file.md")
|
|
88
|
+
docs = load_directory("docs/", "*.md")
|
|
89
|
+
chunks = chunk_text(text, chunk_size=512, chunk_overlap=50, doc_id="id")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Hyperparameter Optimization
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from ragit import RagitExperiment, Document, BenchmarkQuestion
|
|
96
|
+
|
|
97
|
+
def my_embed(text: str) -> list[float]:
|
|
98
|
+
return embedding_vector
|
|
99
|
+
|
|
100
|
+
def my_generate(prompt: str, system_prompt: str = "") -> str:
|
|
101
|
+
return llm_response
|
|
102
|
+
|
|
103
|
+
docs = [Document(id="1", content="...")]
|
|
104
|
+
benchmark = [BenchmarkQuestion(question="...", ground_truth="...")]
|
|
105
|
+
|
|
106
|
+
experiment = RagitExperiment(
|
|
107
|
+
docs, benchmark,
|
|
108
|
+
embed_fn=my_embed,
|
|
109
|
+
generate_fn=my_generate
|
|
110
|
+
)
|
|
111
|
+
results = experiment.run(max_configs=20)
|
|
112
|
+
print(results[0]) # Best config
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
Apache-2.0 - RODMENA LIMITED
|
|
@@ -59,6 +59,8 @@ dev = [
|
|
|
59
59
|
|
|
60
60
|
test = ["pytest", "pytest-cov", "pytest-mock"]
|
|
61
61
|
|
|
62
|
+
transformers = ["sentence-transformers>=2.2.0"]
|
|
63
|
+
|
|
62
64
|
docs = [
|
|
63
65
|
"sphinx>=7.0",
|
|
64
66
|
"sphinx-rtd-theme>=2.0",
|
|
@@ -79,7 +81,7 @@ python_classes = ["Test*"]
|
|
|
79
81
|
python_functions = ["test_*"]
|
|
80
82
|
log_cli = false
|
|
81
83
|
log_level = "INFO"
|
|
82
|
-
addopts = "-v --tb=short"
|
|
84
|
+
addopts = "-v --tb=short -m 'not integration'"
|
|
83
85
|
markers = [
|
|
84
86
|
"integration: marks tests as integration tests (may require external services)",
|
|
85
87
|
]
|
|
@@ -9,14 +9,21 @@ Quick Start
|
|
|
9
9
|
-----------
|
|
10
10
|
>>> from ragit import RAGAssistant
|
|
11
11
|
>>>
|
|
12
|
-
>>> #
|
|
13
|
-
>>>
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
>>> # With custom embedding function (retrieval-only)
|
|
13
|
+
>>> def my_embed(text: str) -> list[float]:
|
|
14
|
+
... # Your embedding implementation
|
|
15
|
+
... pass
|
|
16
|
+
>>> assistant = RAGAssistant("docs/", embed_fn=my_embed)
|
|
17
|
+
>>> results = assistant.retrieve("How do I create a REST API?")
|
|
18
|
+
>>>
|
|
19
|
+
>>> # With SentenceTransformers (offline, requires ragit[transformers])
|
|
20
|
+
>>> from ragit.providers import SentenceTransformersProvider
|
|
21
|
+
>>> assistant = RAGAssistant("docs/", provider=SentenceTransformersProvider())
|
|
16
22
|
>>>
|
|
17
|
-
>>> #
|
|
18
|
-
>>>
|
|
19
|
-
>>>
|
|
23
|
+
>>> # With Ollama (explicit)
|
|
24
|
+
>>> from ragit.providers import OllamaProvider
|
|
25
|
+
>>> assistant = RAGAssistant("docs/", provider=OllamaProvider())
|
|
26
|
+
>>> answer = assistant.ask("How do I create a REST API?")
|
|
20
27
|
|
|
21
28
|
Optimization
|
|
22
29
|
------------
|
|
@@ -25,7 +32,8 @@ Optimization
|
|
|
25
32
|
>>> docs = [Document(id="doc1", content="...")]
|
|
26
33
|
>>> benchmark = [BenchmarkQuestion(question="What is X?", ground_truth="...")]
|
|
27
34
|
>>>
|
|
28
|
-
>>>
|
|
35
|
+
>>> # With explicit provider
|
|
36
|
+
>>> experiment = RagitExperiment(docs, benchmark, provider=OllamaProvider())
|
|
29
37
|
>>> results = experiment.run()
|
|
30
38
|
>>> print(results[0]) # Best configuration
|
|
31
39
|
"""
|
|
@@ -63,7 +71,12 @@ from ragit.loaders import ( # noqa: E402
|
|
|
63
71
|
load_directory,
|
|
64
72
|
load_text,
|
|
65
73
|
)
|
|
66
|
-
from ragit.providers import
|
|
74
|
+
from ragit.providers import ( # noqa: E402
|
|
75
|
+
BaseEmbeddingProvider,
|
|
76
|
+
BaseLLMProvider,
|
|
77
|
+
FunctionProvider,
|
|
78
|
+
OllamaProvider,
|
|
79
|
+
)
|
|
67
80
|
|
|
68
81
|
__all__ = [
|
|
69
82
|
"__version__",
|
|
@@ -79,7 +92,11 @@ __all__ = [
|
|
|
79
92
|
# Core classes
|
|
80
93
|
"Document",
|
|
81
94
|
"Chunk",
|
|
95
|
+
# Providers
|
|
82
96
|
"OllamaProvider",
|
|
97
|
+
"FunctionProvider",
|
|
98
|
+
"BaseLLMProvider",
|
|
99
|
+
"BaseEmbeddingProvider",
|
|
83
100
|
# Optimization
|
|
84
101
|
"RagitExperiment",
|
|
85
102
|
"BenchmarkQuestion",
|
|
@@ -87,3 +104,13 @@ __all__ = [
|
|
|
87
104
|
"EvaluationResult",
|
|
88
105
|
"ExperimentResults",
|
|
89
106
|
]
|
|
107
|
+
|
|
108
|
+
# Conditionally add SentenceTransformersProvider if available
|
|
109
|
+
try:
|
|
110
|
+
from ragit.providers import ( # noqa: E402
|
|
111
|
+
SentenceTransformersProvider as SentenceTransformersProvider,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
__all__ += ["SentenceTransformersProvider"]
|
|
115
|
+
except ImportError:
|
|
116
|
+
pass
|
|
@@ -10,16 +10,17 @@ Provides a simple interface for RAG-based tasks.
|
|
|
10
10
|
Note: This class is NOT thread-safe. Do not share instances across threads.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
+
from collections.abc import Callable
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from typing import TYPE_CHECKING
|
|
15
16
|
|
|
16
17
|
import numpy as np
|
|
17
18
|
from numpy.typing import NDArray
|
|
18
19
|
|
|
19
|
-
from ragit.config import config
|
|
20
20
|
from ragit.core.experiment.experiment import Chunk, Document
|
|
21
21
|
from ragit.loaders import chunk_document, chunk_rst_sections, load_directory, load_text
|
|
22
|
-
from ragit.providers import
|
|
22
|
+
from ragit.providers.base import BaseEmbeddingProvider, BaseLLMProvider
|
|
23
|
+
from ragit.providers.function_adapter import FunctionProvider
|
|
23
24
|
|
|
24
25
|
if TYPE_CHECKING:
|
|
25
26
|
from numpy.typing import NDArray
|
|
@@ -38,48 +39,100 @@ class RAGAssistant:
|
|
|
38
39
|
- List of Document objects
|
|
39
40
|
- Path to a single file
|
|
40
41
|
- Path to a directory (will load all .txt, .md, .rst files)
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
embed_fn : Callable[[str], list[float]], optional
|
|
43
|
+
Function that takes text and returns an embedding vector.
|
|
44
|
+
If provided, creates a FunctionProvider internally.
|
|
45
|
+
generate_fn : Callable, optional
|
|
46
|
+
Function for text generation. Supports (prompt) or (prompt, system_prompt).
|
|
47
|
+
If provided without embed_fn, must also provide embed_fn.
|
|
48
|
+
provider : BaseEmbeddingProvider, optional
|
|
49
|
+
Provider for embeddings (and optionally LLM). If embed_fn is provided,
|
|
50
|
+
this is ignored for embeddings.
|
|
43
51
|
embedding_model : str, optional
|
|
44
|
-
Embedding model name
|
|
52
|
+
Embedding model name (used with provider).
|
|
45
53
|
llm_model : str, optional
|
|
46
|
-
LLM model name
|
|
54
|
+
LLM model name (used with provider).
|
|
47
55
|
chunk_size : int, optional
|
|
48
56
|
Chunk size for splitting documents (default: 512).
|
|
49
57
|
chunk_overlap : int, optional
|
|
50
58
|
Overlap between chunks (default: 50).
|
|
51
59
|
|
|
60
|
+
Raises
|
|
61
|
+
------
|
|
62
|
+
ValueError
|
|
63
|
+
If neither embed_fn nor provider is provided.
|
|
64
|
+
|
|
52
65
|
Note
|
|
53
66
|
----
|
|
54
67
|
This class is NOT thread-safe. Each thread should have its own instance.
|
|
55
68
|
|
|
56
69
|
Examples
|
|
57
70
|
--------
|
|
58
|
-
>>> #
|
|
59
|
-
>>> assistant = RAGAssistant(
|
|
71
|
+
>>> # With custom embedding function (retrieval-only)
|
|
72
|
+
>>> assistant = RAGAssistant(docs, embed_fn=my_embed)
|
|
73
|
+
>>> results = assistant.retrieve("query")
|
|
74
|
+
>>>
|
|
75
|
+
>>> # With custom embedding and LLM functions (full RAG)
|
|
76
|
+
>>> assistant = RAGAssistant(docs, embed_fn=my_embed, generate_fn=my_llm)
|
|
60
77
|
>>> answer = assistant.ask("What is X?")
|
|
61
|
-
|
|
62
|
-
>>> #
|
|
63
|
-
>>>
|
|
64
|
-
>>>
|
|
65
|
-
|
|
66
|
-
>>> #
|
|
67
|
-
>>>
|
|
68
|
-
>>>
|
|
78
|
+
>>>
|
|
79
|
+
>>> # With explicit provider
|
|
80
|
+
>>> from ragit.providers import OllamaProvider
|
|
81
|
+
>>> assistant = RAGAssistant(docs, provider=OllamaProvider())
|
|
82
|
+
>>>
|
|
83
|
+
>>> # With SentenceTransformers (offline)
|
|
84
|
+
>>> from ragit.providers import SentenceTransformersProvider
|
|
85
|
+
>>> assistant = RAGAssistant(docs, provider=SentenceTransformersProvider())
|
|
69
86
|
"""
|
|
70
87
|
|
|
71
88
|
def __init__(
|
|
72
89
|
self,
|
|
73
90
|
documents: list[Document] | str | Path,
|
|
74
|
-
|
|
91
|
+
embed_fn: Callable[[str], list[float]] | None = None,
|
|
92
|
+
generate_fn: Callable[..., str] | None = None,
|
|
93
|
+
provider: BaseEmbeddingProvider | BaseLLMProvider | None = None,
|
|
75
94
|
embedding_model: str | None = None,
|
|
76
95
|
llm_model: str | None = None,
|
|
77
96
|
chunk_size: int = 512,
|
|
78
97
|
chunk_overlap: int = 50,
|
|
79
98
|
):
|
|
80
|
-
|
|
81
|
-
self.
|
|
82
|
-
self.
|
|
99
|
+
# Resolve provider from embed_fn/generate_fn or explicit provider
|
|
100
|
+
self._embedding_provider: BaseEmbeddingProvider
|
|
101
|
+
self._llm_provider: BaseLLMProvider | None = None
|
|
102
|
+
|
|
103
|
+
if embed_fn is not None:
|
|
104
|
+
# Create FunctionProvider from provided functions
|
|
105
|
+
function_provider = FunctionProvider(
|
|
106
|
+
embed_fn=embed_fn,
|
|
107
|
+
generate_fn=generate_fn,
|
|
108
|
+
)
|
|
109
|
+
self._embedding_provider = function_provider
|
|
110
|
+
if generate_fn is not None:
|
|
111
|
+
self._llm_provider = function_provider
|
|
112
|
+
elif provider is not None and isinstance(provider, BaseLLMProvider):
|
|
113
|
+
# Use explicit provider for LLM if function_provider doesn't have LLM
|
|
114
|
+
self._llm_provider = provider
|
|
115
|
+
elif provider is not None:
|
|
116
|
+
# Use explicit provider
|
|
117
|
+
if not isinstance(provider, BaseEmbeddingProvider):
|
|
118
|
+
raise ValueError(
|
|
119
|
+
"Provider must implement BaseEmbeddingProvider for embeddings. "
|
|
120
|
+
"Alternatively, provide embed_fn."
|
|
121
|
+
)
|
|
122
|
+
self._embedding_provider = provider
|
|
123
|
+
if isinstance(provider, BaseLLMProvider):
|
|
124
|
+
self._llm_provider = provider
|
|
125
|
+
else:
|
|
126
|
+
raise ValueError(
|
|
127
|
+
"Must provide embed_fn or provider for embeddings. "
|
|
128
|
+
"Examples:\n"
|
|
129
|
+
" RAGAssistant(docs, embed_fn=my_embed_function)\n"
|
|
130
|
+
" RAGAssistant(docs, provider=OllamaProvider())\n"
|
|
131
|
+
" RAGAssistant(docs, provider=SentenceTransformersProvider())"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
self.embedding_model = embedding_model or "default"
|
|
135
|
+
self.llm_model = llm_model or "default"
|
|
83
136
|
self.chunk_size = chunk_size
|
|
84
137
|
self.chunk_overlap = chunk_overlap
|
|
85
138
|
|
|
@@ -128,7 +181,7 @@ class RAGAssistant:
|
|
|
128
181
|
|
|
129
182
|
# Batch embed all chunks at once (single API call)
|
|
130
183
|
texts = [chunk.content for chunk in all_chunks]
|
|
131
|
-
responses = self.
|
|
184
|
+
responses = self._embedding_provider.embed_batch(texts, self.embedding_model)
|
|
132
185
|
|
|
133
186
|
# Build embedding matrix directly (skip storing in chunks to avoid duplication)
|
|
134
187
|
embedding_matrix = np.array([response.embedding for response in responses], dtype=np.float64)
|
|
@@ -169,7 +222,7 @@ class RAGAssistant:
|
|
|
169
222
|
return []
|
|
170
223
|
|
|
171
224
|
# Get query embedding and normalize
|
|
172
|
-
query_response = self.
|
|
225
|
+
query_response = self._embedding_provider.embed(query, self.embedding_model)
|
|
173
226
|
query_vec = np.array(query_response.embedding, dtype=np.float64)
|
|
174
227
|
query_norm = np.linalg.norm(query_vec)
|
|
175
228
|
if query_norm == 0:
|
|
@@ -209,6 +262,15 @@ class RAGAssistant:
|
|
|
209
262
|
results = self.retrieve(query, top_k)
|
|
210
263
|
return "\n\n---\n\n".join(chunk.content for chunk, _ in results)
|
|
211
264
|
|
|
265
|
+
def _ensure_llm(self) -> BaseLLMProvider:
|
|
266
|
+
"""Ensure LLM provider is available."""
|
|
267
|
+
if self._llm_provider is None:
|
|
268
|
+
raise NotImplementedError(
|
|
269
|
+
"No LLM configured. Provide generate_fn or a provider with LLM support "
|
|
270
|
+
"to use ask(), generate(), or generate_code() methods."
|
|
271
|
+
)
|
|
272
|
+
return self._llm_provider
|
|
273
|
+
|
|
212
274
|
def generate(
|
|
213
275
|
self,
|
|
214
276
|
prompt: str,
|
|
@@ -231,8 +293,14 @@ class RAGAssistant:
|
|
|
231
293
|
-------
|
|
232
294
|
str
|
|
233
295
|
Generated text.
|
|
296
|
+
|
|
297
|
+
Raises
|
|
298
|
+
------
|
|
299
|
+
NotImplementedError
|
|
300
|
+
If no LLM is configured.
|
|
234
301
|
"""
|
|
235
|
-
|
|
302
|
+
llm = self._ensure_llm()
|
|
303
|
+
response = llm.generate(
|
|
236
304
|
prompt=prompt,
|
|
237
305
|
model=self.llm_model,
|
|
238
306
|
system_prompt=system_prompt,
|
|
@@ -266,6 +334,11 @@ class RAGAssistant:
|
|
|
266
334
|
str
|
|
267
335
|
Generated answer.
|
|
268
336
|
|
|
337
|
+
Raises
|
|
338
|
+
------
|
|
339
|
+
NotImplementedError
|
|
340
|
+
If no LLM is configured.
|
|
341
|
+
|
|
269
342
|
Examples
|
|
270
343
|
--------
|
|
271
344
|
>>> answer = assistant.ask("How do I create a REST API?")
|
|
@@ -315,6 +388,11 @@ Answer:"""
|
|
|
315
388
|
str
|
|
316
389
|
Generated code (cleaned, without markdown).
|
|
317
390
|
|
|
391
|
+
Raises
|
|
392
|
+
------
|
|
393
|
+
NotImplementedError
|
|
394
|
+
If no LLM is configured.
|
|
395
|
+
|
|
318
396
|
Examples
|
|
319
397
|
--------
|
|
320
398
|
>>> code = assistant.generate_code("create a REST API with user endpoints")
|
|
@@ -357,3 +435,8 @@ Generate the {language} code:"""
|
|
|
357
435
|
def num_documents(self) -> int:
|
|
358
436
|
"""Return number of loaded documents."""
|
|
359
437
|
return len(self.documents)
|
|
438
|
+
|
|
439
|
+
@property
|
|
440
|
+
def has_llm(self) -> bool:
|
|
441
|
+
"""Check if LLM is configured."""
|
|
442
|
+
return self._llm_provider is not None
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
Ragit configuration management.
|
|
7
7
|
|
|
8
8
|
Loads configuration from environment variables and .env files.
|
|
9
|
+
|
|
10
|
+
Note: As of v0.8.0, ragit no longer has default LLM or embedding models.
|
|
11
|
+
Users must explicitly configure providers.
|
|
9
12
|
"""
|
|
10
13
|
|
|
11
14
|
import os
|
|
@@ -27,21 +30,27 @@ else:
|
|
|
27
30
|
|
|
28
31
|
|
|
29
32
|
class Config:
|
|
30
|
-
"""Ragit configuration loaded from environment variables.
|
|
33
|
+
"""Ragit configuration loaded from environment variables.
|
|
34
|
+
|
|
35
|
+
Note: As of v0.8.0, DEFAULT_LLM_MODEL and DEFAULT_EMBEDDING_MODEL are
|
|
36
|
+
no longer used as defaults. They are only read from environment variables
|
|
37
|
+
for backwards compatibility with user configurations.
|
|
38
|
+
"""
|
|
31
39
|
|
|
32
|
-
# Ollama LLM API Configuration (
|
|
40
|
+
# Ollama LLM API Configuration (used when explicitly using OllamaProvider)
|
|
33
41
|
OLLAMA_BASE_URL: str = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
|
|
34
42
|
OLLAMA_API_KEY: str | None = os.getenv("OLLAMA_API_KEY")
|
|
35
43
|
OLLAMA_TIMEOUT: int = int(os.getenv("OLLAMA_TIMEOUT", "120"))
|
|
36
44
|
|
|
37
|
-
# Ollama Embedding API Configuration
|
|
45
|
+
# Ollama Embedding API Configuration
|
|
38
46
|
OLLAMA_EMBEDDING_URL: str = os.getenv(
|
|
39
47
|
"OLLAMA_EMBEDDING_URL", os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
|
|
40
48
|
)
|
|
41
49
|
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
# Model settings (only used if explicitly requested, no defaults)
|
|
51
|
+
# These can still be set via environment variables for convenience
|
|
52
|
+
DEFAULT_LLM_MODEL: str | None = os.getenv("RAGIT_DEFAULT_LLM_MODEL")
|
|
53
|
+
DEFAULT_EMBEDDING_MODEL: str | None = os.getenv("RAGIT_DEFAULT_EMBEDDING_MODEL")
|
|
45
54
|
|
|
46
55
|
# Logging
|
|
47
56
|
LOG_LEVEL: str = os.getenv("RAGIT_LOG_LEVEL", "INFO")
|