Coerce a string db_path to Path at the Store and capability boundaries
The documented `HaikuRAG("knowledge.lancedb")` and `rag(db_path="my.lancedb")`
forms both raised `AttributeError: 'str' object has no attribute 'exists'`.
`Store.__init__` assigned its argument to a `Path`-annotated attribute without
coercing, and `resolve_db_path` returned a non-None argument unchanged. Every
runnable example wraps the path in `Path(...)`, which is why it survived.
Coerce in `Store.__init__` and `resolve_db_path`; widen the annotations on
`Store`, `HaikuRAG` and both `create_capability` factories to accept `str`.
This commit is contained in:
parent
def24a2434
commit
971ec0a5b0
8 changed files with 46 additions and 11 deletions
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
### Fixed
|
||||
|
||||
- `Store`, `HaikuRAG` and `create_capability` coerce a string `db_path` to `Path`, as the documented
|
||||
`HaikuRAG("knowledge.lancedb")` and `rag(db_path="my.lancedb")` forms require.
|
||||
- A capability search that matches nothing returns `No results found.` instead of an empty string.
|
||||
- Repeating a search query within a question accumulates the results of both calls instead of replacing the earlier ones.
|
||||
- `file://` URIs resolve to a Windows path through `url2pathname`: `file:///C:/docs/a.pdf` was read as `\C:\docs\a.pdf`, so ingestion reported `File does not exist` for every discovered file. A URI authority is kept as a UNC server/share (`file://server/share/a.pdf`) except `localhost`, which is dropped.
|
||||
|
|
|
|||
|
|
@ -70,9 +70,9 @@ def _nearest_known_id(chunk_id: str, known_ids: list[str]) -> str:
|
|||
return match[0] if match else chunk_id
|
||||
|
||||
|
||||
def resolve_db_path(db_path: Path | None, config: AppConfig) -> Path:
|
||||
def resolve_db_path(db_path: Path | str | None, config: AppConfig) -> Path:
|
||||
if db_path is not None:
|
||||
return db_path
|
||||
return Path(db_path)
|
||||
if env_db := os.environ.get("HAIKU_RAG_DB"):
|
||||
return Path(env_db).expanduser()
|
||||
return config.storage.data_dir / "haiku.rag.lancedb"
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ class AnalysisCapability(RAGCapabilityBase[AnalysisState]):
|
|||
|
||||
|
||||
def create_capability(
|
||||
db_path: Path | None = None,
|
||||
db_path: Path | str | None = None,
|
||||
config: AppConfig | None = None,
|
||||
*,
|
||||
defer_loading: bool = True,
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ class RAGCapability(RAGCapabilityBase[RAGState]):
|
|||
|
||||
|
||||
def create_capability(
|
||||
db_path: Path | None = None,
|
||||
db_path: Path | str | None = None,
|
||||
config: AppConfig | None = None,
|
||||
*,
|
||||
defer_loading: bool = True,
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class HaikuRAG:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
db_path: Path | None = None,
|
||||
db_path: Path | str | None = None,
|
||||
config: AppConfig | None = None,
|
||||
skip_validation: bool = False,
|
||||
create: bool = False,
|
||||
|
|
@ -75,7 +75,8 @@ class HaikuRAG:
|
|||
"""Initialize the RAG client with a database path.
|
||||
|
||||
Args:
|
||||
db_path: Path to the database file. If None, uses config.storage.data_dir.
|
||||
db_path: Path or string path to the database file. If None, uses
|
||||
config.storage.data_dir.
|
||||
config: Configuration to use. Defaults to the current global config.
|
||||
skip_validation: Whether to skip configuration validation on database load.
|
||||
create: Whether to create the database if it doesn't exist.
|
||||
|
|
|
|||
|
|
@ -170,14 +170,14 @@ class TagInfo:
|
|||
class Store:
|
||||
def __init__(
|
||||
self,
|
||||
db_path: Path,
|
||||
db_path: Path | str,
|
||||
config: AppConfig | None = None,
|
||||
skip_validation: bool = False,
|
||||
create: bool = False,
|
||||
read_only: bool = False,
|
||||
skip_migration_check: bool = False,
|
||||
):
|
||||
self.db_path: Path = db_path
|
||||
self.db_path: Path = Path(db_path)
|
||||
self._config = config if config is not None else get_config()
|
||||
self._read_only = read_only
|
||||
self._create = create
|
||||
|
|
@ -191,7 +191,7 @@ class Store:
|
|||
self._is_new_db = False
|
||||
|
||||
if self._connection_mode == ConnectionMode.LOCAL:
|
||||
if not db_path.exists():
|
||||
if not self.db_path.exists():
|
||||
if not create:
|
||||
raise FileNotFoundError(
|
||||
f"Database does not exist at {self.db_path.absolute()}. "
|
||||
|
|
@ -199,8 +199,8 @@ class Store:
|
|||
)
|
||||
self._is_new_db = True
|
||||
# Ensure parent directories exist for new databases
|
||||
if not db_path.parent.exists():
|
||||
Path.mkdir(db_path.parent, parents=True)
|
||||
if not self.db_path.parent.exists():
|
||||
Path.mkdir(self.db_path.parent, parents=True)
|
||||
|
||||
# Create embedder (sync — no LanceDB needed)
|
||||
self.embedder = get_embedder(config=self._config)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, cast
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
|
@ -102,11 +103,33 @@ def test_capability_factories_resolve_environment_and_defaults(
|
|||
config.storage.data_dir / "haiku.rag.lancedb"
|
||||
)
|
||||
|
||||
for factory in (create_rag, create_analysis):
|
||||
db_path = factory(db_path=str(temp_db_path), config=config).db_path
|
||||
assert db_path == temp_db_path
|
||||
assert isinstance(db_path, Path)
|
||||
|
||||
with patch("haiku.rag.config.get_config", return_value=config):
|
||||
assert create_rag().config is config
|
||||
assert create_analysis().config is config
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_a_string_db_path_opens_a_store(temp_db_path):
|
||||
"""Store calls `absolute()` and `exists()` on db_path, which a str lacks."""
|
||||
from haiku.rag.client import HaikuRAG
|
||||
|
||||
config = AppConfig()
|
||||
async with HaikuRAG(temp_db_path, config, create=True):
|
||||
pass
|
||||
|
||||
capability = create_rag(db_path=str(temp_db_path), config=config)
|
||||
try:
|
||||
rag = await capability._ensure_rag()
|
||||
assert rag.store.db_path == temp_db_path
|
||||
finally:
|
||||
await capability._close()
|
||||
|
||||
|
||||
def test_domain_preamble_is_added_to_capability_instructions(temp_db_path):
|
||||
config = AppConfig(
|
||||
prompts=PromptsConfig(domain_preamble="The corpus contains solar manuals.")
|
||||
|
|
|
|||
|
|
@ -30,6 +30,15 @@ def vcr_cassette_dir():
|
|||
return str(Path(__file__).parent / "cassettes" / "test_client")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_a_string_db_path_is_accepted(temp_db_path):
|
||||
"""The documented `HaikuRAG("knowledge.lancedb")` form: Store calls
|
||||
`exists()` and `absolute()` on db_path, which a str lacks."""
|
||||
async with HaikuRAG(str(temp_db_path), create=True) as client:
|
||||
assert client.store.db_path == temp_db_path
|
||||
assert isinstance(client.store.db_path, Path)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prepare_document_from_docling_runs_off_event_loop_thread(monkeypatch):
|
||||
import haiku.rag.client.documents as documents
|
||||
|
|
|
|||
Loading…
Reference in a new issue