feat(rag): feed whole matched documents to the LLM — no context truncation (A7 revised)
This commit is contained in:
+15
-23
@@ -10,10 +10,12 @@
|
||||
fused score ranks; :meth:`select_documents` and :func:`weak_hit_titles`
|
||||
keep working off ``score``.
|
||||
|
||||
The product requirement is unchanged (LOCKED A7): the LLM receives the
|
||||
**entire relevant document**, not just the chunk — chunk hits map back to
|
||||
their parents, dedupe, rank by best fused score, and the combined context
|
||||
is capped at ``BOR_MAX_CONTEXT_CHARS``.
|
||||
The product requirement (LOCKED A7, revised 2026-08-24): the LLM receives
|
||||
the **entire relevant document**, not just the chunk — chunk hits map back
|
||||
to their parents, dedupe, rank by best fused score, and the full text of
|
||||
the top-N documents is always fed through, never truncated. If a future KB
|
||||
ever makes the prompt too large for the model, the ``LLMError`` → SSE
|
||||
``error`` path surfaces it loudly — no silent partial context.
|
||||
|
||||
Deterministic tie-break for equal fused scores:
|
||||
``(−fused, −cosine, document.path, chunk.position)``.
|
||||
@@ -31,7 +33,9 @@ from sqlalchemy.orm import Session
|
||||
from app.config import get_settings
|
||||
from app.models import Chunk, Document
|
||||
|
||||
#: Marker appended when the context budget is exceeded (PLAN §6).
|
||||
#: Shared overflow marker — now used by the steering (<tuning>) section
|
||||
#: only (phase 15; imported by ``app.rag.prompts``). The document context
|
||||
#: path never truncates (A7 revised, owner permission 2026-08-24).
|
||||
TRUNCATION_MARKER = "[…truncated…]"
|
||||
|
||||
#: Alphanumeric tokens of a question (``to_tsquery`` input, OR-joined).
|
||||
@@ -254,19 +258,17 @@ def weak_hit_titles(chunks: Sequence[RetrievedChunk]) -> list[str]:
|
||||
def select_documents(
|
||||
chunks: Sequence[RetrievedChunk],
|
||||
n: int | None = None,
|
||||
max_chars: int | None = None,
|
||||
) -> list[Document]:
|
||||
"""Map chunk hits to distinct parent documents, ranked by best fused score.
|
||||
|
||||
At most *n* documents are returned (default ``BOR_TOP_N_DOCS``). The
|
||||
returned rows carry the full document content; if the combined content
|
||||
would exceed *max_chars* (default ``BOR_MAX_CONTEXT_CHARS``), the
|
||||
lowest-ranked overflowing document is truncated in place with the
|
||||
``[…truncated…]`` marker so the assembled context never exceeds the
|
||||
budget (PLAN §6).
|
||||
returned rows carry the full document content, byte-identical — a
|
||||
matched parent document is **never truncated** (A7 revised, owner
|
||||
permission 2026-08-24). There is deliberately no context budget: an
|
||||
oversized prompt must fail loudly through the ``LLMError`` → SSE
|
||||
``error`` path, never arrive as silent partial context.
|
||||
"""
|
||||
top_n = n if n is not None else get_settings().top_n_docs
|
||||
budget = max_chars if max_chars is not None else get_settings().max_context_chars
|
||||
|
||||
docs: list[Document] = []
|
||||
seen: set[uuid.UUID] = set()
|
||||
@@ -275,14 +277,4 @@ def select_documents(
|
||||
continue
|
||||
seen.add(rc.document.id)
|
||||
docs.append(rc.document)
|
||||
docs = docs[:top_n]
|
||||
|
||||
remaining = budget
|
||||
for doc in docs:
|
||||
if len(doc.content) <= remaining:
|
||||
remaining -= len(doc.content)
|
||||
else:
|
||||
keep = max(0, remaining - len(TRUNCATION_MARKER))
|
||||
doc.content = doc.content[:keep] + TRUNCATION_MARKER
|
||||
remaining = 0
|
||||
return docs
|
||||
return docs[:top_n]
|
||||
|
||||
Reference in New Issue
Block a user