feat(rag): steering notes — tune how Brain answers, stored in Postgres and injected into every system prompt
This commit is contained in:
+42
-10
@@ -15,6 +15,9 @@ Implements just enough of the aipi surface:
|
||||
- otherwise -> upbeat answer quoting the provided document context
|
||||
- user message containing ``pretend to think slowly`` -> 3s warm-up delay
|
||||
(used by the loading-feedback story).
|
||||
- system prompt containing ``<tuning>`` (phase 15, steering notes) ->
|
||||
the composed answer ends with `` (tuning: <first note line>)`` —
|
||||
makes prompt injection observable in the UI deterministically.
|
||||
|
||||
``max_tokens`` is honored deterministically (token ≈ whitespace word),
|
||||
like a real endpoint: an answer longer than the cap is truncated. This
|
||||
@@ -89,25 +92,54 @@ def long_answer() -> str:
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
#: First numbered note line of a ``<tuning>`` section (phase 15).
|
||||
_TUNING_BLOCK_RE = re.compile(r"<tuning>\n(.*?)\n</tuning>", re.S)
|
||||
_NOTE_LINE_RE = re.compile(r"^\d+\.\s*(.+)$")
|
||||
|
||||
|
||||
def first_tuning_note(system: str) -> str | None:
|
||||
"""The first steering note in the system prompt, or ``None``.
|
||||
|
||||
The prompt numbers notes 1..N oldest-first (see
|
||||
``app.rag.prompts.build_steering_section``); the mock echoes the first
|
||||
one into its answer so prompt injection is observable in the UI.
|
||||
"""
|
||||
block = _TUNING_BLOCK_RE.search(system)
|
||||
if not block:
|
||||
return None
|
||||
for line in block.group(1).splitlines():
|
||||
m = _NOTE_LINE_RE.match(line.strip())
|
||||
if m:
|
||||
return m.group(1).strip()
|
||||
return None
|
||||
|
||||
|
||||
def compose_answer(body: dict[str, Any]) -> str:
|
||||
system = _system(body)
|
||||
user = _user(body)
|
||||
if LONG_ANSWER_TRIGGER in user.lower():
|
||||
return long_answer()
|
||||
if "DEFLECT_MODE" in system:
|
||||
return (
|
||||
answer = long_answer()
|
||||
elif "DEFLECT_MODE" in system:
|
||||
answer = (
|
||||
"Ah — I haven't done anything like that, so I don't want to make stuff up! "
|
||||
"You're thinking bigger than my notes for a second. Try asking about "
|
||||
"kubernetes, backups, or deploying a new service — I know those inside out. "
|
||||
"You've got this!"
|
||||
)
|
||||
ctx = _context(body)
|
||||
snippet = ctx[:220].replace("\n", " ").strip()
|
||||
return (
|
||||
f"Great question — you've absolutely got this! Here's what my notes say about "
|
||||
f"“{user.strip()[:80]}”: {snippet}… That's the gist from the docs; happy to "
|
||||
"dig into any of it. (Deterministic mock answer for E2E.)"
|
||||
)
|
||||
else:
|
||||
ctx = _context(body)
|
||||
snippet = ctx[:220].replace("\n", " ").strip()
|
||||
answer = (
|
||||
f"Great question — you've absolutely got this! Here's what my notes say about "
|
||||
f"“{user.strip()[:80]}”: {snippet}… That's the gist from the docs; happy to "
|
||||
"dig into any of it. (Deterministic mock answer for E2E.)"
|
||||
)
|
||||
# Steering (phase 15): when the system prompt carries <tuning>, the
|
||||
# answer ends with the first note — deterministically observable.
|
||||
note = first_tuning_note(system)
|
||||
if note:
|
||||
answer = f"{answer} (tuning: {note})"
|
||||
return answer
|
||||
|
||||
|
||||
@app.post("/__shutdown__")
|
||||
|
||||
Reference in New Issue
Block a user