dsh-mem0-memory 0.1.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/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.en.md +270 -0
- package/README.md +272 -0
- package/bin/dsh-mem0-import.mjs +211 -0
- package/bin/dsh-mem0-search.mjs +124 -0
- package/cordis.patch.yml +4 -0
- package/excluded-workspaces.example.json +4 -0
- package/lib/index.js +299 -0
- package/package.json +38 -0
- package/scripts/.env.example +22 -0
- package/scripts/excluded-workspaces.json +4 -0
- package/scripts/g1-session-parser.mjs +816 -0
- package/scripts/mem0-server.py +743 -0
- package/scripts/setup.py +188 -0
- package/scripts/start-server.bat +47 -0
- package/scripts/start-server.sh +44 -0
package/scripts/setup.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
setup.py — dsh-mem0-memory 一键初始化(三件套之「装机服务」)
|
|
5
|
+
|
|
6
|
+
用法:
|
|
7
|
+
python setup.py --init 检查/补依赖、建数据目录、生成 .env(不存在时)、打印下一步
|
|
8
|
+
python setup.py --install 在 --init 基础上自动 pip 安装缺失依赖
|
|
9
|
+
python setup.py --check 只检查环境(Python 版本 / 依赖 / key 配置状态),不改任何东西
|
|
10
|
+
|
|
11
|
+
职责(对应 README 安装五步的 ②③④):
|
|
12
|
+
1. Python 版本检查(>= 3.10)
|
|
13
|
+
2. pip 依赖检查:mem0ai>=2.0 qdrant-client fastembed jieba flask(缺失 → 提示,--install 自动装)
|
|
14
|
+
3. 建数据目录(默认 ./mem0_store,可被 MEM0_DATA_DIR 或 --data-dir 覆盖)
|
|
15
|
+
4. 生成 .env(不存在时从 .env.example 复制;已存在不覆盖)
|
|
16
|
+
5. 打印下一步(启动 server + 首次导入)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import importlib.metadata
|
|
21
|
+
import importlib.util
|
|
22
|
+
import io
|
|
23
|
+
import os
|
|
24
|
+
import shutil
|
|
25
|
+
import subprocess
|
|
26
|
+
import sys
|
|
27
|
+
|
|
28
|
+
# Windows 控制台默认 GBK:统一 UTF-8 输出(与 mem0-server.py 一致)
|
|
29
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
|
|
30
|
+
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
|
|
31
|
+
|
|
32
|
+
# (分发名 pip 名, import 模块名, 最低版本) —— 注意 mem0ai vs mem0、qdrant-client vs qdrant_client
|
|
33
|
+
REQUIRED = [
|
|
34
|
+
("mem0ai", "mem0", "2.0"),
|
|
35
|
+
("qdrant-client", "qdrant_client", None),
|
|
36
|
+
("fastembed", "fastembed", None),
|
|
37
|
+
("jieba", "jieba", None),
|
|
38
|
+
("flask", "flask", None),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def py_version_ok():
|
|
43
|
+
return sys.version_info >= (3, 10)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def check_dependency(dist_name, import_name, min_version):
|
|
47
|
+
ok = importlib.util.find_spec(import_name) is not None
|
|
48
|
+
if not ok:
|
|
49
|
+
return False, None
|
|
50
|
+
try:
|
|
51
|
+
ver = importlib.metadata.version(dist_name)
|
|
52
|
+
except Exception:
|
|
53
|
+
ver = None
|
|
54
|
+
if min_version and ver:
|
|
55
|
+
def _norm(v):
|
|
56
|
+
try:
|
|
57
|
+
return tuple(int(x) for x in v.split(".")[:2])
|
|
58
|
+
except Exception:
|
|
59
|
+
return (0, 0)
|
|
60
|
+
if _norm(ver) < _norm(min_version):
|
|
61
|
+
return False, ver
|
|
62
|
+
return True, ver
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def load_dotenv_state(env_file):
|
|
66
|
+
"""只读 .env 看 key 是否已填(不修改环境)。"""
|
|
67
|
+
filled = {}
|
|
68
|
+
try:
|
|
69
|
+
with open(env_file, "r", encoding="utf-8") as f:
|
|
70
|
+
for line in f:
|
|
71
|
+
line = line.strip()
|
|
72
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
73
|
+
continue
|
|
74
|
+
k, _, v = line.partition("=")
|
|
75
|
+
k = k.strip()
|
|
76
|
+
v = v.strip().strip('"').strip("'")
|
|
77
|
+
if k and v:
|
|
78
|
+
filled[k] = True
|
|
79
|
+
except FileNotFoundError:
|
|
80
|
+
pass
|
|
81
|
+
return filled
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def main():
|
|
85
|
+
parser = argparse.ArgumentParser(description="dsh-mem0-memory 一键初始化")
|
|
86
|
+
parser.add_argument("--init", action="store_true", help="检查/补依赖 + 建目录 + 生成 .env")
|
|
87
|
+
parser.add_argument("--install", action="store_true", help="(与 --init 同用)自动 pip 安装缺失依赖")
|
|
88
|
+
parser.add_argument("--check", action="store_true", help="只检查环境,不改任何东西")
|
|
89
|
+
parser.add_argument("--data-dir", default=None, help="数据目录(默认 ./mem0_store 或 $MEM0_DATA_DIR)")
|
|
90
|
+
args = parser.parse_args()
|
|
91
|
+
|
|
92
|
+
mode = "check" if args.check else ("init" if args.init else None)
|
|
93
|
+
if mode is None:
|
|
94
|
+
parser.print_help()
|
|
95
|
+
sys.exit(0)
|
|
96
|
+
|
|
97
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
98
|
+
env_example = os.path.join(script_dir, ".env.example")
|
|
99
|
+
env_file = os.path.join(os.getcwd(), ".env")
|
|
100
|
+
data_dir = args.data_dir or os.environ.get("MEM0_DATA_DIR") or os.path.join(os.getcwd(), "mem0_store")
|
|
101
|
+
|
|
102
|
+
print("=" * 56)
|
|
103
|
+
print(" dsh-mem0-memory setup")
|
|
104
|
+
print("=" * 56)
|
|
105
|
+
|
|
106
|
+
# 1) Python 版本
|
|
107
|
+
print(f"[1/4] Python: {sys.version.split()[0]} (需要 >= 3.10)")
|
|
108
|
+
if not py_version_ok():
|
|
109
|
+
print(" FAIL: Python 版本过低,请升级到 3.10+")
|
|
110
|
+
sys.exit(1)
|
|
111
|
+
|
|
112
|
+
# 2) 依赖
|
|
113
|
+
print("[2/4] 依赖检查:")
|
|
114
|
+
missing = []
|
|
115
|
+
for dist_name, import_name, minv in REQUIRED:
|
|
116
|
+
ok, ver = check_dependency(dist_name, import_name, minv)
|
|
117
|
+
if ok:
|
|
118
|
+
print(f" ✅ {dist_name} {ver or ''}".rstrip())
|
|
119
|
+
else:
|
|
120
|
+
print(f" ❌ {dist_name} {('(需要 >= ' + minv + ',当前 ' + str(ver) + ')') if (minv and ver) else ('(需要 >= ' + minv + ')' if minv else '未安装')}")
|
|
121
|
+
missing.append(dist_name)
|
|
122
|
+
|
|
123
|
+
if mode == "check":
|
|
124
|
+
env_state = load_dotenv_state(env_file) if os.path.exists(env_file) else {}
|
|
125
|
+
key_state = "已填" if env_state.get("MEM0_LLM_API_KEY") or os.environ.get("MEM0_LLM_API_KEY") else "未填"
|
|
126
|
+
emb_state = "已填" if env_state.get("MEM0_EMBEDDING_API_KEY") or os.environ.get("MEM0_EMBEDDING_API_KEY") else "未填"
|
|
127
|
+
print(f" .env: {'存在' if os.path.exists(env_file) else '不存在'}(LLM key {key_state} / embedding key {emb_state})")
|
|
128
|
+
print(f" 数据目录: {data_dir}({'存在' if os.path.isdir(data_dir) else '不存在'})")
|
|
129
|
+
ok = not missing and py_version_ok()
|
|
130
|
+
print("=" * 56)
|
|
131
|
+
print(" ✅ 环境就绪" if ok else " ❌ 有缺失项,运行 `python setup.py --init [--install]`")
|
|
132
|
+
sys.exit(0 if ok else 1)
|
|
133
|
+
|
|
134
|
+
# init 模式
|
|
135
|
+
if missing:
|
|
136
|
+
cmd = " ".join(f"pip install {m}" for m in missing)
|
|
137
|
+
if args.install:
|
|
138
|
+
print(f" ↳ 自动安装: {cmd}")
|
|
139
|
+
r = subprocess.run(cmd, shell=True)
|
|
140
|
+
if r.returncode != 0:
|
|
141
|
+
print(" FAIL: pip 安装失败,请手动执行上面的命令")
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
for m in list(missing):
|
|
144
|
+
entry = next((e for e in REQUIRED if e[0] == m), None)
|
|
145
|
+
ok, _ = check_dependency(entry[0], entry[1], entry[2]) if entry else (False, None)
|
|
146
|
+
if ok:
|
|
147
|
+
missing.remove(m)
|
|
148
|
+
if missing:
|
|
149
|
+
print(f" FAIL: 仍有缺失: {missing}")
|
|
150
|
+
sys.exit(1)
|
|
151
|
+
else:
|
|
152
|
+
print(f" ↳ 运行: {cmd}")
|
|
153
|
+
print(" (或加 --install 自动安装)")
|
|
154
|
+
sys.exit(1)
|
|
155
|
+
|
|
156
|
+
# 3) 数据目录
|
|
157
|
+
print("[3/4] 数据目录:")
|
|
158
|
+
if os.path.isdir(data_dir):
|
|
159
|
+
print(f" ✅ 已存在: {data_dir}")
|
|
160
|
+
else:
|
|
161
|
+
os.makedirs(data_dir, exist_ok=True)
|
|
162
|
+
print(f" ✅ 已创建: {data_dir}")
|
|
163
|
+
|
|
164
|
+
# 4) .env
|
|
165
|
+
print("[4/4] 环境配置:")
|
|
166
|
+
if os.path.exists(env_file):
|
|
167
|
+
print(f" ✅ .env 已存在(不覆盖): {env_file}")
|
|
168
|
+
elif os.path.exists(env_example):
|
|
169
|
+
shutil.copy2(env_example, env_file)
|
|
170
|
+
print(f" ✅ 已从模板生成: {env_file}")
|
|
171
|
+
print(" ⚠️ 请编辑 .env 填写 MEM0_LLM_API_KEY 与 MEM0_EMBEDDING_API_KEY")
|
|
172
|
+
else:
|
|
173
|
+
print(f" ❌ 模板缺失: {env_example}(发布包异常)")
|
|
174
|
+
sys.exit(1)
|
|
175
|
+
|
|
176
|
+
print("=" * 56)
|
|
177
|
+
print(" ✅ setup 完成。下一步:")
|
|
178
|
+
print(f" 1. 编辑 {env_file} 填写两个 API key")
|
|
179
|
+
print(" 2. 启动 server:")
|
|
180
|
+
print(" - Windows: start-server.bat (或 python mem0-server.py --port 18200)")
|
|
181
|
+
print(" - Linux/macOS: ./start-server.sh (或 python mem0-server.py --port 18200)")
|
|
182
|
+
print(" 3. 首次导入历史会话:node dsh-mem0-import.mjs --since-last")
|
|
183
|
+
print(" (或由插件在会话结束时自动增量导入)")
|
|
184
|
+
print("=" * 56)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
if __name__ == "__main__":
|
|
188
|
+
main()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
REM ============================================================
|
|
3
|
+
REM dsh-mem0-memory - start local mem0 server (Windows)
|
|
4
|
+
REM Usage: start-server.bat [port] (default 18200)
|
|
5
|
+
REM Starts server in a background window, polls /health until
|
|
6
|
+
REM healthy, then prints status. Stop: close "mem0-server" window
|
|
7
|
+
REM or run: taskkill /fi "WINDOWTITLE eq mem0-server"
|
|
8
|
+
REM ============================================================
|
|
9
|
+
setlocal enabledelayedexpansion
|
|
10
|
+
|
|
11
|
+
set PORT=%~1
|
|
12
|
+
if "%PORT%"=="" set PORT=18200
|
|
13
|
+
set DIR=%~dp0
|
|
14
|
+
|
|
15
|
+
REM quick dependency check
|
|
16
|
+
python -c "import mem0, jieba, flask" >nul 2>&1
|
|
17
|
+
if errorlevel 1 (
|
|
18
|
+
echo [start-server] dependencies missing - run: python setup.py --init --install
|
|
19
|
+
exit /b 1
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
echo [start-server] starting mem0-server on port %PORT% ...
|
|
23
|
+
start "mem0-server" /min python "%DIR%mem0-server.py" --port %PORT%
|
|
24
|
+
|
|
25
|
+
REM health self-check: wait up to 120s
|
|
26
|
+
set /a tries=0
|
|
27
|
+
:waitloop
|
|
28
|
+
set /a tries+=1
|
|
29
|
+
if %tries% gtr 60 (
|
|
30
|
+
echo [start-server] FAIL: /health timeout after 120s, check server log
|
|
31
|
+
exit /b 1
|
|
32
|
+
)
|
|
33
|
+
ping -n 3 127.0.0.1 >nul
|
|
34
|
+
curl -s -m 3 "http://127.0.0.1:%PORT%/health" 2>nul | findstr "healthy" >nul
|
|
35
|
+
if errorlevel 1 (
|
|
36
|
+
curl -s -m 3 "http://127.0.0.1:%PORT%/health" 2>nul | findstr "init_failed" >nul
|
|
37
|
+
if not errorlevel 1 (
|
|
38
|
+
echo [start-server] FAIL: init failed
|
|
39
|
+
exit /b 1
|
|
40
|
+
)
|
|
41
|
+
echo [start-server] initializing... tries=!tries!
|
|
42
|
+
goto waitloop
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
echo [start-server] OK: healthy, port %PORT% - server running in background window
|
|
46
|
+
echo [start-server] stop: close "mem0-server" window, or taskkill /fi "WINDOWTITLE eq mem0-server"
|
|
47
|
+
endlocal
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================
|
|
3
|
+
# dsh-mem0-memory — 启动本地 mem0 server(Linux / macOS)
|
|
4
|
+
# 用法: ./start-server.sh [port] (默认 18200)
|
|
5
|
+
# 启动后轮询 /health 直到 healthy,然后保持前台运行(Ctrl+C 停止)
|
|
6
|
+
# ============================================================
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
PORT="${1:-18200}"
|
|
10
|
+
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
|
|
12
|
+
# 依赖快速检查
|
|
13
|
+
python3 -c "import mem0, jieba, flask" 2>/dev/null || {
|
|
14
|
+
echo "[start-server] 依赖缺失:请先运行 python3 setup.py --init --install"
|
|
15
|
+
exit 1
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
echo "[start-server] 启动 mem0-server on port ${PORT} ..."
|
|
19
|
+
python3 "${DIR}/mem0-server.py" --port "${PORT}" &
|
|
20
|
+
SERVER_PID=$!
|
|
21
|
+
trap 'kill ${SERVER_PID} 2>/dev/null || true' EXIT
|
|
22
|
+
|
|
23
|
+
# 健康自检:最多等 120 秒
|
|
24
|
+
for i in $(seq 1 60); do
|
|
25
|
+
STATUS=$(curl -s -m 3 "http://127.0.0.1:${PORT}/health" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status',''))" 2>/dev/null || true)
|
|
26
|
+
case "$STATUS" in
|
|
27
|
+
healthy)
|
|
28
|
+
echo "[start-server] OK: healthy(端口 ${PORT})"
|
|
29
|
+
echo "[start-server] 停止:Ctrl+C"
|
|
30
|
+
wait ${SERVER_PID}
|
|
31
|
+
exit 0
|
|
32
|
+
;;
|
|
33
|
+
init_failed)
|
|
34
|
+
echo "[start-server] FAIL: 初始化失败"
|
|
35
|
+
exit 1
|
|
36
|
+
;;
|
|
37
|
+
*)
|
|
38
|
+
echo "[start-server] 初始化中($((i*2))s)..."
|
|
39
|
+
sleep 2
|
|
40
|
+
;;
|
|
41
|
+
esac
|
|
42
|
+
done
|
|
43
|
+
echo "[start-server] FAIL: /health 超时(120s)"
|
|
44
|
+
exit 1
|