prova-core / brain /tests /test_builder.py
chintakapp's picture
core 2.0
210ef30
Raw History Blame Contribute Delete
8.42 kB
"""Prompt assembly — master prompt 12 (block order) and blueprint 16 (what must be tested).
Block order is significant and every position here was moved because of a reported bug. The
tests assert ORDER, not wording, so the persona text can be edited freely.
"""
from datetime import datetime
import pytest
from brain.persona.builder import Attachment, Channel, PromptInput, build_prompt
from brain.persona.prova import HEADINGS
from brain.persona.routine import WeeklyRoutine
from brain.persona.types import Mode
NOW = datetime(2024, 1, 1, 11, 0) # Monday, at work
def make_input(**kwargs) -> PromptInput:
base = {
"ai_name": "Prova",
"user_name": "Karnak",
"mode": Mode.GF,
"mood_id": "playful",
"memories": ["His sister's name is Ria."],
"now": NOW,
"routine": WeeklyRoutine.default(),
}
return PromptInput(**{**base, **kwargs})
def order_of(prompt: str, *headings: str) -> list[int]:
return [prompt.index(h) for h in headings]
def is_sorted(values: list[int]) -> bool:
return values == sorted(values)
# --- GF mode ---------------------------------------------------------------
def test_gf_block_order():
prompt = build_prompt(make_input())
positions = order_of(
prompt,
HEADINGS["persona"],
HEADINGS["mode"],
HEADINGS["time"],
HEADINGS["routine"],
HEADINGS["memories"],
HEADINGS["mood"],
HEADINGS["rules"],
)
assert is_sorted(positions)
def test_gf_mood_sits_directly_above_the_rules():
# Load-bearing: the rules block names the mood as the strongest tone signal.
prompt = build_prompt(make_input())
between = prompt[prompt.index(HEADINGS["mood"]) : prompt.index(HEADINGS["rules"])]
assert not any(h in between for h in HEADINGS.values() if h != HEADINGS["mood"])
def test_gf_includes_the_relationship_layer():
assert "been together a long time" in build_prompt(make_input())
def test_a_proactive_turn_replaces_the_rules_block():
prompt = build_prompt(make_input(proactive=True))
assert HEADINGS["rules"] not in prompt
assert HEADINGS["proactive"] in prompt
def test_a_proactive_turn_carries_the_checkin_block():
# PROACTIVE_PROMPT names this block by name, so omitting it would point at nothing.
prompt = build_prompt(make_input(proactive=True))
assert HEADINGS["checkin"] in prompt
def test_an_ordinary_turn_has_no_checkin_block():
assert HEADINGS["checkin"] not in build_prompt(make_input())
# --- Normal mode -----------------------------------------------------------
def test_normal_block_order():
prompt = build_prompt(make_input(mode=Mode.NORMAL))
positions = order_of(
prompt,
HEADINGS["persona"],
HEADINGS["mode"],
HEADINGS["time"],
HEADINGS["memories"],
HEADINGS["rules"],
)
assert is_sorted(positions)
def test_normal_never_sends_a_mood_block():
assert HEADINGS["mood"] not in build_prompt(make_input(mode=Mode.NORMAL, mood_id="naughty"))
def test_normal_never_sends_the_relationship_layer():
prompt = build_prompt(make_input(mode=Mode.NORMAL))
assert "been together a long time" not in prompt
assert "your partner" not in prompt.lower()
def test_normal_uses_the_professional_mode_block():
prompt = build_prompt(make_input(mode=Mode.NORMAL))
assert "MODE: PROFESSIONAL (NORMAL)" in prompt
assert "executive assistant" in prompt
def test_normal_carries_hud_context_when_the_client_sends_it():
prompt = build_prompt(make_input(mode=Mode.NORMAL, hud={"city": "Kolkata", "weather": "30C"}))
assert HEADINGS["hud"] in prompt
assert "Kolkata" in prompt
def test_normal_omits_the_hud_block_when_there_is_nothing_to_say():
assert HEADINGS["hud"] not in build_prompt(make_input(mode=Mode.NORMAL))
def test_normal_gets_a_routine_status_line_not_the_full_week():
prompt = build_prompt(make_input(mode=Mode.NORMAL))
assert "at the office, working" in prompt
assert "leaves home around" not in prompt
# --- Memory scope ----------------------------------------------------------
def test_normal_mode_rejects_private_memories_even_if_they_are_passed_in():
# Defence in depth: retrieval filters by scope, and so does the builder.
prompt = build_prompt(
make_input(
mode=Mode.NORMAL,
memories=["His sister's name is Ria."],
private_memories=["He calls her shona."],
)
)
assert "Ria" in prompt
assert "shona" not in prompt
def test_normal_sees_private_is_the_one_switch_that_lets_private_facts_through():
# Master prompt 4.5: off by default; Karnak can turn it on, with a warning in Settings.
prompt = build_prompt(
make_input(
mode=Mode.NORMAL,
memories=["His sister's name is Ria."],
private_memories=["He calls her shona."],
normal_sees_private=True,
)
)
assert "shona" in prompt
def test_gf_mode_sees_both_scopes():
prompt = build_prompt(
make_input(memories=["His sister's name is Ria."], private_memories=["He calls her shona."])
)
assert "Ria" in prompt
assert "shona" in prompt
def test_the_memory_block_is_omitted_when_nothing_cleared_the_floor():
assert HEADINGS["memories"] not in build_prompt(make_input(memories=[]))
# --- Style bank (master prompt 5.3) ---------------------------------------
def test_style_bank_sits_after_memories_and_before_mood_in_gf():
prompt = build_prompt(
make_input(style_examples=[("aaj boss ta abar...", "Abar? 😏 ki bollo ebar")])
)
positions = order_of(prompt, HEADINGS["memories"], HEADINGS["style"], HEADINGS["mood"])
assert is_sorted(positions)
def test_style_bank_sits_before_the_rules_in_normal():
prompt = build_prompt(
make_input(mode=Mode.NORMAL, style_examples=[("what's on my plate?", "Three items today.")])
)
positions = order_of(prompt, HEADINGS["memories"], HEADINGS["style"], HEADINGS["rules"])
assert is_sorted(positions)
def test_no_style_block_when_the_bank_is_empty_or_disabled():
assert HEADINGS["style"] not in build_prompt(make_input(style_examples=[]))
# --- Turn overrides, which must outrank the mood ---------------------------
@pytest.mark.parametrize("attachment", [Attachment.PHOTO, Attachment.STICKER])
def test_attachment_blocks_sit_after_the_rules(attachment):
# A playful mood turning a photo of breakfast into a joke is the bug this ordering fixes.
prompt = build_prompt(make_input(attachment=attachment))
assert prompt.index(HEADINGS["rules"]) < prompt.index(HEADINGS[attachment.value])
def test_a_photo_in_normal_mode_is_told_to_stay_friendly_not_romantic():
prompt = build_prompt(make_input(mode=Mode.NORMAL, attachment=Attachment.PHOTO))
assert "friendly, not romantic" in prompt
def test_the_ambiguity_block_sits_after_the_rules():
prompt = build_prompt(make_input(ambiguous_message=True))
assert prompt.index(HEADINGS["rules"]) < prompt.index(HEADINGS["ambiguity"])
def test_the_corrective_note_is_last():
prompt = build_prompt(
make_input(attachment=Attachment.PHOTO, ambiguous_message=True, corrective_note="FIX IT")
)
assert prompt.rstrip().endswith("FIX IT")
def test_a_mode_switch_adds_a_marker():
prompt = build_prompt(make_input(mode=Mode.NORMAL, mode_just_changed=True))
assert HEADINGS["mode_changed"] in prompt
assert "others may be around" in prompt.lower()
def test_call_channel_adds_the_speaking_line():
prompt = build_prompt(make_input(channel=Channel.CALL))
assert "voice call" in prompt.lower()
# --- Names -----------------------------------------------------------------
def test_names_are_substituted_and_no_placeholder_survives():
prompt = build_prompt(make_input())
assert "{{ai_name}}" not in prompt
assert "{{user_name}}" not in prompt
assert "Prova" in prompt
assert "Karnak" in prompt
def test_a_blank_user_name_falls_back_to_something_non_assistant():
prompt = build_prompt(make_input(user_name=""))
assert "the user" not in prompt.lower()
assert "{{user_name}}" not in prompt
def test_blocks_are_separated_by_a_blank_line_and_never_start_with_one():
prompt = build_prompt(make_input())
assert "\n\n\n" not in prompt
assert not prompt.startswith("\n")