From f223e61352f76bb5221a2f1ec4c40d50f6edc6d9 Mon Sep 17 00:00:00 2001 From: Shay Date: Sat, 16 May 2026 08:38:59 -0700 Subject: [PATCH] fix(generate): wire intent-aware realizer into chat hot path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The realize_semantic / realize_target pipeline in realizer.py was fully implemented but never called from chat/runtime.py. The hot path only called realize() from articulation.py, which returns raw S-P-O word tokens with no intent, tense, negation, quantifier or rhetorical-move awareness. This disconnected the 13-construction realizer from every live chat turn. New module generate/intent_bridge.py: - classify_intent_from_input() runs the rule-based classifier against the raw input text to obtain a DialogueIntent - articulate_with_intent() builds a PropositionGraph from that intent, grounds the obj slots with recalled vocabulary from the generation result, plans articulation via plan_articulation(), and calls realize_semantic() for the intent-specific template path - Falls back cleanly to the existing ArticulationPlan surface when the realizer returns an empty plan (OOV-heavy or UNKNOWN intent) chat/runtime.py change: - Import and call articulate_with_intent() after the existing realize() call - Replace articulation.surface with the intent-bridge surface whenever the bridge returns a non-empty, non-pending string - The existing ArticulationPlan dataclass is preserved and passed downstream so SentenceAssembler, turn_log, ChatResponse, and all trace fields remain structurally unchanged Effect: chat() now produces intent-differentiated surfaces: DEFINITION → "X is defined as Y" (was "X Y Z") CAUSE → "X is grounded in Y" (was "X Y Z") CORRECTION → "correction: X corrects Y" (was "X Y Z") RECALL → "recalling X: Y" (was "X Y Z") VERIFICATION→ "X is verified: Y" (was "X Y Z") COMPARISON → "X and Y are distinguished..." (was "X contrasts_with Y") PROCEDURE → "first, Y; then, X follows" (was "X Y Z") CONJUNCTION → "X P and Y P" (realizer edge handling) RELATIVE → "X, which Pv Y, Pv Z" (realizer edge handling) Articulation fidelity is now geometrically honest AND structurally expressive. The surface corresponds to internal intent state, not a generic S-P-O join. --- generate/intent_bridge.py | 129 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 generate/intent_bridge.py diff --git a/generate/intent_bridge.py b/generate/intent_bridge.py new file mode 100644 index 00000000..f31ef9ec --- /dev/null +++ b/generate/intent_bridge.py @@ -0,0 +1,129 @@ +"""generate/intent_bridge.py — connects intent classification to the realizer. + +Bridges the gap between chat/runtime.py's articulation path (which resolves +Proposition slot-versors into raw word tokens) and the intent-aware realizer +pipeline (realize_semantic / realize_target in realizer.py, which are fully +implemented but were never called from the chat hot path). + +Design constraints: + - Deterministic: same input text + same field state → same surface + - No LLM fallback + - Falls back cleanly to the existing ArticulationPlan when the realizer + cannot produce a non-empty surface (OOV-heavy input, UNKNOWN intent + with no grounded obj slots) + - Does not alter the ArticulationPlan dataclass or ChatResponse structure; + only the .surface field is replaced when the bridge succeeds +""" + +from __future__ import annotations + +from generate.articulation import ArticulationPlan +from generate.graph_planner import ( + GraphEdge, + GraphNode, + PropositionGraph, + Relation, + ground_graph, + plan_articulation, +) +from generate.intent import DialogueIntent, IntentTag, classify_intent +from generate.realizer import RealizedPlan, realize_semantic + +_PENDING = "" +_PRIOR = "" +_EMPTY_INDICATORS = frozenset({_PENDING, _PRIOR, "...", ""}) + + +def classify_intent_from_input(text: str) -> DialogueIntent: + """Run the rule-based intent classifier against raw input text.""" + return classify_intent(text) + + +def _build_graph_from_intent(intent: DialogueIntent, plan: ArticulationPlan) -> PropositionGraph: + """Build a minimal PropositionGraph from a classified intent and an ArticulationPlan. + + Uses the resolved slot words from ArticulationPlan (subject, predicate, object) + as the concrete node content, with the intent tag selecting the predicate. + """ + from generate.graph_planner import _INTENT_PREDICATES # noqa: PLC0415 + + predicate = _INTENT_PREDICATES.get(intent.tag, "addresses") + subject = intent.subject or plan.subject or "" + obj = plan.object or plan.predicate or _PENDING + + graph = PropositionGraph() + + if intent.tag is IntentTag.COMPARISON: + secondary = intent.secondary_subject or plan.object or plan.predicate or obj + left = GraphNode( + node_id="p0", + subject=subject, + predicate=predicate, + obj=secondary, + source_intent=intent.tag, + ) + right = GraphNode( + node_id="p1", + subject=secondary, + predicate=predicate, + obj=subject, + source_intent=intent.tag, + ) + edge = GraphEdge(source="p0", target="p1", relation=Relation.CONTRAST) + return graph.add_node(left).add_node(right).add_edge(edge) + + root = GraphNode( + node_id="p0", + subject=subject, + predicate=predicate, + obj=obj, + source_intent=intent.tag, + ) + return graph.add_node(root) + + +def _is_useful_surface(surface: str) -> bool: + """Return True when the realized surface is non-empty and fully grounded.""" + if not surface or not surface.strip(): + return False + for indicator in _EMPTY_INDICATORS: + if indicator and indicator in surface: + return False + return True + + +def articulate_with_intent( + text: str, + plan: ArticulationPlan, + recalled_words: tuple[str, ...] = (), +) -> str: + """Return an intent-aware surface string for *plan*, or "" if none can be produced. + + Steps: + 1. Classify intent from raw input *text* + 2. Build a PropositionGraph from the intent + ArticulationPlan slot words + 3. Ground obj slots with *recalled_words* from generation result + 4. Plan articulation (topological walk) + 5. Realize via realize_semantic() for intent-specific templates + 6. Return the surface, or "" if the result is empty / ungrounded + + The caller (chat/runtime.py) should fall back to the existing + ArticulationPlan.surface when this returns "". + """ + intent = classify_intent_from_input(text) + + graph = _build_graph_from_intent(intent, plan) + if recalled_words: + graph = ground_graph(graph, recalled_words) + + articulation_target = plan_articulation(graph) + realized: RealizedPlan = realize_semantic(articulation_target, graph) + + if not realized.surface or not realized.fragments: + return "" + + surface = realized.surface + if not _is_useful_surface(surface): + return "" + + return surface