A chunk id is unique within a database and says nothing across them, so a database copied from another holds the same ids. `qualified_id` keys the two in-memory identity sites on the database and the id together: `merge_results` was dropping the second database's result when a query repeated, and the arrival map that breaks fused score ties was ranking one of the pair as the other. Everything serialized records the id alone, so there ambiguity is refused rather than qualified. `resolve_citations` raises `AmbiguousCitationError` for a cited id held by two of the databases searched, where it used to resolve to whichever result came last; `_register_citations` raises for one already cited from another database in an earlier question. `_cite` turns both into a `ModelRetry` asking for other evidence. The direct-id fallback asks every database the question covers instead of taking the first that answers, so an id no search returned is refused on the same terms. `all_found` collects them and `first_found` reads its first, which document reads keep doing on purpose. Also drop a duplicated 0.77.0 heading from the changelog.
55 lines
1.5 KiB
Python
55 lines
1.5 KiB
Python
from collections.abc import Iterable
|
|
|
|
from pydantic import BaseModel
|
|
|
|
from haiku.rag.client import HaikuRAG
|
|
from haiku.rag.store.models.chunk import SearchResult, qualified_id
|
|
|
|
|
|
class CodeExecutionEntry(BaseModel):
|
|
code: str
|
|
stdout: str
|
|
stderr: str = ""
|
|
success: bool = True
|
|
|
|
|
|
async def search_corpus(
|
|
rag: HaikuRAG,
|
|
query: str,
|
|
limit: int | None = None,
|
|
document_filter: str | None = None,
|
|
sources: list[str] | None = None,
|
|
) -> tuple[str, list[SearchResult]]:
|
|
"""Search and context-expand results for a capability tool."""
|
|
results = await rag.search(
|
|
query, limit=limit, filter=document_filter, sources=sources
|
|
)
|
|
results = await rag.expand_context(results)
|
|
formatted = "\n\n---\n\n".join(
|
|
result.format_for_agent(rank=index + 1, total=len(results))
|
|
for index, result in enumerate(results)
|
|
)
|
|
return formatted or "No results found.", list(results)
|
|
|
|
|
|
def merge_results(
|
|
existing: list[SearchResult], incoming: Iterable[SearchResult]
|
|
) -> None:
|
|
"""Add the results not already held.
|
|
|
|
Identity is the database and the chunk id: results built by hand carry
|
|
neither and cannot be told apart, so they collapse to the first.
|
|
"""
|
|
seen = {qualified_id(result.source, result.chunk_id) for result in existing}
|
|
for result in incoming:
|
|
key = qualified_id(result.source, result.chunk_id)
|
|
if key not in seen:
|
|
existing.append(result)
|
|
seen.add(key)
|
|
|
|
|
|
__all__ = [
|
|
"CodeExecutionEntry",
|
|
"merge_results",
|
|
"search_corpus",
|
|
]
|