Correct what the client and the docs claim

`HaikuRAG.__init__` said an omitted `db_path` uses `storage.data_dir`, which
is the last of three; and that `sources` is ignored for a single `uri`, where
it raises, since only `lancedb.databases` names databases.

The name is not the only identity leaving the configuration: results,
citations, model input and errors opening a named database carry it, while
`info`, `init` and `tag` print the location. `sources=[]` returns no search
results, but `ask` and `analyze` still answer, without evidence.

Trim the comments that still narrated a failure or an alternative to the
invariant they were there for.
This commit is contained in:
Yiorgis Gozadinos 2026-08-26 13:58:38 +03:00
parent ca2e28559e
commit 8f41d42ab2
No known key found for this signature in database
7 changed files with 31 additions and 40 deletions

View file

@ -214,9 +214,10 @@ lancedb:
A location is a URI or a local path. `databases` and `uri` are mutually A location is a URI or a local path. `databases` and `uri` are mutually
exclusive, and setting both fails validation. exclusive, and setting both fails validation.
The name is the only identity that leaves the configuration. Results, citations Results, citations, model input and errors opening a named database carry the
and error messages carry it, so a path or a bucket never reaches a log, a trace configured name rather than the location, so a path or a bucket does not reach a
or a model. trace or a model. Commands that report on a database — `info`, `init`, `tag`
print its location, as does an error about a path.
Every database in the set is opened with the same embedding configuration. A Every database in the set is opened with the same embedding configuration. A
different `vector_dim` raises `ConfigMismatchError` on open. A different provider different `vector_dim` raises `ConfigMismatchError` on open. A different provider

View file

@ -258,8 +258,9 @@ result = await client.analyze("How many documents mention it?", sources=["medic"
A question scoped to some databases can only cite those, and the analysis A question scoped to some databases can only cite those, and the analysis
sandbox mounts only their documents. sandbox mounts only their documents.
`sources=None` covers every database the client covers; `sources=[]` covers none `sources=None` covers every database the client covers. `sources=[]` covers
and returns nothing, which is not the same thing. none: `search` returns no results, and `ask` and `analyze` run with no evidence
from any database.
#### Asking a client what it covers #### Asking a client what it covers

View file

@ -59,9 +59,8 @@ class HaikuRAGApp:
def _connection(self) -> "tuple[AppConfig, Path]": def _connection(self) -> "tuple[AppConfig, Path]":
"""How to open the one database this command works on, directly. """How to open the one database this command works on, directly.
The configuration a command was given names a *set*; the one it opens Derived per database: opening one of a set against the set's own
needs its own, or a named database behind a URI is opened as the local configuration would reach the local path that stands in for it.
path that stands in for it.
""" """
from haiku.rag.client.session import default_db_path from haiku.rag.client.session import default_db_path

View file

@ -146,9 +146,8 @@ class DocumentFilterModal(ModalScreen):
filter_list = self.query_one("#filter-list", VerticalScroll) filter_list = self.query_one("#filter-list", VerticalScroll)
await filter_list.remove_children() await filter_list.remove_children()
# The page is picked to represent every database; sorting is so it reads # Sort the interleaved page and label each document's database, which
# like a list rather than in whatever order the tables returned. The label # a title alone does not say.
# names the database, since a title alone does not say which one it is in.
labelled = sorted( labelled = sorted(
( (
( (

View file

@ -133,15 +133,17 @@ class HaikuRAG:
"""Initialize the RAG client with a database path. """Initialize the RAG client with a database path.
Args: Args:
db_path: Path or string path to the database file. If None, uses db_path: Path or string path to the database. When omitted, resolves
config.storage.data_dir. ``lancedb.databases``, then ``lancedb.uri``, then the default
path under ``storage.data_dir``.
config: Configuration to use. Defaults to the current global config. config: Configuration to use. Defaults to the current global config.
skip_validation: Whether to skip configuration validation on database load. skip_validation: Whether to skip configuration validation on database load.
create: Whether to create the database if it doesn't exist. create: Whether to create the database if it doesn't exist.
read_only: Whether to open the database in read-only mode. read_only: Whether to open the database in read-only mode.
sources: Names from ``config.lancedb.databases`` this client covers. sources: Names from ``config.lancedb.databases`` this client covers,
None means all of them. Ignored when a single ``uri`` or an None for all of them. Only that setting names databases, so a
explicit ``db_path`` is given. name raises when ``lancedb.uri`` placed the database. Ignored
when ``db_path`` says which database to open.
""" """
self._configured = config if config is not None else get_config() self._configured = config if config is not None else get_config()
# What the caller configured, kept intact: entering derives a # What the caller configured, kept intact: entering derives a
@ -290,14 +292,9 @@ class HaikuRAG:
async def __aenter__(self): async def __aenter__(self):
"""Async context manager entry — initializes store and repositories. """Async context manager entry — initializes store and repositories.
A client borrowing a database is already open and returns itself. A borrowed client reuses its session, which its owner closes. A client
Opening a second session would leak it: teardown declines to close what covering several opens their sessions lazily, so `store` and the
this client did not open. repositories stay unset until one database is named.
A client covering several databases opens none of them here: which are
searched is a per-query choice, so they open on first use. `store` and the
repositories stay unset in that case, since they have no unambiguous
meaning across a set.
""" """
if not self._owns_session: if not self._owns_session:
assert self._session is not None assert self._session is not None

View file

@ -93,32 +93,27 @@ class SingleDatabaseSession:
create=self._create, create=self._create,
read_only=self.read_only, read_only=self.read_only,
) )
# If _initialize fails mid-way (e.g. migration check raises after # Close a partially initialized store: the caller's `async with`
# connect), close the store so we don't leak the LanceDB connection — # never entered, so its exit will not run.
# the caller's `async with` never entered, so its exit won't run.
try: try:
await self.store._initialize() await self.store._initialize()
except BaseException: except BaseException:
self.store.close() self.store.close()
raise raise
except _NAMEABLE_FAILURES as error: except _NAMEABLE_FAILURES as error:
# These say what to run and never where the database is, so the name # These name the remedy and not the database, so the name is added
# is added to the message rather than replacing it: the operator needs # rather than substituted.
# both which database failed and what to do about it.
if self.source is None: if self.source is None:
raise raise
raise type(error)(f"database {self.source!r}: {error}") from error raise type(error)(f"database {self.source!r}: {error}") from error
except Exception as error: except Exception as error:
# A legacy `uri` or `db_path` session has no name to report instead, # Without a name there is nothing to report in the location's place.
# so its error passes through as it always has.
if self.source is None: if self.source is None:
raise raise
failure = type(error).__name__ failure = type(error).__name__
if failure is not None: if failure is not None:
# Raised outside the except block on purpose. A database named in # Raised outside the handler to discard the location-bearing
# config is reported by name, and the original spells out the path or # context, which `from None` would only stop printing.
# the bucket: `from None` would only stop it being *printed*, leaving
# it on `__context__` for anything that walks the chain.
raise SourceUnavailableError( raise SourceUnavailableError(
f"database {self.source!r} could not be opened: {failure}" f"database {self.source!r} could not be opened: {failure}"
) )

View file

@ -247,10 +247,9 @@ class Sandbox:
async def _documents(self) -> "tuple[list[Any], dict[str, HaikuRAG]]": async def _documents(self) -> "tuple[list[Any], dict[str, HaikuRAG]]":
"""Every document in scope, and the client holding each of them. """Every document in scope, and the client holding each of them.
The owners are empty where one connection serves every read: a single Owners are empty where one connection serves every read. The selection
database, or the ephemeral connection opened per read when no client was resolves as a search resolves it, so a database the question excluded
supplied. The selection is resolved the same way a search resolves it, so cannot be mounted.
a database the question excluded cannot be mounted.
""" """
async with self._connection() as rag: async with self._connection() as rag:
if not rag.covers_multiple: if not rag.covers_multiple: