haiku.rag 0.7.2__tar.gz → 0.7.4__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.7.2 → haiku_rag-0.7.4}/.gitignore +1 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/PKG-INFO +1 -1
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/docs/cli.md +22 -2
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/pyproject.toml +1 -1
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/app.py +7 -2
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/cli.py +69 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/embeddings/base.py +1 -1
- haiku_rag-0.7.4/src/haiku/rag/embeddings/ollama.py +17 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/embeddings/openai.py +5 -2
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/embeddings/vllm.py +5 -2
- haiku_rag-0.7.4/src/haiku/rag/embeddings/voyageai.py +17 -0
- haiku_rag-0.7.4/src/haiku/rag/logging.py +53 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/store/repositories/chunk.py +1 -7
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_embedder.py +28 -4
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/uv.lock +1 -1
- haiku_rag-0.7.2/src/haiku/rag/embeddings/ollama.py +0 -11
- haiku_rag-0.7.2/src/haiku/rag/embeddings/voyageai.py +0 -13
- haiku_rag-0.7.2/src/haiku/rag/logging.py +0 -29
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/.github/FUNDING.yml +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/.github/workflows/build-docs.yml +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/.github/workflows/build-publish.yml +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/.pre-commit-config.yaml +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/.python-version +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/LICENSE +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/README.md +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/docs/benchmarks.md +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/docs/configuration.md +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/docs/index.md +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/docs/installation.md +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/docs/mcp.md +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/docs/python.md +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/docs/server.md +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/mkdocs.yml +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/__init__.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/chunker.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/client.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/config.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/embeddings/__init__.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/mcp.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/migration.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/monitor.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/qa/__init__.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/qa/agent.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/qa/prompts.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/reader.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/reranking/__init__.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/reranking/base.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/reranking/cohere.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/reranking/mxbai.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/reranking/vllm.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/store/__init__.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/store/engine.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/store/models/__init__.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/store/models/chunk.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/store/models/document.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/store/repositories/__init__.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/store/repositories/document.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/store/repositories/settings.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/store/upgrades/__init__.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/src/haiku/rag/utils.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/__init__.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/conftest.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/generate_benchmark_db.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/llm_judge.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_app.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_chunk.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_chunker.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_cli.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_client.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_document.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_lancedb_connection.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_monitor.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_qa.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_reader.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_rebuild.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_reranker.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_search.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_settings.py +0 -0
- {haiku_rag-0.7.2 → haiku_rag-0.7.4}/tests/test_utils.py +0 -0
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
The `haiku-rag` CLI provides complete document management functionality.
|
|
4
4
|
|
|
5
|
+
## Shell Autocompletion
|
|
6
|
+
|
|
7
|
+
Enable shell autocompletion for faster, error‑free usage.
|
|
8
|
+
|
|
9
|
+
- Temporary (current shell only):
|
|
10
|
+
```bash
|
|
11
|
+
eval "$(haiku-rag --show-completion)"
|
|
12
|
+
```
|
|
13
|
+
- Permanent installation:
|
|
14
|
+
```bash
|
|
15
|
+
haiku-rag --install-completion
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
What’s completed:
|
|
19
|
+
- `get` and `delete`/`rm`: Document IDs from the selected database (respects `--db`).
|
|
20
|
+
- `add-src`: Local filesystem paths (URLs can still be typed manually).
|
|
21
|
+
|
|
5
22
|
## Document Management
|
|
6
23
|
|
|
7
24
|
### List Documents
|
|
@@ -26,13 +43,16 @@ haiku-rag add-src https://example.com/article.html
|
|
|
26
43
|
### Get Document
|
|
27
44
|
|
|
28
45
|
```bash
|
|
29
|
-
haiku-rag get
|
|
46
|
+
haiku-rag get <TAB>
|
|
47
|
+
# or
|
|
48
|
+
haiku-rag get 3f4a... # document ID (autocomplete supported)
|
|
30
49
|
```
|
|
31
50
|
|
|
32
51
|
### Delete Document
|
|
33
52
|
|
|
34
53
|
```bash
|
|
35
|
-
haiku-rag delete
|
|
54
|
+
haiku-rag delete <TAB>
|
|
55
|
+
haiku-rag rm <TAB> # alias
|
|
36
56
|
```
|
|
37
57
|
|
|
38
58
|
### Rebuild Database
|
|
@@ -50,8 +50,13 @@ class HaikuRAGApp:
|
|
|
50
50
|
|
|
51
51
|
async def delete_document(self, doc_id: str):
|
|
52
52
|
async with HaikuRAG(db_path=self.db_path) as self.client:
|
|
53
|
-
await self.client.delete_document(doc_id)
|
|
54
|
-
|
|
53
|
+
deleted = await self.client.delete_document(doc_id)
|
|
54
|
+
if deleted:
|
|
55
|
+
self.console.print(f"[b]Document {doc_id} deleted successfully.[/b]")
|
|
56
|
+
else:
|
|
57
|
+
self.console.print(
|
|
58
|
+
f"[yellow]Document with id {doc_id} not found.[/yellow]"
|
|
59
|
+
)
|
|
55
60
|
|
|
56
61
|
async def search(self, query: str, limit: int = 5):
|
|
57
62
|
async with HaikuRAG(db_path=self.db_path) as self.client:
|
|
@@ -8,6 +8,7 @@ from rich.console import Console
|
|
|
8
8
|
|
|
9
9
|
from haiku.rag.app import HaikuRAGApp
|
|
10
10
|
from haiku.rag.config import Config
|
|
11
|
+
from haiku.rag.logging import configure_cli_logging
|
|
11
12
|
from haiku.rag.migration import migrate_sqlite_to_lancedb
|
|
12
13
|
from haiku.rag.utils import is_up_to_date
|
|
13
14
|
|
|
@@ -21,6 +22,65 @@ cli = typer.Typer(
|
|
|
21
22
|
console = Console()
|
|
22
23
|
|
|
23
24
|
|
|
25
|
+
def complete_document_ids(ctx: typer.Context, incomplete: str):
|
|
26
|
+
"""Autocomplete document IDs from the selected DB."""
|
|
27
|
+
db_path = ctx.params.get("db") or (Config.DEFAULT_DATA_DIR / "haiku.rag.lancedb")
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from haiku.rag.client import HaikuRAG
|
|
31
|
+
|
|
32
|
+
async def _list_ids():
|
|
33
|
+
async with HaikuRAG(db_path) as client:
|
|
34
|
+
docs = await client.list_documents()
|
|
35
|
+
return [d.id for d in docs if d.id]
|
|
36
|
+
|
|
37
|
+
ids = asyncio.run(_list_ids())
|
|
38
|
+
except Exception:
|
|
39
|
+
return []
|
|
40
|
+
|
|
41
|
+
return [i for i in ids if i and i.startswith(incomplete)]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def complete_local_paths(ctx: typer.Context, incomplete: str) -> list[str]:
|
|
45
|
+
"""Autocomplete local filesystem paths.
|
|
46
|
+
|
|
47
|
+
Provides directory/file suggestions based on the current incomplete input.
|
|
48
|
+
Does not validate or restrict to specific extensions to keep it flexible
|
|
49
|
+
(URLs are still allowed to be typed manually).
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
text = incomplete or ""
|
|
53
|
+
|
|
54
|
+
# Expand user home
|
|
55
|
+
from os.path import expanduser
|
|
56
|
+
|
|
57
|
+
expanded = expanduser(text)
|
|
58
|
+
p = Path(expanded)
|
|
59
|
+
|
|
60
|
+
# Choose directory to list and prefix to filter
|
|
61
|
+
if text == "" or text.endswith(("/", "\\")):
|
|
62
|
+
directory = p
|
|
63
|
+
prefix = ""
|
|
64
|
+
else:
|
|
65
|
+
directory = p.parent
|
|
66
|
+
prefix = p.name
|
|
67
|
+
|
|
68
|
+
if not directory.exists():
|
|
69
|
+
return []
|
|
70
|
+
|
|
71
|
+
suggestions: list[str] = []
|
|
72
|
+
for entry in directory.iterdir():
|
|
73
|
+
name = entry.name
|
|
74
|
+
if not prefix or name.startswith(prefix):
|
|
75
|
+
suggestion = str(directory / name)
|
|
76
|
+
if entry.is_dir():
|
|
77
|
+
suggestion += "/"
|
|
78
|
+
suggestions.append(suggestion)
|
|
79
|
+
return suggestions
|
|
80
|
+
except Exception:
|
|
81
|
+
return []
|
|
82
|
+
|
|
83
|
+
|
|
24
84
|
async def check_version():
|
|
25
85
|
"""Check if haiku.rag is up to date and show warning if not."""
|
|
26
86
|
up_to_date, current_version, latest_version = await is_up_to_date()
|
|
@@ -49,6 +109,8 @@ def main(
|
|
|
49
109
|
),
|
|
50
110
|
):
|
|
51
111
|
"""haiku.rag CLI - Vector database RAG system"""
|
|
112
|
+
# Ensure only haiku.rag logs are emitted in CLI context
|
|
113
|
+
configure_cli_logging()
|
|
52
114
|
# Run version check before any command
|
|
53
115
|
asyncio.run(check_version())
|
|
54
116
|
|
|
@@ -84,6 +146,7 @@ def add_document_text(
|
|
|
84
146
|
def add_document_src(
|
|
85
147
|
source: str = typer.Argument(
|
|
86
148
|
help="The file path or URL of the document to add",
|
|
149
|
+
autocompletion=complete_local_paths,
|
|
87
150
|
),
|
|
88
151
|
db: Path = typer.Option(
|
|
89
152
|
Config.DEFAULT_DATA_DIR / "haiku.rag.lancedb",
|
|
@@ -99,6 +162,7 @@ def add_document_src(
|
|
|
99
162
|
def get_document(
|
|
100
163
|
doc_id: str = typer.Argument(
|
|
101
164
|
help="The ID of the document to get",
|
|
165
|
+
autocompletion=complete_document_ids,
|
|
102
166
|
),
|
|
103
167
|
db: Path = typer.Option(
|
|
104
168
|
Config.DEFAULT_DATA_DIR / "haiku.rag.lancedb",
|
|
@@ -114,6 +178,7 @@ def get_document(
|
|
|
114
178
|
def delete_document(
|
|
115
179
|
doc_id: str = typer.Argument(
|
|
116
180
|
help="The ID of the document to delete",
|
|
181
|
+
autocompletion=complete_document_ids,
|
|
117
182
|
),
|
|
118
183
|
db: Path = typer.Option(
|
|
119
184
|
Config.DEFAULT_DATA_DIR / "haiku.rag.lancedb",
|
|
@@ -125,6 +190,10 @@ def delete_document(
|
|
|
125
190
|
asyncio.run(app.delete_document(doc_id=doc_id))
|
|
126
191
|
|
|
127
192
|
|
|
193
|
+
# Add alias `rm` for delete
|
|
194
|
+
cli.command("rm", help="Alias for delete: remove a document by its ID")(delete_document)
|
|
195
|
+
|
|
196
|
+
|
|
128
197
|
@cli.command("search", help="Search for documents by a query")
|
|
129
198
|
def search(
|
|
130
199
|
query: str = typer.Argument(
|
|
@@ -9,7 +9,7 @@ class EmbedderBase:
|
|
|
9
9
|
self._model = model
|
|
10
10
|
self._vector_dim = vector_dim
|
|
11
11
|
|
|
12
|
-
async def embed(self, text: str) -> list[float]:
|
|
12
|
+
async def embed(self, text: str | list[str]) -> list[float] | list[list[float]]:
|
|
13
13
|
raise NotImplementedError(
|
|
14
14
|
"Embedder is an abstract class. Please implement the embed method in a subclass."
|
|
15
15
|
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from openai import AsyncOpenAI
|
|
2
|
+
|
|
3
|
+
from haiku.rag.config import Config
|
|
4
|
+
from haiku.rag.embeddings.base import EmbedderBase
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Embedder(EmbedderBase):
|
|
8
|
+
async def embed(self, text: str | list[str]) -> list[float] | list[list[float]]:
|
|
9
|
+
client = AsyncOpenAI(base_url=f"{Config.OLLAMA_BASE_URL}/v1", api_key="dummy")
|
|
10
|
+
response = await client.embeddings.create(
|
|
11
|
+
model=self._model,
|
|
12
|
+
input=text,
|
|
13
|
+
)
|
|
14
|
+
if isinstance(text, str):
|
|
15
|
+
return response.data[0].embedding
|
|
16
|
+
else:
|
|
17
|
+
return [item.embedding for item in response.data]
|
|
@@ -4,10 +4,13 @@ from haiku.rag.embeddings.base import EmbedderBase
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class Embedder(EmbedderBase):
|
|
7
|
-
async def embed(self, text: str) -> list[float]:
|
|
7
|
+
async def embed(self, text: str | list[str]) -> list[float] | list[list[float]]:
|
|
8
8
|
client = AsyncOpenAI()
|
|
9
9
|
response = await client.embeddings.create(
|
|
10
10
|
model=self._model,
|
|
11
11
|
input=text,
|
|
12
12
|
)
|
|
13
|
-
|
|
13
|
+
if isinstance(text, str):
|
|
14
|
+
return response.data[0].embedding
|
|
15
|
+
else:
|
|
16
|
+
return [item.embedding for item in response.data]
|
|
@@ -5,7 +5,7 @@ from haiku.rag.embeddings.base import EmbedderBase
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class Embedder(EmbedderBase):
|
|
8
|
-
async def embed(self, text: str) -> list[float]:
|
|
8
|
+
async def embed(self, text: str | list[str]) -> list[float] | list[list[float]]:
|
|
9
9
|
client = AsyncOpenAI(
|
|
10
10
|
base_url=f"{Config.VLLM_EMBEDDINGS_BASE_URL}/v1", api_key="dummy"
|
|
11
11
|
)
|
|
@@ -13,4 +13,7 @@ class Embedder(EmbedderBase):
|
|
|
13
13
|
model=self._model,
|
|
14
14
|
input=text,
|
|
15
15
|
)
|
|
16
|
-
|
|
16
|
+
if isinstance(text, str):
|
|
17
|
+
return response.data[0].embedding
|
|
18
|
+
else:
|
|
19
|
+
return [item.embedding for item in response.data]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from voyageai.client import Client # type: ignore
|
|
3
|
+
|
|
4
|
+
from haiku.rag.embeddings.base import EmbedderBase
|
|
5
|
+
|
|
6
|
+
class Embedder(EmbedderBase):
|
|
7
|
+
async def embed(self, text: str | list[str]) -> list[float] | list[list[float]]:
|
|
8
|
+
client = Client()
|
|
9
|
+
if isinstance(text, str):
|
|
10
|
+
res = client.embed([text], model=self._model, output_dtype="float")
|
|
11
|
+
return res.embeddings[0] # type: ignore[return-value]
|
|
12
|
+
else:
|
|
13
|
+
res = client.embed(text, model=self._model, output_dtype="float")
|
|
14
|
+
return res.embeddings # type: ignore[return-value]
|
|
15
|
+
|
|
16
|
+
except ImportError:
|
|
17
|
+
pass
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.logging import RichHandler
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_logger() -> logging.Logger:
|
|
8
|
+
"""Return the library logger configured with a Rich handler."""
|
|
9
|
+
logger = logging.getLogger("haiku.rag")
|
|
10
|
+
|
|
11
|
+
handler = RichHandler(
|
|
12
|
+
console=Console(stderr=True),
|
|
13
|
+
rich_tracebacks=True,
|
|
14
|
+
)
|
|
15
|
+
formatter = logging.Formatter("%(message)s")
|
|
16
|
+
handler.setFormatter(formatter)
|
|
17
|
+
|
|
18
|
+
logger.setLevel(logging.INFO)
|
|
19
|
+
|
|
20
|
+
# Remove any existing handlers to avoid duplicates on reconfiguration
|
|
21
|
+
for hdlr in logger.handlers[:]:
|
|
22
|
+
logger.removeHandler(hdlr)
|
|
23
|
+
|
|
24
|
+
logger.addHandler(handler)
|
|
25
|
+
# Do not let messages propagate to the root logger
|
|
26
|
+
logger.propagate = False
|
|
27
|
+
return logger
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def configure_cli_logging(level: int = logging.INFO) -> logging.Logger:
|
|
31
|
+
"""Configure logging for CLI runs.
|
|
32
|
+
|
|
33
|
+
- Silence ALL non-haiku.rag loggers by detaching root handlers and setting
|
|
34
|
+
their level to ERROR.
|
|
35
|
+
- Attach a Rich handler only to the "haiku.rag" logger.
|
|
36
|
+
- Prevent propagation so only our logger prints in the CLI.
|
|
37
|
+
"""
|
|
38
|
+
# Silence root logger completely
|
|
39
|
+
root = logging.getLogger()
|
|
40
|
+
for hdlr in root.handlers[:]:
|
|
41
|
+
root.removeHandler(hdlr)
|
|
42
|
+
root.setLevel(logging.ERROR)
|
|
43
|
+
|
|
44
|
+
# Optionally silence some commonly noisy libraries explicitly as a safeguard
|
|
45
|
+
for noisy in ("httpx", "httpcore", "docling", "urllib3", "asyncio"):
|
|
46
|
+
logging.getLogger(noisy).setLevel(logging.ERROR)
|
|
47
|
+
logging.getLogger(noisy).propagate = False
|
|
48
|
+
|
|
49
|
+
# Configure and return our app logger
|
|
50
|
+
logger = get_logger()
|
|
51
|
+
logger.setLevel(level)
|
|
52
|
+
logger.propagate = False
|
|
53
|
+
return logger
|
|
@@ -154,13 +154,7 @@ class ChunkRepository:
|
|
|
154
154
|
"""Create chunks and embeddings for a document from DoclingDocument."""
|
|
155
155
|
chunk_texts = await chunker.chunk(document)
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
embeddings_tasks = []
|
|
159
|
-
for chunk_text in chunk_texts:
|
|
160
|
-
embeddings_tasks.append(self.embedder.embed(chunk_text))
|
|
161
|
-
|
|
162
|
-
# Wait for all embeddings to complete
|
|
163
|
-
embeddings = await asyncio.gather(*embeddings_tasks)
|
|
157
|
+
embeddings = await self.embedder.embed(chunk_texts)
|
|
164
158
|
|
|
165
159
|
# Prepare all chunk records for batch insertion
|
|
166
160
|
chunk_records = []
|
|
@@ -28,7 +28,13 @@ async def test_ollama_embedder():
|
|
|
28
28
|
"Python is my favorite programming language.",
|
|
29
29
|
"I love to travel and see new places.",
|
|
30
30
|
]
|
|
31
|
-
|
|
31
|
+
|
|
32
|
+
# Test batch embedding
|
|
33
|
+
embeddings = await embedder.embed(phrases)
|
|
34
|
+
assert isinstance(embeddings, list)
|
|
35
|
+
assert len(embeddings) == 3
|
|
36
|
+
assert all(isinstance(emb, list) for emb in embeddings)
|
|
37
|
+
embeddings = [np.array(emb) for emb in embeddings]
|
|
32
38
|
|
|
33
39
|
test_phrase = "I am going for a camping trip."
|
|
34
40
|
test_embedding = await embedder.embed(test_phrase)
|
|
@@ -58,7 +64,13 @@ async def test_openai_embedder():
|
|
|
58
64
|
"Python is my favorite programming language.",
|
|
59
65
|
"I love to travel and see new places.",
|
|
60
66
|
]
|
|
61
|
-
|
|
67
|
+
|
|
68
|
+
# Test batch embedding
|
|
69
|
+
embeddings = await embedder.embed(phrases)
|
|
70
|
+
assert isinstance(embeddings, list)
|
|
71
|
+
assert len(embeddings) == 3
|
|
72
|
+
assert all(isinstance(emb, list) for emb in embeddings)
|
|
73
|
+
embeddings = [np.array(emb) for emb in embeddings]
|
|
62
74
|
|
|
63
75
|
test_phrase = "I am going for a camping trip."
|
|
64
76
|
test_embedding = await embedder.embed(test_phrase)
|
|
@@ -91,7 +103,13 @@ async def test_voyageai_embedder():
|
|
|
91
103
|
"Python is my favorite programming language.",
|
|
92
104
|
"I love to travel and see new places.",
|
|
93
105
|
]
|
|
94
|
-
|
|
106
|
+
|
|
107
|
+
# Test batch embedding
|
|
108
|
+
embeddings = await embedder.embed(phrases)
|
|
109
|
+
assert isinstance(embeddings, list)
|
|
110
|
+
assert len(embeddings) == 3
|
|
111
|
+
assert all(isinstance(emb, list) for emb in embeddings)
|
|
112
|
+
embeddings = [np.array(emb) for emb in embeddings]
|
|
95
113
|
|
|
96
114
|
test_phrase = "I am going for a camping trip."
|
|
97
115
|
test_embedding = await embedder.embed(test_phrase)
|
|
@@ -126,7 +144,13 @@ async def test_vllm_embedder():
|
|
|
126
144
|
"Python is my favorite programming language.",
|
|
127
145
|
"I love to travel and see new places.",
|
|
128
146
|
]
|
|
129
|
-
|
|
147
|
+
|
|
148
|
+
# Test batch embedding
|
|
149
|
+
embeddings = await embedder.embed(phrases)
|
|
150
|
+
assert isinstance(embeddings, list)
|
|
151
|
+
assert len(embeddings) == 3
|
|
152
|
+
assert all(isinstance(emb, list) for emb in embeddings)
|
|
153
|
+
embeddings = [np.array(emb) for emb in embeddings]
|
|
130
154
|
|
|
131
155
|
test_phrase = "I am going for a camping trip."
|
|
132
156
|
test_embedding = await embedder.embed(test_phrase)
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
from ollama import AsyncClient
|
|
2
|
-
|
|
3
|
-
from haiku.rag.config import Config
|
|
4
|
-
from haiku.rag.embeddings.base import EmbedderBase
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class Embedder(EmbedderBase):
|
|
8
|
-
async def embed(self, text: str) -> list[float]:
|
|
9
|
-
client = AsyncClient(host=Config.OLLAMA_BASE_URL)
|
|
10
|
-
res = await client.embeddings(model=self._model, prompt=text)
|
|
11
|
-
return list(res["embedding"])
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
try:
|
|
2
|
-
from voyageai.client import Client # type: ignore
|
|
3
|
-
|
|
4
|
-
from haiku.rag.embeddings.base import EmbedderBase
|
|
5
|
-
|
|
6
|
-
class Embedder(EmbedderBase):
|
|
7
|
-
async def embed(self, text: str) -> list[float]:
|
|
8
|
-
client = Client()
|
|
9
|
-
res = client.embed([text], model=self._model, output_dtype="float")
|
|
10
|
-
return res.embeddings[0] # type: ignore[return-value]
|
|
11
|
-
|
|
12
|
-
except ImportError:
|
|
13
|
-
pass
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
|
-
from rich.console import Console
|
|
4
|
-
from rich.logging import RichHandler
|
|
5
|
-
|
|
6
|
-
logging.basicConfig(level=logging.DEBUG)
|
|
7
|
-
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
8
|
-
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
|
9
|
-
logging.getLogger("docling").setLevel(logging.WARNING)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def get_logger() -> logging.Logger:
|
|
13
|
-
logger = logging.getLogger("haiku.rag")
|
|
14
|
-
|
|
15
|
-
handler = RichHandler(
|
|
16
|
-
console=Console(stderr=True),
|
|
17
|
-
rich_tracebacks=True,
|
|
18
|
-
)
|
|
19
|
-
formatter = logging.Formatter("%(message)s")
|
|
20
|
-
handler.setFormatter(formatter)
|
|
21
|
-
|
|
22
|
-
logger.setLevel("INFO")
|
|
23
|
-
|
|
24
|
-
# Remove any existing handlers to avoid duplicates on reconfiguration
|
|
25
|
-
for hdlr in logger.handlers[:]:
|
|
26
|
-
logger.removeHandler(hdlr)
|
|
27
|
-
|
|
28
|
-
logger.addHandler(handler)
|
|
29
|
-
return logger
|
|
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
|