119 lines
4.7 KiB
Python
119 lines
4.7 KiB
Python
"""Unit: locked persona prompt builder (PLAN §6 verbatim + both modes)."""
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
from app.models import Document
|
|
from app.rag.prompts import PERSONA, _base, build_deflect_prompt, build_high_prompt
|
|
|
|
|
|
def _doc(path: str, content: str, title: str) -> Document:
|
|
return Document(
|
|
id=uuid.uuid4(),
|
|
source="Homelab",
|
|
path=path,
|
|
full_path=f"/tmp/{path}",
|
|
title=title,
|
|
content=content,
|
|
content_hash="0" * 64,
|
|
)
|
|
|
|
|
|
def test_persona_rules_present_verbatim() -> None:
|
|
# Aligned to the owner's working-tree persona edits (PLAN §6 revision,
|
|
# 2026-08-22): no "you've got this" tagline, no mandated deflection
|
|
# opening. The honesty gate itself (rule 3) is unchanged.
|
|
for fragment in (
|
|
'You are "Brain of Reese" — the digital brain of Reese, a self-hoster and',
|
|
"optimistic about the user's ability to do things",
|
|
"Answer ONLY from the provided document context. Cite which document(s)",
|
|
"you used, by path.",
|
|
"Be concrete: names, versions, ports, hosts, schedules",
|
|
'HONESTY GATE: if <relevance> is "LOW", you must NOT pretend to know',
|
|
"Offer 2-3 alternative questions about things you DO have notes on.",
|
|
"Never invent facts, hosts, or steps that are not in the context.",
|
|
"Keep answers tight: short paragraphs, bullets where helpful.",
|
|
):
|
|
assert fragment in PERSONA
|
|
|
|
|
|
def test_persona_owner_edits_are_preserved() -> None:
|
|
"""PLAN §6 revision (2026-08-22): the removed elements must stay out."""
|
|
assert 'you\'ve got this' not in PERSONA # tagline removed by the owner
|
|
assert "Start your answer with a variant of" not in PERSONA # no mandated opening
|
|
assert "HONESTY GATE" in PERSONA # the gate itself is intact
|
|
|
|
|
|
def test_high_prompt_carries_relevance_marker_and_full_documents() -> None:
|
|
doc = _doc("kubernetes.md", "Talos Linux on three nodes.", "Kubernetes Homelab Cluster")
|
|
prompt = build_high_prompt([doc])
|
|
assert "<relevance>HIGH</relevance>" in prompt
|
|
assert "DEFLECT_MODE" not in prompt
|
|
assert "<documents>" in prompt and "</documents>" in prompt
|
|
assert 'path="kubernetes.md"' in prompt
|
|
assert "Talos Linux on three nodes." in prompt
|
|
assert "HONESTY GATE" in prompt # persona intact
|
|
|
|
|
|
def test_high_prompt_lists_multiple_documents_in_order() -> None:
|
|
a = _doc("a.md", "CONTENT_A", "Title A")
|
|
b = _doc("b.md", "CONTENT_B", "Title B")
|
|
prompt = build_high_prompt([a, b])
|
|
assert prompt.index("CONTENT_A") < prompt.index("CONTENT_B")
|
|
assert 'title="Title B"' in prompt
|
|
|
|
|
|
def test_high_prompt_without_documents_stays_honest() -> None:
|
|
prompt = build_high_prompt([])
|
|
assert "<documents>" in prompt
|
|
assert "do not invent specifics" in prompt
|
|
|
|
|
|
def test_low_prompt_has_deflect_mode_and_titles_only() -> None:
|
|
titles = ["Kubernetes Homelab Cluster", "Backup Strategy"]
|
|
prompt = build_deflect_prompt(titles)
|
|
assert "<relevance>LOW</relevance>" in prompt
|
|
assert "DEFLECT_MODE" in prompt # marker the E2E mock keys on
|
|
assert "- Kubernetes Homelab Cluster" in prompt
|
|
assert "- Backup Strategy" in prompt
|
|
|
|
|
|
def test_low_prompt_never_contains_document_content() -> None:
|
|
secret = "SECRET_DOCUMENT_CONTENT_12345"
|
|
prompt = build_deflect_prompt(["Some Title"])
|
|
assert secret not in prompt
|
|
assert "<documents>" not in prompt
|
|
assert "HONESTY GATE" in prompt # the LOW rule is what the model must follow
|
|
|
|
|
|
def test_low_prompt_with_no_titles() -> None:
|
|
assert "nothing close at all" in build_deflect_prompt([])
|
|
|
|
|
|
def test_zero_note_prompt_is_byte_identical_to_pre_steering() -> None:
|
|
"""Phase 15 contract: with no steering notes the prompt is exactly what
|
|
it was before the <tuning> section existed."""
|
|
doc = _doc("kubernetes.md", "Talos Linux on three nodes.", "Kubernetes Homelab Cluster")
|
|
block = (
|
|
'<document source="Homelab" path="kubernetes.md" title="Kubernetes Homelab Cluster">\n'
|
|
"Talos Linux on three nodes.\n"
|
|
"</document>"
|
|
)
|
|
assert build_high_prompt([doc]) == _base("HIGH") + "\n<documents>\n" + block + "\n</documents>"
|
|
assert build_deflect_prompt(["T1", "T2"]) == (
|
|
_base("LOW")
|
|
+ "\nDEFLECT_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"
|
|
+ "- T1\n- T2"
|
|
)
|
|
assert "<tuning>" not in build_high_prompt([doc])
|
|
assert "<tuning>" not in build_deflect_prompt([])
|
|
|
|
|
|
def test_relevance_placeholder_rejected_for_garbage() -> None:
|
|
with pytest.raises(ValueError, match="HIGH or LOW"):
|
|
_base("MEDIUM")
|