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:
+63
-4
@@ -27,6 +27,17 @@ caches) is skipped, along with non-content dirs (``.venv``,
|
||||
``build``). Re-runs are cheap: files are diffed by sha256 and unchanged
|
||||
ones are not re-embedded; ``--prune`` also drops documents whose files no
|
||||
longer match the format filter.
|
||||
|
||||
After a run that **changed** the knowledge base (at least one document
|
||||
added or updated — or no outline stored yet), the single ``kb_overview``
|
||||
row is regenerated with the ``lite`` model (phase 31): the plain-text
|
||||
outline of the KB's basic categories that every chat turn injects into
|
||||
the system prompt as ``<knowledge_base>``. The regeneration is
|
||||
**best-effort and change-gated** — unchanged re-imports and ``--limit``
|
||||
debug runs never burn a ``lite`` call, and a ``lite`` failure only
|
||||
reports ``overview=failed`` on the summary line: the import's exit code
|
||||
is about files, and the previous outline stays (an old outline is better
|
||||
than none).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -40,8 +51,11 @@ from pathlib import Path
|
||||
from app.config import Settings, get_settings
|
||||
from app.core.debugging import configure_debugging
|
||||
from app.core.logging import configure_logging
|
||||
from app.rag.importer import import_sources
|
||||
from app.db import SessionLocal
|
||||
from app.models import KbOverview
|
||||
from app.rag.importer import ImportSummary, import_sources
|
||||
from app.rag.llm import LLMClient
|
||||
from app.rag.overview import regenerate_overview
|
||||
from scripts.git_sync import GitSyncError, clone_or_pull
|
||||
|
||||
logger = logging.getLogger("scripts.import_docs")
|
||||
@@ -116,6 +130,18 @@ def _resolve_sources(cli_sources: list[Path] | None, settings: Settings) -> list
|
||||
return [path.expanduser() for path in DEFAULT_SOURCES]
|
||||
|
||||
|
||||
def _overview_row_exists() -> bool:
|
||||
"""Whether the single ``kb_overview`` row (id = 1) is present.
|
||||
|
||||
One indexed PK lookup (phase 31, task 04): a missing outline after an
|
||||
unchanged re-import — e.g. the first run after migration 0005 — still
|
||||
gets a fresh outline, while a present one is left untouched until the
|
||||
KB actually changes.
|
||||
"""
|
||||
with SessionLocal() as session:
|
||||
return session.get(KbOverview, 1) is not None
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = build_parser().parse_args(argv)
|
||||
settings = get_settings()
|
||||
@@ -144,16 +170,49 @@ def main(argv: list[str] | None = None) -> int:
|
||||
return 1
|
||||
|
||||
llm = LLMClient()
|
||||
summary = asyncio.run(import_sources(sources, llm, prune=args.prune, limit=args.limit))
|
||||
|
||||
async def _run() -> tuple[ImportSummary, str]:
|
||||
"""Import, then (change-gated) refresh the stored KB overview.
|
||||
|
||||
One event loop, one ``LLMClient`` (phase 31, task 04): the
|
||||
outline that every chat prompt injects as ``<knowledge_base>`` is
|
||||
regenerated only when this run added/updated at least one
|
||||
document — or when no outline exists yet after a walk that
|
||||
actually saw files (e.g. the first run after migration 0005).
|
||||
``--limit`` debug runs (an incomplete walk must not rewrite the
|
||||
outline — mirrors the ``--prune``-with-``--limit`` guard) and
|
||||
unchanged re-imports never burn a ``lite`` call, and a ``lite``
|
||||
failure only flips the status token (``failed``) — the import's
|
||||
exit code is unchanged.
|
||||
"""
|
||||
summary = await import_sources(sources, llm, prune=args.prune, limit=args.limit)
|
||||
if args.limit is not None:
|
||||
logger.info("overview: skipped (--limit)")
|
||||
return summary, "skipped"
|
||||
if summary.added + summary.updated == 0:
|
||||
if summary.files == 0:
|
||||
logger.info("overview: skipped (nothing imported)")
|
||||
return summary, "skipped"
|
||||
if _overview_row_exists():
|
||||
logger.info("overview: skipped (KB unchanged)")
|
||||
return summary, "skipped"
|
||||
# No outline yet after an unchanged re-import (e.g. the first
|
||||
# run after migration 0005) — fall through and generate one.
|
||||
ok = await regenerate_overview(llm)
|
||||
return summary, "updated" if ok else "failed"
|
||||
|
||||
summary, overview_status = asyncio.run(_run())
|
||||
print(
|
||||
f"import_docs: files={summary.files} added={summary.added} "
|
||||
f"updated={summary.updated} unchanged={summary.unchanged} "
|
||||
f"pruned={summary.pruned} errors={summary.errors} chunks={summary.chunks} "
|
||||
f"embed_batches={summary.embed_batches} summaries={summary.summaries} "
|
||||
f"summary_errors={summary.summary_errors} formats={summary.format_counts()}"
|
||||
f"summary_errors={summary.summary_errors} formats={summary.format_counts()} "
|
||||
f"overview={overview_status}"
|
||||
)
|
||||
# Non-zero if any file failed, so cron/CI notice — the rest of the KB
|
||||
# was imported and the failed files are retried on the next run.
|
||||
# was imported and the failed files are retried on the next run. The
|
||||
# overview is best-effort: a failed outline never changes the exit code.
|
||||
return 1 if summary.errors else 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user