haiku.rag 0.9.2__py3-none-any.whl → 0.14.0__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.
Files changed (53) hide show
  1. README.md +205 -0
  2. haiku_rag-0.14.0.dist-info/METADATA +227 -0
  3. haiku_rag-0.14.0.dist-info/RECORD +6 -0
  4. haiku/rag/__init__.py +0 -0
  5. haiku/rag/app.py +0 -267
  6. haiku/rag/chunker.py +0 -51
  7. haiku/rag/cli.py +0 -359
  8. haiku/rag/client.py +0 -565
  9. haiku/rag/config.py +0 -77
  10. haiku/rag/embeddings/__init__.py +0 -35
  11. haiku/rag/embeddings/base.py +0 -15
  12. haiku/rag/embeddings/ollama.py +0 -17
  13. haiku/rag/embeddings/openai.py +0 -16
  14. haiku/rag/embeddings/vllm.py +0 -19
  15. haiku/rag/embeddings/voyageai.py +0 -17
  16. haiku/rag/logging.py +0 -56
  17. haiku/rag/mcp.py +0 -144
  18. haiku/rag/migration.py +0 -316
  19. haiku/rag/monitor.py +0 -73
  20. haiku/rag/qa/__init__.py +0 -15
  21. haiku/rag/qa/agent.py +0 -89
  22. haiku/rag/qa/prompts.py +0 -60
  23. haiku/rag/reader.py +0 -115
  24. haiku/rag/reranking/__init__.py +0 -34
  25. haiku/rag/reranking/base.py +0 -13
  26. haiku/rag/reranking/cohere.py +0 -34
  27. haiku/rag/reranking/mxbai.py +0 -28
  28. haiku/rag/reranking/vllm.py +0 -44
  29. haiku/rag/research/__init__.py +0 -37
  30. haiku/rag/research/base.py +0 -130
  31. haiku/rag/research/dependencies.py +0 -45
  32. haiku/rag/research/evaluation_agent.py +0 -42
  33. haiku/rag/research/orchestrator.py +0 -300
  34. haiku/rag/research/presearch_agent.py +0 -34
  35. haiku/rag/research/prompts.py +0 -129
  36. haiku/rag/research/search_agent.py +0 -65
  37. haiku/rag/research/synthesis_agent.py +0 -40
  38. haiku/rag/store/__init__.py +0 -4
  39. haiku/rag/store/engine.py +0 -230
  40. haiku/rag/store/models/__init__.py +0 -4
  41. haiku/rag/store/models/chunk.py +0 -15
  42. haiku/rag/store/models/document.py +0 -16
  43. haiku/rag/store/repositories/__init__.py +0 -9
  44. haiku/rag/store/repositories/chunk.py +0 -399
  45. haiku/rag/store/repositories/document.py +0 -234
  46. haiku/rag/store/repositories/settings.py +0 -148
  47. haiku/rag/store/upgrades/__init__.py +0 -1
  48. haiku/rag/utils.py +0 -162
  49. haiku_rag-0.9.2.dist-info/METADATA +0 -131
  50. haiku_rag-0.9.2.dist-info/RECORD +0 -50
  51. {haiku_rag-0.9.2.dist-info → haiku_rag-0.14.0.dist-info}/WHEEL +0 -0
  52. {haiku_rag-0.9.2.dist-info → haiku_rag-0.14.0.dist-info}/entry_points.txt +0 -0
  53. {haiku_rag-0.9.2.dist-info → haiku_rag-0.14.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,234 +0,0 @@
1
- import json
2
- from datetime import datetime
3
- from typing import TYPE_CHECKING
4
- from uuid import uuid4
5
-
6
- from docling_core.types.doc.document import DoclingDocument
7
-
8
- from haiku.rag.store.engine import DocumentRecord, Store
9
- from haiku.rag.store.models.document import Document
10
-
11
- if TYPE_CHECKING:
12
- from haiku.rag.store.models.chunk import Chunk
13
-
14
-
15
- class DocumentRepository:
16
- """Repository for Document operations."""
17
-
18
- def __init__(self, store: Store) -> None:
19
- self.store = store
20
- self._chunk_repository = None
21
-
22
- @property
23
- def chunk_repository(self):
24
- """Lazy-load ChunkRepository when needed."""
25
- if self._chunk_repository is None:
26
- from haiku.rag.store.repositories.chunk import ChunkRepository
27
-
28
- self._chunk_repository = ChunkRepository(self.store)
29
- return self._chunk_repository
30
-
31
- def _record_to_document(self, record: DocumentRecord) -> Document:
32
- """Convert a DocumentRecord to a Document model."""
33
- return Document(
34
- id=record.id,
35
- content=record.content,
36
- uri=record.uri,
37
- metadata=json.loads(record.metadata) if record.metadata else {},
38
- created_at=datetime.fromisoformat(record.created_at)
39
- if record.created_at
40
- else datetime.now(),
41
- updated_at=datetime.fromisoformat(record.updated_at)
42
- if record.updated_at
43
- else datetime.now(),
44
- )
45
-
46
- async def create(self, entity: Document) -> Document:
47
- """Create a document in the database."""
48
- # Generate new UUID
49
- doc_id = str(uuid4())
50
-
51
- # Create timestamp
52
- now = datetime.now().isoformat()
53
-
54
- # Create document record
55
- doc_record = DocumentRecord(
56
- id=doc_id,
57
- content=entity.content,
58
- uri=entity.uri,
59
- metadata=json.dumps(entity.metadata),
60
- created_at=now,
61
- updated_at=now,
62
- )
63
-
64
- # Add to table
65
- self.store.documents_table.add([doc_record])
66
-
67
- entity.id = doc_id
68
- entity.created_at = datetime.fromisoformat(now)
69
- entity.updated_at = datetime.fromisoformat(now)
70
- return entity
71
-
72
- async def get_by_id(self, entity_id: str) -> Document | None:
73
- """Get a document by its ID."""
74
- results = list(
75
- self.store.documents_table.search()
76
- .where(f"id = '{entity_id}'")
77
- .limit(1)
78
- .to_pydantic(DocumentRecord)
79
- )
80
-
81
- if not results:
82
- return None
83
-
84
- return self._record_to_document(results[0])
85
-
86
- async def update(self, entity: Document) -> Document:
87
- """Update an existing document."""
88
- assert entity.id, "Document ID is required for update"
89
-
90
- # Update timestamp
91
- now = datetime.now().isoformat()
92
- entity.updated_at = datetime.fromisoformat(now)
93
-
94
- # Update the record
95
- self.store.documents_table.update(
96
- where=f"id = '{entity.id}'",
97
- values={
98
- "content": entity.content,
99
- "uri": entity.uri,
100
- "metadata": json.dumps(entity.metadata),
101
- "updated_at": now,
102
- },
103
- )
104
-
105
- return entity
106
-
107
- async def delete(self, entity_id: str) -> bool:
108
- """Delete a document by its ID."""
109
- # Check if document exists
110
- doc = await self.get_by_id(entity_id)
111
- if doc is None:
112
- return False
113
-
114
- # Delete associated chunks first
115
- await self.chunk_repository.delete_by_document_id(entity_id)
116
-
117
- # Delete the document
118
- self.store.documents_table.delete(f"id = '{entity_id}'")
119
- return True
120
-
121
- async def list_all(
122
- self, limit: int | None = None, offset: int | None = None
123
- ) -> list[Document]:
124
- """List all documents with optional pagination."""
125
- query = self.store.documents_table.search()
126
-
127
- if offset is not None:
128
- query = query.offset(offset)
129
- if limit is not None:
130
- query = query.limit(limit)
131
-
132
- results = list(query.to_pydantic(DocumentRecord))
133
- return [self._record_to_document(doc) for doc in results]
134
-
135
- async def get_by_uri(self, uri: str) -> Document | None:
136
- """Get a document by its URI."""
137
- results = list(
138
- self.store.documents_table.search()
139
- .where(f"uri = '{uri}'")
140
- .limit(1)
141
- .to_pydantic(DocumentRecord)
142
- )
143
-
144
- if not results:
145
- return None
146
-
147
- return self._record_to_document(results[0])
148
-
149
- async def delete_all(self) -> None:
150
- """Delete all documents from the database."""
151
- # Delete all chunks first
152
- await self.chunk_repository.delete_all()
153
-
154
- # Get count before deletion
155
- count = len(
156
- list(
157
- self.store.documents_table.search().limit(1).to_pydantic(DocumentRecord)
158
- )
159
- )
160
- if count > 0:
161
- # Drop and recreate table to clear all data
162
- self.store.db.drop_table("documents")
163
- self.store.documents_table = self.store.db.create_table(
164
- "documents", schema=DocumentRecord
165
- )
166
-
167
- async def _create_with_docling(
168
- self,
169
- entity: Document,
170
- docling_document: DoclingDocument,
171
- chunks: list["Chunk"] | None = None,
172
- ) -> Document:
173
- """Create a document with its chunks and embeddings."""
174
- # Snapshot table versions for versioned rollback (if supported)
175
- versions = self.store.current_table_versions()
176
-
177
- # Create the document
178
- created_doc = await self.create(entity)
179
-
180
- # Attempt to create chunks; on failure, prefer version rollback
181
- try:
182
- # Create chunks if not provided
183
- if chunks is None:
184
- assert created_doc.id is not None, (
185
- "Document ID should not be None after creation"
186
- )
187
- await self.chunk_repository.create_chunks_for_document(
188
- created_doc.id, docling_document
189
- )
190
- else:
191
- # Use provided chunks, set order from list position
192
- assert created_doc.id is not None, (
193
- "Document ID should not be None after creation"
194
- )
195
- for order, chunk in enumerate(chunks):
196
- chunk.document_id = created_doc.id
197
- chunk.metadata["order"] = order
198
- await self.chunk_repository.create(chunk)
199
-
200
- return created_doc
201
- except Exception:
202
- # Roll back to the captured versions and re-raise
203
- self.store.restore_table_versions(versions)
204
- raise
205
-
206
- async def _update_with_docling(
207
- self, entity: Document, docling_document: DoclingDocument
208
- ) -> Document:
209
- """Update a document and regenerate its chunks."""
210
- assert entity.id is not None, "Document ID is required for update"
211
-
212
- # Snapshot table versions for versioned rollback
213
- versions = self.store.current_table_versions()
214
-
215
- # Delete existing chunks before writing new ones
216
- await self.chunk_repository.delete_by_document_id(entity.id)
217
-
218
- try:
219
- # Update the document
220
- updated_doc = await self.update(entity)
221
-
222
- # Create new chunks
223
- assert updated_doc.id is not None, (
224
- "Document ID should not be None after update"
225
- )
226
- await self.chunk_repository.create_chunks_for_document(
227
- updated_doc.id, docling_document
228
- )
229
-
230
- return updated_doc
231
- except Exception:
232
- # Roll back to the captured versions and re-raise
233
- self.store.restore_table_versions(versions)
234
- raise
@@ -1,148 +0,0 @@
1
- import json
2
-
3
- from haiku.rag.config import Config
4
- from haiku.rag.store.engine import SettingsRecord, Store
5
-
6
-
7
- class ConfigMismatchError(Exception):
8
- """Raised when stored config doesn't match current config."""
9
-
10
- pass
11
-
12
-
13
- class SettingsRepository:
14
- """Repository for Settings operations."""
15
-
16
- def __init__(self, store: Store) -> None:
17
- self.store = store
18
-
19
- async def create(self, entity: dict) -> dict:
20
- """Create settings in the database."""
21
- settings_record = SettingsRecord(id="settings", settings=json.dumps(entity))
22
- self.store.settings_table.add([settings_record])
23
- return entity
24
-
25
- async def get_by_id(self, entity_id: str) -> dict | None:
26
- """Get settings by ID."""
27
- results = list(
28
- self.store.settings_table.search()
29
- .where(f"id = '{entity_id}'")
30
- .limit(1)
31
- .to_pydantic(SettingsRecord)
32
- )
33
-
34
- if not results:
35
- return None
36
-
37
- return json.loads(results[0].settings) if results[0].settings else {}
38
-
39
- async def update(self, entity: dict) -> dict:
40
- """Update existing settings."""
41
- self.store.settings_table.update(
42
- where="id = 'settings'", values={"settings": json.dumps(entity)}
43
- )
44
- return entity
45
-
46
- async def delete(self, entity_id: str) -> bool:
47
- """Delete settings by ID."""
48
- self.store.settings_table.delete(f"id = '{entity_id}'")
49
- return True
50
-
51
- async def list_all(
52
- self, limit: int | None = None, offset: int | None = None
53
- ) -> list[dict]:
54
- """List all settings."""
55
- results = list(self.store.settings_table.search().to_pydantic(SettingsRecord))
56
- return [
57
- json.loads(record.settings) if record.settings else {} for record in results
58
- ]
59
-
60
- def get_current_settings(self) -> dict:
61
- """Get the current settings."""
62
- results = list(
63
- self.store.settings_table.search()
64
- .where("id = 'settings'")
65
- .limit(1)
66
- .to_pydantic(SettingsRecord)
67
- )
68
-
69
- if not results:
70
- return {}
71
-
72
- return json.loads(results[0].settings) if results[0].settings else {}
73
-
74
- def save_current_settings(self) -> None:
75
- """Save the current configuration to the database."""
76
- current_config = Config.model_dump(mode="json")
77
-
78
- # Check if settings exist
79
- existing = list(
80
- self.store.settings_table.search()
81
- .where("id = 'settings'")
82
- .limit(1)
83
- .to_pydantic(SettingsRecord)
84
- )
85
-
86
- if existing:
87
- # Only update when configuration actually changed to avoid needless new versions
88
- existing_payload = (
89
- json.loads(existing[0].settings) if existing[0].settings else {}
90
- )
91
- if existing_payload != current_config:
92
- self.store.settings_table.update(
93
- where="id = 'settings'",
94
- values={"settings": json.dumps(current_config)},
95
- )
96
- else:
97
- # Create new settings
98
- settings_record = SettingsRecord(
99
- id="settings", settings=json.dumps(current_config)
100
- )
101
- self.store.settings_table.add([settings_record])
102
-
103
- def validate_config_compatibility(self) -> None:
104
- """Validate that the current configuration is compatible with stored settings."""
105
- stored_settings = self.get_current_settings()
106
-
107
- # If no stored settings, this is a new database - save current config and return
108
- if not stored_settings:
109
- self.save_current_settings()
110
- return
111
-
112
- current_config = Config.model_dump(mode="json")
113
-
114
- # Check if embedding provider or model has changed
115
- stored_provider = stored_settings.get("EMBEDDINGS_PROVIDER")
116
- current_provider = current_config.get("EMBEDDINGS_PROVIDER")
117
-
118
- stored_model = stored_settings.get("EMBEDDINGS_MODEL")
119
- current_model = current_config.get("EMBEDDINGS_MODEL")
120
-
121
- stored_vector_dim = stored_settings.get("EMBEDDINGS_VECTOR_DIM")
122
- current_vector_dim = current_config.get("EMBEDDINGS_VECTOR_DIM")
123
-
124
- # Check for incompatible changes
125
- incompatible_changes = []
126
-
127
- if stored_provider and stored_provider != current_provider:
128
- incompatible_changes.append(
129
- f"Embedding provider changed from '{stored_provider}' to '{current_provider}'"
130
- )
131
-
132
- if stored_model and stored_model != current_model:
133
- incompatible_changes.append(
134
- f"Embedding model changed from '{stored_model}' to '{current_model}'"
135
- )
136
-
137
- if stored_vector_dim and stored_vector_dim != current_vector_dim:
138
- incompatible_changes.append(
139
- f"Vector dimension changed from {stored_vector_dim} to {current_vector_dim}"
140
- )
141
-
142
- if incompatible_changes:
143
- error_msg = (
144
- "Database configuration is incompatible with current settings:\n"
145
- + "\n".join(f" - {change}" for change in incompatible_changes)
146
- )
147
- error_msg += "\n\nPlease rebuild the database using: haiku-rag rebuild"
148
- raise ConfigMismatchError(error_msg)
@@ -1 +0,0 @@
1
- upgrades = []
haiku/rag/utils.py DELETED
@@ -1,162 +0,0 @@
1
- import asyncio
2
- import importlib
3
- import importlib.util
4
- import sys
5
- from collections.abc import Callable
6
- from functools import wraps
7
- from importlib import metadata
8
- from io import BytesIO
9
- from pathlib import Path
10
- from types import ModuleType
11
-
12
- import httpx
13
- from docling.document_converter import DocumentConverter
14
- from docling_core.types.doc.document import DoclingDocument
15
- from docling_core.types.io import DocumentStream
16
- from packaging.version import Version, parse
17
-
18
-
19
- def debounce(wait: float) -> Callable:
20
- """
21
- A decorator to debounce a function, ensuring it is called only after a specified delay
22
- and always executes after the last call.
23
-
24
- Args:
25
- wait (float): The debounce delay in seconds.
26
-
27
- Returns:
28
- Callable: The decorated function.
29
- """
30
-
31
- def decorator(func: Callable) -> Callable:
32
- last_call = None
33
- task = None
34
-
35
- @wraps(func)
36
- async def debounced(*args, **kwargs):
37
- nonlocal last_call, task
38
- last_call = asyncio.get_event_loop().time()
39
-
40
- if task:
41
- task.cancel()
42
-
43
- async def call_func():
44
- await asyncio.sleep(wait)
45
- if asyncio.get_event_loop().time() - last_call >= wait: # type: ignore
46
- await func(*args, **kwargs)
47
-
48
- task = asyncio.create_task(call_func())
49
-
50
- return debounced
51
-
52
- return decorator
53
-
54
-
55
- def get_default_data_dir() -> Path:
56
- """Get the user data directory for the current system platform.
57
-
58
- Linux: ~/.local/share/haiku.rag
59
- macOS: ~/Library/Application Support/haiku.rag
60
- Windows: C:/Users/<USER>/AppData/Roaming/haiku.rag
61
-
62
- Returns:
63
- User Data Path.
64
- """
65
- home = Path.home()
66
-
67
- system_paths = {
68
- "win32": home / "AppData/Roaming/haiku.rag",
69
- "linux": home / ".local/share/haiku.rag",
70
- "darwin": home / "Library/Application Support/haiku.rag",
71
- }
72
-
73
- data_path = system_paths[sys.platform]
74
- return data_path
75
-
76
-
77
- async def is_up_to_date() -> tuple[bool, Version, Version]:
78
- """Check whether haiku.rag is current.
79
-
80
- Returns:
81
- A tuple containing a boolean indicating whether haiku.rag is current,
82
- the running version and the latest version.
83
- """
84
-
85
- async with httpx.AsyncClient() as client:
86
- running_version = parse(metadata.version("haiku.rag"))
87
- try:
88
- response = await client.get("https://pypi.org/pypi/haiku.rag/json")
89
- data = response.json()
90
- pypi_version = parse(data["info"]["version"])
91
- except Exception:
92
- # If no network connection, do not raise alarms.
93
- pypi_version = running_version
94
- return running_version >= pypi_version, running_version, pypi_version
95
-
96
-
97
- def text_to_docling_document(text: str, name: str = "content.md") -> DoclingDocument:
98
- """Convert text content to a DoclingDocument.
99
-
100
- Args:
101
- text: The text content to convert.
102
- name: The name to use for the document stream (defaults to "content.md").
103
-
104
- Returns:
105
- A DoclingDocument created from the text content.
106
- """
107
- bytes_io = BytesIO(text.encode("utf-8"))
108
- doc_stream = DocumentStream(name=name, stream=bytes_io)
109
- converter = DocumentConverter()
110
- result = converter.convert(doc_stream)
111
- return result.document
112
-
113
-
114
- def load_callable(path: str):
115
- """Load a callable from a dotted path or file path.
116
-
117
- Supported formats:
118
- - "package.module:func" or "package.module.func"
119
- - "path/to/file.py:func"
120
-
121
- Returns the loaded callable. Raises ValueError on failure.
122
- """
123
- if not path:
124
- raise ValueError("Empty callable path provided")
125
-
126
- module_part = None
127
- func_name = None
128
-
129
- if ":" in path:
130
- module_part, func_name = path.split(":", 1)
131
- else:
132
- # split by last dot for module.attr
133
- if "." in path:
134
- module_part, func_name = path.rsplit(".", 1)
135
- else:
136
- raise ValueError(
137
- "Invalid callable path format. Use 'module:func' or 'module.func' or 'file.py:func'."
138
- )
139
-
140
- # Try file path first
141
- mod: ModuleType | None = None
142
- module_path = Path(module_part)
143
- if module_path.suffix == ".py" and module_path.exists():
144
- spec = importlib.util.spec_from_file_location(module_path.stem, module_path)
145
- if spec and spec.loader:
146
- mod = importlib.util.module_from_spec(spec)
147
- spec.loader.exec_module(mod)
148
- else:
149
- # Import as a module path
150
- try:
151
- mod = importlib.import_module(module_part)
152
- except Exception as e:
153
- raise ValueError(f"Failed to import module '{module_part}': {e}")
154
-
155
- if not hasattr(mod, func_name):
156
- raise ValueError(f"Callable '{func_name}' not found in module '{module_part}'")
157
- func = getattr(mod, func_name)
158
- if not callable(func):
159
- raise ValueError(
160
- f"Attribute '{func_name}' in module '{module_part}' is not callable"
161
- )
162
- return func
@@ -1,131 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: haiku.rag
3
- Version: 0.9.2
4
- Summary: Agentic Retrieval Augmented Generation (RAG) with LanceDB
5
- Author-email: Yiorgis Gozadinos <ggozadinos@gmail.com>
6
- License: MIT
7
- License-File: LICENSE
8
- Keywords: RAG,lancedb,mcp,ml,vector-database
9
- Classifier: Development Status :: 4 - Beta
10
- Classifier: Environment :: Console
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Operating System :: MacOS
13
- Classifier: Operating System :: Microsoft :: Windows :: Windows 10
14
- Classifier: Operating System :: Microsoft :: Windows :: Windows 11
15
- Classifier: Operating System :: POSIX :: Linux
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Typing :: Typed
20
- Requires-Python: >=3.12
21
- Requires-Dist: docling>=2.52.0
22
- Requires-Dist: fastmcp>=2.12.3
23
- Requires-Dist: httpx>=0.28.1
24
- Requires-Dist: lancedb>=0.25.0
25
- Requires-Dist: pydantic-ai>=1.0.8
26
- Requires-Dist: pydantic>=2.11.9
27
- Requires-Dist: python-dotenv>=1.1.1
28
- Requires-Dist: rich>=14.1.0
29
- Requires-Dist: tiktoken>=0.11.0
30
- Requires-Dist: typer>=0.16.1
31
- Requires-Dist: watchfiles>=1.1.0
32
- Provides-Extra: mxbai
33
- Requires-Dist: mxbai-rerank>=0.1.6; extra == 'mxbai'
34
- Provides-Extra: voyageai
35
- Requires-Dist: voyageai>=0.3.5; extra == 'voyageai'
36
- Description-Content-Type: text/markdown
37
-
38
- # Haiku RAG
39
-
40
- Retrieval-Augmented Generation (RAG) library built on LanceDB.
41
-
42
- `haiku.rag` is a Retrieval-Augmented Generation (RAG) library built to work with LanceDB as a local vector database. It uses LanceDB for storing embeddings and performs semantic (vector) search as well as full-text search combined through native hybrid search with Reciprocal Rank Fusion. Both open-source (Ollama) as well as commercial (OpenAI, VoyageAI) embedding providers are supported.
43
-
44
- > **Note**: Starting with version 0.7.0, haiku.rag uses LanceDB instead of SQLite. If you have an existing SQLite database, use `haiku-rag migrate old_database.sqlite` to migrate your data safely.
45
-
46
- ## Features
47
-
48
- - **Local LanceDB**: No external servers required, supports also LanceDB cloud storage, S3, Google Cloud & Azure
49
- - **Multiple embedding providers**: Ollama, VoyageAI, OpenAI, vLLM
50
- - **Multiple QA providers**: Any provider/model supported by Pydantic AI
51
- - **Native hybrid search**: Vector + full-text search with native LanceDB RRF reranking
52
- - **Reranking**: Default search result reranking with MixedBread AI, Cohere, or vLLM
53
- - **Question answering**: Built-in QA agents on your documents
54
- - **File monitoring**: Auto-index files when run as server
55
- - **40+ file formats**: PDF, DOCX, HTML, Markdown, code files, URLs
56
- - **MCP server**: Expose as tools for AI assistants
57
- - **CLI & Python API**: Use from command line or Python
58
-
59
- ## Quick Start
60
-
61
- ```bash
62
- # Install
63
- uv pip install haiku.rag
64
-
65
- # Add documents
66
- haiku-rag add "Your content here"
67
- haiku-rag add-src document.pdf
68
-
69
- # Search
70
- haiku-rag search "query"
71
-
72
- # Ask questions
73
- haiku-rag ask "Who is the author of haiku.rag?"
74
-
75
- # Ask questions with citations
76
- haiku-rag ask "Who is the author of haiku.rag?" --cite
77
-
78
- # Rebuild database (re-chunk and re-embed all documents)
79
- haiku-rag rebuild
80
-
81
- # Migrate from SQLite to LanceDB
82
- haiku-rag migrate old_database.sqlite
83
-
84
- # Start server with file monitoring
85
- export MONITOR_DIRECTORIES="/path/to/docs"
86
- haiku-rag serve
87
- ```
88
-
89
- ## Python Usage
90
-
91
- ```python
92
- from haiku.rag.client import HaikuRAG
93
-
94
- async with HaikuRAG("database.lancedb") as client:
95
- # Add document
96
- doc = await client.create_document("Your content")
97
-
98
- # Search (reranking enabled by default)
99
- results = await client.search("query")
100
- for chunk, score in results:
101
- print(f"{score:.3f}: {chunk.content}")
102
-
103
- # Ask questions
104
- answer = await client.ask("Who is the author of haiku.rag?")
105
- print(answer)
106
-
107
- # Ask questions with citations
108
- answer = await client.ask("Who is the author of haiku.rag?", cite=True)
109
- print(answer)
110
- ```
111
-
112
- ## MCP Server
113
-
114
- Use with AI assistants like Claude Desktop:
115
-
116
- ```bash
117
- haiku-rag serve --stdio
118
- ```
119
-
120
- Provides tools for document management and search directly in your AI assistant.
121
-
122
- ## Documentation
123
-
124
- Full documentation at: https://ggozad.github.io/haiku.rag/
125
-
126
- - [Installation](https://ggozad.github.io/haiku.rag/installation/) - Provider setup
127
- - [Configuration](https://ggozad.github.io/haiku.rag/configuration/) - Environment variables
128
- - [CLI](https://ggozad.github.io/haiku.rag/cli/) - Command reference
129
- - [Python API](https://ggozad.github.io/haiku.rag/python/) - Complete API docs
130
- - [Agents](https://ggozad.github.io/haiku.rag/agents/) - QA agent and multi-agent research
131
- - [Benchmarks](https://ggozad.github.io/haiku.rag/benchmarks/) - Performance Benchmarks