Wire the guarded conversational RAG answer layer end-to-end
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Protocol
|
||||
|
||||
from .models import ParentDocument, SearchHit
|
||||
|
||||
|
||||
class QueryEmbeddingUnavailable(RuntimeError):
|
||||
"""The similarity route's embedding provider could not be reached.
|
||||
|
||||
Raised by an adapter and caught by the domain, which abstains. It exists so
|
||||
a provider outage refuses to answer instead of returning a 500: an
|
||||
unreachable embedder means the question was never actually searched, and an
|
||||
error page hides that from the caller just as effectively as a wrong answer
|
||||
would. The domain catches this without importing any SDK.
|
||||
"""
|
||||
|
||||
|
||||
class AnswerGenerationUnavailable(RuntimeError):
|
||||
"""The answer generator could not be reached.
|
||||
|
||||
Same contract as `QueryEmbeddingUnavailable`: the adapter translates its
|
||||
SDK's failure into this, and the domain degrades to the extractive answer
|
||||
rather than returning an error. Generation is a presentation improvement
|
||||
over quoting the source; losing it must never lose the answer.
|
||||
"""
|
||||
|
||||
|
||||
class AnswerGenerator(Protocol):
|
||||
"""Rewrites retrieved evidence into prose. Never a source of facts.
|
||||
|
||||
Whatever it returns is checked by `rag.grounding.verify` before a caller
|
||||
sees it, so this port carries no trust: an implementation that fabricates
|
||||
a dose produces a discarded generation, not a wrong answer.
|
||||
"""
|
||||
|
||||
def generate(self, system: str, user: str, schema: dict) -> str: ...
|
||||
|
||||
|
||||
class Retriever(Protocol):
|
||||
def search(self, query: str, drug_id: str, limit: int) -> list[SearchHit]: ...
|
||||
|
||||
|
||||
class SectionRetriever(Protocol):
|
||||
"""Exact retrieval of one whole section, with no similarity involved.
|
||||
|
||||
Separate from `Retriever` so a store that cannot filter by payload is still
|
||||
a valid `Retriever` (interface segregation). `find_by_section` must return
|
||||
**every** part of the section: a partial contraindication list reads as a
|
||||
complete one, which is worse than returning nothing.
|
||||
"""
|
||||
|
||||
def find_by_section(self, drug_id: str, section_key: str) -> list[SearchHit]: ...
|
||||
|
||||
|
||||
class ParentStore(Protocol):
|
||||
def get(self, parent_id: str) -> ParentDocument | None: ...
|
||||
Reference in New Issue
Block a user