ultra-memory 3.1.0 → 4.0.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/integrations/__init__.py +1 -0
- package/integrations/langchain_memory.py +118 -0
- package/integrations/langgraph_checkpointer.py +76 -0
- package/integrations/n8n_nodes.py +150 -0
- package/package.json +17 -4
- package/scripts/__pycache__/conflict_detector.cpython-313.pyc +0 -0
- package/scripts/__pycache__/recall.cpython-313.pyc +0 -0
- package/scripts/auto_decay.py +351 -0
- package/scripts/cleanup.py +21 -0
- package/scripts/conflict_detector.py +319 -0
- package/scripts/detect_contradictions.py +537 -0
- package/scripts/evolve_profile.py +414 -0
- package/scripts/extract_facts.py +471 -0
- package/scripts/log_op.py +70 -0
- package/scripts/multimodal/__init__.py +2 -0
- package/scripts/multimodal/extract_from_image.py +138 -0
- package/scripts/multimodal/extract_from_pdf.py +182 -0
- package/scripts/multimodal/transcribe_video.py +157 -0
- package/scripts/recall.py +41 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# ultra-memory integrations package
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
ultra-memory: LangChain Memory 集成
|
|
4
|
+
提供 UltraMemoryMemory 类,实现 LangChain BaseMemory 接口,
|
|
5
|
+
可直接用于 LC agents。
|
|
6
|
+
|
|
7
|
+
用法:
|
|
8
|
+
from integrations.langchain_memory import UltraMemoryMemory
|
|
9
|
+
memory = UltraMemoryMemory(session_id="sess_langchain_test", project="my-agent")
|
|
10
|
+
agent = OpenAIAgent(..., memory=memory)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from langchain.schema import BaseMemory
|
|
21
|
+
from langchain.schema import HumanMessage, AIMessage
|
|
22
|
+
HAS_LANGCHAIN = True
|
|
23
|
+
except ImportError:
|
|
24
|
+
HAS_LANGCHAIN = False
|
|
25
|
+
|
|
26
|
+
ULTRA_MEMORY_HOME = Path(os.environ.get("ULTRA_MEMORY_HOME", Path.home() / ".ultra-memory"))
|
|
27
|
+
_SCRIPTS_DIR = Path(__file__).parent.parent / "scripts"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class UltraMemoryMemory:
|
|
31
|
+
"""
|
|
32
|
+
LangChain memory backed by ultra-memory's 5-layer system.
|
|
33
|
+
Implements BaseMemory-compatible interface.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
session_id: str,
|
|
39
|
+
project: str = "langchain",
|
|
40
|
+
top_k: int = 5,
|
|
41
|
+
):
|
|
42
|
+
self.session_id = session_id
|
|
43
|
+
self.project = project
|
|
44
|
+
self.top_k = top_k
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def memory_variables(self) -> list[str]:
|
|
48
|
+
return ["ultra_memory_context"]
|
|
49
|
+
|
|
50
|
+
def load_memory_variables(self, inputs: dict) -> dict:
|
|
51
|
+
"""加载与当前上下文相关的记忆"""
|
|
52
|
+
query = inputs.get("query", "")
|
|
53
|
+
|
|
54
|
+
if not self.session_id:
|
|
55
|
+
return {"ultra_memory_context": ""}
|
|
56
|
+
|
|
57
|
+
if query:
|
|
58
|
+
# 使用 recall 获取相关记忆
|
|
59
|
+
import subprocess, io
|
|
60
|
+
recall_script = _SCRIPTS_DIR / "recall.py"
|
|
61
|
+
old_stdout = sys.stdout
|
|
62
|
+
sys.stdout = io.StringIO()
|
|
63
|
+
try:
|
|
64
|
+
subprocess.run(
|
|
65
|
+
[sys.executable, str(recall_script),
|
|
66
|
+
"--session", self.session_id,
|
|
67
|
+
"--query", query,
|
|
68
|
+
"--top-k", str(self.top_k)],
|
|
69
|
+
capture_output=True,
|
|
70
|
+
timeout=30,
|
|
71
|
+
)
|
|
72
|
+
context = sys.stdout.getvalue()
|
|
73
|
+
except Exception:
|
|
74
|
+
context = ""
|
|
75
|
+
finally:
|
|
76
|
+
sys.stdout = old_stdout
|
|
77
|
+
else:
|
|
78
|
+
# 加载最新摘要
|
|
79
|
+
summary_file = ULTRA_MEMORY_HOME / "sessions" / self.session_id / "summary.md"
|
|
80
|
+
if summary_file.exists():
|
|
81
|
+
context = summary_file.read_text(encoding="utf-8")
|
|
82
|
+
else:
|
|
83
|
+
context = ""
|
|
84
|
+
|
|
85
|
+
return {"ultra_memory_context": context}
|
|
86
|
+
|
|
87
|
+
def save_context(self, inputs: dict, outputs: dict) -> None:
|
|
88
|
+
"""保存一轮对话到 ultra-memory"""
|
|
89
|
+
import subprocess
|
|
90
|
+
|
|
91
|
+
input_text = inputs.get("input", "")[:200]
|
|
92
|
+
output_text = outputs.get("output", "")[:200]
|
|
93
|
+
|
|
94
|
+
detail = {
|
|
95
|
+
"input": inputs.get("input", ""),
|
|
96
|
+
"output": outputs.get("output", ""),
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
subprocess.run(
|
|
101
|
+
[
|
|
102
|
+
sys.executable,
|
|
103
|
+
str(_SCRIPTS_DIR / "log_op.py"),
|
|
104
|
+
"--session", self.session_id,
|
|
105
|
+
"--type", "tool_call",
|
|
106
|
+
"--summary", f"LC: {input_text[:60]}",
|
|
107
|
+
"--detail", json.dumps(detail, ensure_ascii=False),
|
|
108
|
+
"--tags", "langchain",
|
|
109
|
+
],
|
|
110
|
+
capture_output=True,
|
|
111
|
+
timeout=5,
|
|
112
|
+
)
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
def clear(self) -> None:
|
|
117
|
+
"""清除当前记忆(不删除 session)"""
|
|
118
|
+
self.session_id = None
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
ultra-memory: LangGraph Checkpointer 集成
|
|
4
|
+
提供 UltraMemoryCheckpointer 类,作为 LangGraph 的状态持久化后端。
|
|
5
|
+
|
|
6
|
+
用法:
|
|
7
|
+
from integrations.langgraph_checkpointer import UltraMemoryCheckpointer
|
|
8
|
+
checkpointer = UltraMemoryCheckpointer(session_id="sess_langgraph_proj")
|
|
9
|
+
compiled = graph.compile(checkpointer=checkpointer)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Optional
|
|
16
|
+
|
|
17
|
+
ULTRA_MEMORY_HOME = Path(os.environ.get("ULTRA_MEMORY_HOME", Path.home() / ".ultra-memory"))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class UltraMemoryCheckpointer:
|
|
21
|
+
"""
|
|
22
|
+
LangGraph checkpointer backed by ultra-memory。
|
|
23
|
+
在每个节点执行后保存/恢复 agent graph 状态。
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, session_id: str):
|
|
27
|
+
self.session_id = session_id
|
|
28
|
+
self.checkpoint_dir = ULTRA_MEMORY_HOME / "sessions" / session_id / "checkpoints"
|
|
29
|
+
self.checkpoint_dir.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
|
|
31
|
+
def _checkpoint_file(self, thread_id: str, step: int) -> Path:
|
|
32
|
+
"""获取检查点文件路径"""
|
|
33
|
+
return self.checkpoint_dir / f"thread_{thread_id}_step_{step:04d}.json"
|
|
34
|
+
|
|
35
|
+
def get(self, thread_id: str, step: int) -> Optional[dict[str, Any]]:
|
|
36
|
+
"""获取指定 thread 和 step 的检查点状态"""
|
|
37
|
+
checkpoint_file = self._checkpoint_file(thread_id, step)
|
|
38
|
+
if not checkpoint_file.exists():
|
|
39
|
+
return None
|
|
40
|
+
try:
|
|
41
|
+
with open(checkpoint_file, encoding="utf-8") as f:
|
|
42
|
+
data = json.load(f)
|
|
43
|
+
return data.get("state")
|
|
44
|
+
except (json.JSONDecodeError, IOError):
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
def put(self, thread_id: str, step: int, state: dict[str, Any]) -> None:
|
|
48
|
+
"""保存检查点状态"""
|
|
49
|
+
checkpoint_file = self._checkpoint_file(thread_id, step)
|
|
50
|
+
data = {
|
|
51
|
+
"step": step,
|
|
52
|
+
"state": state,
|
|
53
|
+
"session_id": self.session_id,
|
|
54
|
+
"thread_id": thread_id,
|
|
55
|
+
}
|
|
56
|
+
with open(checkpoint_file, "w", encoding="utf-8") as f:
|
|
57
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
58
|
+
|
|
59
|
+
def get_latest(self, thread_id: str) -> Optional[dict[str, Any]]:
|
|
60
|
+
"""获取指定 thread 的最新检查点"""
|
|
61
|
+
checkpoints = sorted(
|
|
62
|
+
self.checkpoint_dir.glob(f"thread_{thread_id}_step_*.json"),
|
|
63
|
+
key=lambda p: int(p.stem.split("_")[-1]),
|
|
64
|
+
)
|
|
65
|
+
if not checkpoints:
|
|
66
|
+
return None
|
|
67
|
+
return self.get(thread_id, int(checkpoints[-1].stem.split("_")[-1]))
|
|
68
|
+
|
|
69
|
+
def list_threads(self) -> list[str]:
|
|
70
|
+
"""列出所有已有 thread ID"""
|
|
71
|
+
threads = set()
|
|
72
|
+
for f in self.checkpoint_dir.glob("thread_*_step_*.json"):
|
|
73
|
+
parts = f.stem.split("_")
|
|
74
|
+
if len(parts) >= 2:
|
|
75
|
+
threads.add(parts[1])
|
|
76
|
+
return sorted(threads)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
ultra-memory: n8n 集成节点
|
|
4
|
+
作为 n8n "Execute Command" 节点的 Python 脚本后端,
|
|
5
|
+
支持 init / log / recall 三种操作。
|
|
6
|
+
|
|
7
|
+
n8n 配置示例:
|
|
8
|
+
Execute Command 节点
|
|
9
|
+
命令: python3
|
|
10
|
+
参数: /path/to/ultra-memory/integrations/n8n_nodes.py <operation> <args>
|
|
11
|
+
|
|
12
|
+
Operations:
|
|
13
|
+
init --project <proj> → 返回 session_id
|
|
14
|
+
log --session <id> --summary "..." --type <type> --detail '{}'
|
|
15
|
+
recall --session <id> --query "..."
|
|
16
|
+
profile --action read|update --field <field> --value <value>
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
import re
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
ULTRA_MEMORY_HOME = Path(os.environ.get("ULTRA_MEMORY_HOME", Path.home() / ".ultra-memory"))
|
|
26
|
+
_SCRIPTS_DIR = Path(__file__).parent.parent / "scripts"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _run_script(script_name: str, args: list[str]) -> str:
|
|
30
|
+
"""运行脚本并返回输出"""
|
|
31
|
+
import subprocess
|
|
32
|
+
|
|
33
|
+
script_path = _SCRIPTS_DIR / script_name
|
|
34
|
+
result = subprocess.run(
|
|
35
|
+
[sys.executable, str(script_path)] + args,
|
|
36
|
+
capture_output=True, text=True, timeout=30,
|
|
37
|
+
)
|
|
38
|
+
return result.stdout + result.stderr
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def cmd_init(project: str) -> dict:
|
|
42
|
+
"""初始化会话"""
|
|
43
|
+
output = _run_script("init.py", ["--project", project, "--resume"])
|
|
44
|
+
|
|
45
|
+
session_id = None
|
|
46
|
+
memory_ready = False
|
|
47
|
+
|
|
48
|
+
for line in output.split("\n"):
|
|
49
|
+
if "session_id:" in line:
|
|
50
|
+
match = re.search(r"session_id:\s*(sess_\w+)", line)
|
|
51
|
+
if match:
|
|
52
|
+
session_id = match.group(1)
|
|
53
|
+
if "MEMORY_READY" in line:
|
|
54
|
+
memory_ready = True
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
"success": memory_ready,
|
|
58
|
+
"session_id": session_id,
|
|
59
|
+
"output": output,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def cmd_log(session_id: str, summary: str, op_type: str, detail: str = "{}") -> dict:
|
|
64
|
+
"""记录操作"""
|
|
65
|
+
output = _run_script("log_op.py", [
|
|
66
|
+
"--session", session_id,
|
|
67
|
+
"--type", op_type,
|
|
68
|
+
"--summary", summary,
|
|
69
|
+
"--detail", detail,
|
|
70
|
+
])
|
|
71
|
+
return {"success": True, "output": output}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def cmd_recall(session_id: str, query: str, top_k: int = 5) -> dict:
|
|
75
|
+
"""检索记忆"""
|
|
76
|
+
output = _run_script("recall.py", [
|
|
77
|
+
"--session", session_id,
|
|
78
|
+
"--query", query,
|
|
79
|
+
"--top-k", str(top_k),
|
|
80
|
+
])
|
|
81
|
+
return {"success": True, "output": output}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def cmd_profile(action: str, field: str = None, value: str = None) -> dict:
|
|
85
|
+
"""读取或更新用户画像"""
|
|
86
|
+
if action == "read":
|
|
87
|
+
output = _run_script("evolve_profile.py", [])
|
|
88
|
+
return {"success": True, "output": output}
|
|
89
|
+
elif action == "update" and field and value:
|
|
90
|
+
output = _run_script("evolve_profile.py", [
|
|
91
|
+
"--field", field, "--value", value,
|
|
92
|
+
])
|
|
93
|
+
return {"success": True, "output": output}
|
|
94
|
+
return {"success": False, "error": "invalid profile command"}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ── CLI ─────────────────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
if len(sys.argv) < 2:
|
|
102
|
+
print("Usage: n8n_nodes.py <init|log|recall|profile> [args...]")
|
|
103
|
+
sys.exit(1)
|
|
104
|
+
|
|
105
|
+
operation = sys.argv[1].lower()
|
|
106
|
+
args = sys.argv[2:]
|
|
107
|
+
|
|
108
|
+
result = {}
|
|
109
|
+
try:
|
|
110
|
+
if operation == "init":
|
|
111
|
+
project = next((a for a in args if a.startswith("--project=")),
|
|
112
|
+
"--project=default").split("=", 1)[1]
|
|
113
|
+
result = cmd_init(project)
|
|
114
|
+
|
|
115
|
+
elif operation == "log":
|
|
116
|
+
session_id = next((a for a in args if a.startswith("--session=")),
|
|
117
|
+
None).split("=", 1)[1]
|
|
118
|
+
summary = next((a for a in args if a.startswith("--summary=")),
|
|
119
|
+
"").split("=", 1)[1]
|
|
120
|
+
op_type = next((a for a in args if a.startswith("--type=")),
|
|
121
|
+
"tool_call").split("=", 1)[1]
|
|
122
|
+
detail = next((a for a in args if a.startswith("--detail=")),
|
|
123
|
+
"{}").split("=", 1)[1]
|
|
124
|
+
result = cmd_log(session_id, summary, op_type, detail)
|
|
125
|
+
|
|
126
|
+
elif operation == "recall":
|
|
127
|
+
session_id = next((a for a in args if a.startswith("--session=")),
|
|
128
|
+
None).split("=", 1)[1]
|
|
129
|
+
query = next((a for a in args if a.startswith("--query=")),
|
|
130
|
+
"").split("=", 1)[1]
|
|
131
|
+
result = cmd_recall(session_id, query)
|
|
132
|
+
|
|
133
|
+
elif operation == "profile":
|
|
134
|
+
action = next((a for a in args if a.startswith("--action=")),
|
|
135
|
+
"read").split("=", 1)[1]
|
|
136
|
+
field = next((a for a in args if a.startswith("--field=")),
|
|
137
|
+
None)
|
|
138
|
+
field = field.split("=", 1)[1] if field else None
|
|
139
|
+
value = next((a for a in args if a.startswith("--value=")),
|
|
140
|
+
None)
|
|
141
|
+
value = value.split("=", 1)[1] if value else None
|
|
142
|
+
result = cmd_profile(action, field, value)
|
|
143
|
+
|
|
144
|
+
else:
|
|
145
|
+
result = {"success": False, "error": f"unknown operation: {operation}"}
|
|
146
|
+
|
|
147
|
+
except Exception as e:
|
|
148
|
+
result = {"success": False, "error": str(e)}
|
|
149
|
+
|
|
150
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra-memory",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "超长会话记忆系统 — 5
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "超长会话记忆系统 — 5层记忆架构+自我进化引擎+时序矛盾检测,支持所有LLM平台",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
7
7
|
"memory",
|
|
@@ -46,7 +46,9 @@
|
|
|
46
46
|
"ultra-memory-recall": "scripts/recall.py",
|
|
47
47
|
"ultra-memory-summarize": "scripts/summarize.py",
|
|
48
48
|
"ultra-memory-restore": "scripts/restore.py",
|
|
49
|
-
"ultra-memory-knowledge": "scripts/log_knowledge.py"
|
|
49
|
+
"ultra-memory-knowledge": "scripts/log_knowledge.py",
|
|
50
|
+
"ultra-memory-extract-facts": "scripts/extract_facts.py",
|
|
51
|
+
"ultra-memory-evolve": "scripts/evolve_profile.py"
|
|
50
52
|
},
|
|
51
53
|
"scripts": {
|
|
52
54
|
"test": "python3 test_e2e.py",
|
|
@@ -57,6 +59,14 @@
|
|
|
57
59
|
"node": ">=18.0.0",
|
|
58
60
|
"python": ">=3.8.0"
|
|
59
61
|
},
|
|
62
|
+
"optionalDependencies": {
|
|
63
|
+
"pdfminer.six": "*",
|
|
64
|
+
"pytesseract": "*",
|
|
65
|
+
"whisper": "*"
|
|
66
|
+
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"langchain": ">=0.1.0"
|
|
69
|
+
},
|
|
60
70
|
"os": [
|
|
61
71
|
"darwin",
|
|
62
72
|
"linux",
|
|
@@ -64,7 +74,9 @@
|
|
|
64
74
|
],
|
|
65
75
|
"files": [
|
|
66
76
|
"scripts/",
|
|
77
|
+
"scripts/multimodal/",
|
|
67
78
|
"platform/",
|
|
79
|
+
"integrations/",
|
|
68
80
|
"SKILL.md",
|
|
69
81
|
"README.md",
|
|
70
82
|
"CLAWHUB.md"
|
|
@@ -102,7 +114,8 @@
|
|
|
102
114
|
"2": "summary.md — 会话摘要层(里程碑压缩)",
|
|
103
115
|
"3": "semantic/ — 跨会话语义层(知识库+实体索引)",
|
|
104
116
|
"4": "entities.jsonl — 结构化实体索引(7类实体)",
|
|
105
|
-
"5": "tfidf_cache.json — 向量语义层(TF-IDF/sentence-transformers)"
|
|
117
|
+
"5": "tfidf_cache.json — 向量语义层(TF-IDF/sentence-transformers)",
|
|
118
|
+
"6": "evolution/ — 自我进化层(事实提取+矛盾检测+遗忘)"
|
|
106
119
|
}
|
|
107
120
|
}
|
|
108
121
|
}
|
|
Binary file
|
|
Binary file
|