openclaw-memory-alibaba-mysql 0.2.0 → 0.2.2
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/README.md +37 -51
- package/config.ts +32 -9
- package/db.ts +14 -0
- package/index.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,33 +1,36 @@
|
|
|
1
|
-
#
|
|
1
|
+
# openclaw-memory-alibaba-mysql
|
|
2
2
|
|
|
3
|
-
OpenClaw 记忆插件,使用阿里云 RDS MySQL
|
|
3
|
+
OpenClaw 记忆插件,使用阿里云 RDS MySQL 做向量存储。支持用户记忆、全文对话记录、自进化记忆;可与 OpenClaw 的 before_agent_start / agent_end 配合,自动召回与自动落库。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 功能概览
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
- **用户记忆**:从对话中自动抽取事实、偏好、决策(如「喜欢寿司」「打算用 Python」),支持按向量检索并注入到每轮上下文。
|
|
8
|
+
- **全文记忆**:按消息来源分为用户、助手、系统、工具等 6 类,每类按会话保留一份,每轮更新;只存真实对话内容,不存系统注入的上下文块。
|
|
9
|
+
- **自进化记忆**:从对话中抽取学习要点、错误、需求等,可选参与召回。
|
|
10
|
+
- **冲突与去重**:可选用 LLM 判断新记忆与已有记忆是否矛盾或重复,避免重复、矛盾条目堆积。
|
|
11
|
+
- **时间衰减**:召回时可对旧记忆降权,让近期信息更突出。
|
|
12
|
+
- **工具**:提供 `memory_recall`(按查询搜记忆)、`memory_store`(显式写入)、`memory_forget`(删除);若只依赖自动召回与自动抓取,可不给 agent 开放 recall/store,仅保留 forget 用于删除。
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
- **memory_store 工具**:可显式指定 `category`,默认 `user_memory_fact`。
|
|
15
|
-
- **召回**:`before_agent_start` 与 `memory_recall` 统一按向量检索用户记忆(及可选 self_improving),注入上下文。
|
|
14
|
+
## 记忆分类
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
| 类型 | 说明 |
|
|
17
|
+
|------|------|
|
|
18
|
+
| 用户事实 / 偏好 / 决策 | 从用户话里抽取的事实、喜好、决定,用于后续对话的上下文 |
|
|
19
|
+
| 全文·用户 / 助手 / 系统 / 工具 / 工具结果 / 其它 | 按消息角色分的完整对话记录,按会话维护、每轮更新 |
|
|
20
|
+
| 自进化(学习 / 错误 / 需求) | 从对话中抽取的可复用经验,可选参与召回 |
|
|
18
21
|
|
|
19
22
|
## 配置示例
|
|
20
23
|
|
|
21
|
-
###
|
|
24
|
+
### 最简配置
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
只接 MySQL 和向量服务,其余用默认(自动召回 + 自动抓取用户记忆,不开启全文与自进化):
|
|
24
27
|
|
|
25
28
|
```json
|
|
26
29
|
{
|
|
27
30
|
"plugins": {
|
|
28
|
-
"slots": { "memory": "
|
|
31
|
+
"slots": { "memory": "openclaw-memory-alibaba-mysql" },
|
|
29
32
|
"entries": {
|
|
30
|
-
"
|
|
33
|
+
"openclaw-memory-alibaba-mysql": {
|
|
31
34
|
"enabled": true,
|
|
32
35
|
"config": {
|
|
33
36
|
"mysql": {
|
|
@@ -50,16 +53,16 @@ OpenClaw 记忆插件,使用阿里云 RDS MySQL 向量存储。用户记忆细
|
|
|
50
53
|
}
|
|
51
54
|
```
|
|
52
55
|
|
|
53
|
-
###
|
|
56
|
+
### 功能全开
|
|
54
57
|
|
|
55
|
-
|
|
58
|
+
开启全文记忆、自进化、LLM 抽取与冲突处理、时间衰减等:
|
|
56
59
|
|
|
57
60
|
```json
|
|
58
61
|
{
|
|
59
62
|
"plugins": {
|
|
60
|
-
"slots": { "memory": "
|
|
63
|
+
"slots": { "memory": "openclaw-memory-alibaba-mysql" },
|
|
61
64
|
"entries": {
|
|
62
|
-
"
|
|
65
|
+
"openclaw-memory-alibaba-mysql": {
|
|
63
66
|
"enabled": true,
|
|
64
67
|
"config": {
|
|
65
68
|
"mysql": {
|
|
@@ -89,7 +92,7 @@ OpenClaw 记忆插件,使用阿里云 RDS MySQL 向量存储。用户记忆细
|
|
|
89
92
|
"memoryExtractionMethod": "llm",
|
|
90
93
|
"autoRecall": true,
|
|
91
94
|
"autoCapture": true,
|
|
92
|
-
"captureMaxChars":
|
|
95
|
+
"captureMaxChars": 5000,
|
|
93
96
|
"enableMemoryDecay": true,
|
|
94
97
|
"tableName": "openclaw_memories"
|
|
95
98
|
}
|
|
@@ -99,38 +102,21 @@ OpenClaw 记忆插件,使用阿里云 RDS MySQL 向量存储。用户记忆细
|
|
|
99
102
|
}
|
|
100
103
|
```
|
|
101
104
|
|
|
102
|
-
|
|
105
|
+
**常用配置说明**
|
|
103
106
|
|
|
104
|
-
-
|
|
105
|
-
- **
|
|
106
|
-
- **
|
|
107
|
-
- **
|
|
108
|
-
- **
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
- **mysql** / **embedding**:必填,用于连接数据库与生成向量。
|
|
108
|
+
- **llm**:使用「LLM 抽取」或「冲突/去重」时必填。
|
|
109
|
+
- **memoryExtractionMethod**:`"llm"`(默认)或 `"regex"`,控制如何从对话里抽取用户记忆与自改进记忆。
|
|
110
|
+
- **enableFullContextMemory**:是否按角色保存全文对话(每会话每类一份,每轮更新)。
|
|
111
|
+
- **enableSelfImprovingMemory**:是否启用自进化记忆的写入与召回。
|
|
112
|
+
- **memory_duplication_conflict_process**:是否用 LLM 做写入前的去重与矛盾判断。
|
|
113
|
+
- **captureMaxChars**:单条记忆与全文截断的最大字符数,建议 5000 以保留完整当轮对话。
|
|
114
|
+
- **enableMemoryDecay**:召回时是否对旧记忆做时间衰减(近期记忆权重更高)。
|
|
115
|
+
- **tableName**:存储表名,默认 `openclaw_memories`。
|
|
111
116
|
|
|
112
117
|
## 环境变量
|
|
113
118
|
|
|
114
|
-
|
|
115
|
-
- `DASHSCOPE_API_KEY`:DashScope 用于 embedding(若 apiKey 使用 `${DASHSCOPE_API_KEY}`)。
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## 测试
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
export MYSQL_HOST=your-host MYSQL_PASSWORD=xxx DASHSCOPE_API_KEY=xxx
|
|
123
|
-
npx tsx test-agent-isolation.ts
|
|
124
|
-
npx tsx test-three-memory-types.ts
|
|
125
|
-
npx tsx test-full-context-memory.ts
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
---
|
|
129
|
-
|
|
130
|
-
## 文件说明
|
|
119
|
+
配置中可使用占位符引用环境变量,例如 `${MYSQL_PASSWORD}`、`${DASHSCOPE_API_KEY}`。常见需要准备的有:
|
|
131
120
|
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
- `db.ts`:MemoryDB 建表、store、search、softDelete(向量 COSINE)。
|
|
135
|
-
- `index.ts`:插件注册、memory_recall / memory_store / memory_forget、before_agent_start / agent_end。
|
|
136
|
-
- `prompts.ts`:用户记忆与 self_improving 的 LLM 抽取 Prompt。
|
|
121
|
+
- **MYSQL_HOST**, **MYSQL_USER**, **MYSQL_PASSWORD**:MySQL 连接信息。
|
|
122
|
+
- **DASHSCOPE_API_KEY**:DashScope API Key,用于 embedding(以及 LLM 若使用 DashScope)。
|
package/config.ts
CHANGED
|
@@ -114,43 +114,66 @@ function assertAllowedKeys(value: Record<string, unknown>, allowed: string[], la
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
function requireString(obj: Record<string, unknown>, key: string, label: string): string {
|
|
117
|
+
function requireString(obj: Record<string, unknown>, key: string, label: string, allowEmpty = false): string {
|
|
118
118
|
const v = obj[key];
|
|
119
|
-
if (typeof v !== "string"
|
|
119
|
+
if (typeof v !== "string") {
|
|
120
|
+
throw new Error(`${label}.${key} is required and must be a string`);
|
|
121
|
+
}
|
|
122
|
+
if (!allowEmpty && v.length === 0) {
|
|
120
123
|
throw new Error(`${label}.${key} is required and must be a non-empty string`);
|
|
121
124
|
}
|
|
122
125
|
return v;
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
function parseMysqlConfig(raw: unknown): MysqlConnectionConfig {
|
|
129
|
+
// Allow empty mysql config - will be validated at connection time
|
|
126
130
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
127
|
-
|
|
131
|
+
return {
|
|
132
|
+
host: "",
|
|
133
|
+
port: 3306,
|
|
134
|
+
user: "",
|
|
135
|
+
password: "",
|
|
136
|
+
database: "openclaw_memory",
|
|
137
|
+
ssl: false,
|
|
138
|
+
};
|
|
128
139
|
}
|
|
129
140
|
const m = raw as Record<string, unknown>;
|
|
130
141
|
assertAllowedKeys(m, ["host", "port", "user", "password", "database", "ssl"], "mysql");
|
|
131
142
|
|
|
143
|
+
const host = typeof m.host === "string" ? m.host : "";
|
|
144
|
+
const user = typeof m.user === "string" ? m.user : "";
|
|
145
|
+
const password = typeof m.password === "string" ? resolveEnvVars(m.password) : "";
|
|
146
|
+
const database = typeof m.database === "string" ? m.database : "openclaw_memory";
|
|
147
|
+
|
|
132
148
|
return {
|
|
133
|
-
host
|
|
149
|
+
host,
|
|
134
150
|
port: typeof m.port === "number" ? m.port : 3306,
|
|
135
|
-
user
|
|
136
|
-
password
|
|
137
|
-
database
|
|
151
|
+
user,
|
|
152
|
+
password,
|
|
153
|
+
database,
|
|
138
154
|
ssl: m.ssl === true,
|
|
139
155
|
};
|
|
140
156
|
}
|
|
141
157
|
|
|
142
158
|
function parseEmbeddingConfig(raw: unknown): EmbeddingConfig {
|
|
159
|
+
// Allow empty embedding config - will be validated at connection time
|
|
143
160
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
144
|
-
|
|
161
|
+
return {
|
|
162
|
+
apiKey: "",
|
|
163
|
+
model: DEFAULT_MODEL,
|
|
164
|
+
baseUrl: DEFAULT_BASE_URL,
|
|
165
|
+
dimensions: undefined,
|
|
166
|
+
};
|
|
145
167
|
}
|
|
146
168
|
const e = raw as Record<string, unknown>;
|
|
147
169
|
assertAllowedKeys(e, ["apiKey", "model", "baseUrl", "dimensions"], "embedding");
|
|
148
170
|
|
|
149
171
|
const model = typeof e.model === "string" ? e.model : DEFAULT_MODEL;
|
|
150
172
|
const explicitDims = typeof e.dimensions === "number" ? e.dimensions : undefined;
|
|
173
|
+
const apiKey = typeof e.apiKey === "string" ? resolveEnvVars(e.apiKey) : "";
|
|
151
174
|
|
|
152
175
|
return {
|
|
153
|
-
apiKey
|
|
176
|
+
apiKey,
|
|
154
177
|
model,
|
|
155
178
|
baseUrl: typeof e.baseUrl === "string" ? resolveEnvVars(e.baseUrl) : DEFAULT_BASE_URL,
|
|
156
179
|
dimensions: explicitDims,
|
package/db.ts
CHANGED
|
@@ -51,6 +51,20 @@ export class MemoryDB {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
private async doInitialize(): Promise<void> {
|
|
54
|
+
// Validate MySQL config before connecting
|
|
55
|
+
if (!this.mysqlConfig.host || this.mysqlConfig.host.length === 0) {
|
|
56
|
+
throw new Error("MySQL host is not configured. Please set mysql.host in the plugin config.");
|
|
57
|
+
}
|
|
58
|
+
if (!this.mysqlConfig.user || this.mysqlConfig.user.length === 0) {
|
|
59
|
+
throw new Error("MySQL user is not configured. Please set mysql.user in the plugin config.");
|
|
60
|
+
}
|
|
61
|
+
if (!this.mysqlConfig.password || this.mysqlConfig.password.length === 0) {
|
|
62
|
+
throw new Error("MySQL password is not configured. Please set mysql.password in the plugin config.");
|
|
63
|
+
}
|
|
64
|
+
if (!this.mysqlConfig.database || this.mysqlConfig.database.length === 0) {
|
|
65
|
+
throw new Error("MySQL database is not configured. Please set mysql.database in the plugin config.");
|
|
66
|
+
}
|
|
67
|
+
|
|
54
68
|
this.pool = mysql.createPool({
|
|
55
69
|
host: this.mysqlConfig.host,
|
|
56
70
|
port: this.mysqlConfig.port,
|
package/index.ts
CHANGED
|
@@ -81,6 +81,10 @@ class Embeddings {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
async embed(text: string): Promise<number[]> {
|
|
84
|
+
// Validate API key before making request
|
|
85
|
+
if (!this.client.apiKey || this.client.apiKey.length === 0) {
|
|
86
|
+
throw new Error("Embedding API key is not configured. Please set embedding.apiKey in the plugin config.");
|
|
87
|
+
}
|
|
84
88
|
const params: { model: string; input: string; dimensions?: number } = {
|
|
85
89
|
model: this.model,
|
|
86
90
|
input: text,
|