- add deterministic ArticulationTarget realizer - add rhetorical move templates and predicate humanization - handle definition, comparison, correction, unknown, and empty targets - keep runtime ChatResponse path unchanged - add focused realizer tests
93 lines
3.3 KiB
Python
93 lines
3.3 KiB
Python
"""Tests for ArticulationRealizerV2 — deterministic template-based realization."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from generate.graph_planner import (
|
|
ArticulationTarget,
|
|
graph_from_intent,
|
|
plan_articulation,
|
|
)
|
|
from generate.intent import IntentTag, classify_intent
|
|
from generate.realizer import RealizedPlan, realize_target
|
|
|
|
|
|
def _realize_from_prompt(prompt: str, *, prior_node_id: str | None = None) -> RealizedPlan:
|
|
intent = classify_intent(prompt)
|
|
graph = graph_from_intent(intent, prior_node_id=prior_node_id)
|
|
target = plan_articulation(graph)
|
|
return realize_target(target, graph)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. Definition realizer mentions subject
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_definition_realizer_mentions_subject() -> None:
|
|
plan = _realize_from_prompt("What is a multivector?")
|
|
|
|
assert len(plan.fragments) == 1
|
|
assert "multivector" in plan.surface.lower()
|
|
assert "is defined as" in plan.surface.lower()
|
|
assert plan.surface.endswith(".")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. Comparison realizer mentions both terms
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_comparison_realizer_mentions_both_terms() -> None:
|
|
plan = _realize_from_prompt("Compare MLX and PyTorch")
|
|
|
|
assert len(plan.fragments) == 2
|
|
surface_lower = plan.surface.lower()
|
|
assert "mlx" in surface_lower
|
|
assert "pytorch" in surface_lower
|
|
assert "in contrast" in surface_lower
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. Correction realizer mentions prior or correction
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_correction_realizer_mentions_prior_or_correction() -> None:
|
|
plan = _realize_from_prompt(
|
|
"No, that's wrong — it should be grade 2",
|
|
prior_node_id="prev_p0",
|
|
)
|
|
|
|
assert len(plan.fragments) == 1
|
|
surface_lower = plan.surface.lower()
|
|
assert "correction:" in surface_lower
|
|
assert "corrects" in surface_lower
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 4. Unknown or empty graph is bounded
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_unknown_or_empty_graph_is_bounded() -> None:
|
|
empty_target = ArticulationTarget(steps=(), source_intent=IntentTag.UNKNOWN)
|
|
plan = realize_target(empty_target, graph=None)
|
|
|
|
assert plan.surface == ""
|
|
assert plan.fragments == ()
|
|
|
|
unknown_plan = _realize_from_prompt("xyzzy foobar")
|
|
assert unknown_plan.surface
|
|
assert len(unknown_plan.fragments) >= 1
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 5. Realizer output is deterministic
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_realizer_output_is_deterministic() -> None:
|
|
plan_a = _realize_from_prompt("What is light?")
|
|
plan_b = _realize_from_prompt("What is light?")
|
|
|
|
assert plan_a.surface == plan_b.surface
|
|
assert len(plan_a.fragments) == len(plan_b.fragments)
|
|
for fa, fb in zip(plan_a.fragments, plan_b.fragments):
|
|
assert fa.surface == fb.surface
|
|
assert fa.move == fb.move
|
|
assert fa.node_id == fb.node_id
|