dsh-plugin-xujie-memory 0.5.0 → 0.7.0
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.
- package/ACCEPTANCE.md +37 -0
- package/BUILD-PROVENANCE.json +53 -0
- package/CHANGELOG.md +40 -1
- package/LICENSE +21 -21
- package/README.md +67 -17
- package/README_EN.md +54 -8
- package/cordis.patch.yml +49 -49
- package/lib/client.js +17219 -0
- package/lib/controller.js +307 -0
- package/lib/index.js +200 -14
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +15104 -0
- package/lib/typert.remote-client.d.ts +40 -0
- package/lib/typert.remote-client.d.ts.map +1 -0
- package/lib/typert.remote-client.js +15093 -0
- package/lib/types.d.ts +13 -0
- package/lib/types.js +1 -0
- package/package.json +49 -5
- package/python/specs/characters/aloof.json +14 -14
- package/python/specs/characters/gentle.json +14 -14
- package/python/specs/characters/tsundere.json +14 -14
- package/python/specs/debt.v1.json +31 -31
- package/python/specs/nickname.v1.json +16 -16
- package/python/specs/preference.v1.json +16 -16
- package/python/xujie_memory/__init__.py +123 -123
- package/python/xujie_memory/admission.py +211 -211
- package/python/xujie_memory/audit.py +377 -377
- package/python/xujie_memory/benchmark.py +281 -281
- package/python/xujie_memory/character.py +199 -199
- package/python/xujie_memory/console.py +41 -41
- package/python/xujie_memory/consolidate.py +356 -356
- package/python/xujie_memory/due.py +121 -121
- package/python/xujie_memory/emotion.py +573 -573
- package/python/xujie_memory/engine.py +1333 -1312
- package/python/xujie_memory/errors.py +98 -98
- package/python/xujie_memory/importance.py +104 -104
- package/python/xujie_memory/judge.py +350 -350
- package/python/xujie_memory/management.py +203 -0
- package/python/xujie_memory/memory.py +71 -10
- package/python/xujie_memory/orchestrator.py +690 -666
- package/python/xujie_memory/phrasing.py +292 -292
- package/python/xujie_memory/privacy.py +105 -0
- package/python/xujie_memory/schema.sql +492 -480
- package/python/xujie_memory/server.py +76 -0
- package/python/xujie_memory/specs.py +170 -170
- package/python/xujie_memory/summary.py +238 -238
- package/scripts/build.mjs +19 -0
- package/scripts/prepack.mjs +9 -0
- package/scripts/test-all.mjs +9 -0
- package/scripts/test-node.mjs +10 -0
- package/src/client/MemoryPanel.tsx +536 -0
- package/src/client/NeuralField.tsx +88 -0
- package/src/client/css.d.ts +4 -0
- package/src/client/icons.tsx +28 -0
- package/src/client/index.tsx +59 -0
- package/src/client/memory-adapter.ts +67 -0
- package/src/client/model.ts +79 -0
- package/src/client/neural/engine.ts +999 -0
- package/src/client/neural/morphology.ts +219 -0
- package/src/client/panel.css +337 -0
- package/src/client/types.ts +6 -0
- package/src/controller.ts +88 -0
- package/src/generated/typert.host.js +449 -0
- package/src/generated/typert.remote-client.js +438 -0
- package/src/index.ts +2 -0
- package/src/types.ts +13 -0
- package/test/cordis-fixture.mjs +5 -0
- package/test/management_regression.py +187 -0
- package/test/node-regression.entry.mjs +52 -0
- package/vendor/DSH-LICENSE.txt +21 -0
- package/vendor/THIRD-PARTY.md +6 -0
- package/vendor/dsh-protocol/index.ts +484 -0
- package/vendor/dsh-protocol/json-value.ts +53 -0
- package/vendor/dsh-protocol/owned-value.ts +39 -0
- package/vendor/dsh-protocol/principal.ts +17 -0
- package/vendor/dsh-protocol/remote-access.ts +38 -0
- package/vendor/dsh-protocol/remote-error.ts +49 -0
- package/vendor/dsh-protocol/remote-info.ts +161 -0
- package/vendor/dsh-protocol/types.ts +939 -0
- package/vendor/licenses/jridgewell-gen-mapping-MIT.txt +19 -0
- package/vendor/licenses/jridgewell-resolve-uri-MIT.txt +19 -0
- package/vendor/licenses/jridgewell-sourcemap-codec-MIT.txt +19 -0
- package/vendor/licenses/jridgewell-trace-mapping-MIT.txt +19 -0
- package/vendor/licenses/zod-MIT.txt +21 -0
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
"""内核错误类型。
|
|
2
|
-
|
|
3
|
-
原则:所有拒绝都必须是一个具体类型,而不是 None 或空值。
|
|
4
|
-
调用方必须能区分"没查到"与"查不了"(设计文档 I5)。
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class MemoryError(Exception):
|
|
9
|
-
"""所有内核错误的基类。"""
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ScopeNotFound(MemoryError):
|
|
13
|
-
pass
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class TraceNotFound(MemoryError):
|
|
17
|
-
pass
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class TraceErased(MemoryError):
|
|
21
|
-
"""痕迹已被显式删除,不得继续作为证据使用。"""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class SpecNotFound(MemoryError):
|
|
25
|
-
pass
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class SpecVersionMismatch(MemoryError):
|
|
29
|
-
"""事件使用的规格版本与状态流当前版本不一致。"""
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class IdempotencyConflict(MemoryError):
|
|
33
|
-
"""同一幂等键被用于不同载荷。必须拒绝,不得静默覆盖。"""
|
|
34
|
-
|
|
35
|
-
def __init__(self, key: str, existing_event_id: str):
|
|
36
|
-
self.key = key
|
|
37
|
-
self.existing_event_id = existing_event_id
|
|
38
|
-
super().__init__(
|
|
39
|
-
f"idempotency_key={key!r} already used with a different payload "
|
|
40
|
-
f"(existing event {existing_event_id})"
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class VersionConflict(MemoryError):
|
|
45
|
-
"""乐观并发失败:状态版本已被其他写入推进。"""
|
|
46
|
-
|
|
47
|
-
def __init__(self, state_key: str, expected: int, actual: int):
|
|
48
|
-
self.state_key = state_key
|
|
49
|
-
self.expected = expected
|
|
50
|
-
self.actual = actual
|
|
51
|
-
super().__init__(
|
|
52
|
-
f"state_key={state_key!r} expected_version={expected} but actual={actual}"
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class EvidenceMissing(MemoryError):
|
|
57
|
-
"""提交事件时未提供任何可解析的证据。违反 I1。"""
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class DependencyCycle(MemoryError):
|
|
61
|
-
"""依赖图出现环。登记时必须拒绝(设计文档 9.5)。"""
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class LockTimeout(MemoryError):
|
|
65
|
-
"""写锁等待超时。
|
|
66
|
-
|
|
67
|
-
SQLite 只有一把写锁,`BEGIN IMMEDIATE` 拿不到时按 `busy_timeout` 等待,
|
|
68
|
-
超时抛的是裸的 `sqlite3.OperationalError: database is locked`。
|
|
69
|
-
|
|
70
|
-
这条之所以要单独成类型,是因为它**不是"数据错了",是"现在不行"**:
|
|
71
|
-
|
|
72
|
-
- `IdempotencyConflict` / `VersionConflict` → 改数据才有用
|
|
73
|
-
- `LockTimeout` → 原样重试就可能成功
|
|
74
|
-
|
|
75
|
-
把两者混成一个 `sqlite3.Error`,调用方只能全部当致命错误处理,
|
|
76
|
-
于是一次瞬时的锁竞争被报成"记忆系统坏了"。
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
retryable = True
|
|
80
|
-
|
|
81
|
-
def __init__(self, message: str = "database is locked"):
|
|
82
|
-
super().__init__(message)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
class EventAlreadyVoid(MemoryError):
|
|
86
|
-
"""要纠正 / 撤销的事件已经失效。
|
|
87
|
-
|
|
88
|
-
这不是"冲突",更不是"程序错了"——它通常意味着**同一个动作被执行了两次**
|
|
89
|
-
(用户点了两下、消息重投、并发重试)。
|
|
90
|
-
|
|
91
|
-
以前这里抛裸 `ValueError`,调用方只能 catch 一个泛型异常,
|
|
92
|
-
和"参数写错了"混在一起。而这两件事的处理方式完全不同:
|
|
93
|
-
前者忽略即可,后者是 bug。
|
|
94
|
-
"""
|
|
95
|
-
|
|
96
|
-
def __init__(self, event_id: str):
|
|
97
|
-
self.event_id = event_id
|
|
98
|
-
super().__init__(f"event {event_id} already void")
|
|
1
|
+
"""内核错误类型。
|
|
2
|
+
|
|
3
|
+
原则:所有拒绝都必须是一个具体类型,而不是 None 或空值。
|
|
4
|
+
调用方必须能区分"没查到"与"查不了"(设计文档 I5)。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MemoryError(Exception):
|
|
9
|
+
"""所有内核错误的基类。"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ScopeNotFound(MemoryError):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TraceNotFound(MemoryError):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TraceErased(MemoryError):
|
|
21
|
+
"""痕迹已被显式删除,不得继续作为证据使用。"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SpecNotFound(MemoryError):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SpecVersionMismatch(MemoryError):
|
|
29
|
+
"""事件使用的规格版本与状态流当前版本不一致。"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class IdempotencyConflict(MemoryError):
|
|
33
|
+
"""同一幂等键被用于不同载荷。必须拒绝,不得静默覆盖。"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, key: str, existing_event_id: str):
|
|
36
|
+
self.key = key
|
|
37
|
+
self.existing_event_id = existing_event_id
|
|
38
|
+
super().__init__(
|
|
39
|
+
f"idempotency_key={key!r} already used with a different payload "
|
|
40
|
+
f"(existing event {existing_event_id})"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class VersionConflict(MemoryError):
|
|
45
|
+
"""乐观并发失败:状态版本已被其他写入推进。"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, state_key: str, expected: int, actual: int):
|
|
48
|
+
self.state_key = state_key
|
|
49
|
+
self.expected = expected
|
|
50
|
+
self.actual = actual
|
|
51
|
+
super().__init__(
|
|
52
|
+
f"state_key={state_key!r} expected_version={expected} but actual={actual}"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class EvidenceMissing(MemoryError):
|
|
57
|
+
"""提交事件时未提供任何可解析的证据。违反 I1。"""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class DependencyCycle(MemoryError):
|
|
61
|
+
"""依赖图出现环。登记时必须拒绝(设计文档 9.5)。"""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class LockTimeout(MemoryError):
|
|
65
|
+
"""写锁等待超时。
|
|
66
|
+
|
|
67
|
+
SQLite 只有一把写锁,`BEGIN IMMEDIATE` 拿不到时按 `busy_timeout` 等待,
|
|
68
|
+
超时抛的是裸的 `sqlite3.OperationalError: database is locked`。
|
|
69
|
+
|
|
70
|
+
这条之所以要单独成类型,是因为它**不是"数据错了",是"现在不行"**:
|
|
71
|
+
|
|
72
|
+
- `IdempotencyConflict` / `VersionConflict` → 改数据才有用
|
|
73
|
+
- `LockTimeout` → 原样重试就可能成功
|
|
74
|
+
|
|
75
|
+
把两者混成一个 `sqlite3.Error`,调用方只能全部当致命错误处理,
|
|
76
|
+
于是一次瞬时的锁竞争被报成"记忆系统坏了"。
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
retryable = True
|
|
80
|
+
|
|
81
|
+
def __init__(self, message: str = "database is locked"):
|
|
82
|
+
super().__init__(message)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class EventAlreadyVoid(MemoryError):
|
|
86
|
+
"""要纠正 / 撤销的事件已经失效。
|
|
87
|
+
|
|
88
|
+
这不是"冲突",更不是"程序错了"——它通常意味着**同一个动作被执行了两次**
|
|
89
|
+
(用户点了两下、消息重投、并发重试)。
|
|
90
|
+
|
|
91
|
+
以前这里抛裸 `ValueError`,调用方只能 catch 一个泛型异常,
|
|
92
|
+
和"参数写错了"混在一起。而这两件事的处理方式完全不同:
|
|
93
|
+
前者忽略即可,后者是 bug。
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(self, event_id: str):
|
|
97
|
+
self.event_id = event_id
|
|
98
|
+
super().__init__(f"event {event_id} already void")
|
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
"""自动重要性:不等用户说「记住」,先给每句话打个分。
|
|
2
|
-
|
|
3
|
-
分不是拍脑袋的「AI 觉得」——至少 v1 不是。它是一组**可解释的线索**加权:
|
|
4
|
-
|
|
5
|
-
身份/自称 「我是…」「我叫…」 +2.0
|
|
6
|
-
强偏好 「最喜欢/最讨厌/过敏」 +1.6
|
|
7
|
-
承诺/计划 「答应/决定/下个月」 +1.4
|
|
8
|
-
健康/钱/变动 「手术/房租/辞职」 +1.3
|
|
9
|
-
关系人物 「老婆/孩子/老板」 +1.2
|
|
10
|
-
情绪强度 「特别/害怕/永远」 +1.0
|
|
11
|
-
日期事件 「2026 年/生日」 +0.8
|
|
12
|
-
|
|
13
|
-
寒暄/天气/查询 「你好/天气/帮我查」 −1~−2
|
|
14
|
-
|
|
15
|
-
分数映射到 1–5 的 base:普通 1.0,值得记的 1.5–3.0,最高 4.0。
|
|
16
|
-
**5.0 和锚点留给用户明说**——自动判断只决定「淡得慢一点」,不决定「永远不忘」。
|
|
17
|
-
这样误判的代价只是「多留一阵」,不是「钉死一条错的」。
|
|
18
|
-
|
|
19
|
-
以后要换成模型打分,只要实现同样的 `score(text)` 返回结构即可;
|
|
20
|
-
调用方(编排器)不需要知道分数是怎么来的。
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
from __future__ import annotations
|
|
24
|
-
|
|
25
|
-
import re
|
|
26
|
-
from dataclasses import dataclass, field
|
|
27
|
-
|
|
28
|
-
from .decay import Lifecycle
|
|
29
|
-
|
|
30
|
-
# (名字, 正则, 权重)
|
|
31
|
-
_POSITIVE = (
|
|
32
|
-
("identity", r"我(?:是|叫|姓|住在|来自|今年)|我的生日|我养的", 2.0),
|
|
33
|
-
("preference", r"最喜欢|最讨厌|最爱|受不了|吃不惯|过敏|不吃|不喝", 1.6),
|
|
34
|
-
("commitment", r"答应|约好|决定|计划|以后|下次|明天|后天|下周|下个月|月底|回头", 1.8),
|
|
35
|
-
("health_money", r"生病|医院|手术|吃药|过敏|工资|房租|存款|搬家|辞职|入职|考试", 1.3),
|
|
36
|
-
("relationship", r"老婆|老公|男朋友|女朋友|孩子|女儿|儿子|爸妈|父母|朋友|同事|老板", 1.2),
|
|
37
|
-
("emotion", r"特别|非常|永远|恨|害怕|担心|难过|开心|孤独|焦虑|后悔", 1.0),
|
|
38
|
-
("date", r"\d{4}\s*年|\d{1,2}\s*月\s*\d{1,2}\s*日|生日|纪念日", 0.8),
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
_NEGATIVE = (
|
|
42
|
-
("chitchat", r"^\s*(你好|您好|在吗|晚安|早安|哈哈|嗯|哦|好的|谢谢)", -2.0),
|
|
43
|
-
("weather", r"天气|下雨|下雪|好热|好冷|风真大", -1.2),
|
|
44
|
-
("meta", r"帮我(?:查|写|翻译|算)|解释一下|什么是|怎么用", -0.8),
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@dataclass
|
|
49
|
-
class Importance:
|
|
50
|
-
score: float # 0–1
|
|
51
|
-
value: float # 1.0–4.0(base)
|
|
52
|
-
reasons: list[str] = field(default_factory=list)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def score(text: str) -> Importance:
|
|
56
|
-
text = (text or "").strip()
|
|
57
|
-
if not text:
|
|
58
|
-
return Importance(0.0, 1.0, ["empty"])
|
|
59
|
-
|
|
60
|
-
raw = 0.0
|
|
61
|
-
reasons: list[str] = []
|
|
62
|
-
for name, pattern, weight in _POSITIVE:
|
|
63
|
-
if re.search(pattern, text):
|
|
64
|
-
raw += weight
|
|
65
|
-
reasons.append(name)
|
|
66
|
-
for name, pattern, weight in _NEGATIVE:
|
|
67
|
-
if re.search(pattern, text):
|
|
68
|
-
raw += weight
|
|
69
|
-
reasons.append(name)
|
|
70
|
-
|
|
71
|
-
if len(text) >= 15:
|
|
72
|
-
raw += 0.4
|
|
73
|
-
reasons.append("long")
|
|
74
|
-
elif len(text) <= 4:
|
|
75
|
-
raw -= 0.8
|
|
76
|
-
reasons.append("too_short")
|
|
77
|
-
|
|
78
|
-
score_value = max(0.0, min(1.0, raw / 5.0))
|
|
79
|
-
if score_value < 0.25:
|
|
80
|
-
value = 1.0
|
|
81
|
-
elif score_value < 0.4:
|
|
82
|
-
value = 1.5
|
|
83
|
-
elif score_value < 0.6:
|
|
84
|
-
value = 2.0
|
|
85
|
-
elif score_value < 0.8:
|
|
86
|
-
value = 3.0
|
|
87
|
-
else:
|
|
88
|
-
value = 4.0
|
|
89
|
-
return Importance(round(score_value, 4), value, reasons)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def apply(engine, scope_id: str, trace_id: str, text: str,
|
|
93
|
-
*, source: str = "auto") -> Importance | None:
|
|
94
|
-
"""给刚写下的这句话打重要性。普通句不写事件,避免每轮都堆一条。"""
|
|
95
|
-
lifecycle = Lifecycle(engine)
|
|
96
|
-
current = lifecycle.get(trace_id)
|
|
97
|
-
if current.get("anchor"):
|
|
98
|
-
return None # 用户已钉死,自动分不覆盖
|
|
99
|
-
info = score(text)
|
|
100
|
-
if info.value <= 1.0:
|
|
101
|
-
return info
|
|
102
|
-
lifecycle.set_importance(trace_id, info.value, scope_id,
|
|
103
|
-
reasons=info.reasons, source=source)
|
|
104
|
-
return info
|
|
1
|
+
"""自动重要性:不等用户说「记住」,先给每句话打个分。
|
|
2
|
+
|
|
3
|
+
分不是拍脑袋的「AI 觉得」——至少 v1 不是。它是一组**可解释的线索**加权:
|
|
4
|
+
|
|
5
|
+
身份/自称 「我是…」「我叫…」 +2.0
|
|
6
|
+
强偏好 「最喜欢/最讨厌/过敏」 +1.6
|
|
7
|
+
承诺/计划 「答应/决定/下个月」 +1.4
|
|
8
|
+
健康/钱/变动 「手术/房租/辞职」 +1.3
|
|
9
|
+
关系人物 「老婆/孩子/老板」 +1.2
|
|
10
|
+
情绪强度 「特别/害怕/永远」 +1.0
|
|
11
|
+
日期事件 「2026 年/生日」 +0.8
|
|
12
|
+
|
|
13
|
+
寒暄/天气/查询 「你好/天气/帮我查」 −1~−2
|
|
14
|
+
|
|
15
|
+
分数映射到 1–5 的 base:普通 1.0,值得记的 1.5–3.0,最高 4.0。
|
|
16
|
+
**5.0 和锚点留给用户明说**——自动判断只决定「淡得慢一点」,不决定「永远不忘」。
|
|
17
|
+
这样误判的代价只是「多留一阵」,不是「钉死一条错的」。
|
|
18
|
+
|
|
19
|
+
以后要换成模型打分,只要实现同样的 `score(text)` 返回结构即可;
|
|
20
|
+
调用方(编排器)不需要知道分数是怎么来的。
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import re
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
|
|
28
|
+
from .decay import Lifecycle
|
|
29
|
+
|
|
30
|
+
# (名字, 正则, 权重)
|
|
31
|
+
_POSITIVE = (
|
|
32
|
+
("identity", r"我(?:是|叫|姓|住在|来自|今年)|我的生日|我养的", 2.0),
|
|
33
|
+
("preference", r"最喜欢|最讨厌|最爱|受不了|吃不惯|过敏|不吃|不喝", 1.6),
|
|
34
|
+
("commitment", r"答应|约好|决定|计划|以后|下次|明天|后天|下周|下个月|月底|回头", 1.8),
|
|
35
|
+
("health_money", r"生病|医院|手术|吃药|过敏|工资|房租|存款|搬家|辞职|入职|考试", 1.3),
|
|
36
|
+
("relationship", r"老婆|老公|男朋友|女朋友|孩子|女儿|儿子|爸妈|父母|朋友|同事|老板", 1.2),
|
|
37
|
+
("emotion", r"特别|非常|永远|恨|害怕|担心|难过|开心|孤独|焦虑|后悔", 1.0),
|
|
38
|
+
("date", r"\d{4}\s*年|\d{1,2}\s*月\s*\d{1,2}\s*日|生日|纪念日", 0.8),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
_NEGATIVE = (
|
|
42
|
+
("chitchat", r"^\s*(你好|您好|在吗|晚安|早安|哈哈|嗯|哦|好的|谢谢)", -2.0),
|
|
43
|
+
("weather", r"天气|下雨|下雪|好热|好冷|风真大", -1.2),
|
|
44
|
+
("meta", r"帮我(?:查|写|翻译|算)|解释一下|什么是|怎么用", -0.8),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class Importance:
|
|
50
|
+
score: float # 0–1
|
|
51
|
+
value: float # 1.0–4.0(base)
|
|
52
|
+
reasons: list[str] = field(default_factory=list)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def score(text: str) -> Importance:
|
|
56
|
+
text = (text or "").strip()
|
|
57
|
+
if not text:
|
|
58
|
+
return Importance(0.0, 1.0, ["empty"])
|
|
59
|
+
|
|
60
|
+
raw = 0.0
|
|
61
|
+
reasons: list[str] = []
|
|
62
|
+
for name, pattern, weight in _POSITIVE:
|
|
63
|
+
if re.search(pattern, text):
|
|
64
|
+
raw += weight
|
|
65
|
+
reasons.append(name)
|
|
66
|
+
for name, pattern, weight in _NEGATIVE:
|
|
67
|
+
if re.search(pattern, text):
|
|
68
|
+
raw += weight
|
|
69
|
+
reasons.append(name)
|
|
70
|
+
|
|
71
|
+
if len(text) >= 15:
|
|
72
|
+
raw += 0.4
|
|
73
|
+
reasons.append("long")
|
|
74
|
+
elif len(text) <= 4:
|
|
75
|
+
raw -= 0.8
|
|
76
|
+
reasons.append("too_short")
|
|
77
|
+
|
|
78
|
+
score_value = max(0.0, min(1.0, raw / 5.0))
|
|
79
|
+
if score_value < 0.25:
|
|
80
|
+
value = 1.0
|
|
81
|
+
elif score_value < 0.4:
|
|
82
|
+
value = 1.5
|
|
83
|
+
elif score_value < 0.6:
|
|
84
|
+
value = 2.0
|
|
85
|
+
elif score_value < 0.8:
|
|
86
|
+
value = 3.0
|
|
87
|
+
else:
|
|
88
|
+
value = 4.0
|
|
89
|
+
return Importance(round(score_value, 4), value, reasons)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def apply(engine, scope_id: str, trace_id: str, text: str,
|
|
93
|
+
*, source: str = "auto") -> Importance | None:
|
|
94
|
+
"""给刚写下的这句话打重要性。普通句不写事件,避免每轮都堆一条。"""
|
|
95
|
+
lifecycle = Lifecycle(engine)
|
|
96
|
+
current = lifecycle.get(trace_id)
|
|
97
|
+
if current.get("anchor"):
|
|
98
|
+
return None # 用户已钉死,自动分不覆盖
|
|
99
|
+
info = score(text)
|
|
100
|
+
if info.value <= 1.0:
|
|
101
|
+
return info
|
|
102
|
+
lifecycle.set_importance(trace_id, info.value, scope_id,
|
|
103
|
+
reasons=info.reasons, source=source)
|
|
104
|
+
return info
|