feat(rag): steering notes — tune how Brain answers, stored in Postgres and injected into every system prompt

This commit is contained in:
2026-08-22 16:44:42 -04:00
parent 19df7df99d
commit fc0d9a2d5c
19 changed files with 1589 additions and 34 deletions
+74
View File
@@ -0,0 +1,74 @@
"""Steering notes API — tune how Brain answers (phase 15, story
``steering-notes``).
Stateless CRUD under ``/api/steering`` (A10): notes are owner instructions
stored in Postgres (``steering_notes``) and read into the system prompt of
**every** chat turn as the ``<tuning>`` section (see
:func:`app.rag.prompts.build_steering_section` and
:func:`app.api.chat.chat`).
"""
from __future__ import annotations
import uuid
from fastapi import APIRouter, Depends, HTTPException, Response
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.db import get_db
from app.models import SteeringNote
from app.schemas import SteeringNote as SteeringNoteOut
from app.schemas import SteeringNoteIn, SteeringNoteList
router = APIRouter(prefix="/steering", tags=["steering"])
def load_steering_notes(db: Session) -> list[str]:
"""All steering notes, oldest first (the order they are numbered in the
``<tuning>`` prompt section). Used by the chat turn (``app.api.chat``)."""
rows = db.scalars(
select(SteeringNote).order_by(SteeringNote.created_at.asc(), SteeringNote.id.asc())
).all()
return [row.note for row in rows]
@router.get("", response_model=SteeringNoteList)
def list_steering_notes(
db: Session = Depends(get_db), # noqa: B008
) -> SteeringNoteList:
"""All notes, newest first (the UI panel's display order)."""
rows = db.scalars(
select(SteeringNote).order_by(SteeringNote.created_at.desc(), SteeringNote.id.desc())
).all()
return SteeringNoteList(
notes=[
SteeringNoteOut(id=row.id, note=row.note, created_at=row.created_at) for row in rows
]
)
@router.post("", response_model=SteeringNoteOut, status_code=201)
def create_steering_note(
payload: SteeringNoteIn,
db: Session = Depends(get_db), # noqa: B008
) -> SteeringNoteOut:
"""Store one tuning instruction (trimmed, 1–2000 chars — 422 otherwise)."""
row = SteeringNote(note=payload.note)
db.add(row)
db.commit()
db.refresh(row)
return SteeringNoteOut(id=row.id, note=row.note, created_at=row.created_at)
@router.delete("/{note_id}", status_code=204)
def delete_steering_note(
note_id: uuid.UUID,
db: Session = Depends(get_db), # noqa: B008
) -> Response:
"""Remove a note; 404 when the id is unknown."""
row = db.get(SteeringNote, note_id)
if row is None:
raise HTTPException(status_code=404, detail="steering note not found")
db.delete(row)
db.commit()
return Response(status_code=204)