Correct the multi-database prose

`resolve_citations` documented the behaviour it now rejects: a shared chunk id
resolving to whichever result came last.

The storage page ran two embedding checks together as if the second explained
the first. They are separate: each database against the configuration on open,
and the databases in a selection against each other, which raises in read-only
mode too and does not apply to full-text search.

`several` becomes `multiple` where it names the feature, matching the docs and
`covers_multiple`.
This commit is contained in:
Yiorgis Gozadinos 2026-08-27 11:40:52 +03:00
parent c8a9b5df50
commit 746b663a2f
No known key found for this signature in database
9 changed files with 29 additions and 25 deletions

View file

@ -16,7 +16,7 @@
### Fixed
- `client.chunk()` and `client.embedder` work when the client covers several
- `client.chunk()` and `client.embedder` work when the client covers multiple
databases. Operations that require one database raise `AmbiguousDatabaseError`.
- The chat document filter selects by document ID and shows each document's
database.
@ -25,6 +25,7 @@
`lancedb.databases`.
- A `lancedb.uri` without a scheme is treated as a local path. `--db PATH`
overrides it.
- Inspector search results mark truncated previews with an ellipsis.
## [0.78.0] - 2026-08-24

View file

@ -88,7 +88,7 @@ lancedb:
uri: "" # Empty for local, or db://, s3://, az://, gs://
api_key: ""
region: ""
databases: {} # Name-to-location map to search several at once, instead of uri
databases: {} # Name-to-location map to search multiple at once, instead of uri
embeddings:
model:

View file

@ -217,10 +217,18 @@ exclusive.
Results, documents, citations, and model context use the configured name as
`source`. Commands such as `info` and path-related errors still show locations.
All databases in a search must use compatible embeddings because the query is
embedded once. A dimension mismatch raises `ConfigMismatchError`. A provider or
model-name mismatch at the same dimension warns in read-only mode and raises in
writable mode.
Embedding compatibility is checked against two different things.
On open, each database is compared with the current configuration. A dimension
mismatch raises `ConfigMismatchError`. A provider or model-name mismatch at the
same dimension warns in read-only mode and raises in writable mode.
Across a selection, the databases are compared with each other. Vector and
hybrid search embed the query once, so every database answering it must record
the same provider, model, and dimension. A disagreement raises
`ConfigMismatchError` in read-only mode as well. Only the databases searched
together have to agree, and full-text search embeds nothing, so it is
unaffected.
### Search and Provenance
@ -278,7 +286,7 @@ database.
### Python Operations
Creating, writing, rebuilding, and vacuuming require one database. Calling these
operations on a client that covers several raises `AmbiguousDatabaseError`.
operations on a client that covers multiple raises `AmbiguousDatabaseError`.
Select one at creation time or obtain a single-database client:
```python

View file

@ -45,8 +45,7 @@ def instructions() -> str:
@cache
def multiple_databases_instructions() -> str:
"""Appended only where the capability covers several databases, so a single
database is instructed exactly as it was before they could be named."""
"""Appended only where the capability covers multiple databases."""
return _multiple_databases_path.read_text().rstrip()
@ -215,8 +214,8 @@ def create_capability(
scope = resolve_scope(db_path, config)
instruction_text = instructions()
# A lent client covers what it covers; otherwise the scope says.
several = rag.covers_multiple if rag is not None else scope.covers_multiple
if several:
covers_multiple = rag.covers_multiple if rag is not None else scope.covers_multiple
if covers_multiple:
instruction_text += multiple_databases_instructions()
return AnalysisCapability(
scope=scope,

View file

@ -45,8 +45,7 @@ def instructions() -> str:
@cache
def multiple_databases_instructions() -> str:
"""Appended only where the capability covers several databases, so a single
database is instructed exactly as it was before they could be named."""
"""Appended only where the capability covers multiple databases."""
return _multiple_databases_path.read_text().rstrip()
@ -118,8 +117,8 @@ def create_capability(
scope = resolve_scope(db_path, config)
instruction_text = instructions()
# A lent client covers what it covers; otherwise the scope says.
several = rag.covers_multiple if rag is not None else scope.covers_multiple
if several:
covers_multiple = rag.covers_multiple if rag is not None else scope.covers_multiple
if covers_multiple:
instruction_text += multiple_databases_instructions()
return RAGCapability(
scope=scope,

View file

@ -65,11 +65,11 @@ _db_name: str | None = None
def create_app(db: Path | None = None, *, covers_set: bool = False) -> "HaikuRAGApp":
"""The application for a command, on the database(s) it works on.
`covers_set` is the command declaring that it can read several: `search`,
`covers_set` is the command declaring that it can read multiple: `search`,
`ask`, `analyze` and `chat` can, and everything else names one.
Raises:
AmbiguousDatabaseError: several databases are configured and this
AmbiguousDatabaseError: multiple databases are configured and this
command works on one, without `--db` or `--db-name` naming which.
"""
from haiku.rag.app import HaikuRAGApp

View file

@ -102,7 +102,7 @@ class LanceDBConfig(ConfigModel):
The cache sizes are per process, since the session is shared across
connections.
`databases` maps a name to a location, for searching several at once. The
`databases` maps a name to a location, for searching multiple at once. The
name is what results and citations carry, so a location never leaves the
configuration. Mutually exclusive with `uri`.
"""

View file

@ -75,8 +75,8 @@ def resolve_citations(
"""Resolve chunk IDs to full Citation objects with metadata.
Raises ``AmbiguousCitationError`` when a cited id names a chunk in more than
one of the databases searched. An id held by two of them, as after copying a
database, resolves to whichever result came last, attributing the answer to a
one of the databases searched, as after copying a database. A citation
records the id alone, so resolving one would attribute the answer to a
database it may not have come from.
"""
by_id: dict[str, SearchResult] = {}

View file

@ -363,11 +363,8 @@ def format_citations(citations: "list[Citation]") -> str:
def truncated(text: str, limit: int) -> str:
"""`text` cut to `limit` characters, marked where anything was dropped.
Without the mark a clipped value reads as the value: a sentence ending
"commissioned in 1991" becomes "commissioned in 1".
"""
"""The first `limit` characters of `text`, with `…` appended when anything
was dropped. A cut result is `limit` characters plus the mark."""
if len(text) <= limit:
return text
return text[:limit].rstrip() + ""