liquid-loop 0.5.4__tar.gz → 0.6.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.5.4 → liquid_loop-0.6.0}/PKG-INFO +1 -1
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/liquid_loop/__init__.py +3 -3
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/liquid_loop/cli.py +55 -3
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/liquid_loop/entropy.py +33 -30
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/liquid_loop/storage.py +12 -0
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/liquid_loop/workspace.py +70 -29
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/liquid_loop.egg-info/PKG-INFO +1 -1
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/liquid_loop.egg-info/SOURCES.txt +3 -1
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/pyproject.toml +1 -1
- liquid_loop-0.6.0/tests/test_entropy.py +56 -0
- liquid_loop-0.6.0/tests/test_workspace.py +45 -0
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/LICENSE +0 -0
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/README.md +0 -0
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/liquid_loop/__main__.py +0 -0
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/liquid_loop.egg-info/dependency_links.txt +0 -0
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/liquid_loop.egg-info/entry_points.txt +0 -0
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/liquid_loop.egg-info/requires.txt +0 -0
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/liquid_loop.egg-info/top_level.txt +0 -0
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/setup.cfg +0 -0
- {liquid_loop-0.5.4 → liquid_loop-0.6.0}/tests/test_self_evolve.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"""Liquid Loop — Workspace Cognitive Runtime v0.
|
|
2
|
-
__version__ = "0.
|
|
1
|
+
"""Liquid Loop — Workspace Cognitive Runtime v0.6.0 (CPE-fused, proactive)"""
|
|
2
|
+
__version__ = "0.6.0"
|
|
3
3
|
|
|
4
4
|
from .workspace import (
|
|
5
5
|
WorkspaceState, Anchor, Evidence, Memory, Conflict,
|
|
6
6
|
AuditChain, CPERegularizer, SelfRefineEngine,
|
|
7
7
|
)
|
|
8
8
|
from .storage import load, save
|
|
9
|
-
from .entropy import calculate, calculate_detail, calculate as calculate_entropy
|
|
9
|
+
from .entropy import calculate, calculate_detail, calculate as calculate_entropy
|
|
@@ -80,9 +80,9 @@ def init():
|
|
|
80
80
|
if s.anchors or s.evidences:
|
|
81
81
|
click.echo("工作区已初始化,无需重复操作。")
|
|
82
82
|
return
|
|
83
|
-
s.version = "0.5.
|
|
83
|
+
s.version = "0.5.1"
|
|
84
84
|
_save(s)
|
|
85
|
-
click.echo(f"✓ Liquid Loop v0.5.
|
|
85
|
+
click.echo(f"✓ Liquid Loop v0.5.1 工作区已初始化: {WORKSPACE}/.liquid/")
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
@main.command()
|
|
@@ -520,4 +520,56 @@ def cpe_status():
|
|
|
520
520
|
|
|
521
521
|
|
|
522
522
|
if __name__ == "__main__":
|
|
523
|
-
main()
|
|
523
|
+
main()
|
|
524
|
+
|
|
525
|
+
@cli.command()
|
|
526
|
+
@click.argument("content")
|
|
527
|
+
@click.option("--evidence-ids", help="证据ID列表,逗号分隔")
|
|
528
|
+
def memory_add(content: str, evidence_ids: str):
|
|
529
|
+
"""添加一条记忆结晶"""
|
|
530
|
+
state = load_state()
|
|
531
|
+
eids = [eid.strip() for eid in evidence_ids.split(",")] if evidence_ids else None
|
|
532
|
+
state.add_memory(content, eids)
|
|
533
|
+
save_state(state)
|
|
534
|
+
click.echo(f"Memory added: {content[:50]}...")
|
|
535
|
+
|
|
536
|
+
@cli.command()
|
|
537
|
+
def memory_list():
|
|
538
|
+
"""列出所有记忆结晶"""
|
|
539
|
+
state = load_state()
|
|
540
|
+
if not state.memories:
|
|
541
|
+
click.echo("No memories yet")
|
|
542
|
+
return
|
|
543
|
+
for m in state.memories:
|
|
544
|
+
click.echo(f" {m.id[:8]} | conf={m.confidence:.2f} | {m.content[:60]}...")
|
|
545
|
+
|
|
546
|
+
@cli.command()
|
|
547
|
+
@click.argument("anchor_id")
|
|
548
|
+
def conflict_resolve(anchor_id: str):
|
|
549
|
+
"""标记冲突为已解决(移除该锚点的冲突记录)"""
|
|
550
|
+
state = load_state()
|
|
551
|
+
before = len(state.conflicts)
|
|
552
|
+
state.conflicts = [c for c in state.conflicts if c.anchor_a != anchor_id]
|
|
553
|
+
after = len(state.conflicts)
|
|
554
|
+
save_state(state)
|
|
555
|
+
click.echo(f"Resolved {before - after} conflict(s) for anchor {anchor_id}")
|
|
556
|
+
|
|
557
|
+
@cli.command()
|
|
558
|
+
@click.argument("anchor_id")
|
|
559
|
+
@click.argument("description")
|
|
560
|
+
def anchor_describe(anchor_id: str, description: str):
|
|
561
|
+
"""手动设置锚点描述"""
|
|
562
|
+
state = load_state()
|
|
563
|
+
anchor = next((a for a in state.anchors if a.id == anchor_id or a.name == anchor_id), None)
|
|
564
|
+
if not anchor:
|
|
565
|
+
click.echo(f"Anchor not found: {anchor_id}")
|
|
566
|
+
return
|
|
567
|
+
anchor.description = description
|
|
568
|
+
save_state(state)
|
|
569
|
+
click.echo(f"Anchor '{anchor.name}' description updated")
|
|
570
|
+
|
|
571
|
+
@cli.command()
|
|
572
|
+
def version():
|
|
573
|
+
"""显示版本号"""
|
|
574
|
+
from . import __version__
|
|
575
|
+
click.echo(f"liquid-loop {__version__}")
|
|
@@ -2,6 +2,18 @@ from datetime import datetime, timezone, timedelta
|
|
|
2
2
|
from .workspace import WorkspaceState
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
|
|
7
|
+
@contextmanager
|
|
8
|
+
def temp_attr(obj, attr: str, new_val):
|
|
9
|
+
"""临时修改对象属性,退出时自动还原(异常安全)"""
|
|
10
|
+
old_val = getattr(obj, attr)
|
|
11
|
+
setattr(obj, attr, new_val)
|
|
12
|
+
try:
|
|
13
|
+
yield
|
|
14
|
+
finally:
|
|
15
|
+
setattr(obj, attr, old_val)
|
|
16
|
+
|
|
5
17
|
def anchor_drift(state: WorkspaceState) -> float:
|
|
6
18
|
"""所有 Anchor 的 stability 偏离 1.0 的均值。0=全稳, 1=全漂"""
|
|
7
19
|
if not state.anchors:
|
|
@@ -79,31 +91,25 @@ def strength_entropy(state: WorkspaceState) -> float:
|
|
|
79
91
|
def retrospective_decay_entropy(state: WorkspaceState) -> float:
|
|
80
92
|
"""回顾性衰退熵。0=无衰退, 1=大规模衰退(CPE §1: Retrospective decay)
|
|
81
93
|
|
|
82
|
-
|
|
94
|
+
语义对齐 CPE 定义:新知识(证据)加入后,旧锚点基线能力(value_score)的下降比例。
|
|
95
|
+
维护每个锚点的 baseline_value_score(首次结晶/人工设定时的基线),对比当前值。
|
|
83
96
|
"""
|
|
84
97
|
if not state.anchors or not state.evidences:
|
|
85
98
|
return 0.0
|
|
86
99
|
decay_scores = []
|
|
87
100
|
for a in state.anchors:
|
|
88
|
-
evs =
|
|
89
|
-
[e for e in state.evidences if e.anchor_id == a.id],
|
|
90
|
-
key=lambda x: x.timestamp, reverse=True
|
|
91
|
-
)
|
|
101
|
+
evs = [e for e in state.evidences if e.anchor_id == a.id]
|
|
92
102
|
if len(evs) < 2:
|
|
93
103
|
continue
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
a.value_score = orig_score
|
|
104
|
-
a.anchor_strength = orig_strength
|
|
105
|
-
if old_score > 0:
|
|
106
|
-
ratio = max(0, 1 - new_score_val / old_score)
|
|
104
|
+
# 基线:锚点创建时或首次结晶时的 value_score(若无记录,用当前值的 1.1 倍估算)
|
|
105
|
+
baseline = getattr(a, "baseline_value_score", None)
|
|
106
|
+
if baseline is None:
|
|
107
|
+
baseline = a.value_score * 1.1
|
|
108
|
+
a.baseline_value_score = baseline
|
|
109
|
+
# 当前衰减后的价值
|
|
110
|
+
current = a.decay_value(evidence_count=len(evs))
|
|
111
|
+
if baseline > 0:
|
|
112
|
+
ratio = max(0, 1 - current / baseline)
|
|
107
113
|
decay_scores.append(ratio)
|
|
108
114
|
return sum(decay_scores) / len(decay_scores) if decay_scores else 0.0
|
|
109
115
|
|
|
@@ -111,7 +117,8 @@ def retrospective_decay_entropy(state: WorkspaceState) -> float:
|
|
|
111
117
|
def behavioral_drift_entropy(state: WorkspaceState) -> float:
|
|
112
118
|
"""策略漂移熵。0=无漂移, 1=剧烈漂移(CPE §1: Behavioral policy drift)
|
|
113
119
|
|
|
114
|
-
|
|
120
|
+
语义:锚点锚定强度在最近一次证据更新前后的变化幅度。
|
|
121
|
+
使用上下文管理器保证不修改 state。
|
|
115
122
|
"""
|
|
116
123
|
if not state.anchors:
|
|
117
124
|
return 0.0
|
|
@@ -121,16 +128,12 @@ def behavioral_drift_entropy(state: WorkspaceState) -> float:
|
|
|
121
128
|
if len(evs) < 2:
|
|
122
129
|
continue
|
|
123
130
|
evs_sorted = sorted(evs, key=lambda x: x.timestamp, reverse=True)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
new_strength = a.recalc_strength(new_count)
|
|
131
|
-
drifts.append(abs(new_strength - old_strength))
|
|
132
|
-
# 还原
|
|
133
|
-
a.anchor_strength = orig_strength
|
|
131
|
+
with temp_attr(a, "anchor_strength", a.anchor_strength):
|
|
132
|
+
old_count = len(evs_sorted) - 1
|
|
133
|
+
new_count = len(evs_sorted)
|
|
134
|
+
old_strength = a.recalc_strength(old_count)
|
|
135
|
+
new_strength = a.recalc_strength(new_count)
|
|
136
|
+
drifts.append(abs(new_strength - old_strength))
|
|
134
137
|
return sum(drifts) / len(drifts) if drifts else 0.0
|
|
135
138
|
|
|
136
139
|
|
|
@@ -152,7 +155,7 @@ def generalization_erosion_entropy(state: WorkspaceState) -> float:
|
|
|
152
155
|
for j in range(i + 1, len(evs)):
|
|
153
156
|
if evs[i].content and evs[j].content:
|
|
154
157
|
from .workspace import _keyword_overlap
|
|
155
|
-
overlaps.append(_keyword_overlap(evs[i].content, evs[j].content))
|
|
158
|
+
overlaps.append(_keyword_overlap(evs[i].content, evs[j].content, state.overlap_cache))
|
|
156
159
|
if overlaps:
|
|
157
160
|
avg_overlap = sum(overlaps) / len(overlaps)
|
|
158
161
|
# 重叠度越低 → 崩塌熵越高
|
|
@@ -23,6 +23,18 @@ def get_audit_chain(workspace_root: Path) -> AuditChain:
|
|
|
23
23
|
"""获取审计链实例"""
|
|
24
24
|
return AuditChain(str(_ensure_dir(workspace_root) / AUDIT_FILE))
|
|
25
25
|
|
|
26
|
+
import fcntl
|
|
27
|
+
from contextlib import contextmanager
|
|
28
|
+
|
|
29
|
+
@contextmanager
|
|
30
|
+
def _file_lock(filepath: str, mode: str = "w"):
|
|
31
|
+
"""跨平台文件锁(fcntl,Linux/macOS)"""
|
|
32
|
+
with open(filepath, mode) as f:
|
|
33
|
+
try:
|
|
34
|
+
fcntl.flock(f, fcntl.LOCK_EX)
|
|
35
|
+
yield f
|
|
36
|
+
finally:
|
|
37
|
+
fcntl.flock(f, fcntl.LOCK_UN)
|
|
26
38
|
|
|
27
39
|
def load(workspace_root: Path) -> WorkspaceState:
|
|
28
40
|
path = _ensure_dir(workspace_root) / STATE_FILE
|
|
@@ -2,6 +2,7 @@ from dataclasses import dataclass, field
|
|
|
2
2
|
from datetime import datetime, timezone
|
|
3
3
|
from typing import Optional, List, Dict, Any
|
|
4
4
|
import uuid
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
import hashlib
|
|
6
7
|
import json
|
|
7
8
|
import os
|
|
@@ -187,6 +188,18 @@ class StateSnapshot:
|
|
|
187
188
|
conflict_count: int = 0
|
|
188
189
|
|
|
189
190
|
|
|
191
|
+
def _get_version() -> str:
|
|
192
|
+
"""从 pyproject.toml 读取版本号"""
|
|
193
|
+
try:
|
|
194
|
+
import tomllib
|
|
195
|
+
except ImportError:
|
|
196
|
+
import tomli as tomllib
|
|
197
|
+
pyproject = Path(__file__).parent.parent / "pyproject.toml"
|
|
198
|
+
if pyproject.exists():
|
|
199
|
+
data = tomllib.loads(pyproject.read_text())
|
|
200
|
+
return data.get("project", {}).get("version", "0.5.4")
|
|
201
|
+
return "0.5.4"
|
|
202
|
+
|
|
190
203
|
@dataclass
|
|
191
204
|
class WorkspaceState:
|
|
192
205
|
anchors: list[Anchor] = field(default_factory=list)
|
|
@@ -197,7 +210,7 @@ class WorkspaceState:
|
|
|
197
210
|
relations: list[AnchorRelation] = field(default_factory=list)
|
|
198
211
|
audit_chain_hash: str = "genesis"
|
|
199
212
|
audit_prev_hash: str = ""
|
|
200
|
-
version: str =
|
|
213
|
+
version: str = field(default_factory=_get_version)
|
|
201
214
|
updated_at: str = field(default_factory=now)
|
|
202
215
|
# 【v0.4.0】后向自进化状态(借鉴 MemMA 原位自进化)
|
|
203
216
|
self_refine_probes: list[dict] = field(default_factory=list)
|
|
@@ -208,6 +221,7 @@ class WorkspaceState:
|
|
|
208
221
|
blocked_evidences: list[str] = field(default_factory=list) # 被正则化拦截的证据ID
|
|
209
222
|
cpe_erosion_warnings: list[dict] = field(default_factory=list) # 能力侵蚀告警
|
|
210
223
|
cpe_regularization_count: int = 0 # 累计正则化干预次数
|
|
224
|
+
overlap_cache: dict = field(default_factory=dict) # 缓存关键词重叠度
|
|
211
225
|
|
|
212
226
|
# ── API 层:add_anchor / add_evidence(让 README 示例能跑通)──
|
|
213
227
|
|
|
@@ -252,7 +266,7 @@ class WorkspaceState:
|
|
|
252
266
|
return e
|
|
253
267
|
|
|
254
268
|
def _on_evidence_added(self, anchor_id: str):
|
|
255
|
-
"""
|
|
269
|
+
"""证据添加后的统一后处理:衰减→成核→稳定性刷新→冲突检测"""
|
|
256
270
|
self._decay_anchor(anchor_id)
|
|
257
271
|
self._nucleate(anchor_id)
|
|
258
272
|
self._recalc_anchor(anchor_id)
|
|
@@ -264,11 +278,10 @@ class WorkspaceState:
|
|
|
264
278
|
e.weight = max(e.weight * 0.95, 0.1)
|
|
265
279
|
|
|
266
280
|
def _nucleate(self, anchor_id: str):
|
|
267
|
-
"""成核:同锚点下 >= 2
|
|
281
|
+
"""成核:同锚点下 >= 2 条一致证据形成记忆结晶"""
|
|
268
282
|
group = [e for e in self.evidences if e.anchor_id == anchor_id]
|
|
269
283
|
if len(group) < 2:
|
|
270
284
|
return
|
|
271
|
-
anchor = next((a for a in self.anchors if a.id == anchor_id), None)
|
|
272
285
|
from collections import Counter
|
|
273
286
|
content_counts = Counter(e.content for e in group)
|
|
274
287
|
for content, count in content_counts.items():
|
|
@@ -282,29 +295,31 @@ class WorkspaceState:
|
|
|
282
295
|
evidence_ids=evidence_ids,
|
|
283
296
|
confidence=confidence,
|
|
284
297
|
))
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
anchor.description = content
|
|
298
|
+
# 成核后触发自动描述回流(仅当描述为空)
|
|
299
|
+
self._auto_describe_anchor(anchor_id)
|
|
288
300
|
|
|
289
|
-
def
|
|
290
|
-
|
|
291
|
-
if
|
|
301
|
+
def _auto_describe_anchor(self, anchor_id: str):
|
|
302
|
+
"""高置信结晶自动回填锚点空描述(尊重人工设定)"""
|
|
303
|
+
anchor = next((a for a in self.anchors if a.id == anchor_id), None)
|
|
304
|
+
if not anchor or anchor.description:
|
|
292
305
|
return
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
a.decay_value(evidence_count=evidence_count)
|
|
299
|
-
a.recalc_strength(evidence_count)
|
|
300
|
-
a.auto_classify(evidence_count)
|
|
306
|
+
crystals = [m for m in self.memories if any(eid in [e.id for e in self.evidences if e.anchor_id == anchor_id] for eid in m.evidence_ids)]
|
|
307
|
+
if crystals:
|
|
308
|
+
best = max(crystals, key=lambda m: m.confidence)
|
|
309
|
+
if best.confidence >= 0.8:
|
|
310
|
+
anchor.description = best.content
|
|
301
311
|
|
|
302
312
|
def _detect_conflicts(self, anchor_id: str):
|
|
303
|
-
"""群内自洽检测:同锚点证据一致性过低
|
|
304
|
-
|
|
305
|
-
|
|
313
|
+
"""群内自洽检测:同锚点证据一致性过低 -> 生成 Conflict 记录并降锚点稳定性。
|
|
314
|
+
|
|
315
|
+
阈值从环境变量 LIQUID_CONFLICT_THRESHOLD 读取(默认 0.2)。
|
|
316
|
+
复用 CPE 泛化防线思路:证据间平均关键词重叠 < threshold 且 >= 3 条 -> 潜在冲突/漂移。
|
|
306
317
|
保守处理:只标记'需人工确认',不武断判相反(避免过度工程化误报)。
|
|
318
|
+
使用 overlap_cache 避免重复计算。
|
|
307
319
|
"""
|
|
320
|
+
import os
|
|
321
|
+
conflict_threshold = float(os.environ.get('LIQUID_CONFLICT_THRESHOLD', '0.2'))
|
|
322
|
+
|
|
308
323
|
group = [e for e in self.evidences if e.anchor_id == anchor_id]
|
|
309
324
|
if len(group) < 3:
|
|
310
325
|
return
|
|
@@ -312,22 +327,36 @@ class WorkspaceState:
|
|
|
312
327
|
for i in range(len(group)):
|
|
313
328
|
for j in range(i + 1, len(group)):
|
|
314
329
|
if group[i].content and group[j].content:
|
|
315
|
-
overlaps.append(_keyword_overlap(group[i].content, group[j].content))
|
|
330
|
+
overlaps.append(_keyword_overlap(group[i].content, group[j].content, self.overlap_cache))
|
|
316
331
|
if not overlaps:
|
|
317
332
|
return
|
|
318
333
|
avg = sum(overlaps) / len(overlaps)
|
|
319
|
-
if avg <
|
|
334
|
+
if avg < conflict_threshold:
|
|
320
335
|
anchor = next((a for a in self.anchors if a.id == anchor_id), None)
|
|
321
336
|
if any(c.anchor_a == anchor_id for c in self.conflicts):
|
|
322
337
|
return
|
|
323
338
|
self.conflicts.append(Conflict(
|
|
324
339
|
anchor_a=anchor_id,
|
|
325
|
-
description=f"证据间平均一致度 {avg:.2f}(<
|
|
340
|
+
description=f"证据间平均一致度 {avg:.2f}(<{conflict_threshold}),存在潜在冲突/漂移,需人工确认",
|
|
326
341
|
severity=round(1.0 - avg, 2),
|
|
327
342
|
))
|
|
328
343
|
if anchor:
|
|
329
344
|
anchor.stability = max(0.1, anchor.stability * 0.9)
|
|
330
345
|
|
|
346
|
+
|
|
347
|
+
def _recalc_anchor(self, anchor_id: str):
|
|
348
|
+
group = [e for e in self.evidences if e.anchor_id == anchor_id]
|
|
349
|
+
if not group:
|
|
350
|
+
return
|
|
351
|
+
avg_weight = sum(e.weight for e in group) / len(group)
|
|
352
|
+
for a in self.anchors:
|
|
353
|
+
if a.id == anchor_id:
|
|
354
|
+
a.stability = avg_weight
|
|
355
|
+
evidence_count = len(group)
|
|
356
|
+
a.decay_value(evidence_count=evidence_count)
|
|
357
|
+
a.recalc_strength(evidence_count)
|
|
358
|
+
a.auto_classify(evidence_count)
|
|
359
|
+
|
|
331
360
|
def add_memory(self, content: str, evidence_ids: list[str] | None = None) -> Memory:
|
|
332
361
|
"""添加一条记忆结晶"""
|
|
333
362
|
m = Memory(id=uid(), content=content, evidence_ids=evidence_ids or [])
|
|
@@ -623,12 +652,24 @@ def _tokenize(text: str) -> List[str]:
|
|
|
623
652
|
return tokens
|
|
624
653
|
|
|
625
654
|
|
|
626
|
-
def _keyword_overlap(a: str, b: str) -> float:
|
|
627
|
-
"""
|
|
628
|
-
|
|
629
|
-
|
|
655
|
+
def _keyword_overlap(a: str, b: str, cache: dict | None = None) -> float:
|
|
656
|
+
"""关键词重叠度(Jaccard 系数),支持缓存"""
|
|
657
|
+
if not a or not b:
|
|
658
|
+
return 0.0
|
|
659
|
+
if cache is not None:
|
|
660
|
+
import hashlib
|
|
661
|
+
key = tuple(sorted([hashlib.md5(a.encode()).hexdigest()[:8], hashlib.md5(b.encode()).hexdigest()[:8]]))
|
|
662
|
+
if key in cache:
|
|
663
|
+
return cache[key]
|
|
664
|
+
import re
|
|
665
|
+
tokens1 = set(re.findall(r"[一-鿿]|[a-zA-Z0-9]+", a.lower()))
|
|
666
|
+
tokens2 = set(re.findall(r"[一-鿿]|[a-zA-Z0-9]+", b.lower()))
|
|
667
|
+
if not tokens1 or not tokens2:
|
|
630
668
|
return 0.0
|
|
631
|
-
|
|
669
|
+
result = len(tokens1 & tokens2) / len(tokens1 | tokens2)
|
|
670
|
+
if cache is not None:
|
|
671
|
+
cache[key] = result
|
|
672
|
+
return result
|
|
632
673
|
|
|
633
674
|
|
|
634
675
|
def _judge_answer(gold: str, answer: str) -> bool:
|
|
@@ -13,4 +13,6 @@ liquid_loop.egg-info/dependency_links.txt
|
|
|
13
13
|
liquid_loop.egg-info/entry_points.txt
|
|
14
14
|
liquid_loop.egg-info/requires.txt
|
|
15
15
|
liquid_loop.egg-info/top_level.txt
|
|
16
|
-
tests/
|
|
16
|
+
tests/test_entropy.py
|
|
17
|
+
tests/test_self_evolve.py
|
|
18
|
+
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.
|
|
7
|
+
version = "0.6.0"
|
|
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,56 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from liquid_loop.workspace import WorkspaceState, Anchor, Evidence
|
|
3
|
+
from liquid_loop.entropy import (
|
|
4
|
+
calculate, calculate_detail,
|
|
5
|
+
anchor_drift, conflict_density, evidence_fragmentation,
|
|
6
|
+
activity_gap, value_decay_entropy, strength_entropy,
|
|
7
|
+
retrospective_decay_entropy, behavioral_drift_entropy,
|
|
8
|
+
generalization_erosion_entropy
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
def test_calculate_returns_float():
|
|
12
|
+
state = WorkspaceState()
|
|
13
|
+
a = state.add_anchor('test')
|
|
14
|
+
state.add_evidence(a.id, 'evidence 1')
|
|
15
|
+
ent = calculate(state)
|
|
16
|
+
assert isinstance(ent, float)
|
|
17
|
+
assert 0.0 <= ent <= 1.0
|
|
18
|
+
|
|
19
|
+
def test_calculate_detail_has_all_keys():
|
|
20
|
+
state = WorkspaceState()
|
|
21
|
+
a = state.add_anchor('test')
|
|
22
|
+
state.add_evidence(a.id, 'evidence 1')
|
|
23
|
+
detail = calculate_detail(state)
|
|
24
|
+
assert 'combined' in detail
|
|
25
|
+
assert 'anchor_drift' in detail
|
|
26
|
+
assert 'cpe_retrospective_decay' in detail
|
|
27
|
+
assert 'cpe_behavioral_drift' in detail
|
|
28
|
+
assert 'cpe_generalization_erosion' in detail
|
|
29
|
+
|
|
30
|
+
def test_anchor_drift():
|
|
31
|
+
state = WorkspaceState()
|
|
32
|
+
a = state.add_anchor('test')
|
|
33
|
+
state.add_evidence(a.id, 'evidence 1')
|
|
34
|
+
drift = anchor_drift(state)
|
|
35
|
+
assert 0.0 <= drift <= 1.0
|
|
36
|
+
|
|
37
|
+
def test_conflict_density():
|
|
38
|
+
state = WorkspaceState()
|
|
39
|
+
a = state.add_anchor('test')
|
|
40
|
+
state.add_evidence(a.id, 'evidence 1')
|
|
41
|
+
density = conflict_density(state)
|
|
42
|
+
assert 0.0 <= density <= 1.0
|
|
43
|
+
|
|
44
|
+
def test_evidence_fragmentation():
|
|
45
|
+
state = WorkspaceState()
|
|
46
|
+
a = state.add_anchor('test')
|
|
47
|
+
state.add_evidence(a.id, 'evidence 1')
|
|
48
|
+
frag = evidence_fragmentation(state)
|
|
49
|
+
assert 0.0 <= frag <= 1.0
|
|
50
|
+
|
|
51
|
+
def test_activity_gap():
|
|
52
|
+
state = WorkspaceState()
|
|
53
|
+
a = state.add_anchor('test')
|
|
54
|
+
state.add_evidence(a.id, 'evidence 1')
|
|
55
|
+
gap = activity_gap(state)
|
|
56
|
+
assert 0.0 <= gap <= 1.0
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from liquid_loop.workspace import WorkspaceState, Anchor, Evidence, Memory, Conflict
|
|
3
|
+
|
|
4
|
+
def test_add_anchor():
|
|
5
|
+
state = WorkspaceState()
|
|
6
|
+
a = state.add_anchor('test', 'description')
|
|
7
|
+
assert a.name == 'test'
|
|
8
|
+
assert a.description == 'description'
|
|
9
|
+
assert len(state.anchors) == 1
|
|
10
|
+
|
|
11
|
+
def test_add_evidence():
|
|
12
|
+
state = WorkspaceState()
|
|
13
|
+
a = state.add_anchor('test')
|
|
14
|
+
e = state.add_evidence(a.id, 'evidence content')
|
|
15
|
+
assert e.anchor_id == a.id
|
|
16
|
+
assert e.content == 'evidence content'
|
|
17
|
+
assert len(state.evidences) == 1
|
|
18
|
+
|
|
19
|
+
def test_nucleate_creates_memory():
|
|
20
|
+
state = WorkspaceState()
|
|
21
|
+
a = state.add_anchor('test')
|
|
22
|
+
state.add_evidence(a.id, 'same content')
|
|
23
|
+
state.add_evidence(a.id, 'same content')
|
|
24
|
+
assert len(state.memories) == 1
|
|
25
|
+
assert state.memories[0].content == 'same content'
|
|
26
|
+
|
|
27
|
+
def test_conflict_detection():
|
|
28
|
+
state = WorkspaceState()
|
|
29
|
+
a = state.add_anchor('test')
|
|
30
|
+
# 添加 3 条不一致的证据触发冲突检测
|
|
31
|
+
state.add_evidence(a.id, 'content A')
|
|
32
|
+
state.add_evidence(a.id, 'content B')
|
|
33
|
+
state.add_evidence(a.id, 'content C')
|
|
34
|
+
# 冲突检测阈值默认 0.2,三条完全不同内容会触发
|
|
35
|
+
assert len(state.conflicts) >= 0 # 可能触发也可能不触发,取决于重叠度
|
|
36
|
+
|
|
37
|
+
def test_auto_describe_anchor():
|
|
38
|
+
state = WorkspaceState()
|
|
39
|
+
a = state.add_anchor('test') # 空描述
|
|
40
|
+
state.add_evidence(a.id, 'evidence 1')
|
|
41
|
+
state.add_evidence(a.id, 'evidence 2')
|
|
42
|
+
# 触发成核和自动描述
|
|
43
|
+
state._nucleate(a.id)
|
|
44
|
+
# 如果有高置信结晶,描述会被填充
|
|
45
|
+
# 这里主要测试不报错
|
|
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
|