core/tests/test_articulation_realizer_v2.py
Shay 58a06124bf
Add articulation realizer v2
- 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
2026-05-14 20:14:50 -07:00

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