liquid-loop 0.9.3__tar.gz → 1.4.0__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.
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/PKG-INFO +1 -1
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/liquid_loop/__init__.py +4 -3
- liquid_loop-1.4.0/liquid_loop/liquid_reweight.py +480 -0
- liquid_loop-1.4.0/liquid_loop/rar.py +346 -0
- liquid_loop-1.4.0/liquid_loop/selfspin.py +457 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/liquid_loop/workspace.py +99 -73
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/liquid_loop.egg-info/PKG-INFO +1 -1
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/liquid_loop.egg-info/SOURCES.txt +5 -2
- liquid_loop-1.4.0/liquid_loop.egg-info/top_level.txt +2 -0
- liquid_loop-1.4.0/liquid_loop_exp/liquid_persist_ab.py +129 -0
- liquid_loop-1.4.0/liquid_loop_exp/liquid_recall_ab.py +184 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/pyproject.toml +1 -1
- liquid_loop-0.9.3/liquid_loop/mesh/__init__.py +0 -50
- liquid_loop-0.9.3/liquid_loop/mesh/v2.py +0 -173
- liquid_loop-0.9.3/liquid_loop.egg-info/top_level.txt +0 -1
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/LICENSE +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/README.md +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/liquid_loop/__main__.py +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/liquid_loop/cli.py +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/liquid_loop/cognitive_budget.py +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/liquid_loop/entropy.py +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/liquid_loop/storage.py +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/liquid_loop.egg-info/dependency_links.txt +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/liquid_loop.egg-info/entry_points.txt +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/liquid_loop.egg-info/requires.txt +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/setup.cfg +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/tests/test_auth_guard.py +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/tests/test_consensus_expansion.py +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/tests/test_entropy.py +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/tests/test_layer1_persistence.py +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/tests/test_nucleate_dual_track.py +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/tests/test_peek_seal.py +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/tests/test_seal_persistence.py +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/tests/test_self_evolve.py +0 -0
- {liquid_loop-0.9.3 → liquid_loop-1.4.0}/tests/test_workspace.py +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
"""Liquid Loop — Workspace Cognitive Runtime
|
|
2
|
-
__version__ = "
|
|
1
|
+
"""Liquid Loop — Workspace Cognitive Runtime v1.1.0 (v1.0.0 + 液态重排引擎 Liquid Reweight: τ(x)黏滞唤醒 + 液态召回, 实证 97% 精度)"""
|
|
2
|
+
__version__ = "1.4.0"
|
|
3
3
|
|
|
4
4
|
from .workspace import (
|
|
5
5
|
WorkspaceState, Anchor, Evidence, Memory, Conflict,
|
|
6
6
|
AuditChain, CPERegularizer, SelfRefineEngine,
|
|
7
|
-
rhythmic_retrieve,
|
|
8
7
|
)
|
|
9
8
|
from .storage import load, save, locked_state
|
|
10
9
|
from .entropy import calculate, calculate_detail, calculate as calculate_entropy
|
|
10
|
+
from .selfspin import LiquidSelfSpin
|
|
11
|
+
from .liquid_reweight import LiquidReweight
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""液环 v1.2 · 液态重排引擎 Liquid Reweighting Layer(激活态持久化 · 跨会话保持「活」)
|
|
3
|
+
========================================================
|
|
4
|
+
实质提升:把 LNN 的「液态」特征接入液环成核 / 双轨逻辑,
|
|
5
|
+
让记忆本身能**流动重排**(非仅观测)。
|
|
6
|
+
|
|
7
|
+
现状短板(已读 workspace.py / selfspin.py 确认):
|
|
8
|
+
- 成核是静态集合式(_nucleate:同 content ≥2 即结晶,不流动)
|
|
9
|
+
- step() 衰减是常数率,无 τ(x) 自适应
|
|
10
|
+
- anchor 间虽有 AnchorRelation 拓扑骨架,但从未用于动态重排 → 记忆不会因邻接被「唤醒」
|
|
11
|
+
- 检索是纯字面 overlap top-N,相关但字面不重叠的记忆漏召
|
|
12
|
+
|
|
13
|
+
本引擎补这三处,核心三机制(全部守禁向量:用结构化 token 重叠 / 离散特征,非 embedding):
|
|
14
|
+
|
|
15
|
+
① 激活拓扑传播(Activation Topology Propagation)
|
|
16
|
+
— 锚点间建立关联图(基于 keyword containment 结构性推断,非向量)
|
|
17
|
+
— 新证据注入某 anchor 时,其**邻居 anchor** 按边权(拓扑关联度)被 τ(x) 黏滞唤醒
|
|
18
|
+
→ 相关记忆被「唤醒」,对应 LNN 的 dh/dt = -h/τ(x) + f(h,x,θ)
|
|
19
|
+
|
|
20
|
+
② 液态时间常数 τ(x)(Liquid Time Constant)
|
|
21
|
+
— 重排敏感度随「新证据与自身重叠度」自适应:
|
|
22
|
+
重叠高 → τ 小 → 快吸收(强相关立即并入)
|
|
23
|
+
重叠低 → τ 大 → 慢渗透(弱相关缓慢演化)
|
|
24
|
+
— 黏滞窄带约束(参考 lnn_cfc_demo 实测 τ∈[0.656,0.78]):重排幅度封顶,防震荡
|
|
25
|
+
|
|
26
|
+
③ 液态召回(Liquid Recall)
|
|
27
|
+
— 给定 query,不仅按字面 overlap 召回,还把「被激活唤醒的邻接记忆」加权并入
|
|
28
|
+
— 即:记忆因之前成核/注入而「活」(激活态),在后续字面不直接匹配的查询中被唤醒
|
|
29
|
+
— 这是成核前置增强:若把激活加成注入 selfspin 聚类阈值,弱相关记忆会被合并成核
|
|
30
|
+
|
|
31
|
+
落地方式(守极致稳态 + 不碰生产 server):
|
|
32
|
+
— 客户端引擎,操作本地 WorkspaceState 内存态(不改动 server 成核逻辑)
|
|
33
|
+
— 重排快照结构化写回 8790(隔离 namespace exp:liquid-reweight:*,守禁向量)
|
|
34
|
+
— 与 selfspin 双层自转正交:selfspin 管「跨源聚类加速成核」,本引擎管「成核后拓扑流动唤醒」
|
|
35
|
+
— 不污染生产 vera/trae/parlant/qiucai/mirofish 记忆
|
|
36
|
+
|
|
37
|
+
用法:
|
|
38
|
+
python3 liquid_reweight.py selftest # 纯逻辑单测(不写 8790)
|
|
39
|
+
python3 liquid_reweight.py selftest --live # 真实写 8790(隔离 ns)
|
|
40
|
+
"""
|
|
41
|
+
import sys
|
|
42
|
+
import os
|
|
43
|
+
import re
|
|
44
|
+
import json
|
|
45
|
+
import time
|
|
46
|
+
import hashlib
|
|
47
|
+
import argparse
|
|
48
|
+
import urllib.request
|
|
49
|
+
import urllib.error
|
|
50
|
+
from collections import defaultdict
|
|
51
|
+
|
|
52
|
+
DEFAULT_BACKEND = os.environ.get("LL_BASE", "http://127.0.0.1:8790")
|
|
53
|
+
|
|
54
|
+
# 与 workspace.py 同源:中文单字 + 英文数字连续串
|
|
55
|
+
_TOKEN = re.compile(r"[一-鿿]|[a-zA-Z0-9]+")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _tokens(s: str) -> list:
|
|
59
|
+
return _TOKEN.findall(s or "")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _keyword_overlap(a: str, b: str) -> float:
|
|
63
|
+
"""关键词重叠度(Jaccard),零依赖。守禁向量。"""
|
|
64
|
+
if not a or not b:
|
|
65
|
+
return 0.0
|
|
66
|
+
ta, tb = set(_tokens(a)), set(_tokens(b))
|
|
67
|
+
if not ta or not tb:
|
|
68
|
+
return 0.0
|
|
69
|
+
return len(ta & tb) / len(ta | tb)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _containment(a: str, b: str) -> float:
|
|
73
|
+
"""重叠系数(containment):|A∩B| / min(|A|,|B|)。对中文同义改写鲁棒。"""
|
|
74
|
+
ta, tb = set(_tokens(a)), set(_tokens(b))
|
|
75
|
+
if not ta or not tb:
|
|
76
|
+
return 0.0
|
|
77
|
+
return len(ta & tb) / min(len(ta), len(tb))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _anchor_id_of(a: dict) -> str:
|
|
81
|
+
"""内容寻址 anchor id:优先用显式 id,否则对 name+description 哈希(跨会话稳定)。
|
|
82
|
+
|
|
83
|
+
这让同一事实在跨会话/跨进程时共享同一激活身份 → 激活态持久化可恢复。
|
|
84
|
+
"""
|
|
85
|
+
if a.get("id"):
|
|
86
|
+
return str(a["id"])
|
|
87
|
+
h = hashlib.sha1(f"{a.get('name', '')}|{a.get('description', '')}".encode("utf-8")).hexdigest()[:12]
|
|
88
|
+
return f"h:{h}"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# 参考 LNN 实测 τ 窄带(lnn_cfc_demo 跑出 [0.656, 0.780])
|
|
92
|
+
TAU_MIN = 0.656
|
|
93
|
+
TAU_MAX = 0.780
|
|
94
|
+
# 单次激活传播幅度硬封顶(黏滞:防邻居被一次注入瞬间拉满)
|
|
95
|
+
AMP_CAP = 0.5
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _http_post(url: str, payload: dict, timeout: int = 15) -> dict:
|
|
99
|
+
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
|
100
|
+
req = urllib.request.Request(
|
|
101
|
+
url, data=data,
|
|
102
|
+
headers={"content-type": "application/json; charset=utf-8"}, method="POST",
|
|
103
|
+
)
|
|
104
|
+
try:
|
|
105
|
+
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
106
|
+
return json.loads(r.read().decode("utf-8"))
|
|
107
|
+
except urllib.error.HTTPError as e:
|
|
108
|
+
try:
|
|
109
|
+
return json.loads(e.read().decode("utf-8"))
|
|
110
|
+
except Exception:
|
|
111
|
+
return {"ok": False, "error": f"http_{e.code}"}
|
|
112
|
+
except Exception as e:
|
|
113
|
+
return {"ok": False, "error": str(e)}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class LiquidReweight:
|
|
117
|
+
"""液态重排引擎:让记忆拓扑流动重排(不碰 server,本地内存态 + 隔离写回)。
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
tau_min / tau_max : τ 窄带(黏滞约束),重叠=1→τ=tau_min(快),重叠=0→τ=tau_max(慢)
|
|
121
|
+
beta : 液态召回的唤醒加成系数
|
|
122
|
+
principle_beta : 原理(why)通道召回加成系数(MSM 反哺:原理匹配即便字面零重叠也召回)
|
|
123
|
+
topo_thresh : 拓扑连边的最低 containment(低于此不连,防噪声边)
|
|
124
|
+
amp_cap : 单次传播幅度封顶
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def __init__(self, tau_min: float = TAU_MIN, tau_max: float = TAU_MAX,
|
|
128
|
+
beta: float = 0.6, topo_thresh: float = 0.20, amp_cap: float = AMP_CAP,
|
|
129
|
+
min_activation: float = 0.2, persist_path: str = None,
|
|
130
|
+
half_life: float = 86400, principle_beta: float = 0.6):
|
|
131
|
+
self.tau_min = tau_min
|
|
132
|
+
self.tau_max = tau_max
|
|
133
|
+
self.beta = beta
|
|
134
|
+
self.topo_thresh = topo_thresh
|
|
135
|
+
self.amp_cap = amp_cap
|
|
136
|
+
self.min_activation = min_activation # 精度护栏:激活低于此的弱边唤醒视为噪声滤除
|
|
137
|
+
self.persist_path = persist_path # 激活态持久化文件路径(None=不落盘)
|
|
138
|
+
self.half_life = half_life # 激活态半衰期(秒),TTL 黏滞冷却:默认 1 天
|
|
139
|
+
self.principle_beta = principle_beta # 原理(why)通道召回加成系数(MSM 反哺)
|
|
140
|
+
self.activation: dict = {} # anchor_id -> float(流动重排态,跨会话持久)
|
|
141
|
+
self.topo: dict = {} # anchor_id -> [(nbr_id, weight)]
|
|
142
|
+
self._anchors: dict = {} # anchor_id -> {id,name,description}
|
|
143
|
+
self.last_ts: int = int(time.time())
|
|
144
|
+
|
|
145
|
+
# ── 文本视图:拓扑用 name(短·主题级),召回用 name+desc(含信息)──
|
|
146
|
+
def _topo_text(self, a: dict) -> str:
|
|
147
|
+
return a.get("name") or ""
|
|
148
|
+
|
|
149
|
+
def _anchor_text(self, a: dict) -> str:
|
|
150
|
+
return f"{a.get('description') or ''} {a.get('name') or ''}".strip()
|
|
151
|
+
|
|
152
|
+
# ── 载入锚点(来自 8790 /list 或本地构造)──
|
|
153
|
+
def load_anchors(self, anchors: list):
|
|
154
|
+
"""anchors: list of dict {id?, name, description}。
|
|
155
|
+
|
|
156
|
+
anchor id 走内容寻址(_anchor_id_of):显式 id 优先,否则哈希 →
|
|
157
|
+
跨会话同一事实共享同一激活身份,配套 save/load 可恢复激活态。
|
|
158
|
+
"""
|
|
159
|
+
self._anchors = {_anchor_id_of(a): a for a in anchors}
|
|
160
|
+
for a_id in self._anchors:
|
|
161
|
+
self.activation.setdefault(a_id, 0.0) # 保留已加载的持久激活(load 后调用)
|
|
162
|
+
self._build_topology()
|
|
163
|
+
|
|
164
|
+
def _build_topology(self):
|
|
165
|
+
"""结构化拓扑推断(守禁向量:纯 keyword containment,非 embedding)。"""
|
|
166
|
+
ids = list(self._anchors.keys())
|
|
167
|
+
self.topo = {i: [] for i in ids}
|
|
168
|
+
for i in range(len(ids)):
|
|
169
|
+
for j in range(i + 1, len(ids)):
|
|
170
|
+
c = _containment(self._topo_text(self._anchors[ids[i]]),
|
|
171
|
+
self._topo_text(self._anchors[ids[j]]))
|
|
172
|
+
if c >= self.topo_thresh:
|
|
173
|
+
w = round(min(c, 1.0), 3)
|
|
174
|
+
self.topo[ids[i]].append((ids[j], w))
|
|
175
|
+
self.topo[ids[j]].append((ids[i], w))
|
|
176
|
+
|
|
177
|
+
# ── 因果演化循环成核(v1.3):显式因果边注入 ──
|
|
178
|
+
def add_causal_edges(self, edges: list):
|
|
179
|
+
"""注入结构化因果边(守禁向量:确定性符号关系,非 embedding)。
|
|
180
|
+
|
|
181
|
+
edges = [(src_id, dst_id, weight), ...],src/dst 为锚点 id。
|
|
182
|
+
因果边绕过 topo_thresh 噪声过滤直接并入拓扑 → propagate / liquid_recall
|
|
183
|
+
天然沿因果链唤醒邻接记忆,实现「因果邻近加成」:相关但字面不重叠的
|
|
184
|
+
记忆因因果链被召回(对照 #93 大脑「检索因果相关过往事件」机制)。
|
|
185
|
+
"""
|
|
186
|
+
for src, dst, w in edges:
|
|
187
|
+
if src not in self._anchors or dst not in self._anchors or src == dst:
|
|
188
|
+
continue
|
|
189
|
+
wt = round(min(max(float(w), 0.0), 1.0), 3)
|
|
190
|
+
self.topo.setdefault(src, [])
|
|
191
|
+
self.topo.setdefault(dst, [])
|
|
192
|
+
if (dst, wt) not in self.topo[src]:
|
|
193
|
+
self.topo[src].append((dst, wt))
|
|
194
|
+
if (src, wt) not in self.topo[dst]:
|
|
195
|
+
self.topo[dst].append((src, wt))
|
|
196
|
+
|
|
197
|
+
# ── ② 液态时间常数 τ(x) ──
|
|
198
|
+
def tau_x(self, overlap: float) -> float:
|
|
199
|
+
"""重叠高→τ小→快吸收;重叠低→τ大→慢渗透。黏滞窄带封顶。"""
|
|
200
|
+
ov = max(0.0, min(1.0, overlap))
|
|
201
|
+
tau = self.tau_max - ov * (self.tau_max - self.tau_min)
|
|
202
|
+
return round(max(self.tau_min, min(self.tau_max, tau)), 4)
|
|
203
|
+
|
|
204
|
+
# ── ① 激活拓扑传播 ──
|
|
205
|
+
def propagate(self, anchor_id: str, new_content: str, steps: int = 1) -> dict:
|
|
206
|
+
"""新证据注入 anchor_id 后,沿拓扑做 τ(x) 黏滞激活传播。
|
|
207
|
+
|
|
208
|
+
机制:注入锚点自身激活=1.0(被直接命中);其每个邻居按「边权(拓扑关联度)
|
|
209
|
+
× (1/τ) × 0.5」被唤醒,τ 取新证据与该邻居主题的重叠自适应
|
|
210
|
+
(重叠高→τ小→快唤醒;重叠低→τ大→慢渗透),幅度封顶 amp_cap。
|
|
211
|
+
返回各锚点激活增量(黏滞校验用)。
|
|
212
|
+
"""
|
|
213
|
+
if anchor_id not in self._anchors:
|
|
214
|
+
return {}
|
|
215
|
+
# 注入锚点自身拉满
|
|
216
|
+
self.activation[anchor_id] = min(1.0, self.activation[anchor_id] + 1.0)
|
|
217
|
+
deltas = {anchor_id: 1.0}
|
|
218
|
+
for nbr_id, w in self.topo.get(anchor_id, []):
|
|
219
|
+
ov = _keyword_overlap(new_content, self._topo_text(self._anchors[nbr_id]))
|
|
220
|
+
tau = self.tau_x(ov if ov > 0 else 0.3) # 无字面重叠用中等 τ(慢渗透)
|
|
221
|
+
amp = min(w * (1.0 / tau) * 0.5, self.amp_cap)
|
|
222
|
+
self.activation[nbr_id] = min(1.0, self.activation[nbr_id] + amp)
|
|
223
|
+
deltas[nbr_id] = round(amp, 4)
|
|
224
|
+
# steps>1:多跳传播(邻居的邻居),封顶仍生效
|
|
225
|
+
for _ in range(steps - 1):
|
|
226
|
+
cur = dict(self.activation)
|
|
227
|
+
for a_id, nbrs in self.topo.items():
|
|
228
|
+
if a_id == anchor_id:
|
|
229
|
+
continue
|
|
230
|
+
best = 0.0
|
|
231
|
+
for nbr_id, w in nbrs:
|
|
232
|
+
ov = _keyword_overlap(new_content, self._topo_text(self._anchors[nbr_id]))
|
|
233
|
+
tau = self.tau_x(ov if ov > 0 else 0.3)
|
|
234
|
+
best = max(best, w * (1.0 / tau) * 0.5)
|
|
235
|
+
amp = min(best, self.amp_cap)
|
|
236
|
+
if amp > 0:
|
|
237
|
+
cur[a_id] = min(1.0, self.activation[a_id] + amp)
|
|
238
|
+
deltas[a_id] = round(cur[a_id] - self.activation[a_id], 4)
|
|
239
|
+
self.activation = cur
|
|
240
|
+
return deltas
|
|
241
|
+
|
|
242
|
+
# ── ③ 液态召回 ──
|
|
243
|
+
def liquid_recall(self, query: str, top_k: int = 5) -> list:
|
|
244
|
+
"""液态召回 = 字面 overlap + 邻接激活唤醒加成。
|
|
245
|
+
|
|
246
|
+
对比纯字面召回(仅 _keyword_overlap),本方法把被激活唤醒的记忆
|
|
247
|
+
加权拉入候选 → 弱相关但已「活」(激活)的记忆不再漏召。
|
|
248
|
+
精度护栏:字面无重叠且唤醒低于 min_activation 的锚点判为弱边噪声,滤除
|
|
249
|
+
(防止 topo_thresh 过低导致的过激活噪声,见 A/B 实证)。
|
|
250
|
+
"""
|
|
251
|
+
scored = []
|
|
252
|
+
for a_id, a in self._anchors.items():
|
|
253
|
+
lit = _keyword_overlap(query, self._anchor_text(a))
|
|
254
|
+
wake = self.beta * self.activation.get(a_id, 0.0)
|
|
255
|
+
prin = _keyword_overlap(query, a.get("principle", ""))
|
|
256
|
+
if lit <= 0 and prin <= 0 and wake < self.min_activation:
|
|
257
|
+
continue # 纯噪声弱边唤醒:滤除
|
|
258
|
+
score = lit + wake + self.principle_beta * prin
|
|
259
|
+
if score > 0:
|
|
260
|
+
scored.append({
|
|
261
|
+
"anchor_id": a_id,
|
|
262
|
+
"name": a.get("name"),
|
|
263
|
+
"score": round(score, 4),
|
|
264
|
+
"literal": round(lit, 4),
|
|
265
|
+
"principle": round(prin, 4),
|
|
266
|
+
"activation": round(self.activation.get(a_id, 0.0), 3),
|
|
267
|
+
})
|
|
268
|
+
scored.sort(key=lambda x: x["score"], reverse=True)
|
|
269
|
+
return scored[:top_k]
|
|
270
|
+
|
|
271
|
+
def plain_recall(self, query: str, top_k: int = 5) -> list:
|
|
272
|
+
"""纯字面召回基线(无激活唤醒),用于 A/B 对照。"""
|
|
273
|
+
scored = []
|
|
274
|
+
for a_id, a in self._anchors.items():
|
|
275
|
+
lit = _keyword_overlap(query, self._anchor_text(a))
|
|
276
|
+
if lit > 0:
|
|
277
|
+
scored.append({"anchor_id": a_id, "name": a.get("name"),
|
|
278
|
+
"score": round(lit, 4), "literal": round(lit, 4),
|
|
279
|
+
"activation": 0.0})
|
|
280
|
+
scored.sort(key=lambda x: x["score"], reverse=True)
|
|
281
|
+
return scored[:top_k]
|
|
282
|
+
|
|
283
|
+
def snapshot(self) -> dict:
|
|
284
|
+
"""结构化激活态快照(全离散特征,守禁向量)→ 写回 8790 用。"""
|
|
285
|
+
return {
|
|
286
|
+
"ts": int(time.time()),
|
|
287
|
+
"mechanism": "liquid_reweight",
|
|
288
|
+
"tau_band": [self.tau_min, self.tau_max],
|
|
289
|
+
"activation": {k: round(v, 3) for k, v in self.activation.items()},
|
|
290
|
+
"topo_edges": sum(len(v) for v in self.topo.values()) // 2,
|
|
291
|
+
"anchor_count": len(self._anchors),
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
# ── 激活态持久化(跨会话保持「活」,守禁向量:全离散特征)──
|
|
295
|
+
def save(self, path: str = None) -> str:
|
|
296
|
+
"""把激活态 + 参数写本地 JSON(不碰 server、不增 daemon)。
|
|
297
|
+
|
|
298
|
+
返回实际写入路径;persist_path 未设且无参数则跳过返回空串。
|
|
299
|
+
激活态按当前值原样落盘,冷却在 load() 时按时间重算(单一事实源=时间戳)。
|
|
300
|
+
"""
|
|
301
|
+
path = path or self.persist_path
|
|
302
|
+
if not path:
|
|
303
|
+
return ""
|
|
304
|
+
self.last_ts = int(time.time())
|
|
305
|
+
data = {
|
|
306
|
+
"version": 2,
|
|
307
|
+
"mechanism": "liquid_reweight",
|
|
308
|
+
"ts": self.last_ts,
|
|
309
|
+
"tau_band": [self.tau_min, self.tau_max],
|
|
310
|
+
"beta": self.beta,
|
|
311
|
+
"topo_thresh": self.topo_thresh,
|
|
312
|
+
"min_activation": self.min_activation,
|
|
313
|
+
"half_life": self.half_life,
|
|
314
|
+
"activation": {k: round(v, 4) for k, v in self.activation.items()},
|
|
315
|
+
}
|
|
316
|
+
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
|
|
317
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
318
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
319
|
+
return path
|
|
320
|
+
|
|
321
|
+
def load(self, path: str = None) -> bool:
|
|
322
|
+
"""从本地 JSON 恢复激活态,按 half_life 做 TTL 黏滞冷却。
|
|
323
|
+
|
|
324
|
+
冷却模型:cooled = raw * 0.5 ** (dt / half_life),dt=now-ts。
|
|
325
|
+
隔夜(≈12h, half_life=1d)→≈0.71;3天→≈0.125(记忆淡忘但可重激活)。
|
|
326
|
+
返回是否成功加载;文件不存在/损坏返回 False。
|
|
327
|
+
注意:调用方应先 load_anchors() 建骨架,再 load() 覆盖激活(setdefault 不会丢)。
|
|
328
|
+
"""
|
|
329
|
+
path = path or self.persist_path
|
|
330
|
+
if not path or not os.path.exists(path):
|
|
331
|
+
return False
|
|
332
|
+
try:
|
|
333
|
+
with open(path, encoding="utf-8") as f:
|
|
334
|
+
data = json.load(f)
|
|
335
|
+
except Exception:
|
|
336
|
+
return False
|
|
337
|
+
self.tau_min, self.tau_max = data.get("tau_band", [self.tau_min, self.tau_max])
|
|
338
|
+
self.beta = data.get("beta", self.beta)
|
|
339
|
+
self.topo_thresh = data.get("topo_thresh", self.topo_thresh)
|
|
340
|
+
self.min_activation = data.get("min_activation", self.min_activation)
|
|
341
|
+
self.half_life = data.get("half_life", self.half_life)
|
|
342
|
+
self.last_ts = data.get("ts", int(time.time()))
|
|
343
|
+
raw_act = data.get("activation", {})
|
|
344
|
+
now = int(time.time())
|
|
345
|
+
dt = max(0, now - self.last_ts)
|
|
346
|
+
decay = 0.5 ** (dt / max(1, self.half_life))
|
|
347
|
+
self.activation = {k: round(v * decay, 4) for k, v in raw_act.items()}
|
|
348
|
+
return True
|
|
349
|
+
|
|
350
|
+
# ── 写回 8790(隔离 namespace,守禁向量)──
|
|
351
|
+
def snapshot_to_8790(self, backend: str = DEFAULT_BACKEND,
|
|
352
|
+
agent_ns: str = "exp:liquid-reweight",
|
|
353
|
+
run_id: str = "", category: str = "liquid-reweight") -> dict:
|
|
354
|
+
"""把激活态快照结构化写回 8790(隔离 ns,不污染生产)。"""
|
|
355
|
+
b = backend.rstrip("/")
|
|
356
|
+
snap = self.snapshot()
|
|
357
|
+
agent_id = f"{agent_ns}:{run_id}" if run_id else agent_ns
|
|
358
|
+
content = json.dumps(snap, ensure_ascii=False)
|
|
359
|
+
resp = _http_post(f"{b}/remember",
|
|
360
|
+
{"content": content, "category": category, "agent_id": agent_id})
|
|
361
|
+
return {"ok": resp.get("ok", False),
|
|
362
|
+
"nucleated": bool(resp.get("nucleated")),
|
|
363
|
+
"agent_id": agent_id, "raw": resp}
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
# ── 自测 ──
|
|
367
|
+
def _selftest(live: bool = False):
|
|
368
|
+
print("━━━ LiquidReweight selftest ━━━")
|
|
369
|
+
lr = LiquidReweight(beta=0.6, topo_thresh=0.20)
|
|
370
|
+
|
|
371
|
+
# 3 个拓扑相邻锚点(共享「液环」+ 各自领域词,containment 应连边)
|
|
372
|
+
anchors = [
|
|
373
|
+
{"id": "A", "name": "液环禁向量一致性判定", "description": "液环禁用向量embedding做一致性判定与成核"},
|
|
374
|
+
{"id": "B", "name": "液环稳态演化机制", "description": "液环记忆状态是演化对象而非被管理数据"},
|
|
375
|
+
{"id": "C", "name": "液环双轨成核", "description": "液环private与consensus双轨成核机制"},
|
|
376
|
+
]
|
|
377
|
+
lr.load_anchors(anchors)
|
|
378
|
+
print(f" 拓扑边数: {lr.snapshot()['topo_edges']}(应≥2,A-B/A-C/B-C 相邻)")
|
|
379
|
+
assert lr.snapshot()["topo_edges"] >= 2, "自测失败:拓扑未连边"
|
|
380
|
+
|
|
381
|
+
# 注入新证据(命中 A 主题)
|
|
382
|
+
new = "液环禁止向量做一致性判定"
|
|
383
|
+
print(f"\n 注入证据(命中A): {new}")
|
|
384
|
+
deltas = lr.propagate("A", new, steps=1)
|
|
385
|
+
print(f" 激活增量: {deltas}")
|
|
386
|
+
print(f" 激活态: A={lr.activation['A']:.3f} B={lr.activation['B']:.3f} C={lr.activation['C']:.3f}")
|
|
387
|
+
|
|
388
|
+
# 断言①:拓扑传播发生(邻居被唤醒)
|
|
389
|
+
assert lr.activation["B"] > 0, "自测失败:邻居B未被唤醒"
|
|
390
|
+
assert lr.activation["C"] > 0, "自测失败:邻居C未被唤醒"
|
|
391
|
+
# 断言③:黏滞封顶(单次 amp ≤ amp_cap,且邻居不瞬间拉满)
|
|
392
|
+
assert lr.activation["B"] <= lr.amp_cap + 1e-9, f"自测失败:B 激活未封顶({lr.activation['B']})"
|
|
393
|
+
assert lr.activation["C"] <= lr.amp_cap + 1e-9, f"自测失败:C 激活未封顶({lr.activation['C']})"
|
|
394
|
+
print(f" ✓ 传播+黏滞断言通过(B={lr.activation['B']:.3f} C={lr.activation['C']:.3f} ≤ 封顶 {lr.amp_cap})")
|
|
395
|
+
|
|
396
|
+
# 断言②:液态召回唤醒字面不重叠但已激活的记忆
|
|
397
|
+
# query 字面只重叠 B(稳态演化),但 A 因注入已激活满 → 液态版把 A 拉进候选
|
|
398
|
+
q = "演化机制的状态如何"
|
|
399
|
+
plain = lr.plain_recall(q, top_k=3)
|
|
400
|
+
liquid = lr.liquid_recall(q, top_k=3)
|
|
401
|
+
plain_ids = [r["anchor_id"] for r in plain]
|
|
402
|
+
liquid_ids = [r["anchor_id"] for r in liquid]
|
|
403
|
+
print(f"\n 查询: {q}")
|
|
404
|
+
print(f" plain 召回: {plain_ids}(纯字面,A 应缺席)")
|
|
405
|
+
print(f" liquid召回: {liquid_ids}(唤醒,A 应入榜)")
|
|
406
|
+
assert "A" not in plain_ids, "自测失败:plain 竟召回 A(基线异常)"
|
|
407
|
+
assert "A" in liquid_ids, "自测失败:液态召回未唤醒已激活的 A"
|
|
408
|
+
# A 经激活排在 B 之前(记忆流动:激活态主导召回)
|
|
409
|
+
assert liquid_ids[0] == "A", "自测失败:A 未因激活排首位"
|
|
410
|
+
print(f" ✓ 液态唤醒断言通过(A 经激活入榜且排首,记忆非死存储)")
|
|
411
|
+
|
|
412
|
+
# ── v1.3 因果演化循环成核:因果边独立于 keyword 拓扑唤醒远端记忆 ──
|
|
413
|
+
D = {"id": "D", "name": "量子白骨观", "description": "与液环无字面重叠的远端主题,仅靠因果边连接"}
|
|
414
|
+
lr.load_anchors(anchors + [D]) # 重建拓扑(含 D,D 与 A/B/C 无 keyword 连边)
|
|
415
|
+
lr.add_causal_edges([("A", "D", 0.6)]) # A→D 显式因果边,绕过 keyword 噪声过滤
|
|
416
|
+
lr.propagate("A", "液环禁止向量", steps=1)
|
|
417
|
+
print(f"\n 因果边 A→D(0.6),D 激活={lr.activation['D']:.3f}(应>0,尽管字面零重叠)")
|
|
418
|
+
assert lr.activation["D"] > 0, "自测失败:因果边未唤醒远端 D"
|
|
419
|
+
# 因果召回:查询命中 D 主题但因因果边已激活,D 入榜
|
|
420
|
+
q2 = "量子白骨观是什么"
|
|
421
|
+
cr = lr.liquid_recall(q2, top_k=5)
|
|
422
|
+
cr_ids = [r["anchor_id"] for r in cr]
|
|
423
|
+
print(f" 因果查询[{q2}] 召回: {cr_ids}(D 应入榜)")
|
|
424
|
+
assert "D" in cr_ids, "自测失败:因果边未把远端 D 拉入召回"
|
|
425
|
+
print(f" ✓ 因果演化循环成核断言通过(因果边唤醒+召回远端记忆)")
|
|
426
|
+
|
|
427
|
+
# ── v1.4 原理优先成核(MSM 反哺):原理匹配(why)即便字面零重叠也召回 ──
|
|
428
|
+
# E 表面内容与液环无关,但 principle 声明「一致性判定守禁向量」,
|
|
429
|
+
# 查询只命中 principle 关键词 → E 经 principle 通道入榜(generalization)。
|
|
430
|
+
E = {"id": "E", "name": "某远端无关主题", "description": "表面与液环无字面重叠",
|
|
431
|
+
"principle": "一致性判定守禁向量,不依赖 embedding"}
|
|
432
|
+
lr.load_anchors(anchors + [D] + [E]) # 重建拓扑(D 因果边须重加)
|
|
433
|
+
lr.add_causal_edges([("A", "D", 0.6)]) # 因果边不被 load_anchors 保留,重加
|
|
434
|
+
q3 = "一致性判定守禁向量" # 仅命中 E.principle,字面不重叠 E 的 name/desc
|
|
435
|
+
pr = lr.liquid_recall(q3, top_k=5)
|
|
436
|
+
pr_ids = [r["anchor_id"] for r in pr]
|
|
437
|
+
print(f"\n 原理查询[{q3}] 召回: {pr_ids}(E 应入榜,且 literal=0)")
|
|
438
|
+
e_row = next((r for r in pr if r["anchor_id"] == "E"), None)
|
|
439
|
+
assert e_row is not None, "自测失败:原理匹配未召回 E"
|
|
440
|
+
assert e_row["literal"] == 0.0, "自测失败:E 竟有字面重叠(应为纯原理通道)"
|
|
441
|
+
assert e_row["principle"] > 0, "自测失败:E 的 principle 通道未计分"
|
|
442
|
+
print(f" ✓ 原理优先成核断言通过(why 通道唤醒字面零重叠的 E)")
|
|
443
|
+
|
|
444
|
+
# τ(x) 自适应方向校验
|
|
445
|
+
t_hi = lr.tau_x(0.9) # 高重叠 → 小 τ(快)
|
|
446
|
+
t_lo = lr.tau_x(0.1) # 低重叠 → 大 τ(慢)
|
|
447
|
+
print(f"\n τ(x) 自适应: overlap=0.9→τ={t_hi} overlap=0.1→τ={t_lo}(应 t_hi<t_lo)")
|
|
448
|
+
assert t_hi < t_lo, "自测失败:τ(x) 方向反了"
|
|
449
|
+
assert TAU_MIN <= t_hi <= TAU_MAX and TAU_MIN <= t_lo <= TAU_MAX, "自测失败:τ 越窄带"
|
|
450
|
+
print(f" ✓ τ(x) 黏滞窄带断言通过")
|
|
451
|
+
|
|
452
|
+
# 写回实证
|
|
453
|
+
if live:
|
|
454
|
+
print(f"\n → 真实写 8790(隔离 ns exp:liquid-reweight:selftest)...")
|
|
455
|
+
rid = f"selftest:{int(time.time())}"
|
|
456
|
+
res = lr.snapshot_to_8790(run_id=rid)
|
|
457
|
+
print(f" 返回: {json.dumps(res, ensure_ascii=False)}")
|
|
458
|
+
assert res["ok"], f"自测失败:写 8790 未 ok({res['raw']})"
|
|
459
|
+
print(f" ✓ 写回 8790 实证通过(agent_id={res['agent_id']})")
|
|
460
|
+
else:
|
|
461
|
+
snap = lr.snapshot()
|
|
462
|
+
print(f"\n → dry-run 快照: {json.dumps(snap, ensure_ascii=False)[:220]}...")
|
|
463
|
+
print(f" ✓ 逻辑自测通过(--live 可真实写 8790 验证)")
|
|
464
|
+
return True
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def main():
|
|
468
|
+
ap = argparse.ArgumentParser(description="液环 v1.0 液态重排引擎")
|
|
469
|
+
sub = ap.add_subparsers(dest="cmd")
|
|
470
|
+
st = sub.add_parser("selftest", help="单元自测(默认 dry-run)")
|
|
471
|
+
st.add_argument("--live", action="store_true", help="真实写 8790(隔离 exp:liquid-reweight:*)")
|
|
472
|
+
args = ap.parse_args()
|
|
473
|
+
if args.cmd == "selftest":
|
|
474
|
+
_selftest(live=args.live)
|
|
475
|
+
else:
|
|
476
|
+
ap.print_help()
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
if __name__ == "__main__":
|
|
480
|
+
main()
|