haiku.rag 0.13.1__py3-none-any.whl → 0.13.3__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/app.py CHANGED
@@ -81,13 +81,10 @@ class HaikuRAGApp:
81
81
  raw = rows[0].get("settings") or "{}"
82
82
  data = json.loads(raw) if isinstance(raw, str) else (raw or {})
83
83
  stored_version = str(data.get("version", stored_version))
84
- embed_provider = data.get("EMBEDDINGS_PROVIDER")
85
- embed_model = data.get("EMBEDDINGS_MODEL")
86
- vector_dim = (
87
- int(data.get("EMBEDDINGS_VECTOR_DIM")) # pyright: ignore[reportArgumentType]
88
- if data.get("EMBEDDINGS_VECTOR_DIM") is not None
89
- else None
90
- )
84
+ embeddings = data.get("embeddings", {})
85
+ embed_provider = embeddings.get("provider")
86
+ embed_model = embeddings.get("model")
87
+ vector_dim = embeddings.get("vector_dim")
91
88
 
92
89
  num_docs = 0
93
90
  if "documents" in table_names:
@@ -195,9 +192,9 @@ class HaikuRAGApp:
195
192
  f"[yellow]Document with id {doc_id} not found.[/yellow]"
196
193
  )
197
194
 
198
- async def search(self, query: str, limit: int = 5):
195
+ async def search(self, query: str, limit: int = 5, filter: str | None = None):
199
196
  async with HaikuRAG(db_path=self.db_path) as self.client:
200
- results = await self.client.search(query, limit=limit)
197
+ results = await self.client.search(query, limit=limit, filter=filter)
201
198
  if not results:
202
199
  self.console.print("[yellow]No results found.[/yellow]")
203
200
  return
@@ -474,9 +471,7 @@ class HaikuRAGApp:
474
471
 
475
472
  # Start file monitor if enabled
476
473
  if enable_monitor:
477
- monitor = FileWatcher(
478
- paths=Config.storage.monitor_directories, client=client
479
- )
474
+ monitor = FileWatcher(client=client)
480
475
  monitor_task = asyncio.create_task(monitor.observe())
481
476
  tasks.append(monitor_task)
482
477
 
haiku/rag/cli.py CHANGED
@@ -221,6 +221,12 @@ def search(
221
221
  "-l",
222
222
  help="Maximum number of results to return",
223
223
  ),
224
+ filter: str | None = typer.Option(
225
+ None,
226
+ "--filter",
227
+ "-f",
228
+ help="SQL WHERE clause to filter documents (e.g., \"uri LIKE '%arxiv%'\")",
229
+ ),
224
230
  db: Path = typer.Option(
225
231
  Config.storage.data_dir / "haiku.rag.lancedb",
226
232
  "--db",
@@ -230,7 +236,7 @@ def search(
230
236
  from haiku.rag.app import HaikuRAGApp
231
237
 
232
238
  app = HaikuRAGApp(db_path=db)
233
- asyncio.run(app.search(query=query, limit=limit))
239
+ asyncio.run(app.search(query=query, limit=limit, filter=filter))
234
240
 
235
241
 
236
242
  @cli.command("ask", help="Ask a question using the QA agent")
haiku/rag/client.py CHANGED
@@ -135,9 +135,6 @@ class HaikuRAG:
135
135
  ValueError: If the file/URL cannot be parsed or doesn't exist
136
136
  httpx.RequestError: If URL request fails
137
137
  """
138
- # Lazy import to avoid loading docling
139
- from haiku.rag.reader import FileReader
140
-
141
138
  # Normalize metadata
142
139
  metadata = metadata or {}
143
140
 
@@ -157,15 +154,17 @@ class HaikuRAG:
157
154
 
158
155
  # Handle directories
159
156
  if source_path.is_dir():
157
+ from haiku.rag.monitor import FileFilter
158
+
160
159
  documents = []
161
- supported_extensions = set(FileReader.extensions)
162
- for file_path in source_path.rglob("*"):
163
- if (
164
- file_path.is_file()
165
- and file_path.suffix.lower() in supported_extensions
166
- ):
160
+ filter = FileFilter(
161
+ ignore_patterns=self._config.monitor.ignore_patterns or None,
162
+ include_patterns=self._config.monitor.include_patterns or None,
163
+ )
164
+ for path in source_path.rglob("*"):
165
+ if path.is_file() and filter.include_file(str(path)):
167
166
  doc = await self._create_document_from_file(
168
- file_path, title=None, metadata=metadata
167
+ path, title=None, metadata=metadata
169
168
  )
170
169
  documents.append(doc)
171
170
  return documents
@@ -424,7 +423,11 @@ class HaikuRAG:
424
423
  return await self.document_repository.list_all(limit=limit, offset=offset)
425
424
 
426
425
  async def search(
427
- self, query: str, limit: int = 5, search_type: str = "hybrid"
426
+ self,
427
+ query: str,
428
+ limit: int = 5,
429
+ search_type: str = "hybrid",
430
+ filter: str | None = None,
428
431
  ) -> list[tuple[Chunk, float]]:
429
432
  """Search for relevant chunks using the specified search method with optional reranking.
430
433
 
@@ -432,6 +435,7 @@ class HaikuRAG:
432
435
  query: The search query string.
433
436
  limit: Maximum number of results to return.
434
437
  search_type: Type of search - "vector", "fts", or "hybrid" (default).
438
+ filter: Optional SQL WHERE clause to filter documents before searching chunks.
435
439
 
436
440
  Returns:
437
441
  List of (chunk, score) tuples ordered by relevance.
@@ -441,12 +445,12 @@ class HaikuRAG:
441
445
 
442
446
  if reranker is None:
443
447
  # No reranking - return direct search results
444
- return await self.chunk_repository.search(query, limit, search_type)
448
+ return await self.chunk_repository.search(query, limit, search_type, filter)
445
449
 
446
450
  # Get more initial results (3X) for reranking
447
451
  search_limit = limit * 3
448
452
  search_results = await self.chunk_repository.search(
449
- query, search_limit, search_type
453
+ query, search_limit, search_type, filter
450
454
  )
451
455
 
452
456
  # Apply reranking
@@ -11,6 +11,7 @@ from haiku.rag.config.models import (
11
11
  AppConfig,
12
12
  EmbeddingsConfig,
13
13
  LanceDBConfig,
14
+ MonitorConfig,
14
15
  OllamaConfig,
15
16
  ProcessingConfig,
16
17
  ProvidersConfig,
@@ -25,6 +26,7 @@ __all__ = [
25
26
  "Config",
26
27
  "AppConfig",
27
28
  "StorageConfig",
29
+ "MonitorConfig",
28
30
  "LanceDBConfig",
29
31
  "EmbeddingsConfig",
30
32
  "RerankingConfig",
@@ -10,7 +10,7 @@ def find_config_file(cli_path: Path | None = None) -> Path | None:
10
10
  Search order:
11
11
  1. CLI-provided path (via HAIKU_RAG_CONFIG_PATH env var or parameter)
12
12
  2. ./haiku.rag.yaml (current directory)
13
- 3. ~/.config/haiku.rag/config.yaml (user config)
13
+ 3. Platform-specific user config directory
14
14
 
15
15
  Returns None if no config file is found.
16
16
  """
@@ -29,8 +29,10 @@ def find_config_file(cli_path: Path | None = None) -> Path | None:
29
29
  if cwd_config.exists():
30
30
  return cwd_config
31
31
 
32
- user_config_dir = Path.home() / ".config" / "haiku.rag"
33
- user_config = user_config_dir / "config.yaml"
32
+ # Use same directory as data storage for config
33
+ from haiku.rag.utils import get_default_data_dir
34
+
35
+ user_config = get_default_data_dir() / "config.yaml"
34
36
  if user_config.exists():
35
37
  return user_config
36
38
 
@@ -50,10 +52,14 @@ def generate_default_config() -> dict:
50
52
  "environment": "production",
51
53
  "storage": {
52
54
  "data_dir": "",
53
- "monitor_directories": [],
54
55
  "disable_autocreate": False,
55
56
  "vacuum_retention_seconds": 60,
56
57
  },
58
+ "monitor": {
59
+ "directories": [],
60
+ "ignore_patterns": [],
61
+ "include_patterns": [],
62
+ },
57
63
  "lancedb": {"uri": "", "api_key": "", "region": ""},
58
64
  "embeddings": {
59
65
  "provider": "ollama",
@@ -88,7 +94,7 @@ def load_config_from_env() -> dict:
88
94
  env_mappings = {
89
95
  "ENV": "environment",
90
96
  "DEFAULT_DATA_DIR": ("storage", "data_dir"),
91
- "MONITOR_DIRECTORIES": ("storage", "monitor_directories"),
97
+ "MONITOR_DIRECTORIES": ("monitor", "directories"),
92
98
  "DISABLE_DB_AUTOCREATE": ("storage", "disable_autocreate"),
93
99
  "VACUUM_RETENTION_SECONDS": ("storage", "vacuum_retention_seconds"),
94
100
  "LANCEDB_URI": ("lancedb", "uri"),
@@ -7,11 +7,16 @@ from haiku.rag.utils import get_default_data_dir
7
7
 
8
8
  class StorageConfig(BaseModel):
9
9
  data_dir: Path = Field(default_factory=get_default_data_dir)
10
- monitor_directories: list[Path] = []
11
10
  disable_autocreate: bool = False
12
11
  vacuum_retention_seconds: int = 60
13
12
 
14
13
 
14
+ class MonitorConfig(BaseModel):
15
+ directories: list[Path] = []
16
+ ignore_patterns: list[str] = []
17
+ include_patterns: list[str] = []
18
+
19
+
15
20
  class LanceDBConfig(BaseModel):
16
21
  uri: str = ""
17
22
  api_key: str = ""
@@ -72,6 +77,7 @@ class A2AConfig(BaseModel):
72
77
  class AppConfig(BaseModel):
73
78
  environment: str = "production"
74
79
  storage: StorageConfig = Field(default_factory=StorageConfig)
80
+ monitor: MonitorConfig = Field(default_factory=MonitorConfig)
75
81
  lancedb: LanceDBConfig = Field(default_factory=LanceDBConfig)
76
82
  embeddings: EmbeddingsConfig = Field(default_factory=EmbeddingsConfig)
77
83
  reranking: RerankingConfig = Field(default_factory=RerankingConfig)
haiku/rag/monitor.py CHANGED
@@ -2,9 +2,12 @@ import logging
2
2
  from pathlib import Path
3
3
  from typing import TYPE_CHECKING
4
4
 
5
+ import pathspec
6
+ from pathspec.patterns.gitwildmatch import GitWildMatchPattern
5
7
  from watchfiles import Change, DefaultFilter, awatch
6
8
 
7
9
  from haiku.rag.client import HaikuRAG
10
+ from haiku.rag.config import AppConfig, Config
8
11
  from haiku.rag.store.models.document import Document
9
12
 
10
13
  if TYPE_CHECKING:
@@ -14,25 +17,70 @@ logger = logging.getLogger(__name__)
14
17
 
15
18
 
16
19
  class FileFilter(DefaultFilter):
17
- def __init__(self, *, ignore_paths: list[Path] | None = None) -> None:
20
+ def __init__(
21
+ self,
22
+ *,
23
+ ignore_patterns: list[str] | None = None,
24
+ include_patterns: list[str] | None = None,
25
+ ) -> None:
18
26
  # Lazy import to avoid loading docling
19
27
  from haiku.rag.reader import FileReader
20
28
 
21
29
  self.extensions = tuple(FileReader.extensions)
22
- super().__init__(ignore_paths=ignore_paths)
30
+ self.ignore_spec = (
31
+ pathspec.PathSpec.from_lines(GitWildMatchPattern, ignore_patterns)
32
+ if ignore_patterns
33
+ else None
34
+ )
35
+ self.include_spec = (
36
+ pathspec.PathSpec.from_lines(GitWildMatchPattern, include_patterns)
37
+ if include_patterns
38
+ else None
39
+ )
40
+ super().__init__()
23
41
 
24
42
  def __call__(self, change: Change, path: str) -> bool:
25
- return path.endswith(self.extensions) and super().__call__(change, path)
43
+ if not self.include_file(path):
44
+ return False
45
+
46
+ # Apply default watchfiles filter
47
+ return super().__call__(change, path)
48
+
49
+ def include_file(self, path: str) -> bool:
50
+ """Check if a file should be included based on filters."""
51
+ # Check extension filter
52
+ if not path.endswith(self.extensions):
53
+ return False
54
+
55
+ # Apply include patterns if specified (whitelist mode)
56
+ if self.include_spec:
57
+ if not self.include_spec.match_file(path):
58
+ return False
59
+
60
+ # Apply ignore patterns (blacklist mode)
61
+ if self.ignore_spec:
62
+ if self.ignore_spec.match_file(path):
63
+ return False
64
+
65
+ return True
26
66
 
27
67
 
28
68
  class FileWatcher:
29
- def __init__(self, paths: list[Path], client: HaikuRAG):
30
- self.paths = paths
69
+ def __init__(
70
+ self,
71
+ client: HaikuRAG,
72
+ config: AppConfig = Config,
73
+ ):
74
+ self.paths = config.monitor.directories
31
75
  self.client = client
76
+ self.ignore_patterns = config.monitor.ignore_patterns or None
77
+ self.include_patterns = config.monitor.include_patterns or None
32
78
 
33
79
  async def observe(self):
34
80
  logger.info(f"Watching files in {self.paths}")
35
- filter = FileFilter()
81
+ filter = FileFilter(
82
+ ignore_patterns=self.ignore_patterns, include_patterns=self.include_patterns
83
+ )
36
84
  await self.refresh()
37
85
 
38
86
  async for changes in awatch(*self.paths, watch_filter=filter):
@@ -49,10 +97,17 @@ class FileWatcher:
49
97
  # Lazy import to avoid loading docling
50
98
  from haiku.rag.reader import FileReader
51
99
 
100
+ # Create filter to apply same logic as observe()
101
+ filter = FileFilter(
102
+ ignore_patterns=self.ignore_patterns, include_patterns=self.include_patterns
103
+ )
104
+
52
105
  for path in self.paths:
53
106
  for f in Path(path).rglob("**/*"):
54
107
  if f.is_file() and f.suffix in FileReader.extensions:
55
- await self._upsert_document(f)
108
+ # Apply pattern filters
109
+ if filter(Change.added, str(f)):
110
+ await self._upsert_document(f)
56
111
 
57
112
  async def _upsert_document(self, file: Path) -> Document | None:
58
113
  try:
@@ -41,5 +41,23 @@ def get_reranker(config: AppConfig = Config) -> RerankerBase | None:
41
41
  except ImportError:
42
42
  reranker = None
43
43
 
44
+ elif config.reranking.provider == "vllm":
45
+ try:
46
+ from haiku.rag.reranking.vllm import VLLMReranker
47
+
48
+ reranker = VLLMReranker(config.reranking.model)
49
+ except ImportError:
50
+ reranker = None
51
+
52
+ elif config.reranking.provider == "zeroentropy":
53
+ try:
54
+ from haiku.rag.reranking.zeroentropy import ZeroEntropyReranker
55
+
56
+ # Use configured model or default to zerank-1
57
+ model = config.reranking.model or "zerank-1"
58
+ reranker = ZeroEntropyReranker(model)
59
+ except ImportError:
60
+ reranker = None
61
+
44
62
  _reranker_cache[config_id] = reranker
45
63
  return reranker
@@ -0,0 +1,59 @@
1
+ from zeroentropy import ZeroEntropy
2
+
3
+ from haiku.rag.reranking.base import RerankerBase
4
+ from haiku.rag.store.models.chunk import Chunk
5
+
6
+
7
+ class ZeroEntropyReranker(RerankerBase):
8
+ """Zero Entropy reranker implementation using the zerank-1 model."""
9
+
10
+ def __init__(self, model: str = "zerank-1"):
11
+ """Initialize the Zero Entropy reranker.
12
+
13
+ Args:
14
+ model: The Zero Entropy model to use (default: "zerank-1")
15
+ """
16
+ self._model = model
17
+ # Zero Entropy SDK reads ZEROENTROPY_API_KEY from environment by default
18
+ self._client = ZeroEntropy()
19
+
20
+ async def rerank(
21
+ self, query: str, chunks: list[Chunk], top_n: int = 10
22
+ ) -> list[tuple[Chunk, float]]:
23
+ """Rerank the given chunks based on relevance to the query.
24
+
25
+ Args:
26
+ query: The query to rank against
27
+ chunks: The chunks to rerank
28
+ top_n: The number of top results to return
29
+
30
+ Returns:
31
+ A list of (chunk, score) tuples, sorted by relevance
32
+ """
33
+ if not chunks:
34
+ return []
35
+
36
+ # Prepare documents for Zero Entropy API
37
+ documents = [chunk.content for chunk in chunks]
38
+
39
+ # Call Zero Entropy reranking API
40
+ response = self._client.models.rerank(
41
+ model=self._model,
42
+ query=query,
43
+ documents=documents,
44
+ )
45
+
46
+ # Extract results and map back to chunks
47
+ # Zero Entropy returns results sorted by relevance with scores
48
+ reranked_results = []
49
+
50
+ # Get top_n results
51
+ for i, result in enumerate(response.results[:top_n]):
52
+ # Zero Entropy returns index and score for each document
53
+ chunk_index = result.index
54
+ score = result.relevance_score
55
+
56
+ if chunk_index < len(chunks):
57
+ reranked_results.append((chunks[chunk_index], score))
58
+
59
+ return reranked_results
@@ -230,7 +230,11 @@ class ChunkRepository:
230
230
  return True
231
231
 
232
232
  async def search(
233
- self, query: str, limit: int = 5, search_type: str = "hybrid"
233
+ self,
234
+ query: str,
235
+ limit: int = 5,
236
+ search_type: str = "hybrid",
237
+ filter: str | None = None,
234
238
  ) -> list[tuple[Chunk, float]]:
235
239
  """Search for relevant chunks using the specified search method.
236
240
 
@@ -238,6 +242,7 @@ class ChunkRepository:
238
242
  query: The search query string.
239
243
  limit: Maximum number of results to return.
240
244
  search_type: Type of search - "vector", "fts", or "hybrid" (default).
245
+ filter: Optional SQL WHERE clause to filter documents before searching chunks.
241
246
 
242
247
  Returns:
243
248
  List of (chunk, score) tuples ordered by relevance.
@@ -245,19 +250,42 @@ class ChunkRepository:
245
250
  if not query.strip():
246
251
  return []
247
252
 
253
+ chunk_where_clause = None
254
+ if filter:
255
+ # We perform filtering as a two-step process, first filtering documents, then
256
+ # filtering chunks based on those document IDs.
257
+ # This is because LanceDB does not support joins directly in search queries.
258
+ matching_doc_ids = self._get_filtered_document_ids(filter)
259
+
260
+ if not matching_doc_ids:
261
+ return []
262
+
263
+ # Build WHERE clause for chunks table
264
+ # Use IN clause with document IDs
265
+ id_list = "', '".join(matching_doc_ids)
266
+ chunk_where_clause = f"document_id IN ('{id_list}')"
267
+
248
268
  if search_type == "vector":
249
269
  query_embedding = await self.embedder.embed(query)
250
270
 
251
271
  results = self.store.chunks_table.search(
252
272
  query_embedding, query_type="vector", vector_column_name="vector"
253
- ).limit(limit)
273
+ )
274
+
275
+ if chunk_where_clause:
276
+ results = results.where(chunk_where_clause)
277
+
278
+ results = results.limit(limit)
254
279
 
255
280
  return await self._process_search_results(results)
256
281
 
257
282
  elif search_type == "fts":
258
- results = self.store.chunks_table.search(query, query_type="fts").limit(
259
- limit
260
- )
283
+ results = self.store.chunks_table.search(query, query_type="fts")
284
+
285
+ if chunk_where_clause:
286
+ results = results.where(chunk_where_clause)
287
+
288
+ results = results.limit(limit)
261
289
  return await self._process_search_results(results)
262
290
 
263
291
  else: # hybrid (default)
@@ -267,9 +295,13 @@ class ChunkRepository:
267
295
  reranker = RRFReranker()
268
296
 
269
297
  # Perform native hybrid search with RRF reranking
298
+ results = self.store.chunks_table.search(query_type="hybrid")
299
+
300
+ if chunk_where_clause:
301
+ results = results.where(chunk_where_clause)
302
+
270
303
  results = (
271
- self.store.chunks_table.search(query_type="hybrid")
272
- .vector(query_embedding)
304
+ results.vector(query_embedding)
273
305
  .text(query)
274
306
  .rerank(reranker)
275
307
  .limit(limit)
@@ -332,6 +364,15 @@ class ChunkRepository:
332
364
 
333
365
  return adjacent_chunks
334
366
 
367
+ def _get_filtered_document_ids(self, filter: str) -> list[str]:
368
+ """Query documents table with filter and return matching document IDs."""
369
+ filtered_docs = (
370
+ self.store.documents_table.search()
371
+ .where(filter)
372
+ .to_pydantic(DocumentRecord)
373
+ )
374
+ return [doc.id for doc in filtered_docs]
375
+
335
376
  async def _process_search_results(self, query_result) -> list[tuple[Chunk, float]]:
336
377
  """Process search results into chunks with document info and scores."""
337
378
  chunks_with_scores = []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haiku.rag
3
- Version: 0.13.1
3
+ Version: 0.13.3
4
4
  Summary: Agentic Retrieval Augmented Generation (RAG) with LanceDB
5
5
  Author-email: Yiorgis Gozadinos <ggozadinos@gmail.com>
6
6
  License: MIT
@@ -17,25 +17,30 @@ Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Typing :: Typed
19
19
  Requires-Python: >=3.12
20
- Requires-Dist: docling>=2.56.1
21
- Requires-Dist: fastmcp>=2.12.4
20
+ Requires-Dist: docling>=2.58.0
21
+ Requires-Dist: fastmcp>=2.13.0.2
22
22
  Requires-Dist: httpx>=0.28.1
23
23
  Requires-Dist: lancedb>=0.25.2
24
- Requires-Dist: pydantic-ai>=1.0.18
25
- Requires-Dist: pydantic-graph>=1.0.18
26
- Requires-Dist: pydantic>=2.12.2
27
- Requires-Dist: python-dotenv>=1.1.1
28
- Requires-Dist: pyyaml>=6.0.1
24
+ Requires-Dist: pathspec>=0.12.1
25
+ Requires-Dist: pydantic-ai>=1.7.0
26
+ Requires-Dist: pydantic-graph>=1.7.0
27
+ Requires-Dist: pydantic>=2.12.3
28
+ Requires-Dist: python-dotenv>=1.2.1
29
+ Requires-Dist: pyyaml>=6.0.3
29
30
  Requires-Dist: rich>=14.2.0
30
31
  Requires-Dist: tiktoken>=0.12.0
31
- Requires-Dist: typer>=0.19.2
32
- Requires-Dist: watchfiles>=1.1.0
32
+ Requires-Dist: typer<0.20.0,>=0.19.2
33
+ Requires-Dist: watchfiles>=1.1.1
33
34
  Provides-Extra: a2a
34
35
  Requires-Dist: fasta2a>=0.1.0; extra == 'a2a'
36
+ Provides-Extra: cohere
37
+ Requires-Dist: cohere>=5.0.0; extra == 'cohere'
35
38
  Provides-Extra: mxbai
36
39
  Requires-Dist: mxbai-rerank>=0.1.6; extra == 'mxbai'
37
40
  Provides-Extra: voyageai
38
41
  Requires-Dist: voyageai>=0.3.5; extra == 'voyageai'
42
+ Provides-Extra: zeroentropy
43
+ Requires-Dist: zeroentropy>=0.1.0a6; extra == 'zeroentropy'
39
44
  Description-Content-Type: text/markdown
40
45
 
41
46
  # Haiku RAG
@@ -55,7 +60,7 @@ Retrieval-Augmented Generation (RAG) library built on LanceDB.
55
60
  - **Multiple QA providers**: Any provider/model supported by Pydantic AI
56
61
  - **Research graph (multi‑agent)**: Plan → Search → Evaluate → Synthesize with agentic AI
57
62
  - **Native hybrid search**: Vector + full-text search with native LanceDB RRF reranking
58
- - **Reranking**: Default search result reranking with MixedBread AI, Cohere, or vLLM
63
+ - **Reranking**: Default search result reranking with MixedBread AI, Cohere, Zero Entropy, or vLLM
59
64
  - **Question answering**: Built-in QA agents on your documents
60
65
  - **File monitoring**: Auto-index files when run as server
61
66
  - **40+ file formats**: PDF, DOCX, HTML, Markdown, code files, URLs
@@ -78,6 +83,9 @@ haiku-rag add-src document.pdf --meta source=manual
78
83
  # Search
79
84
  haiku-rag search "query"
80
85
 
86
+ # Search with filters
87
+ haiku-rag search "query" --filter "uri LIKE '%.pdf' AND title LIKE '%paper%'"
88
+
81
89
  # Ask questions
82
90
  haiku-rag ask "Who is the author of haiku.rag?"
83
91
 
@@ -1,11 +1,11 @@
1
1
  haiku/rag/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- haiku/rag/app.py,sha256=lNPYYWQLOPlOLYiwIR4yQuwzl-LrRNQn7n2nacgdq_k,21594
2
+ haiku/rag/app.py,sha256=8035RY74-RHNwK69D2PKc-NY9KiHdIc0euLhxuno-UU,21414
3
3
  haiku/rag/chunker.py,sha256=pA0S0fFKAuvzGm2dGyp7FAkeFZA0YTCm_ata83Pnflw,1566
4
- haiku/rag/cli.py,sha256=Y42tnlVFGvCZVjBcLWrIVgM0A7KjNYX9MAuk9-zQvvE,14523
5
- haiku/rag/client.py,sha256=cG6DAhzJJ4vdo8QFn9p8iA6YTa0arMrTtIswoZc7sY0,26816
4
+ haiku/rag/cli.py,sha256=k5T59NsV9uVugYNU5HwhVRhQSsDCYSkN3HZvxxKYEDc,14716
5
+ haiku/rag/client.py,sha256=50yJIocbKJt7bSLUQL_aDvWG-pc38PVkEHfUvBtKUkw,26991
6
6
  haiku/rag/logging.py,sha256=dm65AwADpcQsH5OAPtRA-4hsw0w5DK-sGOvzYkj6jzw,1720
7
7
  haiku/rag/mcp.py,sha256=txuEnrUMWvs_shQBk15gEkJD7xNdSYzp3z75UUWaHFM,9328
8
- haiku/rag/monitor.py,sha256=d92oRufhI8oYXH7oF6oYVf1_AcpFUafjM6tl4VhAupI,3322
8
+ haiku/rag/monitor.py,sha256=6Vgty7UxyvFkGsxC5q8DVsHQZdlr1SW-CkieIfn8dq4,5090
9
9
  haiku/rag/reader.py,sha256=aW8LG0X31kVWS7kU2tKVpe8RqP3Ne_oIidd_X3UDLH0,3307
10
10
  haiku/rag/utils.py,sha256=47ehVYJlLz6Of_Ua89qj94JclO5ZPBFU9eyonifvnVg,6131
11
11
  haiku/rag/a2a/__init__.py,sha256=tY_jLSUM0zKzyBctMkjpqmDWpxWc9QVEK1qAsb-plGs,5933
@@ -16,9 +16,9 @@ haiku/rag/a2a/prompts.py,sha256=yCla8x0hbOhKrkuaqVrF1upn-YjQM3-2NsE2TSnet0M,3030
16
16
  haiku/rag/a2a/skills.py,sha256=dwyD2Bn493eL3Vf4uQzmyxj_9IUSb66kQ-085FBAuCs,2701
17
17
  haiku/rag/a2a/storage.py,sha256=c8vmGCiZ3nuV9wUuTnwpoRD2HVVvK2JPySQOc5PVMvg,2759
18
18
  haiku/rag/a2a/worker.py,sha256=S9hiA1ncpJPdtN0eEmMjsvr5LQ4wMVN5R8CjYkTeohU,12367
19
- haiku/rag/config/__init__.py,sha256=PSHsc7gXjvRxpzN4rxR083-WYU-pocqm0hf2uhkr9Vw,1019
20
- haiku/rag/config/loader.py,sha256=eWkD8uVTa19nf7d7yyZImk7t5k0-SagYH4RSBqfkPxQ,4848
21
- haiku/rag/config/models.py,sha256=vkq2WyJfuY1cm8YEFlox0Cd8sVyXb4l1XX2fkBjI6I4,2169
19
+ haiku/rag/config/__init__.py,sha256=1AiYIX9fiVvi4nKlRnCf18ywFHQECZ-mXXvOIRYfGSU,1059
20
+ haiku/rag/config/loader.py,sha256=P2vKmk1fi9O2fbQAyWAq5SqLaFBAq_sbVr8lqi8YtlY,4982
21
+ haiku/rag/config/models.py,sha256=yySklxjYODF5Tj6xTQogG8LTjNaKnO8SLwqdeP6buKY,2334
22
22
  haiku/rag/embeddings/__init__.py,sha256=zwWRU9S5YGEJxlgPv5haHBgj3LUJMe-dEwr3LKLa9RY,1731
23
23
  haiku/rag/embeddings/base.py,sha256=kzca54e2HGzS_0YKt9OLESM9lrFKpBm_97V07jx0aas,783
24
24
  haiku/rag/embeddings/ollama.py,sha256=_uIIObbZX9QVU1lcgWQFooA3b-AeZRNncM7yQ2TxlEU,825
@@ -45,11 +45,12 @@ haiku/rag/qa/deep/models.py,sha256=siZMQXD21_3nk8kaLCv0BCuD9TydLYo-yC4-9CxQy3E,6
45
45
  haiku/rag/qa/deep/nodes.py,sha256=XbDujD_xg-NsJS2oYm3LqkfxeHZCzT2f9wBNhVh0wns,10442
46
46
  haiku/rag/qa/deep/prompts.py,sha256=t1fEvoD5Rdab92eAOvefv2wBVmkPFuR0BQ8Kh4X0-mY,2565
47
47
  haiku/rag/qa/deep/state.py,sha256=ICFIX0oRtBs6Sdq9YnmnP04BkGiQYwucfS3Mf8XLcCU,570
48
- haiku/rag/reranking/__init__.py,sha256=34xH2YZ7OCC3H8Yb-zyPuehTRQdijMSY9TC8AmZDOGQ,1296
48
+ haiku/rag/reranking/__init__.py,sha256=cwkydVEJr7Tgs4uAWB057y9j5N3F1BDO-71YJNVkL-s,1900
49
49
  haiku/rag/reranking/base.py,sha256=Yji15nAR8LyIJGqZvEZifTWmortNQ4k_7ZHst_5mRYk,408
50
50
  haiku/rag/reranking/cohere.py,sha256=BhBPPnaSnDoVlkL_MHF74kegXQBrsZGKnWqC40ztiAk,1050
51
51
  haiku/rag/reranking/mxbai.py,sha256=qR55dmpaBz15lSN_wXD3-Z6Kqr_bmNKU9q4Pwef_wB8,911
52
52
  haiku/rag/reranking/vllm.py,sha256=Ip83qzV2RM7qXTj0mE2St66hvXykovoNW8Hu3AUebDc,1489
53
+ haiku/rag/reranking/zeroentropy.py,sha256=bVW5gcdSEz8A97xVSD0jhWGN1l4lUZ10I-5vufINGKE,1913
53
54
  haiku/rag/research/__init__.py,sha256=xfVzYkt8QETjZaP8v4LdK8MC2R_JmxKDD34LefvrJbo,201
54
55
  haiku/rag/research/common.py,sha256=mrKXolTnDxcONjodmSPVAtXYWqbU0Bie2W-4lOTclGY,2988
55
56
  haiku/rag/research/dependencies.py,sha256=hiGImk7HyJF4LRYnTmsLFGzY1QrGjAyPW5vb_2JQRDo,8148
@@ -64,14 +65,14 @@ haiku/rag/store/models/__init__.py,sha256=kc7Ctf53Jr483tk4QTIrcgqBbXDz4ZoeYSkFXf
64
65
  haiku/rag/store/models/chunk.py,sha256=3EuZav4QekJIeHBCub48EM8SjNX8HEJ6wVDXGot4PEQ,421
65
66
  haiku/rag/store/models/document.py,sha256=cZXy_jEti-hnhq7FKhuhCfd99ccY9fIHMLovB_Thbb8,425
66
67
  haiku/rag/store/repositories/__init__.py,sha256=Olv5dLfBQINRV3HrsfUpjzkZ7Qm7goEYyMNykgo_DaY,291
67
- haiku/rag/store/repositories/chunk.py,sha256=bXa-NBfLdLKJuFLGEKQhFlsLi-XNbojhQYVyBjwUxz8,14205
68
+ haiku/rag/store/repositories/chunk.py,sha256=W3aVKZWQfKULT9T3sNOR6rjIjZv-ONLu05Zm9DjjUuY,15684
68
69
  haiku/rag/store/repositories/document.py,sha256=UOC_5QEUl-3RnGPJzn92KjrCnhmh5TmWln4yd5cJ4Ss,8133
69
70
  haiku/rag/store/repositories/settings.py,sha256=15gS7Xj7cG4qetv_ioxZO_r31by7GuSqtpowOsMkHmc,6129
70
71
  haiku/rag/store/upgrades/__init__.py,sha256=RQ8A6rEXBASLb5PD9vdDnEas_m_GgRzzdVu4B88Snqc,1975
71
72
  haiku/rag/store/upgrades/v0_10_1.py,sha256=qNGnxj6hoHaHJ1rKTiALfw0c9NQOi0KAK-VZCD_073A,1959
72
73
  haiku/rag/store/upgrades/v0_9_3.py,sha256=NrjNilQSgDtFWRbL3ZUtzQzJ8tf9u0dDRJtnDFwwbdw,3322
73
- haiku_rag-0.13.1.dist-info/METADATA,sha256=xoojNWUahlMw6gWdujYr_VNti4ss4We0mL0rkTOkxgo,8139
74
- haiku_rag-0.13.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
75
- haiku_rag-0.13.1.dist-info/entry_points.txt,sha256=G1U3nAkNd5YDYd4v0tuYFbriz0i-JheCsFuT9kIoGCI,48
76
- haiku_rag-0.13.1.dist-info/licenses/LICENSE,sha256=eXZrWjSk9PwYFNK9yUczl3oPl95Z4V9UXH7bPN46iPo,1065
77
- haiku_rag-0.13.1.dist-info/RECORD,,
74
+ haiku_rag-0.13.3.dist-info/METADATA,sha256=eSnLerdKwrwbhBX5UXjOfXTBfCT0gNT4b1cjI7E0ZqQ,8453
75
+ haiku_rag-0.13.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
76
+ haiku_rag-0.13.3.dist-info/entry_points.txt,sha256=G1U3nAkNd5YDYd4v0tuYFbriz0i-JheCsFuT9kIoGCI,48
77
+ haiku_rag-0.13.3.dist-info/licenses/LICENSE,sha256=eXZrWjSk9PwYFNK9yUczl3oPl95Z4V9UXH7bPN46iPo,1065
78
+ haiku_rag-0.13.3.dist-info/RECORD,,