76 lines
2.5 KiB
Python
76 lines
2.5 KiB
Python
"""Turn-level significance scorer (T22).
|
|
|
|
Per Requirements §11.1, each turn is scored on a 0-3 scale:
|
|
|
|
- 0 = Routine: small talk, ordinary action.
|
|
- 1 = Notable: a specific detail or beat worth remembering.
|
|
- 2 = Significant: a scene-level moment, real disagreement, confided secret.
|
|
- 3 = Pivotal: a relationship-altering event (first kiss, betrayal, "I love
|
|
you").
|
|
|
|
The scorer is conservative: pivotal (3) requires a clear signal because the
|
|
auto-pin rule (§8.5) gives those memories permanent shelf space. The
|
|
classifier returns a strict-JSON ``SignificanceVerdict``; a malformed or
|
|
refusal-shaped response falls back to ``score=1`` (Notable) — a safe
|
|
middle-of-the-road default that won't trigger auto-pin.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from chat.llm.classify import classify
|
|
from chat.llm.client import LLMClient
|
|
|
|
|
|
class SignificanceVerdict(BaseModel):
|
|
score: int = Field(ge=0, le=3)
|
|
reason: str = ""
|
|
|
|
|
|
_SYSTEM = """You score the significance of a roleplay turn 0-3:
|
|
0 = Routine: small talk, ordinary action.
|
|
1 = Notable: a specific detail or beat worth remembering.
|
|
2 = Significant: a scene-level moment, real disagreement, confided secret.
|
|
3 = Pivotal: a relationship-altering event (first kiss, betrayal, "I love you").
|
|
|
|
Be conservative — pivotal (3) requires a clear signal. Reply with JSON: {"score": int 0-3, "reason": str}."""
|
|
|
|
|
|
async def compute_significance(
|
|
client: LLMClient,
|
|
*,
|
|
model: str,
|
|
narrative_text: str,
|
|
prior_dialogue: list[dict],
|
|
timeout_s: float = 10.0,
|
|
) -> int:
|
|
"""Score the significance of ``narrative_text`` (the just-written turn).
|
|
|
|
``prior_dialogue`` is a list of ``{"speaker", "text"}`` dicts ordered
|
|
oldest-first; the last 6 entries are stitched into the user prompt as
|
|
context so the classifier can recognize escalation. Returns an int in
|
|
``[0, 3]`` — clamped defensively in case the classifier slips a value
|
|
past the schema validator.
|
|
"""
|
|
user_prompt = "PRIOR DIALOGUE:\n"
|
|
for turn in prior_dialogue[-6:]:
|
|
speaker = turn.get("speaker", "?")
|
|
text = turn.get("text", "")
|
|
user_prompt += f"{speaker}: {text}\n"
|
|
user_prompt += (
|
|
f"\nNEW TURN:\n{narrative_text}\n\n"
|
|
"Score the significance of the NEW TURN."
|
|
)
|
|
|
|
result = await classify(
|
|
client,
|
|
model=model,
|
|
system=_SYSTEM,
|
|
user=user_prompt,
|
|
schema=SignificanceVerdict,
|
|
default=SignificanceVerdict(score=1, reason="fallback"),
|
|
timeout_s=timeout_s,
|
|
)
|
|
return max(0, min(3, result.score))
|