haiku.rag 0.5.1__tar.gz → 0.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of haiku.rag might be problematic. Click here for more details.
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/PKG-INFO +4 -3
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/docs/configuration.md +26 -4
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/pyproject.toml +3 -3
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/app.py +2 -2
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/cli.py +2 -2
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/client.py +4 -3
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/config.py +2 -3
- haiku_rag-0.5.2/src/haiku/rag/reranking/__init__.py +40 -0
- haiku_rag-0.5.2/src/haiku/rag/reranking/ollama.py +84 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/test_reranker.py +25 -8
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/uv.lock +10 -8
- haiku_rag-0.5.1/src/haiku/rag/reranking/__init__.py +0 -37
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/.github/FUNDING.yml +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/.github/workflows/build-docs.yml +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/.github/workflows/build-publish.yml +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/.gitignore +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/.pre-commit-config.yaml +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/.python-version +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/LICENSE +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/README.md +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/docs/benchmarks.md +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/docs/cli.md +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/docs/index.md +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/docs/installation.md +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/docs/mcp.md +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/docs/python.md +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/docs/server.md +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/mkdocs.yml +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/__init__.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/chunker.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/embeddings/__init__.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/embeddings/base.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/embeddings/ollama.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/embeddings/openai.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/embeddings/voyageai.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/logging.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/mcp.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/monitor.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/qa/__init__.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/qa/anthropic.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/qa/base.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/qa/ollama.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/qa/openai.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/qa/prompts.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/reader.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/reranking/base.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/reranking/cohere.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/reranking/mxbai.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/store/__init__.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/store/engine.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/store/models/__init__.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/store/models/chunk.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/store/models/document.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/store/repositories/__init__.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/store/repositories/base.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/store/repositories/chunk.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/store/repositories/document.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/store/repositories/settings.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/store/upgrades/__init__.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/store/upgrades/v0_3_4.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/src/haiku/rag/utils.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/__init__.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/conftest.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/generate_benchmark_db.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/llm_judge.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/test_app.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/test_chunk.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/test_chunker.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/test_cli.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/test_client.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/test_document.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/test_embedder.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/test_monitor.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/test_qa.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/test_reader.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/test_rebuild.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/test_search.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/test_settings.py +0 -0
- {haiku_rag-0.5.1 → haiku_rag-0.5.2}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haiku.rag
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Retrieval Augmented Generation (RAG) with SQLite
|
|
5
5
|
Author-email: Yiorgis Gozadinos <ggozadinos@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -21,8 +21,7 @@ Requires-Python: >=3.11
|
|
|
21
21
|
Requires-Dist: docling>=2.15.0
|
|
22
22
|
Requires-Dist: fastmcp>=2.8.1
|
|
23
23
|
Requires-Dist: httpx>=0.28.1
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
Requires-Dist: ollama>=0.5.1
|
|
24
|
+
Requires-Dist: ollama>=0.5.3
|
|
26
25
|
Requires-Dist: pydantic>=2.11.7
|
|
27
26
|
Requires-Dist: python-dotenv>=1.1.0
|
|
28
27
|
Requires-Dist: rich>=14.0.0
|
|
@@ -34,6 +33,8 @@ Provides-Extra: anthropic
|
|
|
34
33
|
Requires-Dist: anthropic>=0.56.0; extra == 'anthropic'
|
|
35
34
|
Provides-Extra: cohere
|
|
36
35
|
Requires-Dist: cohere>=5.16.1; extra == 'cohere'
|
|
36
|
+
Provides-Extra: mxbai
|
|
37
|
+
Requires-Dist: mxbai-rerank>=0.1.6; extra == 'mxbai'
|
|
37
38
|
Provides-Extra: openai
|
|
38
39
|
Requires-Dist: openai>=1.0.0; extra == 'openai'
|
|
39
40
|
Provides-Extra: voyageai
|
|
@@ -105,15 +105,37 @@ ANTHROPIC_API_KEY="your-api-key"
|
|
|
105
105
|
|
|
106
106
|
## Reranking
|
|
107
107
|
|
|
108
|
-
Reranking
|
|
108
|
+
Reranking improves search quality by re-ordering the initial search results using specialized models. When enabled, the system retrieves more candidates (3x the requested limit) and then reranks them to return the most relevant results.
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
Reranking is **automatically enabled** by default using Ollama, or if you install the appropriate reranking provider package.
|
|
111
|
+
|
|
112
|
+
### Disabling Reranking
|
|
113
|
+
|
|
114
|
+
To disable reranking completely for faster searches:
|
|
111
115
|
|
|
112
116
|
```bash
|
|
113
|
-
|
|
117
|
+
RERANK_PROVIDER=""
|
|
114
118
|
```
|
|
115
119
|
|
|
116
|
-
###
|
|
120
|
+
### Ollama (Default)
|
|
121
|
+
|
|
122
|
+
Ollama reranking uses LLMs with structured output to rank documents by relevance:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
RERANK_PROVIDER="ollama"
|
|
126
|
+
RERANK_MODEL="qwen3:1.7b" # or any model that supports structured output
|
|
127
|
+
OLLAMA_BASE_URL="http://localhost:11434"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### MixedBread AI
|
|
131
|
+
|
|
132
|
+
For MxBAI reranking, install with mxbai extras:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
uv pip install haiku.rag[mxbai]
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Then configure:
|
|
117
139
|
|
|
118
140
|
```bash
|
|
119
141
|
RERANK_PROVIDER="mxbai"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "haiku.rag"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.2"
|
|
4
4
|
description = "Retrieval Augmented Generation (RAG) with SQLite"
|
|
5
5
|
authors = [{ name = "Yiorgis Gozadinos", email = "ggozadinos@gmail.com" }]
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -25,8 +25,7 @@ dependencies = [
|
|
|
25
25
|
"docling>=2.15.0",
|
|
26
26
|
"fastmcp>=2.8.1",
|
|
27
27
|
"httpx>=0.28.1",
|
|
28
|
-
"
|
|
29
|
-
"ollama>=0.5.1",
|
|
28
|
+
"ollama>=0.5.3",
|
|
30
29
|
"pydantic>=2.11.7",
|
|
31
30
|
"python-dotenv>=1.1.0",
|
|
32
31
|
"rich>=14.0.0",
|
|
@@ -41,6 +40,7 @@ voyageai = ["voyageai>=0.3.2"]
|
|
|
41
40
|
openai = ["openai>=1.0.0"]
|
|
42
41
|
anthropic = ["anthropic>=0.56.0"]
|
|
43
42
|
cohere = ["cohere>=5.16.1"]
|
|
43
|
+
mxbai = ["mxbai-rerank>=0.1.6"]
|
|
44
44
|
|
|
45
45
|
[project.scripts]
|
|
46
46
|
haiku-rag = "haiku.rag.cli:cli"
|
|
@@ -32,9 +32,9 @@ class HaikuRAGApp:
|
|
|
32
32
|
f"[b]Document with id [cyan]{doc.id}[/cyan] added successfully.[/b]"
|
|
33
33
|
)
|
|
34
34
|
|
|
35
|
-
async def add_document_from_source(self,
|
|
35
|
+
async def add_document_from_source(self, source: str):
|
|
36
36
|
async with HaikuRAG(db_path=self.db_path) as self.client:
|
|
37
|
-
doc = await self.client.create_document_from_source(
|
|
37
|
+
doc = await self.client.create_document_from_source(source)
|
|
38
38
|
self._rich_print_document(doc, truncate=True)
|
|
39
39
|
self.console.print(
|
|
40
40
|
f"[b]Document with id [cyan]{doc.id}[/cyan] added successfully.[/b]"
|
|
@@ -81,7 +81,7 @@ def add_document_text(
|
|
|
81
81
|
|
|
82
82
|
@cli.command("add-src", help="Add a document from a file path or URL")
|
|
83
83
|
def add_document_src(
|
|
84
|
-
|
|
84
|
+
source: str = typer.Argument(
|
|
85
85
|
help="The file path or URL of the document to add",
|
|
86
86
|
),
|
|
87
87
|
db: Path = typer.Option(
|
|
@@ -91,7 +91,7 @@ def add_document_src(
|
|
|
91
91
|
),
|
|
92
92
|
):
|
|
93
93
|
app = HaikuRAGApp(db_path=db)
|
|
94
|
-
asyncio.run(app.add_document_from_source(
|
|
94
|
+
asyncio.run(app.add_document_from_source(source=source))
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
@cli.command("get", help="Get and display a document by its ID")
|
|
@@ -319,7 +319,7 @@ class HaikuRAG:
|
|
|
319
319
|
return await self.document_repository.list_all(limit=limit, offset=offset)
|
|
320
320
|
|
|
321
321
|
async def search(
|
|
322
|
-
self, query: str, limit: int = 5, k: int = 60
|
|
322
|
+
self, query: str, limit: int = 5, k: int = 60
|
|
323
323
|
) -> list[tuple[Chunk, float]]:
|
|
324
324
|
"""Search for relevant chunks using hybrid search (vector similarity + full-text search) with reranking.
|
|
325
325
|
|
|
@@ -331,8 +331,10 @@ class HaikuRAG:
|
|
|
331
331
|
Returns:
|
|
332
332
|
List of (chunk, score) tuples ordered by relevance.
|
|
333
333
|
"""
|
|
334
|
+
# Get reranker if available
|
|
335
|
+
reranker = get_reranker()
|
|
334
336
|
|
|
335
|
-
if
|
|
337
|
+
if reranker is None:
|
|
336
338
|
return await self.chunk_repository.search_chunks_hybrid(query, limit, k)
|
|
337
339
|
|
|
338
340
|
# Get more initial results (3X) for reranking
|
|
@@ -340,7 +342,6 @@ class HaikuRAG:
|
|
|
340
342
|
query, limit * 3, k
|
|
341
343
|
)
|
|
342
344
|
# Apply reranking
|
|
343
|
-
reranker = get_reranker()
|
|
344
345
|
chunks = [chunk for chunk, _ in search_results]
|
|
345
346
|
reranked_results = await reranker.rerank(query, chunks, top_n=limit)
|
|
346
347
|
|
|
@@ -19,9 +19,8 @@ class AppConfig(BaseModel):
|
|
|
19
19
|
EMBEDDINGS_MODEL: str = "mxbai-embed-large"
|
|
20
20
|
EMBEDDINGS_VECTOR_DIM: int = 1024
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
RERANK_MODEL: str = "mixedbread-ai/mxbai-rerank-base-v2"
|
|
22
|
+
RERANK_PROVIDER: str = "ollama"
|
|
23
|
+
RERANK_MODEL: str = "qwen3"
|
|
25
24
|
|
|
26
25
|
QA_PROVIDER: str = "ollama"
|
|
27
26
|
QA_MODEL: str = "qwen3"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from haiku.rag.config import Config
|
|
2
|
+
from haiku.rag.reranking.base import RerankerBase
|
|
3
|
+
|
|
4
|
+
_reranker: RerankerBase | None = None
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_reranker() -> RerankerBase | None:
|
|
8
|
+
"""
|
|
9
|
+
Factory function to get the appropriate reranker based on the configuration.
|
|
10
|
+
Returns None if if reranking is disabled.
|
|
11
|
+
"""
|
|
12
|
+
global _reranker
|
|
13
|
+
if _reranker is not None:
|
|
14
|
+
return _reranker
|
|
15
|
+
|
|
16
|
+
if Config.RERANK_PROVIDER == "mxbai":
|
|
17
|
+
try:
|
|
18
|
+
from haiku.rag.reranking.mxbai import MxBAIReranker
|
|
19
|
+
|
|
20
|
+
_reranker = MxBAIReranker()
|
|
21
|
+
return _reranker
|
|
22
|
+
except ImportError:
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
if Config.RERANK_PROVIDER == "cohere":
|
|
26
|
+
try:
|
|
27
|
+
from haiku.rag.reranking.cohere import CohereReranker
|
|
28
|
+
|
|
29
|
+
_reranker = CohereReranker()
|
|
30
|
+
return _reranker
|
|
31
|
+
except ImportError:
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
if Config.RERANK_PROVIDER == "ollama":
|
|
35
|
+
from haiku.rag.reranking.ollama import OllamaReranker
|
|
36
|
+
|
|
37
|
+
_reranker = OllamaReranker()
|
|
38
|
+
return _reranker
|
|
39
|
+
|
|
40
|
+
return None
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from ollama import AsyncClient
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from haiku.rag.config import Config
|
|
7
|
+
from haiku.rag.reranking.base import RerankerBase
|
|
8
|
+
from haiku.rag.store.models.chunk import Chunk
|
|
9
|
+
|
|
10
|
+
OLLAMA_OPTIONS = {"temperature": 0.0, "seed": 42, "num_ctx": 16384}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RerankResult(BaseModel):
|
|
14
|
+
"""Individual rerank result with index and relevance score."""
|
|
15
|
+
|
|
16
|
+
index: int
|
|
17
|
+
relevance_score: float
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RerankResponse(BaseModel):
|
|
21
|
+
"""Response from the reranking model containing ranked results."""
|
|
22
|
+
|
|
23
|
+
results: list[RerankResult]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OllamaReranker(RerankerBase):
|
|
27
|
+
def __init__(self, model: str = Config.RERANK_MODEL):
|
|
28
|
+
self._model = model
|
|
29
|
+
self._client = AsyncClient(host=Config.OLLAMA_BASE_URL)
|
|
30
|
+
|
|
31
|
+
async def rerank(
|
|
32
|
+
self, query: str, chunks: list[Chunk], top_n: int = 10
|
|
33
|
+
) -> list[tuple[Chunk, float]]:
|
|
34
|
+
if not chunks:
|
|
35
|
+
return []
|
|
36
|
+
|
|
37
|
+
documents = []
|
|
38
|
+
for i, chunk in enumerate(chunks):
|
|
39
|
+
documents.append({"index": i, "content": chunk.content})
|
|
40
|
+
|
|
41
|
+
# Create the prompt for reranking
|
|
42
|
+
system_prompt = """You are a document reranking assistant. Given a query and a list of document chunks, you must rank them by relevance to the query.
|
|
43
|
+
|
|
44
|
+
Return your response as a JSON object with a "results" array. Each result should have:
|
|
45
|
+
- "index": the original index of the document (integer)
|
|
46
|
+
- "relevance_score": a score between 0.0 and 1.0 indicating relevance (float, where 1.0 is most relevant)
|
|
47
|
+
|
|
48
|
+
Only return the top documents up to the requested limit, ordered by decreasing relevance score."""
|
|
49
|
+
|
|
50
|
+
documents_text = ""
|
|
51
|
+
for doc in documents:
|
|
52
|
+
documents_text += f"Index {doc['index']}: {doc['content']}\n\n"
|
|
53
|
+
|
|
54
|
+
user_prompt = f"""Query: {query}
|
|
55
|
+
|
|
56
|
+
Documents to rerank:
|
|
57
|
+
{documents_text.strip()}
|
|
58
|
+
|
|
59
|
+
Please rank these documents by relevance to the query and return the top {top_n} results as JSON."""
|
|
60
|
+
|
|
61
|
+
messages = [
|
|
62
|
+
{"role": "system", "content": system_prompt},
|
|
63
|
+
{"role": "user", "content": user_prompt},
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
response = await self._client.chat(
|
|
68
|
+
model=self._model,
|
|
69
|
+
messages=messages,
|
|
70
|
+
format=RerankResponse.model_json_schema(),
|
|
71
|
+
options=OLLAMA_OPTIONS,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
content = response["message"]["content"]
|
|
75
|
+
|
|
76
|
+
parsed_response = RerankResponse.model_validate(json.loads(content))
|
|
77
|
+
return [
|
|
78
|
+
(chunks[result.index], result.relevance_score)
|
|
79
|
+
for result in parsed_response.results[:top_n]
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
except Exception:
|
|
83
|
+
# Fallback: return chunks in original order with same score
|
|
84
|
+
return [(chunks[i], 1.0) for i in range(min(top_n, len(chunks)))]
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
3
|
from haiku.rag.reranking.base import RerankerBase
|
|
4
|
-
from haiku.rag.reranking.mxbai import MxBAIReranker
|
|
5
4
|
from haiku.rag.store.models.chunk import Chunk
|
|
6
5
|
|
|
7
6
|
chunks = [
|
|
@@ -22,7 +21,7 @@ chunks = [
|
|
|
22
21
|
@pytest.mark.asyncio
|
|
23
22
|
async def test_reranker_base():
|
|
24
23
|
reranker = RerankerBase()
|
|
25
|
-
assert reranker._model == "
|
|
24
|
+
assert reranker._model == "qwen3"
|
|
26
25
|
|
|
27
26
|
with pytest.raises(NotImplementedError):
|
|
28
27
|
await reranker.rerank("query", [])
|
|
@@ -30,12 +29,17 @@ async def test_reranker_base():
|
|
|
30
29
|
|
|
31
30
|
@pytest.mark.asyncio
|
|
32
31
|
async def test_mxbai_reranker():
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
try:
|
|
33
|
+
from haiku.rag.reranking.mxbai import MxBAIReranker
|
|
34
|
+
|
|
35
|
+
reranker = MxBAIReranker()
|
|
36
|
+
reranked = await reranker.rerank(
|
|
37
|
+
"Who wrote 'To Kill a Mockingbird'?", chunks, top_n=2
|
|
38
|
+
)
|
|
39
|
+
assert [chunk.document_id for chunk, score in reranked] == [0, 2]
|
|
40
|
+
assert all(isinstance(score, float) for chunk, score in reranked)
|
|
41
|
+
except ImportError:
|
|
42
|
+
pytest.skip("MxBAI package not installed")
|
|
39
43
|
|
|
40
44
|
|
|
41
45
|
@pytest.mark.asyncio
|
|
@@ -54,3 +58,16 @@ async def test_cohere_reranker():
|
|
|
54
58
|
|
|
55
59
|
except ImportError:
|
|
56
60
|
pytest.skip("Cohere package not installed")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@pytest.mark.asyncio
|
|
64
|
+
async def test_ollama_reranker():
|
|
65
|
+
from haiku.rag.reranking.ollama import OllamaReranker
|
|
66
|
+
|
|
67
|
+
reranker = OllamaReranker()
|
|
68
|
+
reranked = await reranker.rerank(
|
|
69
|
+
"Who wrote 'To Kill a Mockingbird'?", chunks, top_n=2
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
assert [chunk.document_id for chunk, score in reranked] == [0, 2]
|
|
73
|
+
assert all(isinstance(score, float) for chunk, score in reranked)
|
|
@@ -880,13 +880,12 @@ wheels = [
|
|
|
880
880
|
|
|
881
881
|
[[package]]
|
|
882
882
|
name = "haiku-rag"
|
|
883
|
-
version = "0.5.
|
|
883
|
+
version = "0.5.2"
|
|
884
884
|
source = { editable = "." }
|
|
885
885
|
dependencies = [
|
|
886
886
|
{ name = "docling" },
|
|
887
887
|
{ name = "fastmcp" },
|
|
888
888
|
{ name = "httpx" },
|
|
889
|
-
{ name = "mxbai-rerank" },
|
|
890
889
|
{ name = "ollama" },
|
|
891
890
|
{ name = "pydantic" },
|
|
892
891
|
{ name = "python-dotenv" },
|
|
@@ -904,6 +903,9 @@ anthropic = [
|
|
|
904
903
|
cohere = [
|
|
905
904
|
{ name = "cohere" },
|
|
906
905
|
]
|
|
906
|
+
mxbai = [
|
|
907
|
+
{ name = "mxbai-rerank" },
|
|
908
|
+
]
|
|
907
909
|
openai = [
|
|
908
910
|
{ name = "openai" },
|
|
909
911
|
]
|
|
@@ -931,8 +933,8 @@ requires-dist = [
|
|
|
931
933
|
{ name = "docling", specifier = ">=2.15.0" },
|
|
932
934
|
{ name = "fastmcp", specifier = ">=2.8.1" },
|
|
933
935
|
{ name = "httpx", specifier = ">=0.28.1" },
|
|
934
|
-
{ name = "mxbai-rerank", specifier = ">=0.1.6" },
|
|
935
|
-
{ name = "ollama", specifier = ">=0.5.
|
|
936
|
+
{ name = "mxbai-rerank", marker = "extra == 'mxbai'", specifier = ">=0.1.6" },
|
|
937
|
+
{ name = "ollama", specifier = ">=0.5.3" },
|
|
936
938
|
{ name = "openai", marker = "extra == 'openai'", specifier = ">=1.0.0" },
|
|
937
939
|
{ name = "pydantic", specifier = ">=2.11.7" },
|
|
938
940
|
{ name = "python-dotenv", specifier = ">=1.1.0" },
|
|
@@ -943,7 +945,7 @@ requires-dist = [
|
|
|
943
945
|
{ name = "voyageai", marker = "extra == 'voyageai'", specifier = ">=0.3.2" },
|
|
944
946
|
{ name = "watchfiles", specifier = ">=1.1.0" },
|
|
945
947
|
]
|
|
946
|
-
provides-extras = ["voyageai", "openai", "anthropic", "cohere"]
|
|
948
|
+
provides-extras = ["voyageai", "openai", "anthropic", "cohere", "mxbai"]
|
|
947
949
|
|
|
948
950
|
[package.metadata.requires-dev]
|
|
949
951
|
dev = [
|
|
@@ -1827,15 +1829,15 @@ wheels = [
|
|
|
1827
1829
|
|
|
1828
1830
|
[[package]]
|
|
1829
1831
|
name = "ollama"
|
|
1830
|
-
version = "0.5.
|
|
1832
|
+
version = "0.5.3"
|
|
1831
1833
|
source = { registry = "https://pypi.org/simple" }
|
|
1832
1834
|
dependencies = [
|
|
1833
1835
|
{ name = "httpx" },
|
|
1834
1836
|
{ name = "pydantic" },
|
|
1835
1837
|
]
|
|
1836
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1838
|
+
sdist = { url = "https://files.pythonhosted.org/packages/91/6d/ae96027416dcc2e98c944c050c492789502d7d7c0b95a740f0bb39268632/ollama-0.5.3.tar.gz", hash = "sha256:40b6dff729df3b24e56d4042fd9d37e231cee8e528677e0d085413a1d6692394", size = 43331, upload-time = "2025-08-07T21:44:10.422Z" }
|
|
1837
1839
|
wheels = [
|
|
1838
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1840
|
+
{ url = "https://files.pythonhosted.org/packages/be/f6/2091e50b8b6c3e6901f6eab283d5efd66fb71c86ddb1b4d68766c3eeba0f/ollama-0.5.3-py3-none-any.whl", hash = "sha256:a8303b413d99a9043dbf77ebf11ced672396b59bec27e6d5db67c88f01b279d2", size = 13490, upload-time = "2025-08-07T21:44:09.353Z" },
|
|
1839
1841
|
]
|
|
1840
1842
|
|
|
1841
1843
|
[[package]]
|
|
@@ -1,37 +0,0 @@
|
|
|
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}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|