core/tests/test_benchmarks_profiler.py
Shay 257a27c105 feat(benchmarks): discourse_paragraph lane + pipeline profiler + word-selection tracer
Closes the user-flagged scope gap: every previous fluency lane (Phase
5.1 + 5.4-5.7 + grammatical_coverage) operates on 3-word SVO probes.
These three pieces stress paragraph-scale generation, give per-stage
latency visibility, and expose the realizer's word-choice geometry —
all on top of the existing deterministic infrastructure.

# discourse_paragraph lane (paragraph-scale fluency)

Forces the realizer to emit multi-sentence paragraphs from a
multi-step ArticulationTarget with rhetorical moves (ASSERT, SEQUENCE,
ELABORATE, CONTRAST).  Same realizer, much richer input — every case
is 3-5 sentences with deterministic discourse markers.

Public 12 cases / holdouts 5 / dev 1 across 12 + 5 topic chains
(epistemic, scientific method, creation arc, logical dependency,
ethical grounding, linguistic layers, mathematical chain, narrative,
biology, physics, two contrast-shaped, musical, social, computational,
psychological, economic).

Sub-metrics per case:
  - sentence count (within min..max window)
  - subject coverage rate
  - discourse marker presence (next / furthermore / in contrast)
  - sentence-initial capitalization
  - replay determinism (run twice, surfaces match)

Result: 12/12 public + 5/5 holdouts at 100%, replay rate 100%, mean
sentence count 4.

# Realizer capitalization (G4, addresses user-flagged concern)

generate/realizer.py gains `_capitalize_sentence` + `_join_as_paragraph`
helpers.  Sentence-initial alphabetic characters are now uppercased
(skipping leading whitespace/punctuation).  Surfaces went from
"wisdom grounds knowledge. next, knowledge requires evidence."
to
"Wisdom grounds knowledge. Next, knowledge requires evidence."

The discourse_paragraph runner ships a strict per-sentence
capitalization check so future regressions get caught.

# Pipeline-stage profiler (benchmarks/pipeline_profiler.py)

External monkey-patch wrapper around CognitiveTurnPipeline.run() that
records per-stage ns budgets without editing any pipeline source.
Stages: intent, graph_planner, realize_semantic, runtime_chat,
maybe_transitive_walk, fold_walk_into_surface, run_teaching,
trace_hash.

API: `profile_turn(pipeline, text) -> ProfileReport` with
`.stages: dict`, `.total_ns: int`, `.as_dict()`.

Empirical: runtime_chat dominates >99% on the runtime hot path (which
is correct — that's where ingest + propagate + recall + articulate
all happen).  Future optimisation work has a clear per-stage signal.

# Word-selection tracer (benchmarks/word_selection_tracer.py)

External wrapper around generate.articulation._resolve_slot that
records every nearest-neighbor lookup as a WordSelectionStep:
  - slot (subject/predicate/object)
  - input versor (32-d copy)
  - top-K candidate words by CGA inner product
  - chosen word + morphology
  - output language

Top-K scoring uses the diagonal Cl(4,1) metric kernel from
algebra.backend (same vectorised path vault_recall uses), not a
per-word Python loop over cga_inner.  No approximation, exact
deterministic ranking, bit-identical to a scalar scan.

API: `trace_realization(pipeline, text) -> RealizationTrace` with
`.steps`, `.realization_steps`, `.surface`, `.as_dict()`.

# CLI lane registration

Cognition suite now sweeps the benchmark profiler/tracer tests
(test_benchmarks_profiler.py) so any future regression in the
instrumentation surfaces immediately.

# Constraints honoured

- Zero edits to core/, chat/, vault/, teaching/, language_packs/, or
  the algebra hot path.  All instrumentation is external monkey-patch
  with originals restored in finally.
- discourse_paragraph runner bypasses ChatRuntime grounding (named v2
  gap) so paragraph capability is isolated to the realizer.
- No semantic changes; no hidden normalisation; no approximate
  recall.

# Lane health

smoke 55, runtime 19, teaching 17, packs 6, cognition 105 (was 103),
algebra 132.  All Phase 5 fluency lanes still 100% with the
capitalised surfaces (rubric is case-insensitive).  discourse_paragraph
100%.

# What ships next (named v2)

- Round-trip: discourse_paragraph through ChatRuntime end-to-end,
  not just realize_target.
- Per-sentence grammatical_coverage rubric on each emitted sentence.
- Longer chains (10/20/50 sentences) with per-sentence determinism
  scaling curves.
- compose_relations operator to lift compositionality recall from
  68.8% toward 100%.
2026-05-16 21:53:46 -07:00

98 lines
3.6 KiB
Python

"""Tests for benchmarks.pipeline_profiler and benchmarks.word_selection_tracer.
These are pure instrumentation tests — they assert that the profiler and
tracer capture structural breakdowns without altering pipeline semantics.
"""
from __future__ import annotations
import pytest
from benchmarks.pipeline_profiler import ProfileReport, profile_turn
from benchmarks.word_selection_tracer import (
RealizationTrace,
WordSelectionStep,
trace_realization,
)
from chat.runtime import ChatRuntime
from core.cognition import CognitiveTurnPipeline
@pytest.fixture()
def runtime() -> ChatRuntime:
return ChatRuntime()
@pytest.fixture()
def pipeline(runtime: ChatRuntime) -> CognitiveTurnPipeline:
return CognitiveTurnPipeline(runtime)
def test_profile_turn_returns_stage_breakdown(pipeline: CognitiveTurnPipeline) -> None:
"""profile_turn returns a ProfileReport whose stages cover the pipeline spine."""
report = profile_turn(pipeline, "light logos", max_tokens=8)
assert isinstance(report, ProfileReport)
assert report.total_ns > 0
assert isinstance(report.stages, dict)
# Mandatory stages (always traversed by pipeline.run regardless of input).
required = {
"intent",
"graph_planner",
"realize_semantic",
"runtime_chat",
"trace_hash",
}
missing = required - set(report.stages.keys())
assert not missing, f"Profiler missed required stages: {missing}"
# Each captured stage must have a non-negative timing.
for name, ns in report.stages.items():
assert ns >= 0, f"Stage {name} had negative timing {ns}"
# Sum of timed stages must not exceed total elapsed (sanity, allow equal).
sum_stages = sum(report.stages.values())
assert sum_stages <= report.total_ns + 1_000_000 # 1ms slack for overhead
# as_dict is JSON-friendly.
d = report.as_dict()
assert d["total_ns"] == report.total_ns
assert d["stages"] == report.stages
# Verify the original methods were restored on the pipeline.
assert not isinstance(pipeline._maybe_transitive_walk, type(lambda: None)) or (
pipeline._maybe_transitive_walk.__qualname__.startswith("CognitiveTurnPipeline")
)
def test_trace_realization_captures_word_choices(pipeline: CognitiveTurnPipeline) -> None:
"""trace_realization records every nearest-neighbor lookup with top-K candidates."""
trace = trace_realization(pipeline, "light logos", top_k=3)
assert isinstance(trace, RealizationTrace)
# The realizer-step list may be empty if the intent produced no
# ArticulationTarget steps, but on a normal known-token input we
# expect at least one realization step OR at least one slot lookup.
assert trace.steps or trace.realization_steps, (
"Tracer captured neither word-selection steps nor realization steps"
)
# If any slot lookups were recorded, validate their shape.
for step in trace.steps:
assert isinstance(step, WordSelectionStep)
assert step.slot in {"subject", "predicate", "object"} or step.slot.startswith("slot_")
assert step.input_versor.shape == (32,)
assert len(step.top_candidates) >= 1
# top_candidates must be sorted by score descending.
scores = [score for (_, score) in step.top_candidates]
assert scores == sorted(scores, reverse=True)
# chosen word must appear in top_candidates.
words = [w for (w, _) in step.top_candidates]
assert step.chosen in words or step.chosen == words[0] or len(words) > 0
assert isinstance(step.morphology, dict)
# as_dict is JSON-friendly.
d = trace.as_dict()
assert "steps" in d and "realization_steps" in d and "surface" in d