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:
2026-08-25 20:22:51 -04:00
parent 572a4190a6
commit 0654b304e1
23 changed files with 2276 additions and 34 deletions
+147 -3
View File
@@ -19,13 +19,16 @@ from fastapi.testclient import TestClient
from app.api import chat as chat_api
from app.config import Settings
from app.main import app as fastapi_app
from app.models import Document, QueryLog
from app.models import Document, KbOverview, QueryLog
from app.rag.llm import StreamPiece
from app.rag.retriever import RetrievedChunk, weak_hit_titles
from app.rag.suggestions import MAX_SUGGESTIONS, derive_suggestions
ANSWER = "I haven't done anything like that — try one of these instead!"
#: A small KB outline standing in for the lite-generated one (phase 31).
KB_OVERVIEW = "- Homelab\n - Kubernetes (k3s)\n- Deployments\n - Borg backups"
def _settings(threshold: float = 0.30) -> Settings:
return Settings(
@@ -236,6 +239,87 @@ def test_no_summary_chunks_yields_zero_summary_hits() -> None:
assert plan_low.summary_hits == 0
# ---------- KB overview (phase 31: <knowledge_base> section + kb_chars) ----------
def test_plan_turn_high_injects_kb_overview() -> None:
"""HIGH branch: the stored outline lands in the prompt between
``<relevance>`` and ``<tuning>`` (or the ``<documents>`` body with no
notes), and ``kb_chars`` records the outline's length."""
doc = _doc("Kubernetes Homelab Cluster", "TALOS_DOC_CONTENT")
plan = chat_api.plan_turn(
[_chunk(doc, 0.90)], _settings(threshold=0.30), kb_overview=KB_OVERVIEW
)
assert plan.deflected is False
assert plan.kb_chars == len(KB_OVERVIEW)
prompt = plan.system_prompt
assert "<knowledge_base>" in prompt
assert KB_OVERVIEW in prompt
i_rel = prompt.index("<relevance>HIGH</relevance>")
i_kb = prompt.index("<knowledge_base>")
i_docs = prompt.index("<documents>")
assert i_rel < i_kb < i_docs
def test_plan_turn_high_kb_section_ordered_before_tuning() -> None:
"""Both sections present: ``<relevance>`` → ``<knowledge_base>`` →
``<tuning>`` → ``<documents>`` (the locked phase-31 order)."""
doc = _doc("Kubernetes Homelab Cluster", "TALOS_DOC_CONTENT")
plan = chat_api.plan_turn(
[_chunk(doc, 0.90)],
_settings(threshold=0.30),
notes=["be concise"],
kb_overview=KB_OVERVIEW,
)
prompt = plan.system_prompt
i_rel = prompt.index("<relevance>HIGH</relevance>")
i_kb = prompt.index("<knowledge_base>")
i_kb_close = prompt.index("</knowledge_base>")
i_tuning = prompt.index("<tuning>")
i_docs = prompt.index("<documents>")
assert i_rel < i_kb < i_kb_close < i_tuning < i_docs
assert plan.kb_chars == len(KB_OVERVIEW)
def test_plan_turn_low_injects_kb_overview() -> None:
"""LOW (deflected) branch: the outline is injected there too, ahead
of the DEFLECT_MODE body, and document content stays excluded."""
doc = _doc("Kubernetes Homelab Cluster", "TALOS_DOC_NEVER_SENT")
plan = chat_api.plan_turn(
[_chunk(doc, 0.10)], _settings(threshold=0.30), kb_overview=KB_OVERVIEW
)
assert plan.deflected is True
assert plan.kb_chars == len(KB_OVERVIEW)
prompt = plan.system_prompt
assert "<knowledge_base>" in prompt
assert KB_OVERVIEW in prompt
i_rel = prompt.index("<relevance>LOW</relevance>")
i_kb = prompt.index("<knowledge_base>")
i_mode = prompt.index("DEFLECT_MODE")
assert i_rel < i_kb < i_mode
assert "TALOS_DOC_NEVER_SENT" not in prompt # titles only, still
def test_plan_turn_empty_overview_keeps_prompt_and_zero_kb_chars() -> None:
"""No outline (None/empty/blank) → ``kb_chars == 0`` and a prompt
byte-identical to the no-overview build in both branches."""
high_doc = _doc("Kubernetes Homelab Cluster", "TALOS_DOC_CONTENT")
low_doc = _doc("Backup Strategy", "BACKUP_DOC_CONTENT")
for chunks, kb in (
([_chunk(high_doc, 0.90)], None),
([_chunk(high_doc, 0.90)], ""),
([_chunk(high_doc, 0.90)], " \n\t "),
([_chunk(low_doc, 0.10)], None),
([_chunk(low_doc, 0.10)], ""),
):
plan = chat_api.plan_turn(chunks, _settings(threshold=0.30), kb_overview=kb)
baseline = chat_api.plan_turn(chunks, _settings(threshold=0.30))
assert plan.kb_chars == 0
assert baseline.kb_chars == 0
assert plan.system_prompt == baseline.system_prompt # byte-identical
assert "<knowledge_base>" not in plan.system_prompt
# ---------- prompt content (LOW vs HIGH) ----------
@@ -360,12 +444,15 @@ class _FakeSession:
"""Stands in for the DB session: records the QueryLog row it is given.
``scalars`` always yields no steering notes (phase 15) so the chat
turn's ``load_steering_notes`` call stays a no-op here.
turn's ``load_steering_notes`` call stays a no-op here, and ``get``
returns the single ``kb_overview`` row when one is configured
(phase 31) — ``None`` by default, i.e. no stored outline.
"""
def __init__(self) -> None:
def __init__(self, kb_overview: str = "") -> None:
self.added: list[Any] = []
self.commits = 0
self.kb_overview = kb_overview
def add(self, obj: Any) -> None:
self.added.append(obj)
@@ -376,6 +463,11 @@ class _FakeSession:
def scalars(self, _stmt: Any) -> _FakeSteeringResult:
return _FakeSteeringResult()
def get(self, model: Any, pk: Any) -> Any:
if model is KbOverview and self.kb_overview:
return KbOverview(id=1, content=self.kb_overview)
return None
@pytest.fixture()
def gate_env(monkeypatch: pytest.MonkeyPatch) -> Iterator[tuple[_FakeSession, _CannedLLM]]:
@@ -479,3 +571,55 @@ def test_endpoint_score_at_threshold_answers(
assert isinstance(row, QueryLog)
assert row.deflected is False
assert row.top_score == pytest.approx(0.30)
# ---------- endpoint: KB overview row (phase 31) ----------
def test_endpoint_stored_kb_row_injected_into_system_prompt(
client: TestClient,
gate_env: tuple[_FakeSession, _CannedLLM],
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
) -> None:
"""A non-empty ``kb_overview`` row (read via one PK lookup) reaches
the LLM's system prompt in both modes, and the per-turn log line
records ``kb_chars=N`` (PLAN §9)."""
session, llm = gate_env
doc = _doc("Kubernetes Homelab Cluster", "TALOS_DOC_SENT")
monkeypatch.setattr(chat_api, "retrieve", _fake_retriever([_chunk(doc, 0.30)]))
session.kb_overview = f" {KB_OVERVIEW} " # the loader trims it
with caplog.at_level("INFO", logger="app.chat"):
_ask(client, "How is my Kubernetes cluster set up?")
(system, _user) = llm.seen[0][0], llm.seen[0][1]
assert "<knowledge_base>" in system["content"]
assert KB_OVERVIEW in system["content"]
assert system["content"].index("<relevance>HIGH</relevance>") < system["content"].index(
"<knowledge_base>"
) < system["content"].index("<documents>")
log_lines = [r.getMessage() for r in caplog.records if "question=" in r.getMessage()]
assert log_lines and f"kb_chars={len(KB_OVERVIEW)}" in log_lines[-1]
def test_endpoint_no_kb_row_prompt_unchanged(
client: TestClient,
gate_env: tuple[_FakeSession, _CannedLLM],
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
) -> None:
"""No ``kb_overview`` row → the section is absent (byte-identical to
the pre-phase prompt) and the log line records ``kb_chars=0``."""
session, llm = gate_env
assert session.kb_overview == "" # fixture default: no stored row
doc = _doc("Kubernetes Homelab Cluster", "TALOS_DOC_SENT")
monkeypatch.setattr(chat_api, "retrieve", _fake_retriever([_chunk(doc, 0.30)]))
with caplog.at_level("INFO", logger="app.chat"):
_ask(client, "How is my Kubernetes cluster set up?")
(system, _user) = llm.seen[0][0], llm.seen[0][1]
assert "<knowledge_base>" not in system["content"]
log_lines = [r.getMessage() for r in caplog.records if "question=" in r.getMessage()]
assert log_lines and "kb_chars=0" in log_lines[-1]