Implements the dialogue understanding pipeline:
prompt -> dialogue intent -> proposition graph -> articulation target
New modules:
- generate/intent.py: rule-based classifier (7 intent tags + UNKNOWN)
- generate/graph_planner.py: immutable PropositionGraph DAG, topological
walk to ArticulationTarget with rhetorical moves
Tests cover definition, cause, comparison, correction with prior-turn
linking, and deterministic serialization.
87 lines
2.5 KiB
Python
87 lines
2.5 KiB
Python
"""Tests for the intent -> proposition graph -> articulation target pipeline."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from generate.graph_planner import (
|
|
Relation,
|
|
RhetoricalMove,
|
|
graph_from_intent,
|
|
plan_articulation,
|
|
)
|
|
from generate.intent import IntentTag, classify_intent
|
|
|
|
|
|
def test_what_is_definition_intent() -> None:
|
|
intent = classify_intent("What is a multivector?")
|
|
|
|
assert intent.tag is IntentTag.DEFINITION
|
|
assert "multivector" in intent.subject.lower()
|
|
|
|
graph = graph_from_intent(intent)
|
|
assert len(graph.nodes) == 1
|
|
assert graph.nodes[0].predicate == "is_defined_as"
|
|
assert graph.nodes[0].source_intent is IntentTag.DEFINITION
|
|
|
|
|
|
def test_why_cause_intent() -> None:
|
|
intent = classify_intent("Why does the field diverge?")
|
|
|
|
assert intent.tag is IntentTag.CAUSE
|
|
assert "field" in intent.subject.lower()
|
|
|
|
graph = graph_from_intent(intent)
|
|
assert len(graph.nodes) == 1
|
|
assert graph.nodes[0].predicate == "is_caused_by"
|
|
|
|
|
|
def test_compare_intent() -> None:
|
|
intent = classify_intent("Compare MLX and PyTorch")
|
|
|
|
assert intent.tag is IntentTag.COMPARISON
|
|
assert intent.subject.lower() == "mlx"
|
|
assert intent.secondary_subject is not None
|
|
assert intent.secondary_subject.lower() == "pytorch"
|
|
|
|
graph = graph_from_intent(intent)
|
|
assert len(graph.nodes) == 2
|
|
assert len(graph.edges) == 1
|
|
assert graph.edges[0].relation is Relation.CONTRAST
|
|
|
|
target = plan_articulation(graph)
|
|
assert target.source_intent is IntentTag.COMPARISON
|
|
moves = [s.move for s in target.steps]
|
|
assert RhetoricalMove.CONTRAST in moves
|
|
|
|
|
|
def test_correction_intent_links_prior_turn() -> None:
|
|
intent = classify_intent("No, that's wrong — it should be grade 2")
|
|
|
|
assert intent.tag is IntentTag.CORRECTION
|
|
assert intent.requires_prior_turn()
|
|
|
|
prior_id = "prev_p0"
|
|
graph = graph_from_intent(intent, prior_node_id=prior_id)
|
|
assert len(graph.nodes) == 1
|
|
assert graph.nodes[0].predicate == "corrects"
|
|
assert graph.nodes[0].obj == prior_id
|
|
|
|
assert len(graph.edges) == 1
|
|
assert graph.edges[0].relation is Relation.CORRECTION
|
|
assert graph.edges[0].target == prior_id
|
|
|
|
|
|
def test_graph_serialization_is_deterministic() -> None:
|
|
intent = classify_intent("Compare cats and dogs")
|
|
graph = graph_from_intent(intent)
|
|
|
|
json_a = graph.to_json()
|
|
json_b = graph.to_json()
|
|
assert json_a == json_b
|
|
|
|
parsed = json.loads(json_a)
|
|
assert "nodes" in parsed
|
|
assert "edges" in parsed
|
|
assert len(parsed["nodes"]) == 2
|
|
assert len(parsed["edges"]) == 1
|