Integrate semantic realizer into cognition pipeline

- add intent-aware semantic templates for seed-pack relation predicates
- add semantic realization path for ArticulationTarget outputs
- wire semantic realization into CognitiveTurnPipeline results without changing ChatRuntime.chat
- expand cognition CLI suite coverage for semantic realizer integration
- add focused tests for deterministic semantic surfaces and response contract stability
This commit is contained in:
Shay 2026-05-15 07:08:37 -07:00 committed by GitHub
parent 33aed4e550
commit a7febd48ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 387 additions and 6 deletions

View file

@ -43,6 +43,7 @@ _TEST_SUITES: dict[str, tuple[str, ...]] = {
"tests/test_intent_proposition_graph.py",
"tests/test_cognitive_turn_pipeline.py",
"tests/test_articulation_realizer_v2.py",
"tests/test_semantic_realizer_integration.py",
),
"teaching": (
"tests/test_reviewed_teaching_loop.py",

View file

@ -20,6 +20,7 @@ from core.cognition.result import CognitiveTurnResult
from core.cognition.trace import compute_trace_hash
from generate.intent import classify_intent
from generate.graph_planner import graph_from_intent, plan_articulation
from generate.realizer import realize_semantic
from teaching.correction import CorrectionCandidate, extract_correction
from teaching.review import ReviewedTeachingExample, review_correction
from teaching.store import PackMutationProposal, TeachingStore
@ -55,11 +56,23 @@ class CognitiveTurnPipeline:
graph = graph_from_intent(intent, prior_node_id=prior_node_id)
target = plan_articulation(graph)
# 1c. REALIZE — semantic realization from graph + intent
realized_plan = realize_semantic(target, graph)
# 27. INGEST / UNDERSTAND / RECALL / THINK / ARTICULATE / LEARN
# Delegated to ChatRuntime.chat() in Phase 1.
# Delegated to ChatRuntime.chat().
# ChatResponse is the stable contract surface.
response = self.runtime.chat(text, max_tokens=max_tokens)
# Override surfaces when semantic realizer produced a result.
# The ChatResponse contract fields are preserved; we select
# the better articulation surface from the semantic path.
surface = response.surface
articulation_surface = response.articulation_surface
if realized_plan.surface:
surface = realized_plan.surface
articulation_surface = realized_plan.surface
# Track last node id for correction-intent chaining
if graph.nodes:
self._last_node_id = graph.nodes[-1].node_id
@ -84,7 +97,7 @@ class CognitiveTurnPipeline:
# Advance turn counter and remember surface for next correction binding
self._turn_number += 1
self._prior_surface = response.surface
self._prior_surface = surface
# 11. TRACE — deterministic hash (includes teaching IDs when present)
review_hash = reviewed_example.review_hash if reviewed_example is not None else ""
@ -92,9 +105,9 @@ class CognitiveTurnPipeline:
trace_hash = compute_trace_hash(
input_text=text,
filtered_tokens=filtered_tokens,
surface=response.surface,
surface=surface,
walk_surface=response.walk_surface,
articulation_surface=response.articulation_surface,
articulation_surface=articulation_surface,
dialogue_role=str(response.dialogue_role),
versor_condition=response.versor_condition,
vault_hits=response.vault_hits,
@ -111,9 +124,9 @@ class CognitiveTurnPipeline:
field_state_after=field_state_after,
proposition=response.proposition,
articulation=response.articulation,
surface=response.surface,
surface=surface,
walk_surface=response.walk_surface,
articulation_surface=response.articulation_surface,
articulation_surface=articulation_surface,
dialogue_role=response.dialogue_role,
identity_score=response.identity_score,
vault_hits=response.vault_hits,

View file

@ -22,6 +22,7 @@ from generate.graph_planner import (
RhetoricalMove,
)
from generate.intent import IntentTag
from generate.semantic_templates import render_semantic
from generate.templates import render_step
@ -51,6 +52,67 @@ class RealizedPlan:
}
def realize_semantic(
target: ArticulationTarget,
graph: PropositionGraph | None = None,
) -> RealizedPlan:
"""Realize using intent-aware semantic templates.
Uses the source intent to select a template that produces structurally
better surfaces (e.g. "X is defined as Y" for definition intents)
rather than the generic rhetorical-move templates.
Returns an empty RealizedPlan for empty/None targets so the caller
can fall back to the older articulation path.
"""
if target is None or not target.steps:
return RealizedPlan(fragments=(), surface="")
intent = target.source_intent
fragments: list[RealizedFragment] = []
if intent is IntentTag.COMPARISON and len(target.steps) >= 2:
step_a = target.steps[0]
step_b = target.steps[1]
obj_a = _resolve_obj(step_a, graph)
secondary = step_b.subject if step_b.subject != step_a.subject else obj_a
surface = render_semantic(
intent=intent,
subject=step_a.subject,
predicate=step_a.predicate,
obj=obj_a,
secondary=secondary,
)
fragments.append(RealizedFragment(
node_id=step_a.node_id,
move=RhetoricalMove.CONTRAST,
surface=surface,
))
else:
for step in target.steps:
obj = _resolve_obj(step, graph)
surface = render_semantic(
intent=intent,
subject=step.subject,
predicate=step.predicate,
obj=obj,
)
move = step.move
if move is RhetoricalMove.ASSERT and intent is IntentTag.CORRECTION:
move = RhetoricalMove.CORRECT
fragments.append(RealizedFragment(
node_id=step.node_id,
move=move,
surface=surface,
))
joined = ". ".join(f.surface for f in fragments)
if joined and not joined.endswith("."):
joined += "."
return RealizedPlan(fragments=tuple(fragments), surface=joined)
def _resolve_obj(step: ArticulationStep, graph: PropositionGraph | None) -> str:
"""Look up the object slot from the graph node matching this step."""
if graph is None:

View file

@ -0,0 +1,73 @@
"""Intent-aware semantic templates for the realizer.
Maps (IntentTag, relation_predicate) pairs to deterministic surface
templates that use the seed pack's relation predicates (defines, means,
grounds, supports, contrasts_with, corrects).
Design constraints:
- No LLM fallback
- No random template selection
- Deterministic: same (intent, predicate, subject, object) -> same surface
- Uses seed pack vocabulary directly
"""
from __future__ import annotations
from generate.intent import IntentTag
_INTENT_TEMPLATES: dict[IntentTag, str] = {
IntentTag.DEFINITION: "{subject} is defined as {obj}",
IntentTag.CAUSE: "{subject} is caused by {obj}",
IntentTag.PROCEDURE: "{subject} has the following steps: {obj}",
IntentTag.COMPARISON: "{subject} and {secondary} are contrasted by {predicate_h}",
IntentTag.CORRECTION: "correction: {subject} {predicate_h} {obj}",
IntentTag.RECALL: "{subject} recalls {obj}",
IntentTag.VERIFICATION: "{subject} is verified as {obj}",
IntentTag.UNKNOWN: "{subject} {predicate_h} {obj}",
}
_PREDICATE_HUMANIZE: dict[str, str] = {
"is_defined_as": "is defined as",
"is_caused_by": "is caused by",
"has_steps": "has the following steps",
"contrasts_with": "contrasts with",
"corrects": "corrects",
"recalls": "recalls",
"is_verified_as": "is verified as",
"addresses": "addresses",
"defines": "defines",
"means": "means",
"grounds": "grounds",
"supports": "supports",
"causes": "causes",
"reveals": "reveals",
"precedes": "precedes",
"follows": "follows",
"belongs_to": "belongs to",
"answers": "answers",
}
def humanize_predicate(predicate: str) -> str:
return _PREDICATE_HUMANIZE.get(predicate, predicate.replace("_", " "))
def render_semantic(
intent: IntentTag,
subject: str,
predicate: str,
obj: str,
secondary: str | None = None,
) -> str:
"""Render a semantic surface from intent, subject, predicate, and object."""
template = _INTENT_TEMPLATES.get(intent, _INTENT_TEMPLATES[IntentTag.UNKNOWN])
predicate_h = humanize_predicate(predicate)
obj_display = obj if obj not in ("<pending>", "<prior>") else "..."
return template.format(
subject=subject,
predicate_h=predicate_h,
obj=obj_display,
secondary=secondary or obj_display,
)

View file

@ -24,6 +24,16 @@ _PREDICATE_DISPLAY: dict[str, str] = {
"recalls": "recalls",
"is_verified_as": "is verified as",
"addresses": "addresses",
"defines": "defines",
"means": "means",
"grounds": "grounds",
"supports": "supports",
"causes": "causes",
"reveals": "reveals",
"precedes": "precedes",
"follows": "follows",
"belongs_to": "belongs to",
"answers": "answers",
}

View file

@ -0,0 +1,222 @@
"""Tests for semantic realizer integration into the cognitive pipeline.
Verifies that the semantic realizer produces structurally better surfaces
from intent + proposition graph, and that the ChatResponse contract holds.
"""
from __future__ import annotations
import pytest
from generate.intent import IntentTag, classify_intent
from generate.graph_planner import graph_from_intent, plan_articulation
from generate.realizer import realize_semantic, realize_target, RealizedPlan
from generate.semantic_templates import humanize_predicate, render_semantic
# ---------------------------------------------------------------------------
# Unit tests: semantic_templates
# ---------------------------------------------------------------------------
class TestSemanticTemplates:
def test_humanize_known_predicate(self) -> None:
assert humanize_predicate("is_defined_as") == "is defined as"
assert humanize_predicate("contrasts_with") == "contrasts with"
assert humanize_predicate("defines") == "defines"
assert humanize_predicate("means") == "means"
assert humanize_predicate("grounds") == "grounds"
assert humanize_predicate("supports") == "supports"
assert humanize_predicate("corrects") == "corrects"
def test_humanize_unknown_predicate_uses_underscore_replacement(self) -> None:
assert humanize_predicate("some_new_predicate") == "some new predicate"
def test_render_definition(self) -> None:
surface = render_semantic(
intent=IntentTag.DEFINITION,
subject="truth",
predicate="is_defined_as",
obj="coherence",
)
assert "truth" in surface
assert "is defined as" in surface
assert "coherence" in surface
def test_render_comparison(self) -> None:
surface = render_semantic(
intent=IntentTag.COMPARISON,
subject="truth",
predicate="contrasts_with",
obj="light",
secondary="light",
)
assert "truth" in surface
assert "light" in surface
def test_render_correction(self) -> None:
surface = render_semantic(
intent=IntentTag.CORRECTION,
subject="correction",
predicate="corrects",
obj="reviewed repair",
)
assert "correction" in surface.lower()
def test_pending_obj_displays_as_ellipsis(self) -> None:
surface = render_semantic(
intent=IntentTag.DEFINITION,
subject="truth",
predicate="is_defined_as",
obj="<pending>",
)
assert "<pending>" not in surface
assert "..." in surface
# ---------------------------------------------------------------------------
# Unit tests: realize_semantic
# ---------------------------------------------------------------------------
class TestRealizeSemantic:
def test_definition_prompt_uses_semantic_realizer(self) -> None:
intent = classify_intent("What is truth?")
assert intent.tag is IntentTag.DEFINITION
graph = graph_from_intent(intent)
target = plan_articulation(graph)
plan = realize_semantic(target, graph)
assert isinstance(plan, RealizedPlan)
assert plan.surface
assert "truth" in plan.surface.lower()
assert "is defined as" in plan.surface.lower()
def test_comparison_prompt_mentions_both_terms(self) -> None:
intent = classify_intent("Compare truth and light")
assert intent.tag is IntentTag.COMPARISON
graph = graph_from_intent(intent)
target = plan_articulation(graph)
plan = realize_semantic(target, graph)
assert plan.surface
assert "truth" in plan.surface.lower()
assert "light" in plan.surface.lower()
def test_correction_prompt_uses_correction_template(self) -> None:
intent = classify_intent("No, correction means reviewed repair")
assert intent.tag is IntentTag.CORRECTION
graph = graph_from_intent(intent)
target = plan_articulation(graph)
plan = realize_semantic(target, graph)
assert plan.surface
assert "correction" in plan.surface.lower()
def test_cause_prompt(self) -> None:
intent = classify_intent("Why does light exist?")
assert intent.tag is IntentTag.CAUSE
graph = graph_from_intent(intent)
target = plan_articulation(graph)
plan = realize_semantic(target, graph)
assert plan.surface
assert "is caused by" in plan.surface.lower()
def test_empty_target_returns_empty_plan(self) -> None:
from generate.graph_planner import ArticulationTarget
plan = realize_semantic(
ArticulationTarget(steps=(), source_intent=IntentTag.UNKNOWN),
)
assert plan.surface == ""
assert plan.fragments == ()
def test_none_target_returns_empty_plan(self) -> None:
plan = realize_semantic(None)
assert plan.surface == ""
def test_seed_relation_predicates_humanize_deterministically(self) -> None:
seed_predicates = [
"defines", "means", "grounds", "supports",
"contrasts_with", "corrects", "causes", "reveals",
"precedes", "follows", "belongs_to", "answers",
]
for pred in seed_predicates:
h = humanize_predicate(pred)
assert "_" not in h, f"{pred} humanized to {h!r} still has underscores"
assert h == humanize_predicate(pred), f"{pred} not deterministic"
# ---------------------------------------------------------------------------
# Integration: realize_semantic vs realize_target produce valid plans
# ---------------------------------------------------------------------------
class TestSemanticVsRhetoricalRealization:
@pytest.mark.parametrize("prompt,expected_intent", [
("What is truth?", IntentTag.DEFINITION),
("Compare truth and light", IntentTag.COMPARISON),
("Why does light exist?", IntentTag.CAUSE),
("No, that's wrong", IntentTag.CORRECTION),
])
def test_both_realizers_produce_nonempty_surface(
self, prompt: str, expected_intent: IntentTag,
) -> None:
intent = classify_intent(prompt)
assert intent.tag is expected_intent
graph = graph_from_intent(intent)
target = plan_articulation(graph)
rhetorical = realize_target(target, graph)
semantic = realize_semantic(target, graph)
assert rhetorical.surface, f"rhetorical plan empty for {prompt!r}"
assert semantic.surface, f"semantic plan empty for {prompt!r}"
def test_semantic_surfaces_are_deterministic(self) -> None:
prompt = "What is truth?"
results = set()
for _ in range(5):
intent = classify_intent(prompt)
graph = graph_from_intent(intent)
target = plan_articulation(graph)
plan = realize_semantic(target, graph)
results.add(plan.surface)
assert len(results) == 1, f"Non-deterministic: {results}"
# ---------------------------------------------------------------------------
# Contract: ChatResponse shape still holds through the pipeline
# ---------------------------------------------------------------------------
class TestChatResponseContractStillHolds:
def test_chat_response_has_required_fields(self) -> None:
try:
from chat.runtime import ChatRuntime, ChatResponse
except Exception:
pytest.skip("ChatRuntime not importable in this environment")
runtime = ChatRuntime()
response = runtime.chat("What is truth?")
assert isinstance(response, ChatResponse)
assert isinstance(response.surface, str)
assert response.surface
assert isinstance(response.versor_condition, float)
assert response.versor_condition < 1e-6
assert response.proposition is not None
assert response.articulation is not None
assert isinstance(response.articulation_surface, str)
assert isinstance(response.walk_surface, str)
assert isinstance(response.dialogue_role, str)
assert isinstance(response.vault_hits, int)
def test_pipeline_result_uses_semantic_surface(self) -> None:
try:
from chat.runtime import ChatRuntime
from core.cognition.pipeline import CognitiveTurnPipeline
except Exception:
pytest.skip("ChatRuntime not importable in this environment")
runtime = ChatRuntime()
pipeline = CognitiveTurnPipeline(runtime)
result = pipeline.run("What is truth?")
assert result.surface
assert "truth" in result.surface.lower()
assert "is defined as" in result.surface.lower()
assert result.articulation_surface == result.surface
assert result.versor_condition < 1e-6
assert result.trace_hash