Meta in CLI add-src, add
This commit is contained in:
parent
860c3acbfc
commit
45d0b47a02
9 changed files with 122 additions and 18 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 human‑readable 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]"
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from .engine import Store
|
||||
from .models import Chunk, Document
|
||||
|
||||
__all__ = ["Store", "Chunk", "Document"]
|
||||
__all__ = ["Store", "Chunk", "Document"]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from .chunk import Chunk
|
||||
from .document import Document
|
||||
|
||||
__all__ = ["Chunk", "Document"]
|
||||
__all__ = ["Chunk", "Document"]
|
||||
|
|
|
|||
|
|
@ -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]"
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
Loading…
Reference in a new issue