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%.
273 lines
9 KiB
Python
273 lines
9 KiB
Python
"""Generate cases for the discourse_paragraph benchmark lane.
|
|
|
|
Tests that the realizer can produce **multi-sentence paragraph-scale
|
|
output** from chained propositions, given a multi-step
|
|
ArticulationTarget with rhetorical moves (SEQUENCE, ELABORATE,
|
|
CONTRAST). Each case stresses paragraph length, subject coverage,
|
|
discourse-marker presence, and deterministic replay.
|
|
|
|
Each case carries:
|
|
- a graph of N ≥ 3 nodes (subject-predicate-object triples)
|
|
- an ordered move list ([ASSERT, SEQUENCE, ELABORATE, ...])
|
|
- acceptance constraints (min_sentences, must_contain_subjects,
|
|
discourse_markers)
|
|
|
|
Topics are designed to be **structurally rich** — every case is more
|
|
than a 3-word SVO probe.
|
|
|
|
Run:
|
|
.venv/bin/python scripts/generate_discourse_paragraph.py
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
|
|
# Each topic: ordered triples + ordered rhetorical moves matching length.
|
|
# Moves: ASSERT (open), SEQUENCE (next step), ELABORATE (furthermore),
|
|
# CONTRAST (in contrast), CORRECT (correction). See
|
|
# generate.templates._MOVE_TEMPLATES for emitted discourse markers.
|
|
PUBLIC_TOPICS: list[dict] = [
|
|
{
|
|
"topic": "epistemic_chain",
|
|
"triples": [
|
|
("wisdom", "grounds", "knowledge"),
|
|
("knowledge", "requires", "evidence"),
|
|
("evidence", "supports", "truth"),
|
|
("truth", "reveals", "reality"),
|
|
],
|
|
"moves": ["ASSERT", "SEQUENCE", "ELABORATE", "SEQUENCE"],
|
|
},
|
|
{
|
|
"topic": "scientific_method",
|
|
"triples": [
|
|
("observation", "grounds", "hypothesis"),
|
|
("hypothesis", "implies", "prediction"),
|
|
("prediction", "follows", "experiment"),
|
|
("experiment", "supports", "theory"),
|
|
("theory", "entails", "explanation"),
|
|
],
|
|
"moves": ["ASSERT", "ELABORATE", "SEQUENCE", "ELABORATE", "SEQUENCE"],
|
|
},
|
|
{
|
|
"topic": "creation_arc",
|
|
"triples": [
|
|
("light", "precedes", "form"),
|
|
("form", "grounds", "matter"),
|
|
("matter", "supports", "structure"),
|
|
("structure", "reveals", "order"),
|
|
],
|
|
"moves": ["ASSERT", "SEQUENCE", "ELABORATE", "SEQUENCE"],
|
|
},
|
|
{
|
|
"topic": "logical_dependency",
|
|
"triples": [
|
|
("premise", "supports", "conclusion"),
|
|
("conclusion", "requires", "validity"),
|
|
("validity", "entails", "soundness"),
|
|
],
|
|
"moves": ["ASSERT", "SEQUENCE", "ELABORATE"],
|
|
},
|
|
{
|
|
"topic": "ethical_grounding",
|
|
"triples": [
|
|
("virtue", "grounds", "action"),
|
|
("action", "requires", "intention"),
|
|
("intention", "supports", "consequence"),
|
|
("consequence", "reveals", "character"),
|
|
],
|
|
"moves": ["ASSERT", "SEQUENCE", "ELABORATE", "SEQUENCE"],
|
|
},
|
|
{
|
|
"topic": "linguistic_layers",
|
|
"triples": [
|
|
("sound", "grounds", "phoneme"),
|
|
("phoneme", "supports", "morpheme"),
|
|
("morpheme", "builds", "word"),
|
|
("word", "composes", "sentence"),
|
|
("sentence", "conveys", "meaning"),
|
|
],
|
|
"moves": ["ASSERT", "SEQUENCE", "ELABORATE", "SEQUENCE", "ELABORATE"],
|
|
},
|
|
{
|
|
"topic": "mathematical_chain",
|
|
"triples": [
|
|
("axiom", "grounds", "theorem"),
|
|
("theorem", "entails", "corollary"),
|
|
("corollary", "supports", "application"),
|
|
("application", "yields", "insight"),
|
|
],
|
|
"moves": ["ASSERT", "ELABORATE", "SEQUENCE", "SEQUENCE"],
|
|
},
|
|
{
|
|
"topic": "narrative_progression",
|
|
"triples": [
|
|
("conflict", "drives", "tension"),
|
|
("tension", "precedes", "climax"),
|
|
("climax", "yields", "resolution"),
|
|
("resolution", "reveals", "theme"),
|
|
],
|
|
"moves": ["ASSERT", "SEQUENCE", "ELABORATE", "SEQUENCE"],
|
|
},
|
|
{
|
|
"topic": "biological_hierarchy",
|
|
"triples": [
|
|
("gene", "encodes", "protein"),
|
|
("protein", "builds", "cell"),
|
|
("cell", "composes", "tissue"),
|
|
("tissue", "forms", "organ"),
|
|
("organ", "supports", "organism"),
|
|
],
|
|
"moves": ["ASSERT", "SEQUENCE", "ELABORATE", "SEQUENCE", "ELABORATE"],
|
|
},
|
|
{
|
|
"topic": "physical_causation",
|
|
"triples": [
|
|
("force", "drives", "motion"),
|
|
("motion", "transfers", "energy"),
|
|
("energy", "yields", "heat"),
|
|
("heat", "raises", "temperature"),
|
|
],
|
|
"moves": ["ASSERT", "ELABORATE", "SEQUENCE", "SEQUENCE"],
|
|
},
|
|
# Contrast-shaped cases — exercises the "in contrast" template.
|
|
{
|
|
"topic": "contrastive_definitions",
|
|
"triples": [
|
|
("knowledge", "requires", "evidence"),
|
|
("belief", "requires", "trust"),
|
|
("wisdom", "grounds", "judgment"),
|
|
],
|
|
"moves": ["ASSERT", "CONTRAST", "ELABORATE"],
|
|
},
|
|
{
|
|
"topic": "method_contrast",
|
|
"triples": [
|
|
("deduction", "yields", "certainty"),
|
|
("induction", "yields", "probability"),
|
|
("abduction", "yields", "explanation"),
|
|
],
|
|
"moves": ["ASSERT", "CONTRAST", "ELABORATE"],
|
|
},
|
|
]
|
|
|
|
|
|
HOLDOUT_TOPICS: list[dict] = [
|
|
{
|
|
"topic": "musical_construction",
|
|
"triples": [
|
|
("note", "composes", "chord"),
|
|
("chord", "supports", "harmony"),
|
|
("harmony", "yields", "phrase"),
|
|
("phrase", "builds", "melody"),
|
|
],
|
|
"moves": ["ASSERT", "SEQUENCE", "ELABORATE", "SEQUENCE"],
|
|
},
|
|
{
|
|
"topic": "social_structure",
|
|
"triples": [
|
|
("custom", "grounds", "tradition"),
|
|
("tradition", "supports", "institution"),
|
|
("institution", "shapes", "society"),
|
|
("society", "reveals", "culture"),
|
|
],
|
|
"moves": ["ASSERT", "SEQUENCE", "ELABORATE", "SEQUENCE"],
|
|
},
|
|
{
|
|
"topic": "computational_pipeline",
|
|
"triples": [
|
|
("input", "drives", "computation"),
|
|
("computation", "yields", "output"),
|
|
("output", "supports", "decision"),
|
|
],
|
|
"moves": ["ASSERT", "SEQUENCE", "ELABORATE"],
|
|
},
|
|
{
|
|
"topic": "psychological_development",
|
|
"triples": [
|
|
("sensation", "grounds", "perception"),
|
|
("perception", "supports", "memory"),
|
|
("memory", "yields", "learning"),
|
|
("learning", "shapes", "behavior"),
|
|
("behavior", "reveals", "character"),
|
|
],
|
|
"moves": ["ASSERT", "SEQUENCE", "ELABORATE", "SEQUENCE", "ELABORATE"],
|
|
},
|
|
{
|
|
"topic": "economic_flow",
|
|
"triples": [
|
|
("labor", "yields", "value"),
|
|
("value", "supports", "exchange"),
|
|
("exchange", "drives", "growth"),
|
|
],
|
|
"moves": ["ASSERT", "SEQUENCE", "ELABORATE"],
|
|
},
|
|
]
|
|
|
|
|
|
# Common discourse markers the realizer emits per RhetoricalMove
|
|
# (see generate.templates._MOVE_TEMPLATES).
|
|
_MARKERS_BY_MOVE: dict[str, str] = {
|
|
"ASSERT": "",
|
|
"ELABORATE": "furthermore",
|
|
"CONTRAST": "in contrast",
|
|
"SEQUENCE": "next",
|
|
"CORRECT": "correction:",
|
|
}
|
|
|
|
|
|
def _build_case(prefix: str, idx: int, topic: dict) -> dict:
|
|
triples = topic["triples"]
|
|
moves = topic["moves"]
|
|
assert len(triples) == len(moves), f"length mismatch in {topic['topic']}"
|
|
|
|
nodes = [
|
|
{
|
|
"node_id": f"n{i+1}",
|
|
"subject": s,
|
|
"predicate": p,
|
|
"obj": o,
|
|
}
|
|
for i, (s, p, o) in enumerate(triples)
|
|
]
|
|
steps = [
|
|
{"node_id": f"n{i+1}", "move": m}
|
|
for i, m in enumerate(moves)
|
|
]
|
|
must_contain_subjects = [t[0] for t in triples]
|
|
discourse_markers = sorted(
|
|
{_MARKERS_BY_MOVE[m] for m in moves if _MARKERS_BY_MOVE[m]}
|
|
)
|
|
|
|
return {
|
|
"id": f"{prefix}_{idx:03d}",
|
|
"topic": topic["topic"],
|
|
"graph": {"nodes": nodes, "edges": []},
|
|
"steps": steps,
|
|
"min_sentences": len(triples),
|
|
"must_contain_subjects": must_contain_subjects,
|
|
"discourse_markers": discourse_markers,
|
|
"max_sentences": len(triples) + 2, # tolerate small over-runs from
|
|
# downstream wrapping
|
|
}
|
|
|
|
|
|
def _emit(prefix: str, topics: list[dict], out_path: Path) -> int:
|
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
lines = [
|
|
json.dumps(_build_case(prefix, i + 1, t), ensure_ascii=False)
|
|
for i, t in enumerate(topics)
|
|
]
|
|
out_path.write_text("\n".join(lines) + "\n")
|
|
return len(lines)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
root = Path(__file__).resolve().parent.parent
|
|
lane = root / "evals" / "discourse_paragraph"
|
|
n_pub = _emit("DP-PUB", PUBLIC_TOPICS, lane / "public" / "v1" / "cases.jsonl")
|
|
n_hold = _emit("DP-HOLD", HOLDOUT_TOPICS, lane / "holdouts" / "v1" / "cases.jsonl")
|
|
n_dev = _emit("DP-DEV", PUBLIC_TOPICS[:1], lane / "dev" / "cases.jsonl")
|
|
print(f"discourse_paragraph public={n_pub} holdouts={n_hold} dev={n_dev}")
|