haiku.rag 0.12.1__py3-none-any.whl → 0.13.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.
Potentially problematic release.
This version of haiku.rag might be problematic. Click here for more details.
- haiku/rag/a2a/__init__.py +3 -3
- haiku/rag/app.py +7 -5
- haiku/rag/chunker.py +1 -1
- haiku/rag/cli.py +72 -31
- haiku/rag/client.py +36 -10
- haiku/rag/config/__init__.py +50 -0
- haiku/rag/config/loader.py +137 -0
- haiku/rag/config/models.py +82 -0
- haiku/rag/embeddings/__init__.py +25 -11
- haiku/rag/embeddings/base.py +6 -4
- haiku/rag/embeddings/ollama.py +3 -2
- haiku/rag/embeddings/vllm.py +2 -2
- haiku/rag/graph/common.py +2 -2
- haiku/rag/mcp.py +14 -8
- haiku/rag/monitor.py +17 -4
- haiku/rag/qa/__init__.py +16 -3
- haiku/rag/qa/agent.py +4 -2
- haiku/rag/reranking/__init__.py +24 -16
- haiku/rag/reranking/base.py +1 -1
- haiku/rag/reranking/cohere.py +2 -2
- haiku/rag/reranking/mxbai.py +1 -1
- haiku/rag/reranking/vllm.py +1 -1
- haiku/rag/store/engine.py +19 -12
- haiku/rag/store/repositories/chunk.py +12 -8
- haiku/rag/store/repositories/document.py +4 -4
- haiku/rag/store/repositories/settings.py +19 -9
- haiku/rag/utils.py +9 -9
- {haiku_rag-0.12.1.dist-info → haiku_rag-0.13.1.dist-info}/METADATA +20 -10
- {haiku_rag-0.12.1.dist-info → haiku_rag-0.13.1.dist-info}/RECORD +32 -31
- haiku/rag/config.py +0 -90
- haiku/rag/migration.py +0 -316
- {haiku_rag-0.12.1.dist-info → haiku_rag-0.13.1.dist-info}/WHEEL +0 -0
- {haiku_rag-0.12.1.dist-info → haiku_rag-0.13.1.dist-info}/entry_points.txt +0 -0
- {haiku_rag-0.12.1.dist-info → haiku_rag-0.13.1.dist-info}/licenses/LICENSE +0 -0
haiku/rag/a2a/__init__.py
CHANGED
|
@@ -57,12 +57,12 @@ def create_a2a_app(
|
|
|
57
57
|
"""
|
|
58
58
|
base_storage = InMemoryStorage()
|
|
59
59
|
storage = LRUMemoryStorage(
|
|
60
|
-
storage=base_storage, max_contexts=Config.
|
|
60
|
+
storage=base_storage, max_contexts=Config.a2a.max_contexts
|
|
61
61
|
)
|
|
62
62
|
broker = InMemoryBroker()
|
|
63
63
|
|
|
64
64
|
# Create the agent with native search tool
|
|
65
|
-
model = get_model(Config.
|
|
65
|
+
model = get_model(Config.qa.provider, Config.qa.model)
|
|
66
66
|
agent = Agent(
|
|
67
67
|
model=model,
|
|
68
68
|
deps_type=AgentDependencies,
|
|
@@ -120,7 +120,7 @@ def create_a2a_app(
|
|
|
120
120
|
# Create FastA2A app with custom worker lifecycle
|
|
121
121
|
@asynccontextmanager
|
|
122
122
|
async def lifespan(app):
|
|
123
|
-
logger.info(f"Started A2A server (max contexts: {Config.
|
|
123
|
+
logger.info(f"Started A2A server (max contexts: {Config.a2a.max_contexts})")
|
|
124
124
|
async with app.task_manager:
|
|
125
125
|
async with worker.run():
|
|
126
126
|
yield
|
haiku/rag/app.py
CHANGED
|
@@ -231,8 +231,8 @@ class HaikuRAGApp:
|
|
|
231
231
|
)
|
|
232
232
|
|
|
233
233
|
start_node = DeepQAPlanNode(
|
|
234
|
-
provider=Config.
|
|
235
|
-
model=Config.
|
|
234
|
+
provider=Config.qa.provider,
|
|
235
|
+
model=Config.qa.model,
|
|
236
236
|
)
|
|
237
237
|
|
|
238
238
|
result = await graph.run(
|
|
@@ -278,8 +278,8 @@ class HaikuRAGApp:
|
|
|
278
278
|
)
|
|
279
279
|
|
|
280
280
|
start = PlanNode(
|
|
281
|
-
provider=Config.
|
|
282
|
-
model=Config.
|
|
281
|
+
provider=Config.research.provider or Config.qa.provider,
|
|
282
|
+
model=Config.research.model or Config.qa.model,
|
|
283
283
|
)
|
|
284
284
|
report = None
|
|
285
285
|
async for event in stream_research_graph(graph, start, state, deps):
|
|
@@ -474,7 +474,9 @@ class HaikuRAGApp:
|
|
|
474
474
|
|
|
475
475
|
# Start file monitor if enabled
|
|
476
476
|
if enable_monitor:
|
|
477
|
-
monitor = FileWatcher(
|
|
477
|
+
monitor = FileWatcher(
|
|
478
|
+
paths=Config.storage.monitor_directories, client=client
|
|
479
|
+
)
|
|
478
480
|
monitor_task = asyncio.create_task(monitor.observe())
|
|
479
481
|
tasks.append(monitor_task)
|
|
480
482
|
|
haiku/rag/chunker.py
CHANGED
haiku/rag/cli.py
CHANGED
|
@@ -42,10 +42,21 @@ def main(
|
|
|
42
42
|
callback=version_callback,
|
|
43
43
|
help="Show version and exit",
|
|
44
44
|
),
|
|
45
|
+
config: Path | None = typer.Option(
|
|
46
|
+
None,
|
|
47
|
+
"--config",
|
|
48
|
+
help="Path to YAML configuration file",
|
|
49
|
+
),
|
|
45
50
|
):
|
|
46
51
|
"""haiku.rag CLI - Vector database RAG system"""
|
|
52
|
+
# Store config path in environment for config loader to use
|
|
53
|
+
if config:
|
|
54
|
+
import os
|
|
55
|
+
|
|
56
|
+
os.environ["HAIKU_RAG_CONFIG_PATH"] = str(config.absolute())
|
|
57
|
+
|
|
47
58
|
# Configure logging minimally for CLI context
|
|
48
|
-
if Config.
|
|
59
|
+
if Config.environment == "development":
|
|
49
60
|
# Lazy import logfire only in development
|
|
50
61
|
try:
|
|
51
62
|
import logfire # type: ignore
|
|
@@ -69,7 +80,7 @@ def main(
|
|
|
69
80
|
@cli.command("list", help="List all stored documents")
|
|
70
81
|
def list_documents(
|
|
71
82
|
db: Path = typer.Option(
|
|
72
|
-
Config.
|
|
83
|
+
Config.storage.data_dir / "haiku.rag.lancedb",
|
|
73
84
|
"--db",
|
|
74
85
|
help="Path to the LanceDB database file",
|
|
75
86
|
),
|
|
@@ -116,7 +127,7 @@ def add_document_text(
|
|
|
116
127
|
metavar="KEY=VALUE",
|
|
117
128
|
),
|
|
118
129
|
db: Path = typer.Option(
|
|
119
|
-
Config.
|
|
130
|
+
Config.storage.data_dir / "haiku.rag.lancedb",
|
|
120
131
|
"--db",
|
|
121
132
|
help="Path to the LanceDB database file",
|
|
122
133
|
),
|
|
@@ -145,7 +156,7 @@ def add_document_src(
|
|
|
145
156
|
metavar="KEY=VALUE",
|
|
146
157
|
),
|
|
147
158
|
db: Path = typer.Option(
|
|
148
|
-
Config.
|
|
159
|
+
Config.storage.data_dir / "haiku.rag.lancedb",
|
|
149
160
|
"--db",
|
|
150
161
|
help="Path to the LanceDB database file",
|
|
151
162
|
),
|
|
@@ -167,7 +178,7 @@ def get_document(
|
|
|
167
178
|
help="The ID of the document to get",
|
|
168
179
|
),
|
|
169
180
|
db: Path = typer.Option(
|
|
170
|
-
Config.
|
|
181
|
+
Config.storage.data_dir / "haiku.rag.lancedb",
|
|
171
182
|
"--db",
|
|
172
183
|
help="Path to the LanceDB database file",
|
|
173
184
|
),
|
|
@@ -184,7 +195,7 @@ def delete_document(
|
|
|
184
195
|
help="The ID of the document to delete",
|
|
185
196
|
),
|
|
186
197
|
db: Path = typer.Option(
|
|
187
|
-
Config.
|
|
198
|
+
Config.storage.data_dir / "haiku.rag.lancedb",
|
|
188
199
|
"--db",
|
|
189
200
|
help="Path to the LanceDB database file",
|
|
190
201
|
),
|
|
@@ -211,7 +222,7 @@ def search(
|
|
|
211
222
|
help="Maximum number of results to return",
|
|
212
223
|
),
|
|
213
224
|
db: Path = typer.Option(
|
|
214
|
-
Config.
|
|
225
|
+
Config.storage.data_dir / "haiku.rag.lancedb",
|
|
215
226
|
"--db",
|
|
216
227
|
help="Path to the LanceDB database file",
|
|
217
228
|
),
|
|
@@ -228,7 +239,7 @@ def ask(
|
|
|
228
239
|
help="The question to ask",
|
|
229
240
|
),
|
|
230
241
|
db: Path = typer.Option(
|
|
231
|
-
Config.
|
|
242
|
+
Config.storage.data_dir / "haiku.rag.lancedb",
|
|
232
243
|
"--db",
|
|
233
244
|
help="Path to the LanceDB database file",
|
|
234
245
|
),
|
|
@@ -276,7 +287,7 @@ def research(
|
|
|
276
287
|
help="Max concurrent searches per iteration (planned)",
|
|
277
288
|
),
|
|
278
289
|
db: Path = typer.Option(
|
|
279
|
-
Config.
|
|
290
|
+
Config.storage.data_dir / "haiku.rag.lancedb",
|
|
280
291
|
"--db",
|
|
281
292
|
help="Path to the LanceDB database file",
|
|
282
293
|
),
|
|
@@ -308,13 +319,61 @@ def settings():
|
|
|
308
319
|
app.show_settings()
|
|
309
320
|
|
|
310
321
|
|
|
322
|
+
@cli.command("init-config", help="Generate a YAML configuration file")
|
|
323
|
+
def init_config(
|
|
324
|
+
output: Path = typer.Argument(
|
|
325
|
+
Path("haiku.rag.yaml"),
|
|
326
|
+
help="Output path for the config file",
|
|
327
|
+
),
|
|
328
|
+
from_env: bool = typer.Option(
|
|
329
|
+
False,
|
|
330
|
+
"--from-env",
|
|
331
|
+
help="Migrate settings from .env file",
|
|
332
|
+
),
|
|
333
|
+
):
|
|
334
|
+
"""Generate a YAML configuration file with defaults or from .env."""
|
|
335
|
+
import yaml
|
|
336
|
+
|
|
337
|
+
from haiku.rag.config.loader import generate_default_config, load_config_from_env
|
|
338
|
+
|
|
339
|
+
if output.exists():
|
|
340
|
+
typer.echo(
|
|
341
|
+
f"Error: {output} already exists. Remove it first or choose a different path."
|
|
342
|
+
)
|
|
343
|
+
raise typer.Exit(1)
|
|
344
|
+
|
|
345
|
+
if from_env:
|
|
346
|
+
# Load from environment variables (including .env if present)
|
|
347
|
+
from dotenv import load_dotenv
|
|
348
|
+
|
|
349
|
+
load_dotenv()
|
|
350
|
+
config_data = load_config_from_env()
|
|
351
|
+
if not config_data:
|
|
352
|
+
typer.echo("Warning: No environment variables found to migrate.")
|
|
353
|
+
typer.echo("Generating default configuration instead.")
|
|
354
|
+
config_data = generate_default_config()
|
|
355
|
+
else:
|
|
356
|
+
config_data = generate_default_config()
|
|
357
|
+
|
|
358
|
+
# Write YAML with comments
|
|
359
|
+
with open(output, "w") as f:
|
|
360
|
+
f.write("# haiku.rag configuration file\n")
|
|
361
|
+
f.write(
|
|
362
|
+
"# See https://ggozad.github.io/haiku.rag/configuration/ for details\n\n"
|
|
363
|
+
)
|
|
364
|
+
yaml.dump(config_data, f, default_flow_style=False, sort_keys=False)
|
|
365
|
+
|
|
366
|
+
typer.echo(f"Configuration file created: {output}")
|
|
367
|
+
typer.echo("Edit the file to customize your settings.")
|
|
368
|
+
|
|
369
|
+
|
|
311
370
|
@cli.command(
|
|
312
371
|
"rebuild",
|
|
313
372
|
help="Rebuild the database by deleting all chunks and re-indexing all documents",
|
|
314
373
|
)
|
|
315
374
|
def rebuild(
|
|
316
375
|
db: Path = typer.Option(
|
|
317
|
-
Config.
|
|
376
|
+
Config.storage.data_dir / "haiku.rag.lancedb",
|
|
318
377
|
"--db",
|
|
319
378
|
help="Path to the LanceDB database file",
|
|
320
379
|
),
|
|
@@ -328,7 +387,7 @@ def rebuild(
|
|
|
328
387
|
@cli.command("vacuum", help="Optimize and clean up all tables to reduce disk usage")
|
|
329
388
|
def vacuum(
|
|
330
389
|
db: Path = typer.Option(
|
|
331
|
-
Config.
|
|
390
|
+
Config.storage.data_dir / "haiku.rag.lancedb",
|
|
332
391
|
"--db",
|
|
333
392
|
help="Path to the LanceDB database file",
|
|
334
393
|
),
|
|
@@ -342,7 +401,7 @@ def vacuum(
|
|
|
342
401
|
@cli.command("info", help="Show read-only database info (no upgrades or writes)")
|
|
343
402
|
def info(
|
|
344
403
|
db: Path = typer.Option(
|
|
345
|
-
Config.
|
|
404
|
+
Config.storage.data_dir / "haiku.rag.lancedb",
|
|
346
405
|
"--db",
|
|
347
406
|
help="Path to the LanceDB database file",
|
|
348
407
|
),
|
|
@@ -371,7 +430,7 @@ def download_models_cmd():
|
|
|
371
430
|
)
|
|
372
431
|
def serve(
|
|
373
432
|
db: Path = typer.Option(
|
|
374
|
-
Config.
|
|
433
|
+
Config.storage.data_dir / "haiku.rag.lancedb",
|
|
375
434
|
"--db",
|
|
376
435
|
help="Path to the LanceDB database file",
|
|
377
436
|
),
|
|
@@ -442,24 +501,6 @@ def serve(
|
|
|
442
501
|
)
|
|
443
502
|
|
|
444
503
|
|
|
445
|
-
@cli.command("migrate", help="Migrate an SQLite database to LanceDB")
|
|
446
|
-
def migrate(
|
|
447
|
-
sqlite_path: Path = typer.Argument(
|
|
448
|
-
help="Path to the SQLite database file to migrate",
|
|
449
|
-
),
|
|
450
|
-
):
|
|
451
|
-
# Generate LanceDB path in same parent directory
|
|
452
|
-
lancedb_path = sqlite_path.parent / (sqlite_path.stem + ".lancedb")
|
|
453
|
-
|
|
454
|
-
# Lazy import to avoid heavy deps on simple invocations
|
|
455
|
-
from haiku.rag.migration import migrate_sqlite_to_lancedb
|
|
456
|
-
|
|
457
|
-
success = asyncio.run(migrate_sqlite_to_lancedb(sqlite_path, lancedb_path))
|
|
458
|
-
|
|
459
|
-
if not success:
|
|
460
|
-
raise typer.Exit(1)
|
|
461
|
-
|
|
462
|
-
|
|
463
504
|
@cli.command(
|
|
464
505
|
"a2aclient", help="Run interactive client to chat with haiku.rag's A2A server"
|
|
465
506
|
)
|
haiku/rag/client.py
CHANGED
|
@@ -8,8 +8,7 @@ from urllib.parse import urlparse
|
|
|
8
8
|
|
|
9
9
|
import httpx
|
|
10
10
|
|
|
11
|
-
from haiku.rag.config import Config
|
|
12
|
-
from haiku.rag.reader import FileReader
|
|
11
|
+
from haiku.rag.config import AppConfig, Config
|
|
13
12
|
from haiku.rag.reranking import get_reranker
|
|
14
13
|
from haiku.rag.store.engine import Store
|
|
15
14
|
from haiku.rag.store.models.chunk import Chunk
|
|
@@ -17,7 +16,6 @@ from haiku.rag.store.models.document import Document
|
|
|
17
16
|
from haiku.rag.store.repositories.chunk import ChunkRepository
|
|
18
17
|
from haiku.rag.store.repositories.document import DocumentRepository
|
|
19
18
|
from haiku.rag.store.repositories.settings import SettingsRepository
|
|
20
|
-
from haiku.rag.utils import text_to_docling_document
|
|
21
19
|
|
|
22
20
|
logger = logging.getLogger(__name__)
|
|
23
21
|
|
|
@@ -27,16 +25,23 @@ class HaikuRAG:
|
|
|
27
25
|
|
|
28
26
|
def __init__(
|
|
29
27
|
self,
|
|
30
|
-
db_path: Path
|
|
28
|
+
db_path: Path | None = None,
|
|
29
|
+
config: AppConfig = Config,
|
|
31
30
|
skip_validation: bool = False,
|
|
32
31
|
):
|
|
33
32
|
"""Initialize the RAG client with a database path.
|
|
34
33
|
|
|
35
34
|
Args:
|
|
36
|
-
db_path: Path to the database file.
|
|
35
|
+
db_path: Path to the database file. If None, uses config.storage.data_dir.
|
|
36
|
+
config: Configuration to use. Defaults to global Config.
|
|
37
37
|
skip_validation: Whether to skip configuration validation on database load.
|
|
38
38
|
"""
|
|
39
|
-
self.
|
|
39
|
+
self._config = config
|
|
40
|
+
if db_path is None:
|
|
41
|
+
db_path = self._config.storage.data_dir / "haiku.rag.lancedb"
|
|
42
|
+
self.store = Store(
|
|
43
|
+
db_path, config=self._config, skip_validation=skip_validation
|
|
44
|
+
)
|
|
40
45
|
self.document_repository = DocumentRepository(self.store)
|
|
41
46
|
self.chunk_repository = ChunkRepository(self.store)
|
|
42
47
|
|
|
@@ -91,6 +96,9 @@ class HaikuRAG:
|
|
|
91
96
|
Returns:
|
|
92
97
|
The created Document instance.
|
|
93
98
|
"""
|
|
99
|
+
# Lazy import to avoid loading docling
|
|
100
|
+
from haiku.rag.utils import text_to_docling_document
|
|
101
|
+
|
|
94
102
|
# Convert content to DoclingDocument for processing
|
|
95
103
|
docling_document = text_to_docling_document(content)
|
|
96
104
|
|
|
@@ -127,6 +135,8 @@ class HaikuRAG:
|
|
|
127
135
|
ValueError: If the file/URL cannot be parsed or doesn't exist
|
|
128
136
|
httpx.RequestError: If URL request fails
|
|
129
137
|
"""
|
|
138
|
+
# Lazy import to avoid loading docling
|
|
139
|
+
from haiku.rag.reader import FileReader
|
|
130
140
|
|
|
131
141
|
# Normalize metadata
|
|
132
142
|
metadata = metadata or {}
|
|
@@ -181,6 +191,9 @@ class HaikuRAG:
|
|
|
181
191
|
Raises:
|
|
182
192
|
ValueError: If the file cannot be parsed or doesn't exist
|
|
183
193
|
"""
|
|
194
|
+
# Lazy import to avoid loading docling
|
|
195
|
+
from haiku.rag.reader import FileReader
|
|
196
|
+
|
|
184
197
|
metadata = metadata or {}
|
|
185
198
|
|
|
186
199
|
if source_path.suffix.lower() not in FileReader.extensions:
|
|
@@ -256,6 +269,9 @@ class HaikuRAG:
|
|
|
256
269
|
ValueError: If the content cannot be parsed
|
|
257
270
|
httpx.RequestError: If URL request fails
|
|
258
271
|
"""
|
|
272
|
+
# Lazy import to avoid loading docling
|
|
273
|
+
from haiku.rag.reader import FileReader
|
|
274
|
+
|
|
259
275
|
metadata = metadata or {}
|
|
260
276
|
|
|
261
277
|
async with httpx.AsyncClient() as client:
|
|
@@ -379,6 +395,9 @@ class HaikuRAG:
|
|
|
379
395
|
|
|
380
396
|
async def update_document(self, document: Document) -> Document:
|
|
381
397
|
"""Update an existing document."""
|
|
398
|
+
# Lazy import to avoid loading docling
|
|
399
|
+
from haiku.rag.utils import text_to_docling_document
|
|
400
|
+
|
|
382
401
|
# Convert content to DoclingDocument
|
|
383
402
|
docling_document = text_to_docling_document(document.content)
|
|
384
403
|
|
|
@@ -418,7 +437,7 @@ class HaikuRAG:
|
|
|
418
437
|
List of (chunk, score) tuples ordered by relevance.
|
|
419
438
|
"""
|
|
420
439
|
# Get reranker if available
|
|
421
|
-
reranker = get_reranker()
|
|
440
|
+
reranker = get_reranker(config=self._config)
|
|
422
441
|
|
|
423
442
|
if reranker is None:
|
|
424
443
|
# No reranking - return direct search results
|
|
@@ -440,18 +459,20 @@ class HaikuRAG:
|
|
|
440
459
|
async def expand_context(
|
|
441
460
|
self,
|
|
442
461
|
search_results: list[tuple[Chunk, float]],
|
|
443
|
-
radius: int =
|
|
462
|
+
radius: int | None = None,
|
|
444
463
|
) -> list[tuple[Chunk, float]]:
|
|
445
464
|
"""Expand search results with adjacent chunks, merging overlapping chunks.
|
|
446
465
|
|
|
447
466
|
Args:
|
|
448
467
|
search_results: List of (chunk, score) tuples from search.
|
|
449
468
|
radius: Number of adjacent chunks to include before/after each chunk.
|
|
450
|
-
|
|
469
|
+
If None, uses config.processing.context_chunk_radius.
|
|
451
470
|
|
|
452
471
|
Returns:
|
|
453
472
|
List of (chunk, score) tuples with expanded and merged context chunks.
|
|
454
473
|
"""
|
|
474
|
+
if radius is None:
|
|
475
|
+
radius = self._config.processing.context_chunk_radius
|
|
455
476
|
if radius == 0:
|
|
456
477
|
return search_results
|
|
457
478
|
|
|
@@ -581,7 +602,9 @@ class HaikuRAG:
|
|
|
581
602
|
"""
|
|
582
603
|
from haiku.rag.qa import get_qa_agent
|
|
583
604
|
|
|
584
|
-
qa_agent = get_qa_agent(
|
|
605
|
+
qa_agent = get_qa_agent(
|
|
606
|
+
self, config=self._config, use_citations=cite, system_prompt=system_prompt
|
|
607
|
+
)
|
|
585
608
|
return await qa_agent.answer(question)
|
|
586
609
|
|
|
587
610
|
async def rebuild_database(self) -> AsyncGenerator[str, None]:
|
|
@@ -597,6 +620,9 @@ class HaikuRAG:
|
|
|
597
620
|
Yields:
|
|
598
621
|
int: The ID of the document currently being processed
|
|
599
622
|
"""
|
|
623
|
+
# Lazy import to avoid loading docling
|
|
624
|
+
from haiku.rag.utils import text_to_docling_document
|
|
625
|
+
|
|
600
626
|
await self.chunk_repository.delete_all()
|
|
601
627
|
self.store.recreate_embeddings_table()
|
|
602
628
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from haiku.rag.config.loader import (
|
|
4
|
+
find_config_file,
|
|
5
|
+
generate_default_config,
|
|
6
|
+
load_config_from_env,
|
|
7
|
+
load_yaml_config,
|
|
8
|
+
)
|
|
9
|
+
from haiku.rag.config.models import (
|
|
10
|
+
A2AConfig,
|
|
11
|
+
AppConfig,
|
|
12
|
+
EmbeddingsConfig,
|
|
13
|
+
LanceDBConfig,
|
|
14
|
+
OllamaConfig,
|
|
15
|
+
ProcessingConfig,
|
|
16
|
+
ProvidersConfig,
|
|
17
|
+
QAConfig,
|
|
18
|
+
RerankingConfig,
|
|
19
|
+
ResearchConfig,
|
|
20
|
+
StorageConfig,
|
|
21
|
+
VLLMConfig,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"Config",
|
|
26
|
+
"AppConfig",
|
|
27
|
+
"StorageConfig",
|
|
28
|
+
"LanceDBConfig",
|
|
29
|
+
"EmbeddingsConfig",
|
|
30
|
+
"RerankingConfig",
|
|
31
|
+
"QAConfig",
|
|
32
|
+
"ResearchConfig",
|
|
33
|
+
"ProcessingConfig",
|
|
34
|
+
"OllamaConfig",
|
|
35
|
+
"VLLMConfig",
|
|
36
|
+
"ProvidersConfig",
|
|
37
|
+
"A2AConfig",
|
|
38
|
+
"find_config_file",
|
|
39
|
+
"load_yaml_config",
|
|
40
|
+
"generate_default_config",
|
|
41
|
+
"load_config_from_env",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# Load config from YAML file or use defaults
|
|
45
|
+
config_path = find_config_file(None)
|
|
46
|
+
if config_path:
|
|
47
|
+
yaml_data = load_yaml_config(config_path)
|
|
48
|
+
Config = AppConfig.model_validate(yaml_data)
|
|
49
|
+
else:
|
|
50
|
+
Config = AppConfig()
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def find_config_file(cli_path: Path | None = None) -> Path | None:
|
|
8
|
+
"""Find the YAML config file using the search path.
|
|
9
|
+
|
|
10
|
+
Search order:
|
|
11
|
+
1. CLI-provided path (via HAIKU_RAG_CONFIG_PATH env var or parameter)
|
|
12
|
+
2. ./haiku.rag.yaml (current directory)
|
|
13
|
+
3. ~/.config/haiku.rag/config.yaml (user config)
|
|
14
|
+
|
|
15
|
+
Returns None if no config file is found.
|
|
16
|
+
"""
|
|
17
|
+
# Check environment variable first (set by CLI --config flag)
|
|
18
|
+
if not cli_path:
|
|
19
|
+
env_path = os.getenv("HAIKU_RAG_CONFIG_PATH")
|
|
20
|
+
if env_path:
|
|
21
|
+
cli_path = Path(env_path)
|
|
22
|
+
|
|
23
|
+
if cli_path:
|
|
24
|
+
if cli_path.exists():
|
|
25
|
+
return cli_path
|
|
26
|
+
raise FileNotFoundError(f"Config file not found: {cli_path}")
|
|
27
|
+
|
|
28
|
+
cwd_config = Path.cwd() / "haiku.rag.yaml"
|
|
29
|
+
if cwd_config.exists():
|
|
30
|
+
return cwd_config
|
|
31
|
+
|
|
32
|
+
user_config_dir = Path.home() / ".config" / "haiku.rag"
|
|
33
|
+
user_config = user_config_dir / "config.yaml"
|
|
34
|
+
if user_config.exists():
|
|
35
|
+
return user_config
|
|
36
|
+
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def load_yaml_config(path: Path) -> dict:
|
|
41
|
+
"""Load and parse a YAML config file."""
|
|
42
|
+
with open(path) as f:
|
|
43
|
+
data = yaml.safe_load(f)
|
|
44
|
+
return data or {}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def generate_default_config() -> dict:
|
|
48
|
+
"""Generate a default YAML config structure with documentation."""
|
|
49
|
+
return {
|
|
50
|
+
"environment": "production",
|
|
51
|
+
"storage": {
|
|
52
|
+
"data_dir": "",
|
|
53
|
+
"monitor_directories": [],
|
|
54
|
+
"disable_autocreate": False,
|
|
55
|
+
"vacuum_retention_seconds": 60,
|
|
56
|
+
},
|
|
57
|
+
"lancedb": {"uri": "", "api_key": "", "region": ""},
|
|
58
|
+
"embeddings": {
|
|
59
|
+
"provider": "ollama",
|
|
60
|
+
"model": "qwen3-embedding",
|
|
61
|
+
"vector_dim": 4096,
|
|
62
|
+
},
|
|
63
|
+
"reranking": {"provider": "", "model": ""},
|
|
64
|
+
"qa": {"provider": "ollama", "model": "gpt-oss"},
|
|
65
|
+
"research": {"provider": "", "model": ""},
|
|
66
|
+
"processing": {
|
|
67
|
+
"chunk_size": 256,
|
|
68
|
+
"context_chunk_radius": 0,
|
|
69
|
+
"markdown_preprocessor": "",
|
|
70
|
+
},
|
|
71
|
+
"providers": {
|
|
72
|
+
"ollama": {"base_url": "http://localhost:11434"},
|
|
73
|
+
"vllm": {
|
|
74
|
+
"embeddings_base_url": "",
|
|
75
|
+
"rerank_base_url": "",
|
|
76
|
+
"qa_base_url": "",
|
|
77
|
+
"research_base_url": "",
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
"a2a": {"max_contexts": 1000},
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def load_config_from_env() -> dict:
|
|
85
|
+
"""Load current config from environment variables (for migration)."""
|
|
86
|
+
result = {}
|
|
87
|
+
|
|
88
|
+
env_mappings = {
|
|
89
|
+
"ENV": "environment",
|
|
90
|
+
"DEFAULT_DATA_DIR": ("storage", "data_dir"),
|
|
91
|
+
"MONITOR_DIRECTORIES": ("storage", "monitor_directories"),
|
|
92
|
+
"DISABLE_DB_AUTOCREATE": ("storage", "disable_autocreate"),
|
|
93
|
+
"VACUUM_RETENTION_SECONDS": ("storage", "vacuum_retention_seconds"),
|
|
94
|
+
"LANCEDB_URI": ("lancedb", "uri"),
|
|
95
|
+
"LANCEDB_API_KEY": ("lancedb", "api_key"),
|
|
96
|
+
"LANCEDB_REGION": ("lancedb", "region"),
|
|
97
|
+
"EMBEDDINGS_PROVIDER": ("embeddings", "provider"),
|
|
98
|
+
"EMBEDDINGS_MODEL": ("embeddings", "model"),
|
|
99
|
+
"EMBEDDINGS_VECTOR_DIM": ("embeddings", "vector_dim"),
|
|
100
|
+
"RERANK_PROVIDER": ("reranking", "provider"),
|
|
101
|
+
"RERANK_MODEL": ("reranking", "model"),
|
|
102
|
+
"QA_PROVIDER": ("qa", "provider"),
|
|
103
|
+
"QA_MODEL": ("qa", "model"),
|
|
104
|
+
"RESEARCH_PROVIDER": ("research", "provider"),
|
|
105
|
+
"RESEARCH_MODEL": ("research", "model"),
|
|
106
|
+
"CHUNK_SIZE": ("processing", "chunk_size"),
|
|
107
|
+
"CONTEXT_CHUNK_RADIUS": ("processing", "context_chunk_radius"),
|
|
108
|
+
"MARKDOWN_PREPROCESSOR": ("processing", "markdown_preprocessor"),
|
|
109
|
+
"OLLAMA_BASE_URL": ("providers", "ollama", "base_url"),
|
|
110
|
+
"VLLM_EMBEDDINGS_BASE_URL": ("providers", "vllm", "embeddings_base_url"),
|
|
111
|
+
"VLLM_RERANK_BASE_URL": ("providers", "vllm", "rerank_base_url"),
|
|
112
|
+
"VLLM_QA_BASE_URL": ("providers", "vllm", "qa_base_url"),
|
|
113
|
+
"VLLM_RESEARCH_BASE_URL": ("providers", "vllm", "research_base_url"),
|
|
114
|
+
"A2A_MAX_CONTEXTS": ("a2a", "max_contexts"),
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for env_var, path in env_mappings.items():
|
|
118
|
+
value = os.getenv(env_var)
|
|
119
|
+
if value is not None:
|
|
120
|
+
# Special handling for MONITOR_DIRECTORIES - parse comma-separated list
|
|
121
|
+
if env_var == "MONITOR_DIRECTORIES":
|
|
122
|
+
if value.strip():
|
|
123
|
+
value = [p.strip() for p in value.split(",") if p.strip()]
|
|
124
|
+
else:
|
|
125
|
+
value = []
|
|
126
|
+
|
|
127
|
+
if isinstance(path, tuple):
|
|
128
|
+
current = result
|
|
129
|
+
for key in path[:-1]:
|
|
130
|
+
if key not in current:
|
|
131
|
+
current[key] = {}
|
|
132
|
+
current = current[key]
|
|
133
|
+
current[path[-1]] = value
|
|
134
|
+
else:
|
|
135
|
+
result[path] = value
|
|
136
|
+
|
|
137
|
+
return result
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from haiku.rag.utils import get_default_data_dir
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StorageConfig(BaseModel):
|
|
9
|
+
data_dir: Path = Field(default_factory=get_default_data_dir)
|
|
10
|
+
monitor_directories: list[Path] = []
|
|
11
|
+
disable_autocreate: bool = False
|
|
12
|
+
vacuum_retention_seconds: int = 60
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LanceDBConfig(BaseModel):
|
|
16
|
+
uri: str = ""
|
|
17
|
+
api_key: str = ""
|
|
18
|
+
region: str = ""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EmbeddingsConfig(BaseModel):
|
|
22
|
+
provider: str = "ollama"
|
|
23
|
+
model: str = "qwen3-embedding"
|
|
24
|
+
vector_dim: int = 4096
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RerankingConfig(BaseModel):
|
|
28
|
+
provider: str = ""
|
|
29
|
+
model: str = ""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class QAConfig(BaseModel):
|
|
33
|
+
provider: str = "ollama"
|
|
34
|
+
model: str = "gpt-oss"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ResearchConfig(BaseModel):
|
|
38
|
+
provider: str = "ollama"
|
|
39
|
+
model: str = "gpt-oss"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ProcessingConfig(BaseModel):
|
|
43
|
+
chunk_size: int = 256
|
|
44
|
+
context_chunk_radius: int = 0
|
|
45
|
+
markdown_preprocessor: str = ""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class OllamaConfig(BaseModel):
|
|
49
|
+
base_url: str = Field(
|
|
50
|
+
default_factory=lambda: __import__("os").environ.get(
|
|
51
|
+
"OLLAMA_BASE_URL", "http://localhost:11434"
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class VLLMConfig(BaseModel):
|
|
57
|
+
embeddings_base_url: str = ""
|
|
58
|
+
rerank_base_url: str = ""
|
|
59
|
+
qa_base_url: str = ""
|
|
60
|
+
research_base_url: str = ""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ProvidersConfig(BaseModel):
|
|
64
|
+
ollama: OllamaConfig = Field(default_factory=OllamaConfig)
|
|
65
|
+
vllm: VLLMConfig = Field(default_factory=VLLMConfig)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class A2AConfig(BaseModel):
|
|
69
|
+
max_contexts: int = 1000
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class AppConfig(BaseModel):
|
|
73
|
+
environment: str = "production"
|
|
74
|
+
storage: StorageConfig = Field(default_factory=StorageConfig)
|
|
75
|
+
lancedb: LanceDBConfig = Field(default_factory=LanceDBConfig)
|
|
76
|
+
embeddings: EmbeddingsConfig = Field(default_factory=EmbeddingsConfig)
|
|
77
|
+
reranking: RerankingConfig = Field(default_factory=RerankingConfig)
|
|
78
|
+
qa: QAConfig = Field(default_factory=QAConfig)
|
|
79
|
+
research: ResearchConfig = Field(default_factory=ResearchConfig)
|
|
80
|
+
processing: ProcessingConfig = Field(default_factory=ProcessingConfig)
|
|
81
|
+
providers: ProvidersConfig = Field(default_factory=ProvidersConfig)
|
|
82
|
+
a2a: A2AConfig = Field(default_factory=A2AConfig)
|