eq-layer 0.2.0__tar.gz → 0.2.2__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.0 → eq_layer-0.2.2}/PKG-INFO +6 -2
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/__init__.py +9 -3
- eq_layer-0.2.2/eq_layer/_version.py +57 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/auditor.py +2 -4
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/cli.py +3 -1
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/dialogue_reference.py +5 -2
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/intent.py +2 -1
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/interaction.py +1 -4
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/preferences.py +2 -4
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/response_experiment.py +87 -15
- eq_layer-0.2.2/eq_layer/textnorm.py +42 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/pyproject.toml +10 -1
- {eq_layer-0.2.0 → eq_layer-0.2.2}/.gitignore +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/LICENSE +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/README.md +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/THIRD_PARTY_DATA.md +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/ab_eval.py +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/actions.py +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/affect.py +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/coarse_discourse.py +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/data/intent_train.jsonl +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/dbdc3.py +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/dialogbank_iso.py +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/intent_belief.py +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/policies.py +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/steer.py +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/tracker.py +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/trained_affect.py +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/trained_breakdown.py +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/trained_disagreement.py +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/trained_intent.py +0 -0
- {eq_layer-0.2.0 → eq_layer-0.2.2}/eq_layer/trained_subtext.py +0 -0
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: eq-layer
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
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
|
|
7
|
+
Project-URL: Changelog, https://github.com/Furox-Art/eq-layer/blob/main/CHANGELOG.md
|
|
7
8
|
Project-URL: Issues, https://github.com/Furox-Art/eq-layer/issues
|
|
9
|
+
Project-URL: Security, https://github.com/Furox-Art/eq-layer/blob/main/SECURITY.md
|
|
8
10
|
Author: Furox-Art
|
|
9
11
|
License: MIT
|
|
10
12
|
License-File: LICENSE
|
|
11
|
-
Keywords: affective-computing,agent-skills,agents,dialogue-management,intent-classification,llm,llm-steering
|
|
13
|
+
Keywords: affective-computing,agent-skills,agents,dialogue-management,dialogue-policy,intent-classification,llm,llm-steering,response-quality
|
|
12
14
|
Classifier: Development Status :: 3 - Alpha
|
|
13
15
|
Classifier: Intended Audience :: Developers
|
|
14
16
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -17,6 +19,8 @@ Classifier: Programming Language :: Python :: 3
|
|
|
17
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
24
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
25
|
Requires-Python: >=3.10
|
|
22
26
|
Provides-Extra: dev
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
from ._version import __version__
|
|
3
2
|
from .affect import AffectAdapter, HeuristicAffect, load_cases
|
|
4
3
|
from .auditor import AuditContext, AuditIssue, ResponseAudit, ResponseAuditor, audit_from_generation_metadata
|
|
5
4
|
from .actions import FactoredAction, RealizationControls, compose_action
|
|
@@ -8,7 +7,13 @@ from .intent_belief import IntentBelief, IntentRiskDecision, decide_intent_actio
|
|
|
8
7
|
from .interaction import InteractionQualityState, RepairState, infer_interaction_quality, infer_repair_state
|
|
9
8
|
from .policies import POLICIES, REGISTRY, SUBTEXTS, AffectState, Policy, Register, Selector
|
|
10
9
|
from .preferences import SessionPreferences, infer_session_preferences
|
|
11
|
-
from .response_experiment import
|
|
10
|
+
from .response_experiment import (
|
|
11
|
+
CommandModel,
|
|
12
|
+
FullEQPipeline,
|
|
13
|
+
ModelCommandTimeout,
|
|
14
|
+
generate_pairs,
|
|
15
|
+
validate_experiment_cases,
|
|
16
|
+
)
|
|
12
17
|
from .steer import Steer, aggregate, score_case
|
|
13
18
|
from .trained_affect import DimensionalAffect, TrainedAffect
|
|
14
19
|
from .trained_intent import TrainedIntent
|
|
@@ -62,6 +67,7 @@ __all__ = [
|
|
|
62
67
|
"AnnotationStanceResolver",
|
|
63
68
|
"CommandModel",
|
|
64
69
|
"FullEQPipeline",
|
|
70
|
+
"ModelCommandTimeout",
|
|
65
71
|
"generate_pairs",
|
|
66
72
|
"validate_experiment_cases",
|
|
67
73
|
"score_case",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Single source of truth for the EQ-Layer Python package version.
|
|
2
|
+
|
|
3
|
+
The editable location is the ``version`` field of the ``[project]`` table in
|
|
4
|
+
``pyproject.toml``. When the distribution is installed, ``importlib.metadata``
|
|
5
|
+
is authoritative; from a source checkout the same field is read from the
|
|
6
|
+
nearest ``pyproject.toml`` so both paths report the identical string instead
|
|
7
|
+
of a hardcoded copy that drifts (audit M4).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
DISTRIBUTION = "eq-layer"
|
|
17
|
+
|
|
18
|
+
_PROJECT_HEADER = re.compile(r"(?m)^\[project\]\s*$")
|
|
19
|
+
_NEXT_TABLE = re.compile(r"(?m)^\[")
|
|
20
|
+
_VERSION_KEY = re.compile(r'(?m)^version\s*=\s*"([^"]+)"')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _project_table(pyproject_text: str) -> str:
|
|
24
|
+
"""Return the ``[project]`` table body, or "" when absent.
|
|
25
|
+
|
|
26
|
+
Scoping the search matters: an unrelated ``version =`` key under another
|
|
27
|
+
table must not win over the package version.
|
|
28
|
+
"""
|
|
29
|
+
header = _PROJECT_HEADER.search(pyproject_text)
|
|
30
|
+
if header is None:
|
|
31
|
+
return ""
|
|
32
|
+
rest = pyproject_text[header.end() :]
|
|
33
|
+
next_table = _NEXT_TABLE.search(rest)
|
|
34
|
+
return rest[: next_table.start()] if next_table else rest
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _source_checkout_version() -> str | None:
|
|
38
|
+
here = Path(__file__).resolve()
|
|
39
|
+
for parent in here.parents:
|
|
40
|
+
pyproject = parent / "pyproject.toml"
|
|
41
|
+
if not pyproject.is_file():
|
|
42
|
+
continue
|
|
43
|
+
match = _VERSION_KEY.search(_project_table(pyproject.read_text(encoding="utf-8")))
|
|
44
|
+
if match:
|
|
45
|
+
return match.group(1)
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def detect_version() -> str:
|
|
50
|
+
try:
|
|
51
|
+
return version(DISTRIBUTION)
|
|
52
|
+
except PackageNotFoundError:
|
|
53
|
+
# Source checkout without an installed distribution.
|
|
54
|
+
return _source_checkout_version() or "0.0.0+unknown"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
__version__ = detect_version()
|
|
@@ -4,6 +4,8 @@ import re
|
|
|
4
4
|
from dataclasses import asdict, dataclass
|
|
5
5
|
from difflib import SequenceMatcher
|
|
6
6
|
|
|
7
|
+
from .textnorm import normalise_light as _normalise
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
ABSOLUTE_PRIVACY_PROMISES = (
|
|
9
11
|
"kimseye söylemem",
|
|
@@ -77,10 +79,6 @@ class ResponseAudit:
|
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
|
|
80
|
-
def _normalise(text: str) -> str:
|
|
81
|
-
return " ".join(text.lower().split())
|
|
82
|
-
|
|
83
|
-
|
|
84
82
|
def _sentence_starts(text: str) -> tuple[str, ...]:
|
|
85
83
|
parts = re.split(r"(?<=[.!?])\s+|\n+", _normalise(text))
|
|
86
84
|
return tuple(part.strip(" -—:;,.!?") for part in parts if part.strip())
|
|
@@ -125,9 +125,11 @@ def _route(args: argparse.Namespace) -> int:
|
|
|
125
125
|
|
|
126
126
|
|
|
127
127
|
def _doctor() -> int:
|
|
128
|
+
from . import __version__
|
|
129
|
+
|
|
128
130
|
payload = {
|
|
129
131
|
"package": "eq-layer",
|
|
130
|
-
"version":
|
|
132
|
+
"version": __version__,
|
|
131
133
|
"python": sys.version.split()[0],
|
|
132
134
|
"ml_extra_available": True,
|
|
133
135
|
}
|
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import re
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
|
|
6
|
+
from .textnorm import normalise_plain
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
@dataclass(frozen=True)
|
|
8
10
|
class GoalCandidate:
|
|
@@ -106,8 +108,9 @@ _USER_GOAL_MARKERS = (
|
|
|
106
108
|
|
|
107
109
|
|
|
108
110
|
def _normalise(text: str) -> str:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
# Drop the Turkish combining dot above so dotted and dotless spellings of
|
|
112
|
+
# the same word compare equal before the shared punctuation pass.
|
|
113
|
+
return normalise_plain(text, drop_combining_dot_above=True)
|
|
111
114
|
|
|
112
115
|
|
|
113
116
|
def _excerpt(text: str) -> str:
|
|
@@ -5,6 +5,7 @@ from dataclasses import dataclass
|
|
|
5
5
|
from typing import Protocol
|
|
6
6
|
|
|
7
7
|
from .intent_belief import IntentBelief, IntentRiskDecision
|
|
8
|
+
from .textnorm import normalise_light
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
@dataclass(frozen=True)
|
|
@@ -179,7 +180,7 @@ class HeuristicIntent:
|
|
|
179
180
|
return f'{prefix}: "{request}".{suffix}'
|
|
180
181
|
|
|
181
182
|
def _normalise(self, text: str) -> str:
|
|
182
|
-
return
|
|
183
|
+
return normalise_light(text)
|
|
183
184
|
|
|
184
185
|
def _first_marker(self, text: str, markers: tuple[str, ...]) -> str | None:
|
|
185
186
|
for marker in markers:
|
|
@@ -4,6 +4,7 @@ import re
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
|
|
6
6
|
from .affect import CORRECTION_MARKERS
|
|
7
|
+
from .textnorm import normalise_plain as _normalise
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
@dataclass(frozen=True)
|
|
@@ -42,10 +43,6 @@ class InteractionQualityState:
|
|
|
42
43
|
evidence: tuple[str, ...] = ()
|
|
43
44
|
|
|
44
45
|
|
|
45
|
-
def _normalise(text: str) -> str:
|
|
46
|
-
return " ".join(re.sub(r"[^\w\s]", " ", text.lower()).split())
|
|
47
|
-
|
|
48
|
-
|
|
49
46
|
def _has_correction(text: str) -> bool:
|
|
50
47
|
low = text.lower()
|
|
51
48
|
return any(marker in low for marker in CORRECTION_MARKERS)
|
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import re
|
|
4
4
|
from dataclasses import asdict, dataclass, replace
|
|
5
5
|
|
|
6
|
+
from .textnorm import normalise_light as _normalise
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
SESSION_SCOPE_PATTERNS = (
|
|
8
10
|
r"\bbundan sonra\b",
|
|
@@ -44,10 +46,6 @@ class SessionPreferences:
|
|
|
44
46
|
return asdict(self)
|
|
45
47
|
|
|
46
48
|
|
|
47
|
-
def _normalise(text: str) -> str:
|
|
48
|
-
return " ".join(text.lower().split())
|
|
49
|
-
|
|
50
|
-
|
|
51
49
|
def _session_scoped(text: str) -> bool:
|
|
52
50
|
low = _normalise(text)
|
|
53
51
|
if any(re.search(pattern, low) for pattern in SESSION_SCOPE_PATTERNS):
|
|
@@ -6,6 +6,7 @@ import random
|
|
|
6
6
|
import re
|
|
7
7
|
import shlex
|
|
8
8
|
import subprocess
|
|
9
|
+
import threading
|
|
9
10
|
from dataclasses import dataclass, field
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from typing import Iterable
|
|
@@ -28,6 +29,42 @@ LEAKAGE_FIELDS = {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
|
|
32
|
+
class ModelCommandTimeout(RuntimeError):
|
|
33
|
+
"""A model command failed to answer within its configured timeout.
|
|
34
|
+
|
|
35
|
+
Raised for both the one-shot and the persistent command modes so callers
|
|
36
|
+
(including the A/B generation CLI) can catch one typed error instead of
|
|
37
|
+
pattern-matching messages. Subclasses ``RuntimeError`` like the other
|
|
38
|
+
model-command failures.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _force_kill(process: subprocess.Popen) -> None:
|
|
43
|
+
"""Best-effort hard kill of a wedged child so its pipes close.
|
|
44
|
+
|
|
45
|
+
Used by the persistent-command timeout path and as the escalation step of
|
|
46
|
+
``CommandModel.close``. Terminate first, then kill if the child ignores
|
|
47
|
+
it; both are no-ops when the process has already been reaped.
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
process.terminate()
|
|
51
|
+
except OSError:
|
|
52
|
+
pass
|
|
53
|
+
try:
|
|
54
|
+
process.wait(timeout=5)
|
|
55
|
+
return
|
|
56
|
+
except subprocess.TimeoutExpired:
|
|
57
|
+
pass
|
|
58
|
+
try:
|
|
59
|
+
process.kill()
|
|
60
|
+
except OSError:
|
|
61
|
+
pass
|
|
62
|
+
try:
|
|
63
|
+
process.wait(timeout=5)
|
|
64
|
+
except subprocess.TimeoutExpired:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
31
68
|
def validate_experiment_cases(cases: list[dict], *, final: bool = False) -> dict:
|
|
32
69
|
errors: list[str] = []
|
|
33
70
|
seen: set[str] = set()
|
|
@@ -222,14 +259,20 @@ class CommandModel:
|
|
|
222
259
|
if self.persistent:
|
|
223
260
|
raw = self._persistent_generate(payload)
|
|
224
261
|
else:
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
262
|
+
try:
|
|
263
|
+
completed = subprocess.run(
|
|
264
|
+
list(self.command),
|
|
265
|
+
input=json.dumps(payload, ensure_ascii=False),
|
|
266
|
+
text=True,
|
|
267
|
+
capture_output=True,
|
|
268
|
+
timeout=self.timeout_seconds,
|
|
269
|
+
check=False,
|
|
270
|
+
)
|
|
271
|
+
except subprocess.TimeoutExpired as exc:
|
|
272
|
+
raise ModelCommandTimeout(
|
|
273
|
+
f"Model command did not respond within "
|
|
274
|
+
f"{self.timeout_seconds} seconds: {' '.join(self.command)}"
|
|
275
|
+
) from exc
|
|
233
276
|
if completed.returncode != 0:
|
|
234
277
|
raise RuntimeError(
|
|
235
278
|
f"Model command failed ({completed.returncode}): "
|
|
@@ -261,7 +304,7 @@ class CommandModel:
|
|
|
261
304
|
|
|
262
305
|
process.stdin.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
|
263
306
|
process.stdin.flush()
|
|
264
|
-
raw =
|
|
307
|
+
raw = self._read_line(process, self.timeout_seconds)
|
|
265
308
|
if not raw:
|
|
266
309
|
stderr = process.stderr.read().strip() if process.stderr else ""
|
|
267
310
|
raise RuntimeError(
|
|
@@ -270,6 +313,40 @@ class CommandModel:
|
|
|
270
313
|
)
|
|
271
314
|
return raw
|
|
272
315
|
|
|
316
|
+
def _read_line(self, process: subprocess.Popen, timeout_seconds: float) -> str:
|
|
317
|
+
"""Read one stdout line from the child under a wall-clock deadline.
|
|
318
|
+
|
|
319
|
+
A blocking ``readline()`` on a pipe waits until the child writes a
|
|
320
|
+
newline, and on Windows a blocked pipe read cannot be interrupted, so
|
|
321
|
+
the read runs on a daemon reader thread that is joined with the
|
|
322
|
+
configured timeout. On expiry the child is force-killed: its stdout
|
|
323
|
+
closes, the blocked read unwinds with EOF, and a wedged model command
|
|
324
|
+
can never stall the experiment synchronously.
|
|
325
|
+
"""
|
|
326
|
+
stdout = process.stdout
|
|
327
|
+
if stdout is None:
|
|
328
|
+
raise RuntimeError("Persistent model command pipes are unavailable.")
|
|
329
|
+
|
|
330
|
+
lines: list[str] = []
|
|
331
|
+
|
|
332
|
+
def _read() -> None:
|
|
333
|
+
lines.append(stdout.readline())
|
|
334
|
+
|
|
335
|
+
reader = threading.Thread(
|
|
336
|
+
target=_read,
|
|
337
|
+
name="eq-layer-model-reader",
|
|
338
|
+
daemon=True,
|
|
339
|
+
)
|
|
340
|
+
reader.start()
|
|
341
|
+
reader.join(timeout_seconds)
|
|
342
|
+
if reader.is_alive():
|
|
343
|
+
_force_kill(process)
|
|
344
|
+
raise ModelCommandTimeout(
|
|
345
|
+
f"Persistent model command produced no response within "
|
|
346
|
+
f"{timeout_seconds} seconds: {' '.join(self.command)}"
|
|
347
|
+
)
|
|
348
|
+
return lines[0].strip() if lines else ""
|
|
349
|
+
|
|
273
350
|
@staticmethod
|
|
274
351
|
def _decode_response(raw: str) -> str:
|
|
275
352
|
if not raw:
|
|
@@ -300,12 +377,7 @@ class CommandModel:
|
|
|
300
377
|
try:
|
|
301
378
|
process.wait(timeout=10)
|
|
302
379
|
except subprocess.TimeoutExpired:
|
|
303
|
-
process
|
|
304
|
-
try:
|
|
305
|
-
process.wait(timeout=5)
|
|
306
|
-
except subprocess.TimeoutExpired:
|
|
307
|
-
process.kill()
|
|
308
|
-
process.wait(timeout=5)
|
|
380
|
+
_force_kill(process)
|
|
309
381
|
|
|
310
382
|
|
|
311
383
|
@dataclass
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Shared text-normalisation helpers.
|
|
2
|
+
|
|
3
|
+
Five modules used to carry copy-pasted ``_normalise`` helpers, and the copies
|
|
4
|
+
had already drifted into two behaviours:
|
|
5
|
+
|
|
6
|
+
* a light form — lowercase and collapse whitespace, punctuation preserved
|
|
7
|
+
(``auditor``, ``preferences``, ``intent``);
|
|
8
|
+
* a plain form — also replace punctuation runs with spaces (``interaction``),
|
|
9
|
+
with ``dialogue_reference`` additionally dropping the Turkish combining dot
|
|
10
|
+
above (U+0307) so dotted and dotless spellings compare equal.
|
|
11
|
+
|
|
12
|
+
Both behaviours live here, in one place. Callers must keep using the variant
|
|
13
|
+
their matching logic was written against: this consolidation removes the
|
|
14
|
+
duplicated code without silently changing any matcher's semantics.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
_PUNCTUATION = re.compile(r"[^\w\s]")
|
|
22
|
+
_COMBINING_DOT_ABOVE = "\u0307"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def normalise_light(text: str) -> str:
|
|
26
|
+
"""Lowercase and collapse whitespace. Punctuation is preserved."""
|
|
27
|
+
return " ".join(text.lower().split())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def normalise_plain(text: str, *, drop_combining_dot_above: bool = False) -> str:
|
|
31
|
+
"""Lowercase, replace punctuation with spaces, and collapse whitespace.
|
|
32
|
+
|
|
33
|
+
``drop_combining_dot_above`` removes U+0307 before the punctuation pass so
|
|
34
|
+
that ``i`` + combining dot above normalises like a bare ``i``. This is
|
|
35
|
+
needed by Turkish dotted/dotless matching in the dialogue-reference
|
|
36
|
+
matcher; it is off by default so the default behaviour stays identical to
|
|
37
|
+
the historical copies.
|
|
38
|
+
"""
|
|
39
|
+
lowered = text.lower()
|
|
40
|
+
if drop_combining_dot_above:
|
|
41
|
+
lowered = lowered.replace(_COMBINING_DOT_ABOVE, "")
|
|
42
|
+
return " ".join(_PUNCTUATION.sub(" ", lowered).split())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "eq-layer"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "Inspectable intent, affect, repair, uncertainty, and factored-action control for LLM responses"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -13,9 +13,11 @@ keywords = [
|
|
|
13
13
|
"agents",
|
|
14
14
|
"agent-skills",
|
|
15
15
|
"dialogue-management",
|
|
16
|
+
"dialogue-policy",
|
|
16
17
|
"affective-computing",
|
|
17
18
|
"intent-classification",
|
|
18
19
|
"llm-steering",
|
|
20
|
+
"response-quality",
|
|
19
21
|
]
|
|
20
22
|
classifiers = [
|
|
21
23
|
"Development Status :: 3 - Alpha",
|
|
@@ -26,6 +28,8 @@ classifiers = [
|
|
|
26
28
|
"Programming Language :: Python :: 3.10",
|
|
27
29
|
"Programming Language :: Python :: 3.11",
|
|
28
30
|
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Programming Language :: Python :: 3.13",
|
|
32
|
+
"Programming Language :: Python :: 3.14",
|
|
29
33
|
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
30
34
|
]
|
|
31
35
|
dependencies = []
|
|
@@ -46,7 +50,9 @@ eq-layer = "eq_layer.cli:main"
|
|
|
46
50
|
[project.urls]
|
|
47
51
|
Homepage = "https://github.com/Furox-Art/eq-layer"
|
|
48
52
|
Repository = "https://github.com/Furox-Art/eq-layer"
|
|
53
|
+
Changelog = "https://github.com/Furox-Art/eq-layer/blob/main/CHANGELOG.md"
|
|
49
54
|
Issues = "https://github.com/Furox-Art/eq-layer/issues"
|
|
55
|
+
Security = "https://github.com/Furox-Art/eq-layer/blob/main/SECURITY.md"
|
|
50
56
|
|
|
51
57
|
[build-system]
|
|
52
58
|
requires = ["hatchling"]
|
|
@@ -63,6 +69,9 @@ include = [
|
|
|
63
69
|
"/THIRD_PARTY_DATA.md",
|
|
64
70
|
]
|
|
65
71
|
|
|
72
|
+
[tool.pytest.ini_options]
|
|
73
|
+
testpaths = ["tests"]
|
|
74
|
+
|
|
66
75
|
[tool.ruff]
|
|
67
76
|
line-length = 100
|
|
68
77
|
|
|
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
|