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:
parent
33aed4e550
commit
a7febd48ef
6 changed files with 387 additions and 6 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
# 2–7. 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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
73
generate/semantic_templates.py
Normal file
73
generate/semantic_templates.py
Normal 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,
|
||||
)
|
||||
|
|
@ -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",
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
222
tests/test_semantic_realizer_integration.py
Normal file
222
tests/test_semantic_realizer_integration.py
Normal 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
|
||||
Loading…
Reference in a new issue