Meta in CLI add-src, add

This commit is contained in:
Yiorgis Gozadinos 2025-09-23 16:39:27 +03:00
parent 860c3acbfc
commit 45d0b47a02
No known key found for this signature in database
9 changed files with 122 additions and 18 deletions

View file

@ -28,7 +28,8 @@ uv pip install haiku.rag
# Add documents
haiku-rag add "Your content here"
haiku-rag add-src document.pdf
haiku-rag add "Your content here" --meta author=alice --meta topic=notes
haiku-rag add-src document.pdf --meta source=manual
# Search
haiku-rag search "query"

View file

@ -27,6 +27,9 @@ haiku-rag list
From text:
```bash
haiku-rag add "Your document content here"
# Attach metadata (repeat --meta for multiple entries)
haiku-rag add "Your document content here" --meta author=alice --meta topic=notes
```
From file or URL:
@ -36,6 +39,9 @@ haiku-rag add-src https://example.com/article.html
# Optionally set a humanreadable title stored in the DB schema
haiku-rag add-src /mnt/data/doc1.pdf --title "Q3 Financial Report"
# Optionally attach metadata (repeat --meta)
haiku-rag add-src /mnt/data/doc1.pdf --meta source=manual --meta lang=en
```
!!! note

View file

@ -43,7 +43,8 @@ async with HaikuRAG("database.lancedb") as client:
Or use the CLI:
```bash
haiku-rag add "Your document content"
haiku-rag add-src /path/to/document.pdf --title "Q3 Financial Report"
haiku-rag add "Your document content" --meta author=alice
haiku-rag add-src /path/to/document.pdf --title "Q3 Financial Report" --meta source=manual
haiku-rag search "query"
haiku-rag ask "Who is the author of haiku.rag?"
haiku-rag migrate old_database.sqlite # Migrate from SQLite

View file

@ -144,17 +144,21 @@ class HaikuRAGApp:
for doc in documents:
self._rich_print_document(doc, truncate=True)
async def add_document_from_text(self, text: str):
async def add_document_from_text(self, text: str, metadata: dict | None = None):
async with HaikuRAG(db_path=self.db_path) as self.client:
doc = await self.client.create_document(text)
doc = await self.client.create_document(text, metadata=metadata)
self._rich_print_document(doc, truncate=True)
self.console.print(
f"[bold green]Document {doc.id} added successfully.[/bold green]"
)
async def add_document_from_source(self, source: str, title: str | None = None):
async def add_document_from_source(
self, source: str, title: str | None = None, metadata: dict | None = None
):
async with HaikuRAG(db_path=self.db_path) as self.client:
doc = await self.client.create_document_from_source(source, title=title)
doc = await self.client.create_document_from_source(
source, title=title, metadata=metadata
)
self._rich_print_document(doc, truncate=True)
self.console.print(
f"[bold green]Document {doc.id} added successfully.[/bold green]"

View file

@ -137,11 +137,35 @@ def list_documents(
asyncio.run(app.list_documents())
def _parse_meta_options(meta: list[str] | None) -> dict[str, str]:
"""Parse repeated --meta KEY=VALUE options into a dictionary.
Raises a Typer error if any entry is malformed.
"""
result: dict[str, str] = {}
if not meta:
return result
for item in meta:
if "=" not in item:
raise typer.BadParameter("--meta must be in KEY=VALUE format")
key, value = item.split("=", 1)
if not key:
raise typer.BadParameter("--meta key cannot be empty")
result[key] = value
return result
@cli.command("add", help="Add a document from text input")
def add_document_text(
text: str = typer.Argument(
help="The text content of the document to add",
),
meta: list[str] | None = typer.Option(
None,
"--meta",
help="Metadata entries as KEY=VALUE (repeatable)",
metavar="KEY=VALUE",
),
db: Path = typer.Option(
Config.DEFAULT_DATA_DIR / "haiku.rag.lancedb",
"--db",
@ -151,7 +175,8 @@ def add_document_text(
from haiku.rag.app import HaikuRAGApp
app = HaikuRAGApp(db_path=db)
asyncio.run(app.add_document_from_text(text=text))
metadata = _parse_meta_options(meta)
asyncio.run(app.add_document_from_text(text=text, metadata=metadata or None))
@cli.command("add-src", help="Add a document from a file path or URL")
@ -165,6 +190,12 @@ def add_document_src(
"--title",
help="Optional human-readable title to store with the document",
),
meta: list[str] | None = typer.Option(
None,
"--meta",
help="Metadata entries as KEY=VALUE (repeatable)",
metavar="KEY=VALUE",
),
db: Path = typer.Option(
Config.DEFAULT_DATA_DIR / "haiku.rag.lancedb",
"--db",
@ -174,7 +205,12 @@ def add_document_src(
from haiku.rag.app import HaikuRAGApp
app = HaikuRAGApp(db_path=db)
asyncio.run(app.add_document_from_source(source=source, title=title))
metadata = _parse_meta_options(meta)
asyncio.run(
app.add_document_from_source(
source=source, title=title, metadata=metadata or None
)
)
@cli.command("get", help="Get and display a document by its ID")

View file

@ -1,4 +1,4 @@
from .engine import Store
from .models import Chunk, Document
__all__ = ["Store", "Chunk", "Document"]
__all__ = ["Store", "Chunk", "Document"]

View file

@ -1,4 +1,4 @@
from .chunk import Chunk
from .document import Document
__all__ = ["Chunk", "Document"]
__all__ = ["Chunk", "Document"]

View file

@ -54,7 +54,10 @@ async def test_add_document_from_text(app: HaikuRAGApp, monkeypatch):
with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
await app.add_document_from_text("test document")
mock_client.create_document.assert_called_once_with("test document")
mock_client.create_document.assert_called_once()
args, kwargs = mock_client.create_document.call_args
assert args[0] == "test document"
assert kwargs.get("metadata") is None
mock_rich_print.assert_called_once_with(mock_doc, truncate=True)
mock_print.assert_called_once_with(
"[bold green]Document 1 added successfully.[/bold green]"
@ -78,9 +81,11 @@ async def test_add_document_from_source(app: HaikuRAGApp, monkeypatch):
with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
await app.add_document_from_source(file_path)
mock_client.create_document_from_source.assert_called_once_with(
file_path, title=None
)
mock_client.create_document_from_source.assert_called_once()
args, kwargs = mock_client.create_document_from_source.call_args
assert args[0] == file_path
assert kwargs.get("title") is None
assert kwargs.get("metadata") is None
mock_rich_print.assert_called_once_with(mock_doc, truncate=True)
mock_print.assert_called_once_with(
"[bold green]Document 1 added successfully.[/bold green]"

View file

@ -28,9 +28,10 @@ def test_add_document_text():
result = runner.invoke(cli, ["add", "test document"])
assert result.exit_code == 0
mock_app_instance.add_document_from_text.assert_called_once_with(
text="test document"
)
mock_app_instance.add_document_from_text.assert_called_once()
_, kwargs = mock_app_instance.add_document_from_text.call_args
assert kwargs.get("text") == "test document"
assert kwargs.get("metadata") is None
def test_add_document_src():
@ -57,8 +58,58 @@ def test_add_document_src_with_title():
mock_app_instance.add_document_from_source.assert_called_once()
# Verify title is forwarded (inspect call kwargs)
_, kwargs = mock_app_instance.add_document_from_source.call_args
assert kwargs.get("title") == "Nice Name"
assert kwargs.get("title") == "Nice Name"
assert kwargs.get("source") == "test.txt"
def test_add_document_text_with_meta():
with patch("haiku.rag.app.HaikuRAGApp") as mock_app:
mock_app_instance = MagicMock()
mock_app_instance.add_document_from_text = AsyncMock()
mock_app.return_value = mock_app_instance
result = runner.invoke(
cli,
[
"add",
"some text",
"--meta",
"author=alice",
"--meta",
"topic=notes",
],
)
assert result.exit_code == 0
mock_app_instance.add_document_from_text.assert_called_once()
_, kwargs = mock_app_instance.add_document_from_text.call_args
assert kwargs.get("text") == "some text"
assert kwargs.get("metadata") == {"author": "alice", "topic": "notes"}
def test_add_document_src_with_meta():
with patch("haiku.rag.app.HaikuRAGApp") as mock_app:
mock_app_instance = MagicMock()
mock_app_instance.add_document_from_source = AsyncMock()
mock_app.return_value = mock_app_instance
result = runner.invoke(
cli,
[
"add-src",
"test.txt",
"--meta",
"source=manual",
"--meta",
"lang=en",
],
)
assert result.exit_code == 0
mock_app_instance.add_document_from_source.assert_called_once()
_, kwargs = mock_app_instance.add_document_from_source.call_args
assert kwargs.get("source") == "test.txt"
assert kwargs.get("metadata") == {"source": "manual", "lang": "en"}
def test_get_document():