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
+87 -11
View File
@@ -15,6 +15,14 @@ Steering (phase 15): when the owner has stored tuning notes, both modes
carry a ``<tuning>`` section between ``<relevance>…</relevance>`` and the
mode body. With zero notes the prompt is byte-identical to the
pre-steering text.
KB overview (phase 31): when the single ``kb_overview`` row holds a
lite-generated outline of the knowledge base, both modes carry a
``<knowledge_base>`` section between ``<relevance>…</relevance>`` and
the ``<tuning>`` section (order: ``<relevance>`` →
``<knowledge_base>`` → ``<tuning>`` → mode body) — the agent knows
roughly what the KB contains before retrieval. With an empty row the
prompt is byte-identical to the pre-phase text.
"""
from __future__ import annotations
@@ -51,6 +59,13 @@ _STEERING_INTRO = (
"Where these instructions conflict with the defaults above, follow the owner:\n"
)
#: One-line intro of the ``<knowledge_base>`` section (phase 31): the
#: lite-generated outline is the agent's a-priori picture of the KB.
_KB_INTRO = (
"The basic categories of everything in this knowledge base "
"(generated at import time):\n"
)
def _base(relevance: str) -> str:
if relevance not in ("HIGH", "LOW"):
@@ -95,9 +110,57 @@ def build_steering_section(notes: Sequence[str], max_chars: int | None = None) -
return ""
def build_high_prompt(documents: Sequence[Document], notes: Sequence[str] | None = None) -> str:
"""Grounded turn: locked persona (+ steering) + full texts of the top
documents."""
def build_kb_section(overview: str, max_chars: int | None = None) -> str:
"""The ``<knowledge_base>`` section of the system prompt (phase 31).
* No outline (or only whitespace) → ``""`` — callers then build the
prompt exactly as before, so a no-overview prompt is byte-identical
to the pre-phase text (phase 15 convention).
* Otherwise: the intro line + the stored outline, capped at
*max_chars* (default ``BOR_KB_OVERVIEW_MAX_CHARS``). When the budget
cannot hold the whole outline, the longest-fitting prefix is kept
and the overflow is replaced by the shared ``[…truncated…]`` marker
on its own line — the exact :func:`build_steering_section` pattern,
including its pathological-budget handling (never exceed the cap;
bare marker when even one outline character does not fit).
"""
text = str(overview or "").strip()
if not text:
return ""
limit = max_chars if max_chars is not None else get_settings().kb_overview_max_chars
if limit <= 0:
return ""
def render(cut: int) -> str:
lines = [text[:cut]]
if cut < len(text):
lines.append(TRUNCATION_MARKER)
return f"<knowledge_base>\n{_KB_INTRO}" + "\n".join(lines) + "\n</knowledge_base>"
for cut in range(len(text), 0, -1):
rendered = render(cut)
if len(rendered) <= limit:
return rendered
# Pathological budget: not even one outline character fits. The
# section must still respect the cap — the bare marker when it fits,
# else none (steering precedent, phase 15).
if len(TRUNCATION_MARKER) <= limit:
return TRUNCATION_MARKER
return ""
def build_high_prompt(
documents: Sequence[Document],
notes: Sequence[str] | None = None,
kb_overview: str | None = None,
) -> str:
"""Grounded turn: locked persona (+ steering, + KB overview) + full
texts of the top documents.
Section order (phase 31): ``<relevance>`` → ``<knowledge_base>`` →
``<tuning>`` → ``<documents>``; empty steering/overview omit their
section, keeping the prompt byte-identical to the pre-phase text.
"""
blocks = [
f'<document source="{doc.source}" path="{doc.path}" title="{doc.title}">\n'
f"{doc.content}\n"
@@ -107,21 +170,34 @@ def build_high_prompt(documents: Sequence[Document], notes: Sequence[str] | None
body = "\n\n".join(blocks) if blocks else (
"(no documents matched — do not invent specifics)"
)
section = build_steering_section(notes or [])
prompt = _base("HIGH")
if section:
prompt += "\n" + section
for part in (build_kb_section(kb_overview or ""), build_steering_section(notes or [])):
if part:
prompt += "\n" + part
return prompt + "\n<documents>\n" + body + "\n</documents>"
def build_deflect_prompt(titles: Sequence[str], notes: Sequence[str] | None = None) -> str:
"""Deflection turn: weak-hit titles only (no document content)."""
def build_deflect_prompt(
titles: Sequence[str],
notes: Sequence[str] | None = None,
kb_overview: str | None = None,
) -> str:
"""Deflection turn: weak-hit titles only (no document content).
Section order (phase 31): ``<relevance>`` → ``<knowledge_base>`` →
``<tuning>`` → ``DEFLECT_MODE`` body; empty steering/overview omit
their section, keeping the prompt byte-identical to the pre-phase text.
"""
weak = "\n".join(f"- {t}" for t in titles) if titles else "(nothing close at all)"
section = build_steering_section(notes or [])
mid = f"\n{section}\n" if section else "\n"
mid = "\n".join(
part
for part in (build_kb_section(kb_overview or ""), build_steering_section(notes or []))
if part
)
gap = f"\n{mid}\n" if mid else "\n"
return (
_base("LOW")
+ mid
+ gap
+ "DEFLECT_MODE: retrieval was weak — the titles below are the closest "
"your notes come to the question. They are titles only; do not pretend "
"they answer it. Use them to propose 2-3 alternative questions.\n"