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.
Files changed (29) hide show
  1. ragit-0.8.1/PKG-INFO +166 -0
  2. ragit-0.8.1/README.md +117 -0
  3. {ragit-0.7.5 → ragit-0.8.1}/pyproject.toml +3 -1
  4. {ragit-0.7.5 → ragit-0.8.1}/ragit/__init__.py +36 -9
  5. {ragit-0.7.5 → ragit-0.8.1}/ragit/assistant.py +106 -23
  6. {ragit-0.7.5 → ragit-0.8.1}/ragit/config.py +15 -6
  7. {ragit-0.7.5 → ragit-0.8.1}/ragit/core/experiment/experiment.py +85 -20
  8. ragit-0.8.1/ragit/providers/__init__.py +47 -0
  9. ragit-0.8.1/ragit/providers/function_adapter.py +237 -0
  10. {ragit-0.7.5 → ragit-0.8.1}/ragit/providers/ollama.py +1 -1
  11. ragit-0.8.1/ragit/providers/sentence_transformers.py +225 -0
  12. {ragit-0.7.5 → ragit-0.8.1}/ragit/version.py +1 -1
  13. ragit-0.8.1/ragit.egg-info/PKG-INFO +166 -0
  14. {ragit-0.7.5 → ragit-0.8.1}/ragit.egg-info/SOURCES.txt +2 -0
  15. {ragit-0.7.5 → ragit-0.8.1}/ragit.egg-info/requires.txt +3 -0
  16. ragit-0.7.5/PKG-INFO +0 -553
  17. ragit-0.7.5/README.md +0 -506
  18. ragit-0.7.5/ragit/providers/__init__.py +0 -20
  19. ragit-0.7.5/ragit.egg-info/PKG-INFO +0 -553
  20. {ragit-0.7.5 → ragit-0.8.1}/LICENSE +0 -0
  21. {ragit-0.7.5 → ragit-0.8.1}/ragit/core/__init__.py +0 -0
  22. {ragit-0.7.5 → ragit-0.8.1}/ragit/core/experiment/__init__.py +0 -0
  23. {ragit-0.7.5 → ragit-0.8.1}/ragit/core/experiment/results.py +0 -0
  24. {ragit-0.7.5 → ragit-0.8.1}/ragit/loaders.py +0 -0
  25. {ragit-0.7.5 → ragit-0.8.1}/ragit/providers/base.py +0 -0
  26. {ragit-0.7.5 → ragit-0.8.1}/ragit/utils/__init__.py +0 -0
  27. {ragit-0.7.5 → ragit-0.8.1}/ragit.egg-info/dependency_links.txt +0 -0
  28. {ragit-0.7.5 → ragit-0.8.1}/ragit.egg-info/top_level.txt +0 -0
  29. {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
- >>> # Load docs and ask questions
13
- >>> assistant = RAGAssistant("docs/")
14
- >>> answer = assistant.ask("How do I create a REST API?")
15
- >>> print(answer)
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
- >>> # Generate code
18
- >>> code = assistant.generate_code("create a user authentication API")
19
- >>> print(code)
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
- >>> experiment = RagitExperiment(docs, benchmark)
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 OllamaProvider # noqa: E402
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 OllamaProvider
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
- provider : OllamaProvider, optional
42
- LLM/embedding provider. Defaults to OllamaProvider().
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. Defaults to config.DEFAULT_EMBEDDING_MODEL.
52
+ Embedding model name (used with provider).
45
53
  llm_model : str, optional
46
- LLM model name. Defaults to config.DEFAULT_LLM_MODEL.
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
- >>> # From documents
59
- >>> assistant = RAGAssistant([Document(id="doc1", content="...")])
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
- >>> # From file
63
- >>> assistant = RAGAssistant("docs/tutorial.rst")
64
- >>> answer = assistant.ask("How do I do Y?")
65
-
66
- >>> # From directory
67
- >>> assistant = RAGAssistant("docs/")
68
- >>> answer = assistant.ask("Explain Z")
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
- provider: OllamaProvider | None = None,
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
- self.provider = provider or OllamaProvider()
81
- self.embedding_model = embedding_model or config.DEFAULT_EMBEDDING_MODEL
82
- self.llm_model = llm_model or config.DEFAULT_LLM_MODEL
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.provider.embed_batch(texts, self.embedding_model)
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.provider.embed(query, self.embedding_model)
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
- response = self.provider.generate(
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 (can be cloud)
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 (cloud doesn't support embeddings, use local)
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
- # Default Models
43
- DEFAULT_LLM_MODEL: str = os.getenv("RAGIT_DEFAULT_LLM_MODEL", "qwen3-vl:235b-instruct")
44
- DEFAULT_EMBEDDING_MODEL: str = os.getenv("RAGIT_DEFAULT_EMBEDDING_MODEL", "nomic-embed-text:latest")
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")