feat(rag): lite-generated KB overview in the system prompt — stored single row, regenerated on import, <knowledge_base> section in HIGH+LOW prompts
This commit is contained in:
+35
-4
@@ -36,6 +36,16 @@ assembly is untouched. ``TurnPlan.summary_hits`` counts the hit chunks
|
||||
with ``is_summary`` whose parent document landed in the selected
|
||||
top-N context, and the per-turn log line records ``summary_hits=N``
|
||||
after ``fts_hits`` (PLAN §9 line extension).
|
||||
|
||||
KB overview (phase 31): the lite-generated outline of the knowledge
|
||||
base (single ``kb_overview`` row) is read per turn (one indexed PK
|
||||
lookup — no LLM call) and injected into **both** prompts as the
|
||||
``<knowledge_base>`` section, ordered ``<relevance>`` →
|
||||
``<knowledge_base>`` → ``<tuning>`` → mode body. With an empty row the
|
||||
prompts stay byte-identical to the pre-phase text (phase 15
|
||||
convention); ``TurnPlan.kb_chars`` records the length of the stored
|
||||
outline (0 when absent) and the per-turn log line records
|
||||
``kb_chars=N`` after ``tuning=N`` (PLAN §9 line extension).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -60,6 +70,7 @@ from app.rag.llm import (
|
||||
LLMError,
|
||||
StreamPiece, # noqa: F401 (phase 17 typing: chat_stream yields StreamPiece)
|
||||
)
|
||||
from app.rag.overview import load_kb_overview
|
||||
from app.rag.prompts import build_deflect_prompt, build_high_prompt
|
||||
from app.rag.retriever import RetrievedChunk, retrieve, select_documents, weak_hit_titles
|
||||
from app.rag.suggestions import derive_suggestions
|
||||
@@ -107,12 +118,17 @@ class TurnPlan:
|
||||
#: Hit chunks with ``is_summary`` whose parent document made it into
|
||||
#: *docs* (phase 30; per-turn log line ``summary_hits=N``).
|
||||
summary_hits: int = 0
|
||||
#: Length of the stored KB overview injected as the
|
||||
#: ``<knowledge_base>`` section (phase 31; per-turn log line
|
||||
#: ``kb_chars=N``). 0 when no non-empty row exists.
|
||||
kb_chars: int = 0
|
||||
|
||||
|
||||
def plan_turn(
|
||||
chunks: Sequence[RetrievedChunk],
|
||||
settings: Settings,
|
||||
notes: Sequence[str] | None = None,
|
||||
kb_overview: str | None = None,
|
||||
) -> TurnPlan:
|
||||
"""Apply the honesty gate (A8, revised) and assemble prompt + context.
|
||||
|
||||
@@ -133,11 +149,20 @@ def plan_turn(
|
||||
when non-empty, both the HIGH and the LOW prompt carry the
|
||||
``<tuning>`` section; with no notes the prompts are unchanged.
|
||||
|
||||
*kb_overview* is the stored KB outline (phase 31, one PK lookup per
|
||||
turn): when non-empty, both prompts carry the ``<knowledge_base>``
|
||||
section (between ``<relevance>`` and ``<tuning>``) and
|
||||
``kb_chars`` records the outline's length; with no outline the
|
||||
prompts are byte-identical to the pre-phase text and ``kb_chars``
|
||||
is 0.
|
||||
|
||||
``summary_hits`` (phase 30) counts the hit chunks with
|
||||
``is_summary`` whose parent document is among the selected
|
||||
top-N documents — both the HIGH and the LOW branch record it.
|
||||
"""
|
||||
steering = list(notes or [])
|
||||
kb_text = (kb_overview or "").strip()
|
||||
kb_chars = len(kb_text)
|
||||
best_cosine = max((c.cosine for c in chunks), default=0.0)
|
||||
fts_hits = sum(1 for c in chunks if c.fts_hit)
|
||||
docs = select_documents(chunks, n=settings.top_n_docs)
|
||||
@@ -148,22 +173,24 @@ def plan_turn(
|
||||
best_cosine,
|
||||
fts_hits,
|
||||
False,
|
||||
build_high_prompt(docs, notes=steering),
|
||||
build_high_prompt(docs, notes=steering, kb_overview=kb_text),
|
||||
docs,
|
||||
[],
|
||||
len(steering),
|
||||
summary_hits,
|
||||
kb_chars,
|
||||
)
|
||||
titles = weak_hit_titles(chunks)
|
||||
return TurnPlan(
|
||||
best_cosine,
|
||||
fts_hits,
|
||||
True,
|
||||
build_deflect_prompt(titles, notes=steering),
|
||||
build_deflect_prompt(titles, notes=steering, kb_overview=kb_text),
|
||||
docs,
|
||||
derive_suggestions(titles, settings.suggestions),
|
||||
len(steering),
|
||||
summary_hits,
|
||||
kb_chars,
|
||||
)
|
||||
|
||||
|
||||
@@ -216,8 +243,11 @@ async def chat(
|
||||
settings = get_settings()
|
||||
try:
|
||||
steering_notes = load_steering_notes(db)
|
||||
# KB overview (phase 31): one indexed PK lookup per turn — the
|
||||
# outline is generated at import time, never per chat turn.
|
||||
kb_overview = load_kb_overview(db)
|
||||
chunks = retrieve(db, request.message, question_vec)
|
||||
plan = plan_turn(chunks, settings, notes=steering_notes)
|
||||
plan = plan_turn(chunks, settings, notes=steering_notes, kb_overview=kb_overview)
|
||||
except Exception: # noqa: BLE001 — DB failure mid-turn
|
||||
logger.exception(
|
||||
"chat: retrieval failed question=%r total_ms=%d",
|
||||
@@ -284,13 +314,14 @@ async def chat(
|
||||
|
||||
logger.info(
|
||||
"question=%r embed_ms=%d top_score=%.3f fts_hits=%d summary_hits=%d tuning=%d "
|
||||
"threshold=%.2f deflected=%s sources=%r thinking_chars=%d total_ms=%d",
|
||||
"kb_chars=%d threshold=%.2f deflected=%s sources=%r thinking_chars=%d total_ms=%d",
|
||||
request.message,
|
||||
embed_ms,
|
||||
plan.top_score,
|
||||
plan.fts_hits,
|
||||
plan.summary_hits,
|
||||
plan.tuning_count,
|
||||
plan.kb_chars,
|
||||
settings.relevance_threshold,
|
||||
plan.deflected,
|
||||
source_paths,
|
||||
|
||||
Reference in New Issue
Block a user