core/tests/test_intent_explain_paragraph.py
Shay 6dd8efe7b3 feat(intent): expository-DEFINITION rules for Explain/Paragraph prompts
Extends ``generate/intent.py:_RULES`` with three new expository
patterns so the upstream subject-extraction gap that the dedup
revealed is closed:

* ``^explain\s+``                                  → DEFINITION
* ``^(write|compose|draft) (a )?(short|brief)?
   paragraph (about|on)\s+``                       → DEFINITION
* ``^paragraph (about|on)\s+``                     → DEFINITION

Rules placed AFTER the NARRATIVE family so ``Tell me about X`` and
``Describe X`` continue to route to NARRATIVE.  Subject extraction
re-uses ``_normalize_subject`` so articles and trailing punctuation
are stripped: ``Explain the parent.`` → subject ``parent``.

``ResponseMode`` is untouched and remains orthogonal: the same prompts
still classify as ``EXPLAIN`` / ``PARAGRAPH`` independently.

20 new tests pin: each rule's expected subject, response-mode
preservation, NARRATIVE/EXAMPLE/existing-DEFINITION rules unchanged.

Lane re-measurement (multi_sentence_response, 21 cases):

  flag off: multi=0.1429, primed_multi=0.0000, conn=0.5385, grounded=0.8571
  flag on : multi=0.9048, primed_multi=1.0000, conn=0.8462, grounded=0.8571

Combined lift over the original (pre-wiring) baseline:
* multi_sentence_rate:        +70pp on the substantive predicate
* primed_multi_sentence_rate: +50pp (0.5 → 1.0 post-classifier)
* connective_present_rate:    +74pp (0.10 → 0.85)
* grounded_rate:              +39pp (0.47 → 0.86)

Cognition eval byte-identical: public 100/100/91.7/100, holdout
100/100/83.3/100 — these prompts aren't in cognition cases, and the
new rules don't perturb any rule that fires for cognition prompts.

Conversational thread coherence unchanged.

docs/evals/discourse_runtime_baseline_2026-05-19.md updated with the
full delta table; the planner is now load-bearing across the warm
and cold pack/teaching paths and the lane measures real capability
rather than punctuation artifacts.
2026-05-19 12:07:08 -07:00

120 lines
3.7 KiB
Python

"""Tests for the expository-DEFINITION classifier rules.
Pins:
* ``Explain X`` / ``Explain X.`` → DEFINITION (subject=X)
* ``Write a paragraph about X`` → DEFINITION (subject=X)
* ``Compose/draft a short paragraph about X`` → DEFINITION (subject=X)
* ``Paragraph about X`` → DEFINITION (subject=X)
Orthogonality preservation:
* ``Tell me about X`` stays NARRATIVE.
* ``Describe X`` stays NARRATIVE.
* ``ResponseMode`` classification for the same prompts stays
EXPLAIN / PARAGRAPH (independent axis).
"""
from __future__ import annotations
import pytest
from generate.intent import (
DialogueIntent,
IntentTag,
ResponseMode,
classify_intent,
classify_response_mode,
)
class TestExplainDefinition:
@pytest.mark.parametrize(
"prompt,subject",
[
("Explain truth.", "truth"),
("Explain truth", "truth"),
("explain memory", "memory"),
("Explain the parent.", "parent"),
("Explain a parent.", "parent"),
],
)
def test_explain_classifies_to_definition(
self, prompt: str, subject: str
) -> None:
result = classify_intent(prompt)
assert result.tag is IntentTag.DEFINITION
assert result.subject == subject
def test_explain_response_mode_stays_explain(self) -> None:
assert classify_response_mode("Explain truth.") is ResponseMode.EXPLAIN
class TestParagraphRequestDefinition:
@pytest.mark.parametrize(
"prompt,subject",
[
("Write a paragraph about truth.", "truth"),
("Write a short paragraph about truth.", "truth"),
("Write a brief paragraph about memory.", "memory"),
("Compose a paragraph about light.", "light"),
("Draft a paragraph on knowledge.", "knowledge"),
("Paragraph about wisdom.", "wisdom"),
("Paragraph on understanding", "understanding"),
],
)
def test_paragraph_request_classifies_to_definition(
self, prompt: str, subject: str
) -> None:
result = classify_intent(prompt)
assert result.tag is IntentTag.DEFINITION
assert result.subject == subject
def test_paragraph_request_response_mode_stays_paragraph(self) -> None:
assert (
classify_response_mode("Write a paragraph about truth.")
is ResponseMode.PARAGRAPH
)
assert (
classify_response_mode("Compose a paragraph about light.")
is ResponseMode.PARAGRAPH
)
class TestNarrativeRulesUntouched:
@pytest.mark.parametrize(
"prompt,subject",
[
("Tell me about truth.", "truth"),
("Describe wisdom.", "wisdom"),
("Describe memory", "memory"),
],
)
def test_narrative_rules_still_win(
self, prompt: str, subject: str
) -> None:
result = classify_intent(prompt)
assert result == DialogueIntent(
tag=IntentTag.NARRATIVE, subject=subject
)
class TestExampleRulesUntouched:
def test_example_still_routes_correctly(self) -> None:
result = classify_intent("Give me an example of truth.")
assert result.tag is IntentTag.EXAMPLE
assert result.subject == "truth"
class TestExistingDefinitionRulesUntouched:
@pytest.mark.parametrize(
"prompt,subject",
[
("What is truth?", "truth"),
("Define memory.", "memory"),
],
)
def test_existing_definition_rules_unchanged(
self, prompt: str, subject: str
) -> None:
result = classify_intent(prompt)
assert result.tag is IntentTag.DEFINITION
assert result.subject == subject