liquid-loop 0.6.1__tar.gz → 0.6.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.
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/PKG-INFO +3 -3
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/README.md +2 -2
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/liquid_loop/cli.py +1 -1
- liquid_loop-0.6.4/liquid_loop/cognitive_budget.py +105 -0
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/liquid_loop/entropy.py +1 -1
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/liquid_loop/workspace.py +82 -9
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/liquid_loop.egg-info/PKG-INFO +3 -3
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/liquid_loop.egg-info/SOURCES.txt +3 -0
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/pyproject.toml +1 -1
- liquid_loop-0.6.4/tests/test_peek_seal.py +115 -0
- liquid_loop-0.6.4/tests/test_seal_persistence.py +63 -0
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/tests/test_workspace.py +29 -0
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/LICENSE +0 -0
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/liquid_loop/__init__.py +0 -0
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/liquid_loop/__main__.py +0 -0
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/liquid_loop/storage.py +0 -0
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/liquid_loop.egg-info/dependency_links.txt +0 -0
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/liquid_loop.egg-info/entry_points.txt +0 -0
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/liquid_loop.egg-info/requires.txt +0 -0
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/liquid_loop.egg-info/top_level.txt +0 -0
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/setup.cfg +0 -0
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/tests/test_entropy.py +0 -0
- {liquid_loop-0.6.1 → liquid_loop-0.6.4}/tests/test_self_evolve.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: liquid-loop
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
4
4
|
Summary: Self-Organizing Cognitive Memory for AI Agents — Liquid Loop theory implementation
|
|
5
5
|
Author: fishbook0001
|
|
6
6
|
Maintainer: fishbook0001
|
|
@@ -72,7 +72,7 @@ Dynamic: license-file
|
|
|
72
72
|
| **Anchor** 锚点 | 晶种 | 认知关注点,有稳定性值 s ∈ [0,1] |
|
|
73
73
|
| **Evidence** 证据 | 附着粒子 | 锚点下的具体观察,权重指数衰减 w×0.95ᵗ |
|
|
74
74
|
| **Memory** 结晶 | 结晶体 | 2+ 条一致 Evidence 自动凝聚,有置信度 c |
|
|
75
|
-
| **Entropy** 熵值 | 流体无序度 |
|
|
75
|
+
| **Entropy** 熵值 | 流体无序度 | 八维加权(锚点漂移 / 冲突密度 / 碎片 / 活跃间隔 / 价值衰减 / 锚定强度 / CPE 三维) |
|
|
76
76
|
|
|
77
77
|
**状态判定:**
|
|
78
78
|
```
|
|
@@ -123,7 +123,7 @@ save(state, Path("."))
|
|
|
123
123
|
# 初始化工作区(创建 .liquid/state.json)
|
|
124
124
|
liquid-loop init
|
|
125
125
|
|
|
126
|
-
#
|
|
126
|
+
# 添加锚点(支持自动三维分类:密度 / 认知阶段 / 流动性)
|
|
127
127
|
liquid-loop anchor_add "项目目标" "完成液环论文与开源"
|
|
128
128
|
|
|
129
129
|
# 注入证据
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
| **Anchor** 锚点 | 晶种 | 认知关注点,有稳定性值 s ∈ [0,1] |
|
|
34
34
|
| **Evidence** 证据 | 附着粒子 | 锚点下的具体观察,权重指数衰减 w×0.95ᵗ |
|
|
35
35
|
| **Memory** 结晶 | 结晶体 | 2+ 条一致 Evidence 自动凝聚,有置信度 c |
|
|
36
|
-
| **Entropy** 熵值 | 流体无序度 |
|
|
36
|
+
| **Entropy** 熵值 | 流体无序度 | 八维加权(锚点漂移 / 冲突密度 / 碎片 / 活跃间隔 / 价值衰减 / 锚定强度 / CPE 三维) |
|
|
37
37
|
|
|
38
38
|
**状态判定:**
|
|
39
39
|
```
|
|
@@ -84,7 +84,7 @@ save(state, Path("."))
|
|
|
84
84
|
# 初始化工作区(创建 .liquid/state.json)
|
|
85
85
|
liquid-loop init
|
|
86
86
|
|
|
87
|
-
#
|
|
87
|
+
# 添加锚点(支持自动三维分类:密度 / 认知阶段 / 流动性)
|
|
88
88
|
liquid-loop anchor_add "项目目标" "完成液环论文与开源"
|
|
89
89
|
|
|
90
90
|
# 注入证据
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""认知预算稳态器(落地 PEEK arXiv:2604.09932 固定预算驱逐)
|
|
2
|
+
|
|
3
|
+
把 PEEK 三模块蒸馏并适配液环:
|
|
4
|
+
Distiller evidence.weight + anchor.value_score + recency → 蒸馏价值
|
|
5
|
+
Cartographer 价值 × (0.5+0.5*访问热度) × 流动性 → 重要性
|
|
6
|
+
Evictor 超额时驱逐最低重要性证据,标记 archived(冷归档,零丢失)
|
|
7
|
+
|
|
8
|
+
设计哲学(呼应液环零向量/零丢失):
|
|
9
|
+
驱逐 ≠ 删除。超预算证据标记 archived=True,从活跃检索集移出(recall 跳过),
|
|
10
|
+
但数据与审计链完整保留,可随时解冻。记忆系统因此维持"稳态预算"而非无限膨胀。
|
|
11
|
+
|
|
12
|
+
预算来源:环境变量 LIQUID_EVIDENCE_BUDGET(默认 0 = 不限制)。
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
import os
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
LIQUIDITY_HEAT = {"hot": 1.0, "warm": 0.7, "cold": 0.4, "frozen": 0.1}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class _BudgetItem:
|
|
25
|
+
id: str
|
|
26
|
+
weight: float = 1.0
|
|
27
|
+
anchor_value: float = 1.0
|
|
28
|
+
access_count: int = 0
|
|
29
|
+
liquidity: str = "warm"
|
|
30
|
+
gap_hours: float = 0.0
|
|
31
|
+
distilled_value: float = 0.0
|
|
32
|
+
importance: float = 0.0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _distill(it: _BudgetItem) -> float:
|
|
36
|
+
recency = 1.0 / (1.0 + it.gap_hours / 168.0)
|
|
37
|
+
v = 0.4 * it.weight + 0.3 * it.anchor_value + 0.3 * recency
|
|
38
|
+
it.distilled_value = max(0.0, min(1.0, v))
|
|
39
|
+
return it.distilled_value
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _rank(items: list[_BudgetItem]) -> list[_BudgetItem]:
|
|
43
|
+
for it in items:
|
|
44
|
+
if it.distilled_value == 0.0:
|
|
45
|
+
_distill(it)
|
|
46
|
+
access_heat = min(it.access_count / 10.0, 1.0)
|
|
47
|
+
liquidity_heat = LIQUIDITY_HEAT.get(it.liquidity, 0.4)
|
|
48
|
+
it.importance = it.distilled_value * (0.5 + 0.5 * access_heat) * liquidity_heat
|
|
49
|
+
return sorted(items, key=lambda x: x.importance, reverse=True)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _evict(items: list[_BudgetItem], budget: int) -> list[_BudgetItem]:
|
|
53
|
+
if len(items) <= budget:
|
|
54
|
+
return []
|
|
55
|
+
ranked = _rank(items)
|
|
56
|
+
return ranked[budget:] # 尾部最低重要性
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class CognitiveBudgetStabilizer:
|
|
60
|
+
"""接 WorkspaceState,超预算时冷归档低价值证据"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, state, budget: Optional[int] = None):
|
|
63
|
+
self.state = state
|
|
64
|
+
self.budget = budget if budget is not None else int(os.environ.get("LIQUID_EVIDENCE_BUDGET", "0"))
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def _gap_hours(anchor) -> float:
|
|
68
|
+
if not anchor or not anchor.last_accessed:
|
|
69
|
+
return 9999.0
|
|
70
|
+
try:
|
|
71
|
+
last = datetime.fromisoformat(anchor.last_accessed)
|
|
72
|
+
return (datetime.now(timezone.utc) - last).total_seconds() / 3600
|
|
73
|
+
except ValueError:
|
|
74
|
+
return 9999.0
|
|
75
|
+
|
|
76
|
+
def stabilize(self) -> dict:
|
|
77
|
+
if self.budget <= 0:
|
|
78
|
+
return {"evicted": 0, "budget": 0, "total": len(self.state.evidences),
|
|
79
|
+
"note": "预算未设置(LIQUID_EVIDENCE_BUDGET=0),跳过"}
|
|
80
|
+
evs = self.state.evidences
|
|
81
|
+
if len(evs) <= self.budget:
|
|
82
|
+
return {"evicted": 0, "budget": self.budget, "total": len(evs)}
|
|
83
|
+
anchor_map = {a.id: a for a in self.state.anchors}
|
|
84
|
+
items: list[_BudgetItem] = []
|
|
85
|
+
for e in evs:
|
|
86
|
+
a = anchor_map.get(e.anchor_id)
|
|
87
|
+
items.append(_BudgetItem(
|
|
88
|
+
id=e.id,
|
|
89
|
+
weight=e.weight,
|
|
90
|
+
anchor_value=a.value_score if a else 0.5,
|
|
91
|
+
access_count=a.access_count if a else 0,
|
|
92
|
+
liquidity=a.liquidity if a else "warm",
|
|
93
|
+
gap_hours=self._gap_hours(a),
|
|
94
|
+
))
|
|
95
|
+
evicted = _evict(items, self.budget)
|
|
96
|
+
evicted_ids = {it.id for it in evicted}
|
|
97
|
+
for e in evs:
|
|
98
|
+
if e.id in evicted_ids:
|
|
99
|
+
e.archived = True # 冷归档,零丢失
|
|
100
|
+
return {
|
|
101
|
+
"evicted": len(evicted_ids),
|
|
102
|
+
"evicted_ids": list(evicted_ids),
|
|
103
|
+
"budget": self.budget,
|
|
104
|
+
"total": len(evs),
|
|
105
|
+
}
|
|
@@ -52,7 +52,7 @@ def activity_gap(state: WorkspaceState) -> float:
|
|
|
52
52
|
return min(gap / timedelta(days=7), 1.0)
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
#
|
|
55
|
+
# 熵值分量(综合见 calculate,八维加权含 CPE 三维)
|
|
56
56
|
|
|
57
57
|
def value_decay_entropy(state: WorkspaceState) -> float:
|
|
58
58
|
"""锚点价值衰减熵。0=全部高价值, 1=全部低价值 — 纯函数,不改 state"""
|
|
@@ -9,6 +9,8 @@ import os
|
|
|
9
9
|
import re
|
|
10
10
|
from collections import Counter
|
|
11
11
|
|
|
12
|
+
from .cognitive_budget import CognitiveBudgetStabilizer
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
def now() -> str:
|
|
14
16
|
return datetime.now(timezone.utc).isoformat()
|
|
@@ -63,7 +65,7 @@ class AuditChain:
|
|
|
63
65
|
return expected == stored_hash
|
|
64
66
|
|
|
65
67
|
|
|
66
|
-
# ---
|
|
68
|
+
# --- 三维锚点分类(密度 / 认知阶段 / 流动性)+ 一维证据质量 ---
|
|
67
69
|
DensityLevel = str # "high" | "medium" | "low"
|
|
68
70
|
CognitiveStage = str # "raw" | "wip" | "crystallized" | "tooling"
|
|
69
71
|
LiquidityLevel = str # "hot" | "warm" | "cold" | "frozen"
|
|
@@ -84,6 +86,7 @@ class Anchor:
|
|
|
84
86
|
last_accessed: str = ""
|
|
85
87
|
value_score: float = 1.0
|
|
86
88
|
anchor_strength: float = 1.0
|
|
89
|
+
seal_adjust: float = 0.0 # SEAL 自评层增量;_recalc 合成进 stability,不被覆盖(解 v0.6.3 假落地)
|
|
87
90
|
|
|
88
91
|
def decay_value(self, factor: float = 0.95, evidence_count: int = 0) -> float:
|
|
89
92
|
if not self.created_at:
|
|
@@ -149,6 +152,7 @@ class Evidence:
|
|
|
149
152
|
timestamp: str = field(default_factory=now)
|
|
150
153
|
weight: float = 1.0
|
|
151
154
|
quality: str = "normal"
|
|
155
|
+
archived: bool = False # 预算超额冷归档标记(PEEK 落地,零丢失)
|
|
152
156
|
|
|
153
157
|
|
|
154
158
|
@dataclass
|
|
@@ -266,11 +270,18 @@ class WorkspaceState:
|
|
|
266
270
|
return e
|
|
267
271
|
|
|
268
272
|
def _on_evidence_added(self, anchor_id: str):
|
|
269
|
-
"""
|
|
273
|
+
"""证据添加后的统一后处理:衰减→成核→稳定性刷新→冲突检测→预算稳态"""
|
|
270
274
|
self._decay_anchor(anchor_id)
|
|
271
275
|
self._nucleate(anchor_id)
|
|
272
276
|
self._recalc_anchor(anchor_id)
|
|
273
277
|
self._detect_conflicts(anchor_id)
|
|
278
|
+
self._stabilize_budget()
|
|
279
|
+
|
|
280
|
+
def _stabilize_budget(self):
|
|
281
|
+
"""认知预算稳态(PEEK 落地):超预算时冷归档最低价值证据。默认无预算不动作。"""
|
|
282
|
+
result = CognitiveBudgetStabilizer(self).stabilize()
|
|
283
|
+
if result.get("evicted", 0) > 0:
|
|
284
|
+
self.updated_at = now()
|
|
274
285
|
|
|
275
286
|
def _decay_anchor(self, anchor_id: str):
|
|
276
287
|
for e in self.evidences:
|
|
@@ -278,16 +289,25 @@ class WorkspaceState:
|
|
|
278
289
|
e.weight = max(e.weight * 0.95, 0.1)
|
|
279
290
|
|
|
280
291
|
def _nucleate(self, anchor_id: str):
|
|
281
|
-
"""成核:同锚点下 >= 2
|
|
292
|
+
"""成核:同锚点下 >= 2 条一致证据形成记忆结晶。
|
|
293
|
+
|
|
294
|
+
复合键判定:查重限定在当前锚点内((anchor_id, content)),避免跨锚点
|
|
295
|
+
同 content 证据被错误吞掉或并入同一结晶(P0 跨锚点误结晶修复, v0.6.2)。
|
|
296
|
+
"""
|
|
282
297
|
group = [e for e in self.evidences if e.anchor_id == anchor_id]
|
|
283
298
|
if len(group) < 2:
|
|
284
299
|
return
|
|
285
|
-
|
|
300
|
+
# 本锚点已结晶的 content 集合(复合键判定,杜绝跨锚点污染)
|
|
301
|
+
crystallized_contents: set[str] = set()
|
|
302
|
+
for m in self.memories:
|
|
303
|
+
for eid in m.evidence_ids:
|
|
304
|
+
ev = next((e for e in self.evidences if e.id == eid), None)
|
|
305
|
+
if ev is not None and ev.anchor_id == anchor_id:
|
|
306
|
+
crystallized_contents.add(m.content)
|
|
307
|
+
break
|
|
286
308
|
content_counts = Counter(e.content for e in group)
|
|
287
309
|
for content, count in content_counts.items():
|
|
288
|
-
if count >= 2:
|
|
289
|
-
if any(m.content == content for m in self.memories):
|
|
290
|
-
continue
|
|
310
|
+
if count >= 2 and content not in crystallized_contents:
|
|
291
311
|
evidence_ids = [e.id for e in group if e.content == content]
|
|
292
312
|
confidence = min(count / len(group), 1.0)
|
|
293
313
|
self.memories.append(Memory(
|
|
@@ -351,7 +371,10 @@ class WorkspaceState:
|
|
|
351
371
|
avg_weight = sum(e.weight for e in group) / len(group)
|
|
352
372
|
for a in self.anchors:
|
|
353
373
|
if a.id == anchor_id:
|
|
354
|
-
|
|
374
|
+
# base = 证据权重均值;stability = base + SEAL 自评层(seal_adjust 不被覆盖)
|
|
375
|
+
base = avg_weight
|
|
376
|
+
a.base_stability = base
|
|
377
|
+
a.stability = round(min(1.0, max(0.1, base + a.seal_adjust)), 3)
|
|
355
378
|
evidence_count = len(group)
|
|
356
379
|
a.decay_value(evidence_count=evidence_count)
|
|
357
380
|
a.recalc_strength(evidence_count)
|
|
@@ -842,6 +865,51 @@ class SelfRefineEngine:
|
|
|
842
865
|
repairs.append(f"REPAIR: 为锚点[{target_anchor.name}]新增evidence: {gold[:40]}")
|
|
843
866
|
return repairs
|
|
844
867
|
|
|
868
|
+
# ── SEAL 落地(arXiv:2605.24426 失败诊断双优化)──
|
|
869
|
+
def diagnose(self, failure: Dict[str, Any]) -> Dict[str, Any]:
|
|
870
|
+
"""SEAL 诊断:失败探测归因到锚点,判定失败类型。
|
|
871
|
+
|
|
872
|
+
fail_type:
|
|
873
|
+
retrieval 召回失败(记忆存在但未检索到)→ 策略侧 boost_stability
|
|
874
|
+
reason 推理失败(检索到但判断不中) → 策略侧 downweight_noise
|
|
875
|
+
"""
|
|
876
|
+
reason = failure.get("reason", "")
|
|
877
|
+
if "检索" in reason or "无相关" in reason:
|
|
878
|
+
fail_type = "retrieval"
|
|
879
|
+
else:
|
|
880
|
+
fail_type = "reason"
|
|
881
|
+
source = failure.get("source", "")
|
|
882
|
+
target = None
|
|
883
|
+
if source:
|
|
884
|
+
target = next((a for a in self.state.anchors if a.id == source), None)
|
|
885
|
+
if not target:
|
|
886
|
+
ev = next((e for e in self.state.evidences if e.id == source), None)
|
|
887
|
+
if ev:
|
|
888
|
+
target = next((a for a in self.state.anchors if a.id == ev.anchor_id), None)
|
|
889
|
+
if fail_type == "retrieval":
|
|
890
|
+
tune = "boost_stability"
|
|
891
|
+
root = "召回失败:记忆存在但未检索到(权重/描述召回不足)"
|
|
892
|
+
else:
|
|
893
|
+
tune = "downweight_noise"
|
|
894
|
+
root = "推理失败:检索到但关键词判断不中(噪声/描述歧义)"
|
|
895
|
+
return {"fail_type": fail_type, "target_anchor": target.id if target else None,
|
|
896
|
+
"root_cause": root, "tune": tune}
|
|
897
|
+
|
|
898
|
+
def apply_strategy(self, diag: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
899
|
+
"""SEAL 双优化 - 策略侧:修复确认→boost_stability;噪声→downweight。"""
|
|
900
|
+
if not diag.get("target_anchor"):
|
|
901
|
+
return []
|
|
902
|
+
a = next((x for x in self.state.anchors if x.id == diag["target_anchor"]), None)
|
|
903
|
+
if not a:
|
|
904
|
+
return []
|
|
905
|
+
if diag["tune"] == "boost_stability":
|
|
906
|
+
a.seal_adjust = min(0.5, round(a.seal_adjust + 0.1, 3))
|
|
907
|
+
else:
|
|
908
|
+
a.seal_adjust = max(-0.5, round(a.seal_adjust - 0.1, 3))
|
|
909
|
+
base = getattr(a, "base_stability", a.stability)
|
|
910
|
+
projected = round(min(1.0, max(0.1, base + a.seal_adjust)), 3)
|
|
911
|
+
return [{"op": diag["tune"], "anchor": a.name, "seal_adjust": a.seal_adjust, "projected_stability": projected}]
|
|
912
|
+
|
|
845
913
|
def run(self, evidence_ids: List[str] = None) -> Dict[str, Any]:
|
|
846
914
|
"""执行完整后向自进化周期。"""
|
|
847
915
|
probes = self.generate_probes(evidence_ids)
|
|
@@ -853,10 +921,15 @@ class SelfRefineEngine:
|
|
|
853
921
|
failures = [r for p, r in zip(probes, results)
|
|
854
922
|
if not r.get("passed") and p.get("type") != "overview"]
|
|
855
923
|
repairs = self.repair(failures)
|
|
924
|
+
# SEAL 双优化 - 策略侧:诊断失败 → 调锚点 stability
|
|
925
|
+
diagnoses = [self.diagnose(f) for f in failures]
|
|
926
|
+
strategy_actions = []
|
|
927
|
+
for d in diagnoses:
|
|
928
|
+
strategy_actions.extend(self.apply_strategy(d))
|
|
856
929
|
self.state.self_refine_probes = probes
|
|
857
930
|
self.state.self_refine_results = results
|
|
858
931
|
self.state.self_refine_repair_count += len(repairs)
|
|
859
|
-
return {"total": len(probes), "passed": passed, "failed": len(failures), "pass_rate": round(passed / len(probes), 2), "repairs": repairs, "failed_details": [{"q": r.get("question", "")[:60], "reason": r.get("reason", "")} for r in failures[:5]]}
|
|
932
|
+
return {"total": len(probes), "passed": passed, "failed": len(failures), "pass_rate": round(passed / len(probes), 2), "repairs": repairs, "diagnoses": diagnoses, "strategy_actions": strategy_actions, "failed_details": [{"q": r.get("question", "")[:60], "reason": r.get("reason", "")} for r in failures[:5]]}
|
|
860
933
|
|
|
861
934
|
|
|
862
935
|
# ==============================================================================
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: liquid-loop
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
4
4
|
Summary: Self-Organizing Cognitive Memory for AI Agents — Liquid Loop theory implementation
|
|
5
5
|
Author: fishbook0001
|
|
6
6
|
Maintainer: fishbook0001
|
|
@@ -72,7 +72,7 @@ Dynamic: license-file
|
|
|
72
72
|
| **Anchor** 锚点 | 晶种 | 认知关注点,有稳定性值 s ∈ [0,1] |
|
|
73
73
|
| **Evidence** 证据 | 附着粒子 | 锚点下的具体观察,权重指数衰减 w×0.95ᵗ |
|
|
74
74
|
| **Memory** 结晶 | 结晶体 | 2+ 条一致 Evidence 自动凝聚,有置信度 c |
|
|
75
|
-
| **Entropy** 熵值 | 流体无序度 |
|
|
75
|
+
| **Entropy** 熵值 | 流体无序度 | 八维加权(锚点漂移 / 冲突密度 / 碎片 / 活跃间隔 / 价值衰减 / 锚定强度 / CPE 三维) |
|
|
76
76
|
|
|
77
77
|
**状态判定:**
|
|
78
78
|
```
|
|
@@ -123,7 +123,7 @@ save(state, Path("."))
|
|
|
123
123
|
# 初始化工作区(创建 .liquid/state.json)
|
|
124
124
|
liquid-loop init
|
|
125
125
|
|
|
126
|
-
#
|
|
126
|
+
# 添加锚点(支持自动三维分类:密度 / 认知阶段 / 流动性)
|
|
127
127
|
liquid-loop anchor_add "项目目标" "完成液环论文与开源"
|
|
128
128
|
|
|
129
129
|
# 注入证据
|
|
@@ -4,6 +4,7 @@ pyproject.toml
|
|
|
4
4
|
liquid_loop/__init__.py
|
|
5
5
|
liquid_loop/__main__.py
|
|
6
6
|
liquid_loop/cli.py
|
|
7
|
+
liquid_loop/cognitive_budget.py
|
|
7
8
|
liquid_loop/entropy.py
|
|
8
9
|
liquid_loop/storage.py
|
|
9
10
|
liquid_loop/workspace.py
|
|
@@ -14,5 +15,7 @@ liquid_loop.egg-info/entry_points.txt
|
|
|
14
15
|
liquid_loop.egg-info/requires.txt
|
|
15
16
|
liquid_loop.egg-info/top_level.txt
|
|
16
17
|
tests/test_entropy.py
|
|
18
|
+
tests/test_peek_seal.py
|
|
19
|
+
tests/test_seal_persistence.py
|
|
17
20
|
tests/test_self_evolve.py
|
|
18
21
|
tests/test_workspace.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "liquid-loop"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.4"
|
|
8
8
|
description = "Self-Organizing Cognitive Memory for AI Agents — Liquid Loop theory implementation"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""PEEK 预算稳态 + SEAL 双优化 落地测试(液环仓库内)"""
|
|
2
|
+
import pytest
|
|
3
|
+
from liquid_loop.workspace import WorkspaceState, SelfRefineEngine
|
|
4
|
+
from liquid_loop.cognitive_budget import CognitiveBudgetStabilizer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# ───────────── PEEK 落地:认知预算稳态器 ─────────────
|
|
8
|
+
|
|
9
|
+
def test_budget_stabilizer_evicts_low_value(monkeypatch):
|
|
10
|
+
monkeypatch.setenv("LIQUID_EVIDENCE_BUDGET", "3")
|
|
11
|
+
st = WorkspaceState()
|
|
12
|
+
a_hot = st.add_anchor("hot")
|
|
13
|
+
a_hot.value_score = 0.9
|
|
14
|
+
a_hot.access_count = 20
|
|
15
|
+
a_hot.liquidity = "hot"
|
|
16
|
+
a_cold = st.add_anchor("cold")
|
|
17
|
+
a_cold.value_score = 0.2
|
|
18
|
+
a_cold.access_count = 0
|
|
19
|
+
a_cold.liquidity = "frozen"
|
|
20
|
+
for i in range(3):
|
|
21
|
+
st.add_evidence("hot", f"重要证据{i} 液环锚点")
|
|
22
|
+
for i in range(3):
|
|
23
|
+
st.add_evidence("cold", f"陈旧证据{i} 旧记忆")
|
|
24
|
+
# 6 条证据,预算 3 → 应冷归档 3 条最低价值(cold 锚点)
|
|
25
|
+
arch = [e for e in st.evidences if e.archived]
|
|
26
|
+
assert len(arch) == 3
|
|
27
|
+
assert all(e.anchor_id == a_cold.id for e in arch)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_budget_no_limit_when_unset(monkeypatch):
|
|
31
|
+
monkeypatch.delenv("LIQUID_EVIDENCE_BUDGET", raising=False)
|
|
32
|
+
st = WorkspaceState()
|
|
33
|
+
a = st.add_anchor("x")
|
|
34
|
+
for i in range(10):
|
|
35
|
+
st.add_evidence("x", f"证据{i}")
|
|
36
|
+
arch = [e for e in st.evidences if e.archived]
|
|
37
|
+
assert arch == [] # 无预算不驱逐
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_budget_stabilizer_direct_call(monkeypatch):
|
|
41
|
+
monkeypatch.setenv("LIQUID_EVIDENCE_BUDGET", "2")
|
|
42
|
+
st = WorkspaceState()
|
|
43
|
+
a = st.add_anchor("x")
|
|
44
|
+
a.value_score = 0.5
|
|
45
|
+
for i in range(5):
|
|
46
|
+
st.add_evidence("x", f"证据{i}")
|
|
47
|
+
res = CognitiveBudgetStabilizer(st).stabilize()
|
|
48
|
+
assert res["evicted"] == 3
|
|
49
|
+
assert res["total"] == 5
|
|
50
|
+
assert res["budget"] == 2
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ───────────── SEAL 落地:失败诊断双优化 ─────────────
|
|
54
|
+
|
|
55
|
+
def test_seal_diagnose_retrieval():
|
|
56
|
+
st = WorkspaceState()
|
|
57
|
+
a = st.add_anchor("液环", description="液环记忆理论")
|
|
58
|
+
st.add_evidence("液环", "液环锚点结晶")
|
|
59
|
+
eng = SelfRefineEngine(st)
|
|
60
|
+
failure = {"source": a.id, "reason": "检索失败,无相关记忆", "question": "?", "gold": "?"}
|
|
61
|
+
diag = eng.diagnose(failure)
|
|
62
|
+
assert diag["fail_type"] == "retrieval"
|
|
63
|
+
assert diag["target_anchor"] == a.id
|
|
64
|
+
assert diag["tune"] == "boost_stability"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_seal_diagnose_reason_downweights():
|
|
68
|
+
st = WorkspaceState()
|
|
69
|
+
a = st.add_anchor("稳态")
|
|
70
|
+
eng = SelfRefineEngine(st)
|
|
71
|
+
failure = {"source": a.id, "reason": "关键词不匹配,gold=稳态"}
|
|
72
|
+
diag = eng.diagnose(failure)
|
|
73
|
+
assert diag["fail_type"] == "reason"
|
|
74
|
+
eng.apply_strategy(diag)
|
|
75
|
+
# v0.6.4:改 seal_adjust 层,不直接动 stability
|
|
76
|
+
assert a.seal_adjust == -0.1
|
|
77
|
+
st.add_evidence(a, "补充证据") # 触发 _recalc 合成
|
|
78
|
+
assert a.stability == round(min(1.0, max(0.1, a.base_stability + a.seal_adjust)), 3)
|
|
79
|
+
assert a.stability < a.base_stability # 负增量生效,自洽性降权
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_seal_apply_strategy_boost():
|
|
83
|
+
st = WorkspaceState()
|
|
84
|
+
a = st.add_anchor("液环")
|
|
85
|
+
eng = SelfRefineEngine(st)
|
|
86
|
+
diag = {"fail_type": "retrieval", "target_anchor": a.id, "tune": "boost_stability"}
|
|
87
|
+
acts = eng.apply_strategy(diag)
|
|
88
|
+
assert acts[0]["op"] == "boost_stability"
|
|
89
|
+
assert a.seal_adjust == 0.1
|
|
90
|
+
st.add_evidence(a, "补充证据") # 触发 _recalc 合成
|
|
91
|
+
assert a.stability == round(min(1.0, max(0.1, a.base_stability + a.seal_adjust)), 3)
|
|
92
|
+
assert a.stability >= a.base_stability # 正增量生效
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_seal_run_integration_dual_optimize(monkeypatch):
|
|
96
|
+
st = WorkspaceState()
|
|
97
|
+
a = st.add_anchor("液环", description="液环")
|
|
98
|
+
st.add_evidence("液环", "液环锚点")
|
|
99
|
+
eng = SelfRefineEngine(st)
|
|
100
|
+
|
|
101
|
+
def fake_verify(p):
|
|
102
|
+
if p.get("type") == "fact":
|
|
103
|
+
# 模拟"检索失败但记忆应存在":source 指向锚点,gold 为新内容(需补证据)
|
|
104
|
+
return {"passed": False, "reason": "检索失败,无相关记忆",
|
|
105
|
+
"question": p.get("question", ""), "gold": "全新记忆内容需补回",
|
|
106
|
+
"source": a.id}
|
|
107
|
+
return {"passed": True, "reason": "ok"}
|
|
108
|
+
|
|
109
|
+
monkeypatch.setattr(eng, "verify", fake_verify)
|
|
110
|
+
out = eng.run()
|
|
111
|
+
assert out["failed"] >= 1
|
|
112
|
+
assert len(out["strategy_actions"]) >= 1
|
|
113
|
+
assert out["strategy_actions"][0]["op"] == "boost_stability"
|
|
114
|
+
# 双优化:memory 侧(repairs)与 strategy 侧(strategy_actions)都应存在
|
|
115
|
+
assert len(out["repairs"]) >= 1
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""E8/v0.6.4 — 验证 SEAL 双优化不再被 _recalc_anchor 覆盖(解 v0.6.3 假落地)。
|
|
2
|
+
|
|
3
|
+
修复核心:stability = base(证据权重均值) + seal_adjust(SEAL 自评层增量);
|
|
4
|
+
apply_strategy 改 seal_adjust 而非直接动 stability,_recalc 合成时保留。
|
|
5
|
+
|
|
6
|
+
本测试直接构造诊断(绕过 SelfRefineEngine.verify 的脆弱归因),专注验证
|
|
7
|
+
apply_strategy → seal_adjust → _recalc 合成的持久性。
|
|
8
|
+
"""
|
|
9
|
+
from liquid_loop.workspace import WorkspaceState, SelfRefineEngine
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _boost(a, eng):
|
|
13
|
+
diag = {"fail_type": "retrieval", "target_anchor": a.id,
|
|
14
|
+
"root_cause": "召回失败", "tune": "boost_stability"}
|
|
15
|
+
return eng.apply_strategy(diag)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _down(a, eng):
|
|
19
|
+
diag = {"fail_type": "reason", "target_anchor": a.id,
|
|
20
|
+
"root_cause": "推理失败", "tune": "downweight_noise"}
|
|
21
|
+
return eng.apply_strategy(diag)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_seal_boost_persists_through_recalc():
|
|
25
|
+
"""SEAL boost 后即便再 add_evidence 触发 _recalc,stability 仍含 seal_adjust。"""
|
|
26
|
+
st = WorkspaceState()
|
|
27
|
+
eng = SelfRefineEngine(st)
|
|
28
|
+
a = st.add_anchor("probe_seal")
|
|
29
|
+
st.add_evidence(a, "初始证据")
|
|
30
|
+
_boost(a, eng)
|
|
31
|
+
assert a.seal_adjust == 0.1
|
|
32
|
+
st.add_evidence(a, "再来一条证据") # 触发 _recalc_anchor
|
|
33
|
+
assert a.seal_adjust == 0.1 # 增量持久
|
|
34
|
+
expected = round(min(1.0, max(0.1, a.base_stability + a.seal_adjust)), 3)
|
|
35
|
+
assert a.stability == expected
|
|
36
|
+
assert a.stability >= a.base_stability # 正增量应 >= base
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_seal_downweight():
|
|
40
|
+
st = WorkspaceState()
|
|
41
|
+
eng = SelfRefineEngine(st)
|
|
42
|
+
a = st.add_anchor("noise")
|
|
43
|
+
st.add_evidence(a, "x")
|
|
44
|
+
acts = _down(a, eng)
|
|
45
|
+
assert a.seal_adjust == -0.1
|
|
46
|
+
assert acts[0]["op"] == "downweight_noise"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_seal_adjust_applied_in_recalc():
|
|
50
|
+
"""开 SEAL 的锚点 stability = base + seal_adjust;对照无 SEAL 锚点 adjust=0。"""
|
|
51
|
+
st = WorkspaceState()
|
|
52
|
+
eng = SelfRefineEngine(st)
|
|
53
|
+
a = st.add_anchor("x")
|
|
54
|
+
st.add_evidence(a, "e1")
|
|
55
|
+
_boost(a, eng)
|
|
56
|
+
st.add_evidence(a, "e2") # 触发 _recalc
|
|
57
|
+
# 开 SEAL:stability 合成 seal_adjust(含 clamp,与期望值精确匹配)
|
|
58
|
+
assert a.stability == round(min(1.0, max(0.1, a.base_stability + a.seal_adjust)), 3)
|
|
59
|
+
# 对照:无 SEAL 的锚点 seal_adjust=0,stability=base
|
|
60
|
+
b = st.add_anchor("y")
|
|
61
|
+
st.add_evidence(b, "f1")
|
|
62
|
+
assert b.seal_adjust == 0.0
|
|
63
|
+
assert abs(b.stability - b.base_stability) < 1e-6
|
|
@@ -43,3 +43,32 @@ def test_auto_describe_anchor():
|
|
|
43
43
|
state._nucleate(a.id)
|
|
44
44
|
# 如果有高置信结晶,描述会被填充
|
|
45
45
|
# 这里主要测试不报错
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_nucleate_no_cross_anchor_pollution():
|
|
49
|
+
"""P0 回归(v0.6.2):两锚点存在相同 content 证据时各自独立成核,不互相吞噬/污染。
|
|
50
|
+
|
|
51
|
+
修复前 `_nucleate` 用全局 `m.content == content` 查重,不限定 anchor_id:
|
|
52
|
+
锚点 A 已结晶 content=X 后,锚点 B 同 content 证据被 `continue` 跳过 →
|
|
53
|
+
仅 1 条结晶且 B 的证据丢失。修复后应为 2 条结晶,且各自 evidence_ids 不跨锚点。
|
|
54
|
+
"""
|
|
55
|
+
state = WorkspaceState()
|
|
56
|
+
a1 = state.add_anchor('锚点A')
|
|
57
|
+
a2 = state.add_anchor('锚点B')
|
|
58
|
+
state.add_evidence(a1.id, 'shared fact')
|
|
59
|
+
state.add_evidence(a1.id, 'shared fact')
|
|
60
|
+
state.add_evidence(a2.id, 'shared fact')
|
|
61
|
+
state.add_evidence(a2.id, 'shared fact')
|
|
62
|
+
|
|
63
|
+
# 每个锚点各成核 1 条结晶(共 2 条)
|
|
64
|
+
assert len(state.memories) == 2
|
|
65
|
+
# 每条结晶的 evidence_ids 必须只属于单一锚点(无跨锚点污染)
|
|
66
|
+
for m in state.memories:
|
|
67
|
+
evs = [e for e in state.evidences if e.id in m.evidence_ids]
|
|
68
|
+
assert len({e.anchor_id for e in evs}) == 1
|
|
69
|
+
# 锚点 A 与 B 的结晶分别存在
|
|
70
|
+
a1_ev_ids = {e.id for e in state.evidences if e.anchor_id == a1.id}
|
|
71
|
+
a2_ev_ids = {e.id for e in state.evidences if e.anchor_id == a2.id}
|
|
72
|
+
assert any(set(m.evidence_ids) == a1_ev_ids for m in state.memories)
|
|
73
|
+
assert any(set(m.evidence_ids) == a2_ev_ids for m in state.memories)
|
|
74
|
+
|
|
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
|