haiku.rag 0.3.4__py3-none-any.whl → 0.4.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/chunker.py CHANGED
@@ -6,15 +6,11 @@ from haiku.rag.config import Config
6
6
 
7
7
 
8
8
  class Chunker:
9
- """
10
- A class that chunks text into smaller pieces for embedding and retrieval.
11
-
12
- Parameters
13
- ----------
14
- chunk_size : int
15
- The maximum size of a chunk in characters.
16
- chunk_overlap : int
17
- The number of characters of overlap between chunks.
9
+ """A class that chunks text into smaller pieces for embedding and retrieval.
10
+
11
+ Args:
12
+ chunk_size: The maximum size of a chunk in tokens.
13
+ chunk_overlap: The number of tokens of overlap between chunks.
18
14
  """
19
15
 
20
16
  encoder: ClassVar[tiktoken.Encoding] = tiktoken.encoding_for_model("gpt-4o")
@@ -28,18 +24,13 @@ class Chunker:
28
24
  self.chunk_overlap = chunk_overlap
29
25
 
30
26
  async def chunk(self, text: str) -> list[str]:
31
- """
32
- Split the text into chunks.
27
+ """Split the text into chunks based on token boundaries.
33
28
 
34
- Parameters
35
- ----------
36
- text : str
37
- The text to be split into chunks.
29
+ Args:
30
+ text: The text to be split into chunks.
38
31
 
39
- Returns
40
- -------
41
- list
42
- A list of text chunks.
32
+ Returns:
33
+ A list of text chunks with token-based boundaries and overlap.
43
34
  """
44
35
  if not text:
45
36
  return []
haiku/rag/cli.py CHANGED
@@ -5,7 +5,8 @@ import typer
5
5
  from rich.console import Console
6
6
 
7
7
  from haiku.rag.app import HaikuRAGApp
8
- from haiku.rag.utils import get_default_data_dir, is_up_to_date
8
+ from haiku.rag.config import Config
9
+ from haiku.rag.utils import is_up_to_date
9
10
 
10
11
  cli = typer.Typer(
11
12
  context_settings={"help_option_names": ["-h", "--help"]}, no_args_is_help=True
@@ -35,7 +36,7 @@ def main():
35
36
  @cli.command("list", help="List all stored documents")
36
37
  def list_documents(
37
38
  db: Path = typer.Option(
38
- get_default_data_dir() / "haiku.rag.sqlite",
39
+ Config.DEFAULT_DATA_DIR / "haiku.rag.sqlite",
39
40
  "--db",
40
41
  help="Path to the SQLite database file",
41
42
  ),
@@ -50,7 +51,7 @@ def add_document_text(
50
51
  help="The text content of the document to add",
51
52
  ),
52
53
  db: Path = typer.Option(
53
- get_default_data_dir() / "haiku.rag.sqlite",
54
+ Config.DEFAULT_DATA_DIR / "haiku.rag.sqlite",
54
55
  "--db",
55
56
  help="Path to the SQLite database file",
56
57
  ),
@@ -65,7 +66,7 @@ def add_document_src(
65
66
  help="The file path or URL of the document to add",
66
67
  ),
67
68
  db: Path = typer.Option(
68
- get_default_data_dir() / "haiku.rag.sqlite",
69
+ Config.DEFAULT_DATA_DIR / "haiku.rag.sqlite",
69
70
  "--db",
70
71
  help="Path to the SQLite database file",
71
72
  ),
@@ -80,7 +81,7 @@ def get_document(
80
81
  help="The ID of the document to get",
81
82
  ),
82
83
  db: Path = typer.Option(
83
- get_default_data_dir() / "haiku.rag.sqlite",
84
+ Config.DEFAULT_DATA_DIR / "haiku.rag.sqlite",
84
85
  "--db",
85
86
  help="Path to the SQLite database file",
86
87
  ),
@@ -95,7 +96,7 @@ def delete_document(
95
96
  help="The ID of the document to delete",
96
97
  ),
97
98
  db: Path = typer.Option(
98
- get_default_data_dir() / "haiku.rag.sqlite",
99
+ Config.DEFAULT_DATA_DIR / "haiku.rag.sqlite",
99
100
  "--db",
100
101
  help="Path to the SQLite database file",
101
102
  ),
@@ -121,7 +122,7 @@ def search(
121
122
  help="Reciprocal Rank Fusion k parameter",
122
123
  ),
123
124
  db: Path = typer.Option(
124
- get_default_data_dir() / "haiku.rag.sqlite",
125
+ Config.DEFAULT_DATA_DIR / "haiku.rag.sqlite",
125
126
  "--db",
126
127
  help="Path to the SQLite database file",
127
128
  ),
@@ -136,7 +137,7 @@ def ask(
136
137
  help="The question to ask",
137
138
  ),
138
139
  db: Path = typer.Option(
139
- get_default_data_dir() / "haiku.rag.sqlite",
140
+ Config.DEFAULT_DATA_DIR / "haiku.rag.sqlite",
140
141
  "--db",
141
142
  help="Path to the SQLite database file",
142
143
  ),
@@ -157,7 +158,7 @@ def settings():
157
158
  )
158
159
  def rebuild(
159
160
  db: Path = typer.Option(
160
- get_default_data_dir() / "haiku.rag.sqlite",
161
+ Config.DEFAULT_DATA_DIR / "haiku.rag.sqlite",
161
162
  "--db",
162
163
  help="Path to the SQLite database file",
163
164
  ),
@@ -171,7 +172,7 @@ def rebuild(
171
172
  )
172
173
  def serve(
173
174
  db: Path = typer.Option(
174
- get_default_data_dir() / "haiku.rag.sqlite",
175
+ Config.DEFAULT_DATA_DIR / "haiku.rag.sqlite",
175
176
  "--db",
176
177
  help="Path to the SQLite database file",
177
178
  ),
haiku/rag/client.py CHANGED
@@ -10,6 +10,7 @@ import httpx
10
10
 
11
11
  from haiku.rag.config import Config
12
12
  from haiku.rag.reader import FileReader
13
+ from haiku.rag.reranking import get_reranker
13
14
  from haiku.rag.store.engine import Store
14
15
  from haiku.rag.store.models.chunk import Chunk
15
16
  from haiku.rag.store.models.document import Document
@@ -26,7 +27,12 @@ class HaikuRAG:
26
27
  / "haiku.rag.sqlite",
27
28
  skip_validation: bool = False,
28
29
  ):
29
- """Initialize the RAG client with a database path."""
30
+ """Initialize the RAG client with a database path.
31
+
32
+ Args:
33
+ db_path: Path to the SQLite database file or ":memory:" for in-memory database.
34
+ skip_validation: Whether to skip configuration validation on database load.
35
+ """
30
36
  if isinstance(db_path, Path):
31
37
  if not db_path.parent.exists():
32
38
  Path.mkdir(db_path.parent, parents=True)
@@ -46,7 +52,16 @@ class HaikuRAG:
46
52
  async def create_document(
47
53
  self, content: str, uri: str | None = None, metadata: dict | None = None
48
54
  ) -> Document:
49
- """Create a new document with optional URI and metadata."""
55
+ """Create a new document with optional URI and metadata.
56
+
57
+ Args:
58
+ content: The text content of the document.
59
+ uri: Optional URI identifier for the document.
60
+ metadata: Optional metadata dictionary.
61
+
62
+ Returns:
63
+ The created Document instance.
64
+ """
50
65
  document = Document(
51
66
  content=content,
52
67
  uri=uri,
@@ -219,11 +234,25 @@ class HaikuRAG:
219
234
  return ".html"
220
235
 
221
236
  async def get_document_by_id(self, document_id: int) -> Document | None:
222
- """Get a document by its ID."""
237
+ """Get a document by its ID.
238
+
239
+ Args:
240
+ document_id: The unique identifier of the document.
241
+
242
+ Returns:
243
+ The Document instance if found, None otherwise.
244
+ """
223
245
  return await self.document_repository.get_by_id(document_id)
224
246
 
225
247
  async def get_document_by_uri(self, uri: str) -> Document | None:
226
- """Get a document by its URI."""
248
+ """Get a document by its URI.
249
+
250
+ Args:
251
+ uri: The URI identifier of the document.
252
+
253
+ Returns:
254
+ The Document instance if found, None otherwise.
255
+ """
227
256
  return await self.document_repository.get_by_uri(uri)
228
257
 
229
258
  async def update_document(self, document: Document) -> Document:
@@ -237,32 +266,54 @@ class HaikuRAG:
237
266
  async def list_documents(
238
267
  self, limit: int | None = None, offset: int | None = None
239
268
  ) -> list[Document]:
240
- """List all documents with optional pagination."""
269
+ """List all documents with optional pagination.
270
+
271
+ Args:
272
+ limit: Maximum number of documents to return.
273
+ offset: Number of documents to skip.
274
+
275
+ Returns:
276
+ List of Document instances.
277
+ """
241
278
  return await self.document_repository.list_all(limit=limit, offset=offset)
242
279
 
243
280
  async def search(
244
- self, query: str, limit: int = 5, k: int = 60
281
+ self, query: str, limit: int = 5, k: int = 60, rerank=Config.RERANK
245
282
  ) -> list[tuple[Chunk, float]]:
246
- """Search for relevant chunks using hybrid search (vector similarity + full-text search).
283
+ """Search for relevant chunks using hybrid search (vector similarity + full-text search) with reranking.
247
284
 
248
285
  Args:
249
- query: The search query string
250
- limit: Maximum number of results to return
251
- k: Parameter for Reciprocal Rank Fusion (default: 60)
286
+ query: The search query string.
287
+ limit: Maximum number of results to return.
288
+ k: Parameter for Reciprocal Rank Fusion (default: 60).
252
289
 
253
290
  Returns:
254
- List of (chunk, score) tuples ordered by relevance
291
+ List of (chunk, score) tuples ordered by relevance.
255
292
  """
256
- return await self.chunk_repository.search_chunks_hybrid(query, limit, k)
293
+
294
+ if not rerank:
295
+ return await self.chunk_repository.search_chunks_hybrid(query, limit, k)
296
+
297
+ # Get more initial results (3X) for reranking
298
+ search_results = await self.chunk_repository.search_chunks_hybrid(
299
+ query, limit * 3, k
300
+ )
301
+ # Apply reranking
302
+ reranker = get_reranker()
303
+ chunks = [chunk for chunk, _ in search_results]
304
+ reranked_results = await reranker.rerank(query, chunks, top_n=limit)
305
+
306
+ # Return reranked results with scores from reranker
307
+ return reranked_results
257
308
 
258
309
  async def ask(self, question: str) -> str:
259
310
  """Ask a question using the configured QA agent.
260
311
 
261
312
  Args:
262
- question: The question to ask
313
+ question: The question to ask.
263
314
 
264
315
  Returns:
265
- The generated answer as a string
316
+ The generated answer as a string.
266
317
  """
267
318
  from haiku.rag.qa import get_qa_agent
268
319
 
haiku/rag/config.py CHANGED
@@ -19,6 +19,10 @@ class AppConfig(BaseModel):
19
19
  EMBEDDINGS_MODEL: str = "mxbai-embed-large"
20
20
  EMBEDDINGS_VECTOR_DIM: int = 1024
21
21
 
22
+ RERANK: bool = True
23
+ RERANK_PROVIDER: str = "mxbai"
24
+ RERANK_MODEL: str = "mixedbread-ai/mxbai-rerank-base-v2"
25
+
22
26
  QA_PROVIDER: str = "ollama"
23
27
  QA_MODEL: str = "qwen3"
24
28
 
@@ -31,6 +35,7 @@ class AppConfig(BaseModel):
31
35
  VOYAGE_API_KEY: str = ""
32
36
  OPENAI_API_KEY: str = ""
33
37
  ANTHROPIC_API_KEY: str = ""
38
+ COHERE_API_KEY: str = ""
34
39
 
35
40
  @field_validator("MONITOR_DIRECTORIES", mode="before")
36
41
  @classmethod
@@ -52,3 +57,5 @@ if Config.VOYAGE_API_KEY:
52
57
  os.environ["VOYAGE_API_KEY"] = Config.VOYAGE_API_KEY
53
58
  if Config.ANTHROPIC_API_KEY:
54
59
  os.environ["ANTHROPIC_API_KEY"] = Config.ANTHROPIC_API_KEY
60
+ if Config.COHERE_API_KEY:
61
+ os.environ["CO_API_KEY"] = Config.COHERE_API_KEY
@@ -18,7 +18,7 @@ def get_embedder() -> EmbedderBase:
18
18
  raise ImportError(
19
19
  "VoyageAI embedder requires the 'voyageai' package. "
20
20
  "Please install haiku.rag with the 'voyageai' extra:"
21
- "uv pip install haiku.rag --extra voyageai"
21
+ "uv pip install haiku.rag[voyageai]"
22
22
  )
23
23
  return VoyageAIEmbedder(Config.EMBEDDINGS_MODEL, Config.EMBEDDINGS_VECTOR_DIM)
24
24
 
@@ -29,7 +29,7 @@ def get_embedder() -> EmbedderBase:
29
29
  raise ImportError(
30
30
  "OpenAI embedder requires the 'openai' package. "
31
31
  "Please install haiku.rag with the 'openai' extra:"
32
- "uv pip install haiku.rag --extra openai"
32
+ "uv pip install haiku.rag[openai]"
33
33
  )
34
34
  return OpenAIEmbedder(Config.EMBEDDINGS_MODEL, Config.EMBEDDINGS_VECTOR_DIM)
35
35
 
@@ -1,6 +1,9 @@
1
+ from haiku.rag.config import Config
2
+
3
+
1
4
  class EmbedderBase:
2
- _model: str = ""
3
- _vector_dim: int = 0
5
+ _model: str = Config.EMBEDDINGS_MODEL
6
+ _vector_dim: int = Config.EMBEDDINGS_VECTOR_DIM
4
7
 
5
8
  def __init__(self, model: str, vector_dim: int):
6
9
  self._model = model
@@ -5,9 +5,6 @@ from haiku.rag.embeddings.base import EmbedderBase
5
5
 
6
6
 
7
7
  class Embedder(EmbedderBase):
8
- _model: str = Config.EMBEDDINGS_MODEL
9
- _vector_dim: int = 1024
10
-
11
8
  async def embed(self, text: str) -> list[float]:
12
9
  client = AsyncClient(host=Config.OLLAMA_BASE_URL)
13
10
  res = await client.embeddings(model=self._model, prompt=text)
@@ -1,13 +1,9 @@
1
1
  try:
2
2
  from openai import AsyncOpenAI
3
3
 
4
- from haiku.rag.config import Config
5
4
  from haiku.rag.embeddings.base import EmbedderBase
6
5
 
7
6
  class Embedder(EmbedderBase):
8
- _model: str = Config.EMBEDDINGS_MODEL
9
- _vector_dim: int = 1536
10
-
11
7
  async def embed(self, text: str) -> list[float]:
12
8
  client = AsyncOpenAI()
13
9
  response = await client.embeddings.create(
@@ -1,13 +1,9 @@
1
1
  try:
2
2
  from voyageai.client import Client # type: ignore
3
3
 
4
- from haiku.rag.config import Config
5
4
  from haiku.rag.embeddings.base import EmbedderBase
6
5
 
7
6
  class Embedder(EmbedderBase):
8
- _model: str = Config.EMBEDDINGS_MODEL
9
- _vector_dim: int = 1024
10
-
11
7
  async def embed(self, text: str) -> list[float]:
12
8
  client = Client()
13
9
  res = client.embed([text], model=self._model, output_dtype="float")
haiku/rag/qa/__init__.py CHANGED
@@ -18,7 +18,7 @@ def get_qa_agent(client: HaikuRAG, model: str = "") -> QuestionAnswerAgentBase:
18
18
  raise ImportError(
19
19
  "OpenAI QA agent requires the 'openai' package. "
20
20
  "Please install haiku.rag with the 'openai' extra:"
21
- "uv pip install haiku.rag --extra openai"
21
+ "uv pip install haiku.rag[openai]"
22
22
  )
23
23
  return QuestionAnswerOpenAIAgent(client, model or Config.QA_MODEL)
24
24
 
@@ -29,7 +29,7 @@ def get_qa_agent(client: HaikuRAG, model: str = "") -> QuestionAnswerAgentBase:
29
29
  raise ImportError(
30
30
  "Anthropic QA agent requires the 'anthropic' package. "
31
31
  "Please install haiku.rag with the 'anthropic' extra:"
32
- "uv pip install haiku.rag --extra anthropic"
32
+ "uv pip install haiku.rag[anthropic]"
33
33
  )
34
34
  return QuestionAnswerAnthropicAgent(client, model or Config.QA_MODEL)
35
35
 
haiku/rag/qa/ollama.py CHANGED
@@ -4,7 +4,7 @@ from haiku.rag.client import HaikuRAG
4
4
  from haiku.rag.config import Config
5
5
  from haiku.rag.qa.base import QuestionAnswerAgentBase
6
6
 
7
- OLLAMA_OPTIONS = {"temperature": 0.0, "seed": 42, "num_ctx": 64000}
7
+ OLLAMA_OPTIONS = {"temperature": 0.0, "seed": 42, "num_ctx": 16384}
8
8
 
9
9
 
10
10
  class QuestionAnswerOllamaAgent(QuestionAnswerAgentBase):
haiku/rag/qa/prompts.py CHANGED
@@ -6,7 +6,7 @@ Your process:
6
6
  2. Search with specific keywords and phrases from the user's question
7
7
  3. Review the search results and their relevance scores
8
8
  4. If you need additional context, perform follow-up searches with different keywords
9
- 5. Provide a comprehensive answer based only on the retrieved documents
9
+ 5. Provide a short and to the point comprehensive answer based only on the retrieved documents
10
10
 
11
11
  Guidelines:
12
12
  - Base your answers strictly on the provided document content
@@ -15,6 +15,7 @@ Guidelines:
15
15
  - Indicate when information is incomplete or when you need to search for additional context
16
16
  - If the retrieved documents don't contain sufficient information, clearly state: "I cannot find enough information in the knowledge base to answer this question."
17
17
  - For complex questions, consider breaking them down and performing multiple searches
18
+ - Stick to the answer, do not ellaborate or provide context unless explicitly asked for it.
18
19
 
19
20
  Be concise, and always maintain accuracy over completeness. Prefer short, direct answers that are well-supported by the documents.
20
21
  """
@@ -0,0 +1,37 @@
1
+ from haiku.rag.config import Config
2
+ from haiku.rag.reranking.base import RerankerBase
3
+
4
+ try:
5
+ from haiku.rag.reranking.cohere import CohereReranker
6
+ except ImportError:
7
+ pass
8
+
9
+ _reranker: RerankerBase | None = None
10
+
11
+
12
+ def get_reranker() -> RerankerBase:
13
+ """
14
+ Factory function to get the appropriate reranker based on the configuration.
15
+ """
16
+ global _reranker
17
+ if _reranker is not None:
18
+ return _reranker
19
+ if Config.RERANK_PROVIDER == "mxbai":
20
+ from haiku.rag.reranking.mxbai import MxBAIReranker
21
+
22
+ _reranker = MxBAIReranker()
23
+ return _reranker
24
+
25
+ if Config.RERANK_PROVIDER == "cohere":
26
+ try:
27
+ from haiku.rag.reranking.cohere import CohereReranker
28
+ except ImportError:
29
+ raise ImportError(
30
+ "Cohere reranker requires the 'cohere' package. "
31
+ "Please install haiku.rag with the 'cohere' extra:"
32
+ "uv pip install haiku.rag[cohere]"
33
+ )
34
+ _reranker = CohereReranker()
35
+ return _reranker
36
+
37
+ raise ValueError(f"Unsupported reranker provider: {Config.RERANK_PROVIDER}")
@@ -0,0 +1,13 @@
1
+ from haiku.rag.config import Config
2
+ from haiku.rag.store.models.chunk import Chunk
3
+
4
+
5
+ class RerankerBase:
6
+ _model: str = Config.RERANK_MODEL
7
+
8
+ async def rerank(
9
+ self, query: str, chunks: list[Chunk], top_n: int = 10
10
+ ) -> list[tuple[Chunk, float]]:
11
+ raise NotImplementedError(
12
+ "Reranker is an abstract class. Please implement the rerank method in a subclass."
13
+ )
@@ -0,0 +1,34 @@
1
+ from haiku.rag.config import Config
2
+ from haiku.rag.reranking.base import RerankerBase
3
+ from haiku.rag.store.models.chunk import Chunk
4
+
5
+ try:
6
+ import cohere
7
+ except ImportError as e:
8
+ raise ImportError(
9
+ "cohere is not installed. Please install it with `pip install cohere` or use the cohere optional dependency."
10
+ ) from e
11
+
12
+
13
+ class CohereReranker(RerankerBase):
14
+ def __init__(self):
15
+ self._client = cohere.ClientV2(api_key=Config.COHERE_API_KEY)
16
+
17
+ async def rerank(
18
+ self, query: str, chunks: list[Chunk], top_n: int = 10
19
+ ) -> list[tuple[Chunk, float]]:
20
+ if not chunks:
21
+ return []
22
+
23
+ documents = [chunk.content for chunk in chunks]
24
+
25
+ response = self._client.rerank(
26
+ model=self._model, query=query, documents=documents, top_n=top_n
27
+ )
28
+
29
+ reranked_chunks = []
30
+ for result in response.results:
31
+ original_chunk = chunks[result.index]
32
+ reranked_chunks.append((original_chunk, result.relevance_score))
33
+
34
+ return reranked_chunks
@@ -0,0 +1,28 @@
1
+ from mxbai_rerank import MxbaiRerankV2
2
+
3
+ from haiku.rag.config import Config
4
+ from haiku.rag.reranking.base import RerankerBase
5
+ from haiku.rag.store.models.chunk import Chunk
6
+
7
+
8
+ class MxBAIReranker(RerankerBase):
9
+ def __init__(self):
10
+ self._client = MxbaiRerankV2(
11
+ Config.RERANK_MODEL, disable_transformers_warnings=True
12
+ )
13
+
14
+ async def rerank(
15
+ self, query: str, chunks: list[Chunk], top_n: int = 10
16
+ ) -> list[tuple[Chunk, float]]:
17
+ if not chunks:
18
+ return []
19
+
20
+ documents = [chunk.content for chunk in chunks]
21
+
22
+ results = self._client.rank(query=query, documents=documents, top_k=top_n)
23
+ reranked_chunks = []
24
+ for result in results:
25
+ original_chunk = chunks[result.index]
26
+ reranked_chunks.append((original_chunk, result.score))
27
+
28
+ return reranked_chunks
haiku/rag/utils.py CHANGED
@@ -7,15 +7,14 @@ from packaging.version import Version, parse
7
7
 
8
8
 
9
9
  def get_default_data_dir() -> Path:
10
- """
11
- Get the user data directory for the current system platform.
10
+ """Get the user data directory for the current system platform.
12
11
 
13
12
  Linux: ~/.local/share/haiku.rag
14
13
  macOS: ~/Library/Application Support/haiku.rag
15
14
  Windows: C:/Users/<USER>/AppData/Roaming/haiku.rag
16
15
 
17
- :return: User Data Path
18
- :rtype: Path
16
+ Returns:
17
+ User Data Path.
19
18
  """
20
19
  home = Path.home()
21
20
 
@@ -30,13 +29,13 @@ def get_default_data_dir() -> Path:
30
29
 
31
30
 
32
31
  def semantic_version_to_int(version: str) -> int:
33
- """
34
- Convert a semantic version string to an integer.
32
+ """Convert a semantic version string to an integer.
33
+
34
+ Args:
35
+ version: Semantic version string.
35
36
 
36
- :param version: Semantic version string
37
- :type version: str
38
- :return: Integer representation of semantic version
39
- :rtype: int
37
+ Returns:
38
+ Integer representation of semantic version.
40
39
  """
41
40
  major, minor, patch = version.split(".")
42
41
  major = int(major) << 16
@@ -46,13 +45,13 @@ def semantic_version_to_int(version: str) -> int:
46
45
 
47
46
 
48
47
  def int_to_semantic_version(version: int) -> str:
49
- """
50
- Convert an integer to a semantic version string.
48
+ """Convert an integer to a semantic version string.
49
+
50
+ Args:
51
+ version: Integer representation of semantic version.
51
52
 
52
- :param version: Integer representation of semantic version
53
- :type version: int
54
- :return: Semantic version string
55
- :rtype: str
53
+ Returns:
54
+ Semantic version string.
56
55
  """
57
56
  major = version >> 16
58
57
  minor = (version >> 8) & 255
@@ -61,11 +60,11 @@ def int_to_semantic_version(version: int) -> str:
61
60
 
62
61
 
63
62
  async def is_up_to_date() -> tuple[bool, Version, Version]:
64
- """
65
- Checks whether haiku.rag is current.
63
+ """Check whether haiku.rag is current.
66
64
 
67
- :return: A tuple containing a boolean indicating whether haiku.rag is current, the running version and the latest version
68
- :rtype: tuple[bool, Version, Version]
65
+ Returns:
66
+ A tuple containing a boolean indicating whether haiku.rag is current,
67
+ the running version and the latest version.
69
68
  """
70
69
 
71
70
  async with httpx.AsyncClient() as client:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haiku.rag
3
- Version: 0.3.4
3
+ Version: 0.4.1
4
4
  Summary: Retrieval Augmented Generation (RAG) with SQLite
5
5
  Author-email: Yiorgis Gozadinos <ggozadinos@gmail.com>
6
6
  License: MIT
@@ -21,6 +21,7 @@ Requires-Python: >=3.10
21
21
  Requires-Dist: fastmcp>=2.8.1
22
22
  Requires-Dist: httpx>=0.28.1
23
23
  Requires-Dist: markitdown[audio-transcription,docx,pdf,pptx,xlsx]>=0.1.2
24
+ Requires-Dist: mxbai-rerank>=0.1.6
24
25
  Requires-Dist: ollama>=0.5.1
25
26
  Requires-Dist: pydantic>=2.11.7
26
27
  Requires-Dist: python-dotenv>=1.1.0
@@ -31,6 +32,8 @@ Requires-Dist: typer>=0.16.0
31
32
  Requires-Dist: watchfiles>=1.1.0
32
33
  Provides-Extra: anthropic
33
34
  Requires-Dist: anthropic>=0.56.0; extra == 'anthropic'
35
+ Provides-Extra: cohere
36
+ Requires-Dist: cohere>=5.16.1; extra == 'cohere'
34
37
  Provides-Extra: openai
35
38
  Requires-Dist: openai>=1.0.0; extra == 'openai'
36
39
  Provides-Extra: voyageai
@@ -49,6 +52,7 @@ Retrieval-Augmented Generation (RAG) library on SQLite.
49
52
  - **Multiple embedding providers**: Ollama, VoyageAI, OpenAI
50
53
  - **Multiple QA providers**: Ollama, OpenAI, Anthropic
51
54
  - **Hybrid search**: Vector + full-text search with Reciprocal Rank Fusion
55
+ - **Reranking**: Default search result reranking with MixedBread AI or Cohere
52
56
  - **Question answering**: Built-in QA agents on your documents
53
57
  - **File monitoring**: Auto-index files when run as server
54
58
  - **40+ file formats**: PDF, DOCX, HTML, Markdown, audio, URLs
@@ -88,7 +92,7 @@ async with HaikuRAG("database.db") as client:
88
92
  # Add document
89
93
  doc = await client.create_document("Your content")
90
94
 
91
- # Search
95
+ # Search (reranking enabled by default)
92
96
  results = await client.search("query")
93
97
  for chunk, score in results:
94
98
  print(f"{score:.3f}: {chunk.content}")
@@ -1,25 +1,29 @@
1
1
  haiku/rag/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  haiku/rag/app.py,sha256=FpLVyP1-zAq_XPmU8CPVLkuIAeuhBOGvMqhYS8RbN40,7649
3
- haiku/rag/chunker.py,sha256=lSSPWgNAe7gNZL_yNLmDtqxJix4YclOiG7gbARcEpV8,1871
4
- haiku/rag/cli.py,sha256=8PC7r5odIVLyksSm_BXor2rznIZ2KDug-YhzqbFPvms,5605
5
- haiku/rag/client.py,sha256=AeRXw67E1dr6ICI6EJE1q0WwZgA6ezwFw55v6QVydYk,11014
6
- haiku/rag/config.py,sha256=ctD_pu7nDOieirJofhNMO-OJIONLC5myvcru9iTm_ps,1433
3
+ haiku/rag/chunker.py,sha256=MbCtP66OfTFoIBvqmVT9T9c87fozsYYzAQzJJEfPBVI,1812
4
+ haiku/rag/cli.py,sha256=oCj65JcV2MEhzA2okbLHAK1I0FrClIKuYZx2jtbjbqE,5628
5
+ haiku/rag/client.py,sha256=gqHsRAZqM5s4-c-RjeR4HanOKqPuK0z_MtsfmZMvT-w,12553
6
+ haiku/rag/config.py,sha256=_Ss54kmfxVAJupExLKaYjYUlFxJgb7hEEdbG4-isapY,1662
7
7
  haiku/rag/logging.py,sha256=zTTGpGq5tPdcd7RpCbd9EGw1IZlQDbYkrCg9t9pqRc4,580
8
8
  haiku/rag/mcp.py,sha256=tMN6fNX7ZtAER1R6DL1GkC9HZozTC4HzuQs199p7icI,4551
9
9
  haiku/rag/monitor.py,sha256=r386nkhdlsU8UECwIuVwnrSlgMk3vNIuUZGNIzkZuec,2770
10
10
  haiku/rag/reader.py,sha256=S7-Z72pDvSHedvgt4-RkTOwZadG88Oed9keJ69SVITk,962
11
- haiku/rag/utils.py,sha256=flQqO12OIqApINYAfkg8VDXBgRDFVR_HRaIaydk_OBQ,2310
12
- haiku/rag/embeddings/__init__.py,sha256=4jUPe2FyIf8BGZ7AncWSlBdNXG3URejBbnkhQf3JiD0,1505
13
- haiku/rag/embeddings/base.py,sha256=PTAWKTU-Q-hXIhbRK1o6pIdpaW7DFdzJXQ0Nzc6VI-w,379
14
- haiku/rag/embeddings/ollama.py,sha256=hWdrTiuJwNSRYCqP0WP-z6XXA3RBGkAiknZMsPLH0qU,441
15
- haiku/rag/embeddings/openai.py,sha256=reh8AykG2f9f5hhRDmqSsjiuCPi9SsXfe2YEZFlxXk8,550
16
- haiku/rag/embeddings/voyageai.py,sha256=jc0JywdLJD3Ee1MUv1m8MhWCEo0enNnVcrIBtUvD-Ss,534
17
- haiku/rag/qa/__init__.py,sha256=oso98Ypti7mBLTJ6Zk71YaSJ9Rgc89QXp9RSB6zSpYs,1501
11
+ haiku/rag/utils.py,sha256=Ez_tvNlRO_D8c2CBZ83Hs9Gmzcqdq4cmw_V5GBdKy_8,2214
12
+ haiku/rag/embeddings/__init__.py,sha256=yFBlxS0jBiVHl_rWz5kb43t6Ha132U1ZGdlIPfhzPdg,1491
13
+ haiku/rag/embeddings/base.py,sha256=NTQvuzbZPu0LBo5wAu3qGyJ4xXUaRAt1fjBO0ygWn_Y,465
14
+ haiku/rag/embeddings/ollama.py,sha256=y6-lp0XpbnyIjoOEdtSzMdEVkU5glOwnWQ1FkpUZnpI,370
15
+ haiku/rag/embeddings/openai.py,sha256=i4Ui5hAJkcKqJkH9L3jJo7fuGYHn07td532w-ksg_T8,431
16
+ haiku/rag/embeddings/voyageai.py,sha256=0hiRTIqu-bpl-4OaCtMHvWfPdgbrzhnfZJowSV8pLRA,415
17
+ haiku/rag/qa/__init__.py,sha256=f9ZU7YDzJJoyglV1hGja1j9B6NcWerAImuKO1gFP-qs,1487
18
18
  haiku/rag/qa/anthropic.py,sha256=6I6cf6ySNkYbmDFdy22sA8r3GO5moiiH75tJnHcgJQA,4448
19
19
  haiku/rag/qa/base.py,sha256=4ZTM_l5FAZ9cA0f8NeqRJiUAmjatwCTmSoclFw0gTFQ,1349
20
- haiku/rag/qa/ollama.py,sha256=-UtNFErYlA_66g3WLU6lK38a1Y5zhAL6s_uZ5AP0TFs,2381
20
+ haiku/rag/qa/ollama.py,sha256=EGUi4urSx9nrnsr5j-qHVDVOnvRTbSMKUbMvXEMIcxM,2381
21
21
  haiku/rag/qa/openai.py,sha256=dF32sGgVt8mZi5oVxByaeECs9NqLjvDiZnnpJBsrHm8,3968
22
- haiku/rag/qa/prompts.py,sha256=578LJGZJ0LQ_q7ccyj5hLabtHo8Zcfw5-DiLGN9lC-w,1200
22
+ haiku/rag/qa/prompts.py,sha256=8uYMxHzbzI9vo2FPkCSSNTh_RNL96WkBbUWPCMBlLpo,1315
23
+ haiku/rag/reranking/__init__.py,sha256=DsPCdU94wRzDCYl6hz2DySOMWwOvNxKviqKAUfyykK8,1118
24
+ haiku/rag/reranking/base.py,sha256=LM9yUSSJ414UgBZhFTgxGprlRqzfTe4I1vgjricz2JY,405
25
+ haiku/rag/reranking/cohere.py,sha256=1iTdiaa8vvb6oHVB2qpWzUOVkyfUcimVSZp6Qr4aq4c,1049
26
+ haiku/rag/reranking/mxbai.py,sha256=46sVTsTIkzIX9THgM3u8HaEmgY7evvEyB-N54JTHvK8,867
23
27
  haiku/rag/store/__init__.py,sha256=hq0W0DAC7ysqhWSP2M2uHX8cbG6kbr-sWHxhq6qQcY0,103
24
28
  haiku/rag/store/engine.py,sha256=4ouAD0s-TFwEoEHjVVw_KnV6aaw5nwhe9fdT8PRXfok,6061
25
29
  haiku/rag/store/models/__init__.py,sha256=s0E72zneGlowvZrFWaNxHYjOAUjgWdLxzdYsnvNRVlY,88
@@ -32,8 +36,8 @@ haiku/rag/store/repositories/document.py,sha256=xpWOpjHFbhVwNJ1gpusEKNY6l_Qyibg9
32
36
  haiku/rag/store/repositories/settings.py,sha256=dme3_ulQdQvyF9daavSjAd-SjZ5hh0MJoxP7iXgap-A,2492
33
37
  haiku/rag/store/upgrades/__init__.py,sha256=kKS1YWT_P-CYKhKtokOLTIFNKf9jlfjFFr8lyIMeogM,100
34
38
  haiku/rag/store/upgrades/v0_3_4.py,sha256=GLogKZdZ40NX1vBHKdOJju7fFzNUCHoEnjSZg17Hm2U,663
35
- haiku_rag-0.3.4.dist-info/METADATA,sha256=9FEVS2pZkPrRYVGd1qaMmfjyxr4fc9sHx1NTeyCbTo0,4019
36
- haiku_rag-0.3.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
- haiku_rag-0.3.4.dist-info/entry_points.txt,sha256=G1U3nAkNd5YDYd4v0tuYFbriz0i-JheCsFuT9kIoGCI,48
38
- haiku_rag-0.3.4.dist-info/licenses/LICENSE,sha256=eXZrWjSk9PwYFNK9yUczl3oPl95Z4V9UXH7bPN46iPo,1065
39
- haiku_rag-0.3.4.dist-info/RECORD,,
39
+ haiku_rag-0.4.1.dist-info/METADATA,sha256=Vqg_r9uBqdKh3V4dUgPGzx40cNUtXodIELD9_sU2xYs,4235
40
+ haiku_rag-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
41
+ haiku_rag-0.4.1.dist-info/entry_points.txt,sha256=G1U3nAkNd5YDYd4v0tuYFbriz0i-JheCsFuT9kIoGCI,48
42
+ haiku_rag-0.4.1.dist-info/licenses/LICENSE,sha256=eXZrWjSk9PwYFNK9yUczl3oPl95Z4V9UXH7bPN46iPo,1065
43
+ haiku_rag-0.4.1.dist-info/RECORD,,