eq-layer 0.2.3__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {eq_layer-0.2.3 → eq_layer-0.2.4}/PKG-INFO +2 -2
- {eq_layer-0.2.3 → eq_layer-0.2.4}/README.md +1 -1
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/affect.py +9 -5
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/response_experiment.py +26 -2
- {eq_layer-0.2.3 → eq_layer-0.2.4}/pyproject.toml +1 -1
- {eq_layer-0.2.3 → eq_layer-0.2.4}/.gitignore +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/LICENSE +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/THIRD_PARTY_DATA.md +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/__init__.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/_deps.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/_version.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/ab_eval.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/actions.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/auditor.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/cli.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/coarse_discourse.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/data/intent_train.jsonl +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/dbdc3.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/dialogbank_iso.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/dialogue_reference.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/intent.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/intent_belief.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/interaction.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/model_state.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/policies.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/preferences.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/steer.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/textnorm.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/tracker.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/trained_affect.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/trained_breakdown.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/trained_disagreement.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/trained_intent.py +0 -0
- {eq_layer-0.2.3 → eq_layer-0.2.4}/eq_layer/trained_subtext.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: eq-layer
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Inspectable intent, affect, repair, uncertainty, and factored-action control for LLM responses
|
|
5
5
|
Project-URL: Homepage, https://github.com/Furox-Art/eq-layer
|
|
6
6
|
Project-URL: Repository, https://github.com/Furox-Art/eq-layer
|
|
@@ -51,7 +51,7 @@ So this repo does not retrain the model. It adds a layer:
|
|
|
51
51
|
1. **Affect state** — `{valence, arousal, escalation_delta, stance, subtext}` as an explicit intermediate representation, tracked across turns. `escalation_delta` is the point: snapshot classification cannot see a trend.
|
|
52
52
|
2. **Intent belief state** — the learned classifier preserves the full posterior over `action_request/status_check/explanation/question/statement/unknown` instead of collapsing uncertainty to one confidence number. A transparent one-step Bayes-risk router chooses whether to act or clarify from that distribution. This is POMDP-inspired uncertainty handling, not a full POMDP.
|
|
53
53
|
3. **Conversation health state** — `repair` identifies observable user→assistant misalignment and its target; `interaction_quality` tracks structural conversation failure separately from emotion using current score, delta, repeated failures, unresolved repair, and clarification load.
|
|
54
|
-
4. **Policy selection** — affect and intent jointly choose a response-policy family (`mirror`, `direct`, `repair`, `hold`, `boundary
|
|
54
|
+
4. **Policy selection** — affect and intent jointly choose a response-policy family (`mirror`, `validate_redirect`, `direct`, `ask`, `deflate`, `repair`, `hold`, `boundary`). Selection is a discrete decision, not generation.
|
|
55
55
|
5. **Factored action compilation** — the selected policy is split into orthogonal controls: `task_move`, `social_move`, `repair_move`, and `realization`. The task is driven primarily by user intent, so affective adaptation cannot silently replace what the user actually asked for.
|
|
56
56
|
6. **Steering** — the factored action, source policy, repair state, interaction quality, and canonical request are injected into the decode path so the control decision actually lands in the tokens.
|
|
57
57
|
7. **Structural response audit** — the generated response is checked against inspectable control contracts such as question budget, required clarification, unsupported privacy promises, unknown-stance admissions, repair repetition, and low-verbosity overruns. This is not a factual truth checker.
|
|
@@ -16,7 +16,7 @@ So this repo does not retrain the model. It adds a layer:
|
|
|
16
16
|
1. **Affect state** — `{valence, arousal, escalation_delta, stance, subtext}` as an explicit intermediate representation, tracked across turns. `escalation_delta` is the point: snapshot classification cannot see a trend.
|
|
17
17
|
2. **Intent belief state** — the learned classifier preserves the full posterior over `action_request/status_check/explanation/question/statement/unknown` instead of collapsing uncertainty to one confidence number. A transparent one-step Bayes-risk router chooses whether to act or clarify from that distribution. This is POMDP-inspired uncertainty handling, not a full POMDP.
|
|
18
18
|
3. **Conversation health state** — `repair` identifies observable user→assistant misalignment and its target; `interaction_quality` tracks structural conversation failure separately from emotion using current score, delta, repeated failures, unresolved repair, and clarification load.
|
|
19
|
-
4. **Policy selection** — affect and intent jointly choose a response-policy family (`mirror`, `direct`, `repair`, `hold`, `boundary
|
|
19
|
+
4. **Policy selection** — affect and intent jointly choose a response-policy family (`mirror`, `validate_redirect`, `direct`, `ask`, `deflate`, `repair`, `hold`, `boundary`). Selection is a discrete decision, not generation.
|
|
20
20
|
5. **Factored action compilation** — the selected policy is split into orthogonal controls: `task_move`, `social_move`, `repair_move`, and `realization`. The task is driven primarily by user intent, so affective adaptation cannot silently replace what the user actually asked for.
|
|
21
21
|
6. **Steering** — the factored action, source policy, repair state, interaction quality, and canonical request are injected into the decode path so the control decision actually lands in the tokens.
|
|
22
22
|
7. **Structural response audit** — the generated response is checked against inspectable control contracts such as question budget, required clarification, unsupported privacy promises, unknown-stance admissions, repair repetition, and low-verbosity overruns. This is not a factual truth checker.
|
|
@@ -93,7 +93,9 @@ class HeuristicAffect:
|
|
|
93
93
|
low = text.lower().strip()
|
|
94
94
|
hits = sum(1 for m in ESCALATION_MARKERS if m in low)
|
|
95
95
|
softens = sum(1 for s in SOFTENERS if s in low)
|
|
96
|
-
|
|
96
|
+
# Arousal is a [0, 1] scale: a softening-only turn must floor at 0, not
|
|
97
|
+
# go negative and hand the policy state an impossible value.
|
|
98
|
+
score = max(0.0, min(1.0, 0.25 + hits * 0.22 - softens * 0.12))
|
|
97
99
|
if "?" in low or "!" in low:
|
|
98
100
|
score = min(1.0, score + 0.1)
|
|
99
101
|
return round(score, 3)
|
|
@@ -110,11 +112,13 @@ class HeuristicAffect:
|
|
|
110
112
|
|
|
111
113
|
def _escalating(self, user_turns: list[str]) -> bool:
|
|
112
114
|
"""Three or more consecutive turns opening with a wh-word."""
|
|
113
|
-
|
|
115
|
+
# Blank turns are not wh-word turns. Counting them let three turns in
|
|
116
|
+
# which only one or two actually opened with a wh-word read as a
|
|
117
|
+
# three-turn interrogation run. Same filter as ``_demand_pattern``.
|
|
118
|
+
recent = [t for t in user_turns[-3:] if t.strip()]
|
|
119
|
+
if len(recent) < 3:
|
|
114
120
|
return False
|
|
115
|
-
return all(
|
|
116
|
-
self._first_word(t) in QUESTION_OPENERS for t in user_turns[-3:] if t.strip()
|
|
117
|
-
)
|
|
121
|
+
return all(self._first_word(t) in QUESTION_OPENERS for t in recent)
|
|
118
122
|
|
|
119
123
|
def _first_word(self, text: str) -> str:
|
|
120
124
|
"""First token with punctuation stripped. 'Neden?' must match 'neden',
|
|
@@ -9,7 +9,7 @@ import subprocess
|
|
|
9
9
|
import threading
|
|
10
10
|
from dataclasses import dataclass, field
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import Iterable
|
|
12
|
+
from typing import Callable, Iterable
|
|
13
13
|
|
|
14
14
|
from .auditor import audit_from_generation_metadata, regeneration_instruction
|
|
15
15
|
from .policies import Selector
|
|
@@ -198,6 +198,17 @@ def stable_case_seed(global_seed: int, case_id: str) -> int:
|
|
|
198
198
|
return int.from_bytes(digest[:4], "big", signed=False)
|
|
199
199
|
|
|
200
200
|
|
|
201
|
+
# The experiment's own clock. Reproducibility is a property of the artifact, so
|
|
202
|
+
# the wall clock must never leak into a generated record: the default is a
|
|
203
|
+
# fixed epoch and callers who need a real timestamp inject their own clock.
|
|
204
|
+
DEFAULT_EXPERIMENT_CLOCK_EPOCH = 0.0
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _fixed_experiment_clock() -> float:
|
|
208
|
+
"""Default experiment clock: a constant, so runs differ only by content."""
|
|
209
|
+
return DEFAULT_EXPERIMENT_CLOCK_EPOCH
|
|
210
|
+
|
|
211
|
+
|
|
201
212
|
def file_sha256(path: str | Path) -> str:
|
|
202
213
|
digest = hashlib.sha256()
|
|
203
214
|
with open(path, "rb") as fh:
|
|
@@ -654,7 +665,18 @@ def generate_pairs(
|
|
|
654
665
|
auditor_regenerations: int = 0,
|
|
655
666
|
length_matched_arm: bool = False,
|
|
656
667
|
length_profiles: tuple[str, ...] | None = None,
|
|
668
|
+
rng: random.Random | None = None,
|
|
669
|
+
clock: Callable[[], float] | None = None,
|
|
657
670
|
) -> list[dict]:
|
|
671
|
+
"""Generate the same-model baseline/EQ pairs for one experiment run.
|
|
672
|
+
|
|
673
|
+
Generation is deterministic: the only randomness is the control-arm
|
|
674
|
+
shuffling, seeded from ``seed``, and it is quarantined behind the injectable
|
|
675
|
+
``rng`` so a caller can supply the same generator without re-deriving it
|
|
676
|
+
from the seed. Any timestamp is quarantined behind ``clock``, whose default
|
|
677
|
+
is a fixed epoch, so the same experiment plus the same fixture serialises
|
|
678
|
+
to identical bytes.
|
|
679
|
+
"""
|
|
658
680
|
if auditor_regenerations not in {0, 1}:
|
|
659
681
|
raise ValueError("auditor_regenerations must be 0 or 1.")
|
|
660
682
|
profiles = tuple(length_profiles or ())
|
|
@@ -665,7 +687,8 @@ def generate_pairs(
|
|
|
665
687
|
f"{sorted(LENGTH_CONTROL_PROFILES)}"
|
|
666
688
|
)
|
|
667
689
|
|
|
668
|
-
rng = random.Random(seed)
|
|
690
|
+
rng = rng if rng is not None else random.Random(seed)
|
|
691
|
+
clock = clock if clock is not None else _fixed_experiment_clock
|
|
669
692
|
output: list[dict] = []
|
|
670
693
|
seen: set[str] = set()
|
|
671
694
|
|
|
@@ -769,6 +792,7 @@ def generate_pairs(
|
|
|
769
792
|
"model_id": model.model_id,
|
|
770
793
|
"temperature": model.temperature,
|
|
771
794
|
"pair_seed": pair_seed,
|
|
795
|
+
"generated_at_epoch": round(float(clock()), 6),
|
|
772
796
|
"order": ["baseline", "eq", *controls],
|
|
773
797
|
"same_model_both_arms": True,
|
|
774
798
|
"annotations_used": bool(annotated),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|