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.
120 lines
3.7 KiB
Python
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
|