core/session/correction.py
Shay c9a644e496
feat(dialogue-fluency): wire multi-turn dialogue runtime
Adds referent tracking, session graph traversal, unknown-domain gating, correction propagation, compositional surface assembly, and regression coverage.

Follow-up fixes included before merge:
- split probe/commit/finalize turn flow so unknown-domain checks run before current-query vault writes
- record real input tokens and input versors for sync and async session paths
- return true graph distances from backward walks and consume them in correction decay
- synchronize corrected graph outputs into vault-backed recall and live referent state
- regenerate correction responses from corrected context rather than correction text
- keep coreference pronouns lowercase in question bodies
- centralize elaboration-string construction to avoid plan/surface drift
- add targeted dialogue fluency regression tests
2026-05-15 21:05:59 -07:00

100 lines
2.8 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
import numpy as np
from algebra.backend import cga_inner
if TYPE_CHECKING:
from session.graph import SessionGraph, TurnNode
DECAY_BASE: float = 0.6
MIN_ALIGNMENT: float = 0.05
@dataclass(frozen=True, slots=True)
class CorrectionRecord:
turn_idx: int
graph_distance: int
alignment: float
decay: float
blend_weight: float
old_versor: np.ndarray
new_versor: np.ndarray
@dataclass(frozen=True, slots=True)
class CorrectionResult:
correction_versor: np.ndarray
records: tuple[CorrectionRecord, ...]
turns_affected: int
turns_skipped: int
class CorrectionPass:
def __init__(
self,
decay_base: float = DECAY_BASE,
min_alignment: float = MIN_ALIGNMENT,
max_depth: int = 16,
) -> None:
self._decay_base = decay_base
self._min_alignment = min_alignment
self._max_depth = max_depth
def apply(
self,
graph: "SessionGraph",
correction_versor: np.ndarray,
from_turn: int = -1,
) -> CorrectionResult:
n_turns = len(graph)
C = np.asarray(correction_versor, dtype=np.float32)
if n_turns == 0:
return CorrectionResult(C, (), 0, 0)
start = from_turn if from_turn >= 0 else n_turns - 1
start = min(start, n_turns - 1)
start_node = graph.node_at(start)
prior_nodes_with_dist = graph.backward_walk(start, max_depth=self._max_depth)
nodes_with_distance: list[tuple[int, "TurnNode"]] = [(0, start_node)] + prior_nodes_with_dist
records: list[CorrectionRecord] = []
skipped = 0
for dist, node in nodes_with_distance:
V = node.output_versor
alignment = abs(float(cga_inner(V, C)))
if alignment < self._min_alignment:
skipped += 1
continue
decay = self._decay_base ** dist
blend = alignment * decay
new_V = V + blend * (C - V)
norm = float(np.linalg.norm(new_V))
old_norm = float(np.linalg.norm(V))
if norm > 1e-8:
new_V = new_V / norm * old_norm
updated = graph.update_output(node.turn_idx, new_V)
records.append(
CorrectionRecord(
turn_idx=node.turn_idx,
graph_distance=dist,
alignment=alignment,
decay=decay,
blend_weight=blend,
old_versor=V.copy(),
new_versor=updated.output_versor.copy(),
)
)
return CorrectionResult(
correction_versor=C,
records=tuple(records),
turns_affected=len(records),
turns_skipped=skipped,
)