openclaw-memory-alibaba-local 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/README.md +88 -0
- package/bm25-recall.ts +71 -0
- package/capture-state.ts +206 -0
- package/categories.ts +106 -0
- package/config.ts +570 -0
- package/db.ts +877 -0
- package/embed-chunks.ts +63 -0
- package/embedding-backend.ts +186 -0
- package/index.ts +1638 -0
- package/openclaw.plugin.json +228 -0
- package/package.json +51 -0
- package/prompt-strip.ts +141 -0
- package/prompts.ts +117 -0
- package/web/memory-routes.ts +433 -0
- package/web/memory-ui.ts +2121 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "openclaw-memory-alibaba-local",
|
|
3
|
+
"name": "openclaw-memory-alibaba-local",
|
|
4
|
+
"description": "Local LanceDB long-term memory; user_memory_*, self_improving_*; DashScope-friendly defaults; table openclaw_memories_alibaba_local.",
|
|
5
|
+
"configSchema": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"dbPath": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"default": "",
|
|
12
|
+
"description": "LanceDB directory (default: ~/.openclaw/memory/lancedb, same as official memory-lancedb)."
|
|
13
|
+
},
|
|
14
|
+
"embedding": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"additionalProperties": false,
|
|
17
|
+
"description": "Omitted or {} → local llama-embedding. If apiKey/model/baseUrl/dimensions set without mode → remote (maxToken/dimensions default in plugin parse).",
|
|
18
|
+
"properties": {
|
|
19
|
+
"mode": { "type": "string", "enum": ["local", "remote"], "default": "local" },
|
|
20
|
+
"commandPrefix": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Shell command; stdin = text to embed. Default uses llama-embedding + -f /dev/stdin --embd-output-format json."
|
|
23
|
+
},
|
|
24
|
+
"dimensions": { "type": "number", "description": "768 default (local); 1024 default (remote) when omitted." },
|
|
25
|
+
"maxToken": { "type": "number", "description": "2048 default (remote) when omitted." },
|
|
26
|
+
"apiKey": { "type": "string" },
|
|
27
|
+
"model": { "type": "string" },
|
|
28
|
+
"baseUrl": { "type": "string" }
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"memory_duplication_conflict_process": {
|
|
32
|
+
"type": "boolean",
|
|
33
|
+
"default": true,
|
|
34
|
+
"description": "When true (default), LLM decides insert vs update among similar memories; requires llm. Set false to disable."
|
|
35
|
+
},
|
|
36
|
+
"llm": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"additionalProperties": false,
|
|
39
|
+
"description": "When LLM is required, each field can be omitted and filled from host openclaw.json: bailian apiKey/baseUrl and agents.defaults.model.primary (bailian/model → model id without prefix). Plugin values override file defaults.",
|
|
40
|
+
"properties": {
|
|
41
|
+
"apiKey": { "type": "string", "default": "" },
|
|
42
|
+
"model": { "type": "string", "default": "qwen-plus" },
|
|
43
|
+
"baseUrl": { "type": "string", "default": "https://dashscope.aliyuncs.com/compatible-mode/v1" }
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"similarityThresholdUserMemory": { "type": "number", "default": 0.65 },
|
|
47
|
+
"similarityThresholdSelfImproving": { "type": "number", "default": 0.62 },
|
|
48
|
+
"enableFullContextMemory": {
|
|
49
|
+
"type": "boolean",
|
|
50
|
+
"default": true,
|
|
51
|
+
"description": "When true (default), append full_context_* rows on agent_end (incremental cursor). Stored as plain text with a zero vector placeholder (no embedding); not used in vector recall."
|
|
52
|
+
},
|
|
53
|
+
"enableSelfImprovingMemory": {
|
|
54
|
+
"type": "boolean",
|
|
55
|
+
"default": true,
|
|
56
|
+
"description": "When true (default), allow self_improving_* write and recall. Set false to disable."
|
|
57
|
+
},
|
|
58
|
+
"memoryExtractionMethod": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"enum": ["regex", "llm"],
|
|
61
|
+
"default": "llm",
|
|
62
|
+
"description": "regex or llm; llm needs apiKey+model (plugin llm and/or openclaw.json bailian + agents.defaults.model)."
|
|
63
|
+
},
|
|
64
|
+
"autoRecall": { "type": "boolean", "default": true },
|
|
65
|
+
"autoCapture": { "type": "boolean", "default": true },
|
|
66
|
+
"captureMaxChars": { "type": "number", "default": 50000 },
|
|
67
|
+
"enableMemoryDecay": {
|
|
68
|
+
"type": "boolean",
|
|
69
|
+
"default": true,
|
|
70
|
+
"description": "When true (default), recall uses time decay (30d half-life, exponential). Set false to disable."
|
|
71
|
+
},
|
|
72
|
+
"memoryDecayHalfLifeDays": {
|
|
73
|
+
"type": "number",
|
|
74
|
+
"default": 30,
|
|
75
|
+
"description": "Half-life in days for exponential/linear decay (1–3650)."
|
|
76
|
+
},
|
|
77
|
+
"memoryDecayStrategy": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"enum": ["exponential", "linear", "none"],
|
|
80
|
+
"default": "exponential"
|
|
81
|
+
},
|
|
82
|
+
"adminPanelMemoryTypeOptions": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"additionalProperties": false,
|
|
85
|
+
"description": "管理端「用户记忆 / 自进化记忆 / 全文记忆」Tab 的「记忆类型」筛选项;可写 category + 中文 labelZh。省略则使用内置类别与中文名。",
|
|
86
|
+
"properties": {
|
|
87
|
+
"user": {
|
|
88
|
+
"type": "array",
|
|
89
|
+
"items": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"additionalProperties": false,
|
|
92
|
+
"required": ["category", "labelZh"],
|
|
93
|
+
"properties": {
|
|
94
|
+
"category": { "type": "string" },
|
|
95
|
+
"labelZh": { "type": "string" }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"self": {
|
|
100
|
+
"type": "array",
|
|
101
|
+
"items": {
|
|
102
|
+
"type": "object",
|
|
103
|
+
"additionalProperties": false,
|
|
104
|
+
"required": ["category", "labelZh"],
|
|
105
|
+
"properties": {
|
|
106
|
+
"category": { "type": "string" },
|
|
107
|
+
"labelZh": { "type": "string" }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"full": {
|
|
112
|
+
"type": "array",
|
|
113
|
+
"items": {
|
|
114
|
+
"type": "object",
|
|
115
|
+
"additionalProperties": false,
|
|
116
|
+
"required": ["category", "labelZh"],
|
|
117
|
+
"properties": {
|
|
118
|
+
"category": { "type": "string" },
|
|
119
|
+
"labelZh": { "type": "string" }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"uiHints": {
|
|
128
|
+
"dbPath": {
|
|
129
|
+
"label": "LanceDB Path",
|
|
130
|
+
"placeholder": "~/.openclaw/memory/lancedb",
|
|
131
|
+
"help": "Same default as official memory-lancedb; table openclaw_memories_alibaba_local inside this directory."
|
|
132
|
+
},
|
|
133
|
+
"embedding.mode": {
|
|
134
|
+
"label": "Embedding mode",
|
|
135
|
+
"help": "local: llama-embedding (stdin). remote: OpenAI-compatible HTTP (all remote fields required; first embed may fail if invalid)."
|
|
136
|
+
},
|
|
137
|
+
"embedding.commandPrefix": {
|
|
138
|
+
"label": "Local embed command",
|
|
139
|
+
"placeholder": "llama-embedding -m ~/.openclaw/... -f /dev/stdin --embd-output-format json",
|
|
140
|
+
"help": "Must read prompt from stdin; JSON output recommended for parsing."
|
|
141
|
+
},
|
|
142
|
+
"embedding.apiKey": {
|
|
143
|
+
"label": "Embedding API Key (remote)",
|
|
144
|
+
"sensitive": true,
|
|
145
|
+
"placeholder": "••••••••",
|
|
146
|
+
"help": "OpenAI-compatible /embeddings (${VAR} supported)"
|
|
147
|
+
},
|
|
148
|
+
"embedding.model": {
|
|
149
|
+
"label": "Embedding model (remote)",
|
|
150
|
+
"placeholder": "text-embedding-v3"
|
|
151
|
+
},
|
|
152
|
+
"embedding.baseUrl": {
|
|
153
|
+
"label": "Embedding base URL (remote)",
|
|
154
|
+
"placeholder": "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
|
155
|
+
},
|
|
156
|
+
"embedding.dimensions": {
|
|
157
|
+
"label": "Embedding dimensions",
|
|
158
|
+
"placeholder": "768 or 1024"
|
|
159
|
+
},
|
|
160
|
+
"embedding.maxToken": {
|
|
161
|
+
"label": "Max tokens per chunk (approx)",
|
|
162
|
+
"placeholder": "2048",
|
|
163
|
+
"help": "Paragraphs exceeding this are split further (char length / 4)."
|
|
164
|
+
},
|
|
165
|
+
"llm.apiKey": {
|
|
166
|
+
"label": "LLM API Key",
|
|
167
|
+
"sensitive": true,
|
|
168
|
+
"placeholder": "••••••••",
|
|
169
|
+
"help": "If empty, uses models.providers.bailian.apiKey from openclaw.json when LLM is required"
|
|
170
|
+
},
|
|
171
|
+
"llm.model": {
|
|
172
|
+
"label": "LLM Model",
|
|
173
|
+
"placeholder": "qwen-plus",
|
|
174
|
+
"help": "If empty, uses agents.defaults.model.primary; bailian/name is normalized to name for Chat Completions"
|
|
175
|
+
},
|
|
176
|
+
"llm.baseUrl": {
|
|
177
|
+
"label": "LLM Base URL",
|
|
178
|
+
"placeholder": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
179
|
+
"help": "If empty, uses models.providers.bailian.baseUrl, else DashScope compatible default"
|
|
180
|
+
},
|
|
181
|
+
"memory_duplication_conflict_process": {
|
|
182
|
+
"label": "Conflict / Dedup (LLM)",
|
|
183
|
+
"help": "Default on: LLM decides insert vs update. Turn off to use vector-only dedup."
|
|
184
|
+
},
|
|
185
|
+
"similarityThresholdUserMemory": {
|
|
186
|
+
"label": "User Memory Similarity Threshold",
|
|
187
|
+
"placeholder": "0.65",
|
|
188
|
+
"help": "0–1; L2 score = 1/(1+distance), same as official memory-lancedb"
|
|
189
|
+
},
|
|
190
|
+
"similarityThresholdSelfImproving": {
|
|
191
|
+
"label": "Self-Improving Similarity Threshold",
|
|
192
|
+
"placeholder": "0.62"
|
|
193
|
+
},
|
|
194
|
+
"enableFullContextMemory": {
|
|
195
|
+
"label": "Enable Full Context Memory",
|
|
196
|
+
"help": "Default on: one row per new transcript message on agent_end (batchId + cursor). No embedding/dedup; zero-vector placeholder only. Not injected into recall."
|
|
197
|
+
},
|
|
198
|
+
"enableSelfImprovingMemory": {
|
|
199
|
+
"label": "Enable Self-Improving Memory",
|
|
200
|
+
"help": "Default on: learnings / errors / feature requests. Turn off to disable."
|
|
201
|
+
},
|
|
202
|
+
"memoryExtractionMethod": {
|
|
203
|
+
"label": "Extraction Method",
|
|
204
|
+
"help": "regex or llm (default llm)"
|
|
205
|
+
},
|
|
206
|
+
"autoRecall": {
|
|
207
|
+
"label": "Auto Recall",
|
|
208
|
+
"help": "Inject memories at conversation start"
|
|
209
|
+
},
|
|
210
|
+
"autoCapture": {
|
|
211
|
+
"label": "Auto Capture",
|
|
212
|
+
"help": "Store memories at conversation end"
|
|
213
|
+
},
|
|
214
|
+
"captureMaxChars": {
|
|
215
|
+
"label": "Capture Max Chars",
|
|
216
|
+
"placeholder": "50000",
|
|
217
|
+
"help": "100–100000"
|
|
218
|
+
},
|
|
219
|
+
"enableMemoryDecay": {
|
|
220
|
+
"label": "Enable Memory Decay",
|
|
221
|
+
"help": "Default on: older memories score lower on recall. Turn off for flat scores."
|
|
222
|
+
},
|
|
223
|
+
"adminPanelMemoryTypeOptions": {
|
|
224
|
+
"label": "管理端记忆类型筛选项",
|
|
225
|
+
"help": "可选。按 Tab 配置 { user|self|full: [{ category, labelZh }] };category 须为该 Tab 合法类别,labelZh 为下拉展示中文。不写则用内置列表与中文名。"
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openclaw-memory-alibaba-local",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenClaw memory plugin: local LanceDB + DashScope-compatible embeddings",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/chengjue-2445/openclaw-memory-alibaba-mysql-install-skill.git",
|
|
10
|
+
"directory": "openclaw-memory-alibaba-local"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"openclaw",
|
|
14
|
+
"openclaw-plugin",
|
|
15
|
+
"memory",
|
|
16
|
+
"lancedb",
|
|
17
|
+
"vector-search"
|
|
18
|
+
],
|
|
19
|
+
"files": [
|
|
20
|
+
"index.ts",
|
|
21
|
+
"prompt-strip.ts",
|
|
22
|
+
"bm25-recall.ts",
|
|
23
|
+
"capture-state.ts",
|
|
24
|
+
"config.ts",
|
|
25
|
+
"db.ts",
|
|
26
|
+
"embed-chunks.ts",
|
|
27
|
+
"embedding-backend.ts",
|
|
28
|
+
"categories.ts",
|
|
29
|
+
"prompts.ts",
|
|
30
|
+
"web/memory-routes.ts",
|
|
31
|
+
"web/memory-ui.ts",
|
|
32
|
+
"openclaw.plugin.json",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@lancedb/lancedb": "^0.27.1",
|
|
37
|
+
"@sinclair/typebox": "0.34.48",
|
|
38
|
+
"openai": "^6.25.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"openclaw": "*"
|
|
42
|
+
},
|
|
43
|
+
"openclaw": {
|
|
44
|
+
"extensions": [
|
|
45
|
+
"./index.ts"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"typescript": "^6.0.2"
|
|
50
|
+
}
|
|
51
|
+
}
|
package/prompt-strip.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip OpenClaw channel / injection noise for logical memory extraction and recall query building.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Matches OpenClaw `buildInboundMetadataBlocks` (control-ui / channels): ```json ... ``` after a labeled line. */
|
|
6
|
+
const OPENCLAW_UNTRUSTED_METADATA_BLOCK_RE = new RegExp(
|
|
7
|
+
"(?:^|[\\r\\n])(?:Conversation info|Sender|Thread starter|Replied message|Forwarded message context|Chat history since last reply)\\s*\\([^)]*\\):\\s*" +
|
|
8
|
+
"```" +
|
|
9
|
+
"(?:json)?\\s*[\\s\\S]*?" +
|
|
10
|
+
"```" +
|
|
11
|
+
"\\s*",
|
|
12
|
+
"gim",
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const THREAD_HISTORY_RE = /^\[Thread history - for context\]/;
|
|
16
|
+
const THREAD_STARTER_RE = /^\[Thread starter - for context\]/;
|
|
17
|
+
const MEDIA_ATTACHED_LINE_RE = /^\[media attached/;
|
|
18
|
+
const MEDIA_REPLY_HINT_PREFIX = "To send an image back, prefer the message tool";
|
|
19
|
+
|
|
20
|
+
const MIN_RECALL_QUERY_LEN = 5;
|
|
21
|
+
|
|
22
|
+
function isSystemEventsOnlyParagraph(p: string): boolean {
|
|
23
|
+
const lines = p.split("\n");
|
|
24
|
+
const nonEmpty = lines.map((l) => l.trim()).filter(Boolean);
|
|
25
|
+
if (nonEmpty.length === 0) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return nonEmpty.every((l) => l.startsWith("System:"));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Strip leading media lines + OpenClaw mediaReplyHint (single-line paragraph).
|
|
33
|
+
* OpenClaw joins these with `\n` (not always `\n\n`).
|
|
34
|
+
*/
|
|
35
|
+
function stripLeadingMediaAndHint(text: string): { rest: string; tags: string[] } {
|
|
36
|
+
const tags: string[] = [];
|
|
37
|
+
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
38
|
+
let i = 0;
|
|
39
|
+
let sawMediaLine = false;
|
|
40
|
+
while (i < lines.length) {
|
|
41
|
+
const raw = lines[i] ?? "";
|
|
42
|
+
const t = raw.trim();
|
|
43
|
+
if (!t) {
|
|
44
|
+
i += 1;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (MEDIA_ATTACHED_LINE_RE.test(t)) {
|
|
48
|
+
sawMediaLine = true;
|
|
49
|
+
i += 1;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (t.startsWith(MEDIA_REPLY_HINT_PREFIX)) {
|
|
53
|
+
tags.push("media_reply_hint");
|
|
54
|
+
i += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
if (sawMediaLine) {
|
|
60
|
+
tags.push("media_attached_lines");
|
|
61
|
+
}
|
|
62
|
+
const rest = lines.slice(i).join("\n").replace(/^\n+/, "").trimEnd();
|
|
63
|
+
return { rest, tags };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Strip prompt/channel noise before user_memory / self_improving extraction only.
|
|
68
|
+
* Full-context rows intentionally keep the raw transcript (including XML + OpenClaw metadata).
|
|
69
|
+
*/
|
|
70
|
+
export function stripForLogicalMemoryExtraction(text: string): string {
|
|
71
|
+
let out = text
|
|
72
|
+
.replace(/<\s*relevant-memories\b[\s\S]*?<\s*\/\s*relevant-memories\s*>/gi, "\n")
|
|
73
|
+
.replace(/<\s*knowledge-context\b[\s\S]*?<\s*\/\s*knowledge-context\s*>/gi, "\n");
|
|
74
|
+
|
|
75
|
+
let prev: string;
|
|
76
|
+
do {
|
|
77
|
+
prev = out;
|
|
78
|
+
out = out.replace(OPENCLAW_UNTRUSTED_METADATA_BLOCK_RE, "\n");
|
|
79
|
+
} while (out !== prev);
|
|
80
|
+
|
|
81
|
+
out = out.replace(/^\s*\[(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\b[^[\]]*\]\s+/im, "");
|
|
82
|
+
|
|
83
|
+
out = out.replace(/\n{3,}/g, "\n\n").replace(/^\n+|\n+$/g, "").trim();
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type ExtractUserQueryForRecallResult = {
|
|
88
|
+
/** Text used for embedding + BM25 recall */
|
|
89
|
+
query: string;
|
|
90
|
+
/** True when query came from full-prompt strip fallback (prefix strip yielded too short) */
|
|
91
|
+
usedFallback: boolean;
|
|
92
|
+
/** High-level strip steps for logs */
|
|
93
|
+
removedLabels: string[];
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Derive a recall query closer to "what the user said" than raw `event.prompt`.
|
|
98
|
+
* OpenClaw `prefixedCommandBody` stacks media, thread notes, system events, then user body — see get-reply-run.ts.
|
|
99
|
+
*/
|
|
100
|
+
export function extractUserQueryForRecall(rawPrompt: string): ExtractUserQueryForRecallResult {
|
|
101
|
+
const removedLabels: string[] = [];
|
|
102
|
+
let s = rawPrompt.replace(/\r\n/g, "\n").trim();
|
|
103
|
+
|
|
104
|
+
const mediaPass = stripLeadingMediaAndHint(s);
|
|
105
|
+
s = mediaPass.rest.trim();
|
|
106
|
+
for (const t of mediaPass.tags) {
|
|
107
|
+
removedLabels.push(t);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const segments = s.split(/\n\n+/).map((x) => x.trim()).filter(Boolean);
|
|
111
|
+
const kept: string[] = [];
|
|
112
|
+
for (const seg of segments) {
|
|
113
|
+
if (THREAD_HISTORY_RE.test(seg)) {
|
|
114
|
+
removedLabels.push("thread_history_block");
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (THREAD_STARTER_RE.test(seg)) {
|
|
118
|
+
removedLabels.push("thread_starter_block");
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (isSystemEventsOnlyParagraph(seg)) {
|
|
122
|
+
removedLabels.push("system_events_block");
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
kept.push(seg);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let joined = kept.join("\n\n").trim();
|
|
129
|
+
joined = stripForLogicalMemoryExtraction(joined).trim();
|
|
130
|
+
|
|
131
|
+
const strippedFull = stripForLogicalMemoryExtraction(rawPrompt.replace(/\r\n/g, "\n").trim()).trim();
|
|
132
|
+
|
|
133
|
+
if (joined.length >= MIN_RECALL_QUERY_LEN) {
|
|
134
|
+
return { query: joined, usedFallback: false, removedLabels };
|
|
135
|
+
}
|
|
136
|
+
if (strippedFull.length >= MIN_RECALL_QUERY_LEN) {
|
|
137
|
+
return { query: strippedFull, usedFallback: true, removedLabels };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { query: joined.length > 0 ? joined : strippedFull, usedFallback: true, removedLabels };
|
|
141
|
+
}
|
package/prompts.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt templates for LLM-based memory extraction (memoryExtractionMethod: "llm").
|
|
3
|
+
* Aligned with Mem0-style extraction instructions.
|
|
4
|
+
* @see https://github.com/mem0ai/mem0/blob/main/openclaw/index.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Instruction block for extracting user memories from conversation messages. */
|
|
8
|
+
export const USER_MEMORY_EXTRACTION_INSTRUCTIONS = `Your Task: Extract and maintain a structured, evolving profile of the user from their conversations with an AI assistant. Capture information that would help the assistant provide personalized, context-aware responses in future interactions.
|
|
9
|
+
|
|
10
|
+
Information to Extract (map each to exactly one category):
|
|
11
|
+
|
|
12
|
+
1. user_memory_fact — Identity, context, and verifiable facts:
|
|
13
|
+
- Name, age, location, timezone, language preferences
|
|
14
|
+
- Occupation, employer, job role, industry, education
|
|
15
|
+
- Tech stack, tools, development environment, skill level
|
|
16
|
+
- Names and roles of people they mention (colleagues, family, friends)
|
|
17
|
+
- Current projects (name, description, status)
|
|
18
|
+
- Significant life events, milestones, upcoming plans
|
|
19
|
+
|
|
20
|
+
2. user_memory_preference — Preferences and opinions:
|
|
21
|
+
- Communication style (formal/casual, verbose/concise)
|
|
22
|
+
- Tool and technology preferences (languages, frameworks, editors, OS)
|
|
23
|
+
- Content preferences, learning style, likes and dislikes
|
|
24
|
+
- Strong opinions or values they've expressed
|
|
25
|
+
- Work patterns, routines, how they organize work
|
|
26
|
+
|
|
27
|
+
3. user_memory_decision — Decisions and commitments:
|
|
28
|
+
- Important decisions made and their reasoning
|
|
29
|
+
- Short-term and long-term goals, deadlines, milestones
|
|
30
|
+
- Lessons learned, strategies that worked or failed
|
|
31
|
+
- Changed opinions or updated beliefs
|
|
32
|
+
- Commitments or promises (by user or assistant to the user)
|
|
33
|
+
|
|
34
|
+
Guidelines:
|
|
35
|
+
- Store memories as clear, self-contained statements. Each memory should make sense on its own.
|
|
36
|
+
- Language of the "text" field: match the user's language. If user messages are mainly in Chinese, write every "text" in concise natural Chinese (第三人称,如「用户偏好…」「用户正在…」). If mainly English, use English third person ("User prefers…"). Do not translate Chinese source into English for extraction.
|
|
37
|
+
- Use third person: "User prefers...", "User is working on...", not "I prefer..." (or Chinese equivalents).
|
|
38
|
+
- Include temporal context when relevant: "As of [date], user is working on..." (or 中文日期表述).
|
|
39
|
+
- When information updates, prefer updating the existing memory rather than creating duplicates.
|
|
40
|
+
- Preserve specificity: "User uses Next.js 14 with App Router" is better than "User uses React".
|
|
41
|
+
- Capture the WHY behind preferences when stated: "User prefers Vim because of keyboard-driven workflow".
|
|
42
|
+
|
|
43
|
+
Exclude:
|
|
44
|
+
- Passwords, API keys, tokens, or any authentication credentials
|
|
45
|
+
- Exact financial amounts (unless the user explicitly asks to remember them)
|
|
46
|
+
- Temporary or ephemeral information (one-time questions, debugging with no lasting insight)
|
|
47
|
+
- Generic small talk with no informational content
|
|
48
|
+
- Raw code snippets (capture the intent or decision, not the code itself)
|
|
49
|
+
- Information the user explicitly asks not to remember
|
|
50
|
+
|
|
51
|
+
Importance (required for each extraction):
|
|
52
|
+
- You MUST assign an importance score between 0 and 1 to each memory.
|
|
53
|
+
- 0 = trivial, easily forgotten (e.g. minor preference, one-off mention).
|
|
54
|
+
- 0.5 = moderate (e.g. typical fact or preference the assistant should know).
|
|
55
|
+
- 1 = critical (e.g. identity, strong preference, commitment, safety-related, explicit "remember this").
|
|
56
|
+
- Use decimals as needed (e.g. 0.3, 0.7, 0.9). Do not omit this field.`;
|
|
57
|
+
|
|
58
|
+
/** Suffix that defines the required JSON output format and precedes the user messages. */
|
|
59
|
+
export const USER_MEMORY_EXTRACTION_FORMAT = `
|
|
60
|
+
|
|
61
|
+
Reply with ONLY a single JSON object, no other text or markdown. Use this exact structure:
|
|
62
|
+
{"extractions":[{"category":"user_memory_fact"|"user_memory_preference"|"user_memory_decision","text":"one clear third-person statement (中文若用户主要为中文)","importance":0.0 to 1.0}]}
|
|
63
|
+
Every extraction MUST include "importance" (number 0–1). If nothing to remember, return: {"extractions":[]}
|
|
64
|
+
|
|
65
|
+
User messages (extract from these; write "text" in Chinese when these are in Chinese):
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
/** Full prompt body (instructions + format). Caller appends the actual user messages. */
|
|
69
|
+
export function buildUserMemoryExtractionPrompt(): string {
|
|
70
|
+
return USER_MEMORY_EXTRACTION_INSTRUCTIONS + USER_MEMORY_EXTRACTION_FORMAT;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Instructions for extracting self-improving items (learnings, errors, feature requests) from assistant/user dialogue. */
|
|
74
|
+
export const SELF_IMPROVING_EXTRACTION_INSTRUCTIONS = `Your Task: From a conversation between user and assistant, extract self-improving memory items the assistant (or system) should remember for future behavior.
|
|
75
|
+
|
|
76
|
+
Categories (map each to exactly one):
|
|
77
|
+
|
|
78
|
+
1. self_improving_learnings — Lessons, corrections, or best practices that emerged:
|
|
79
|
+
- "上线前必须重启服务使新代码生效"
|
|
80
|
+
- "User prefers to be addressed by first name"
|
|
81
|
+
- Technical or process learnings from the dialogue
|
|
82
|
+
|
|
83
|
+
2. self_improving_errors — Mistakes, failures, or things to avoid:
|
|
84
|
+
- Errors the user or assistant encountered and how they were resolved
|
|
85
|
+
- "Do not assume X; always check Y first"
|
|
86
|
+
|
|
87
|
+
3. self_improving_feature_requests — User or assistant requests for future behavior:
|
|
88
|
+
- "Remember to always confirm before deleting"
|
|
89
|
+
- Feature or workflow requests the user stated
|
|
90
|
+
|
|
91
|
+
Guidelines:
|
|
92
|
+
- Extract only clear, actionable items. One short sentence per item.
|
|
93
|
+
- Prefer the language of the conversation (Chinese or English).
|
|
94
|
+
- If nothing fits any category, return empty extractions.
|
|
95
|
+
|
|
96
|
+
Exclude:
|
|
97
|
+
- Passwords, API keys, tokens, or any authentication credentials
|
|
98
|
+
- Exact financial amounts or sensitive personal data
|
|
99
|
+
- One-off debugging logs or temporary error messages with no lasting lesson
|
|
100
|
+
- Generic small talk or greetings with no actionable insight
|
|
101
|
+
- Raw code snippets (capture the intent or rule, not the code itself)
|
|
102
|
+
- Information the user or assistant explicitly asks not to remember
|
|
103
|
+
- Injected template text (e.g. <relevant-memories>, <knowledge-context> labels) or metadata
|
|
104
|
+
|
|
105
|
+
Importance (required for each extraction):
|
|
106
|
+
- You MUST assign an importance score between 0 and 1 to each memory.
|
|
107
|
+
- 0 = trivial, one-off tip with little impact on future behavior.
|
|
108
|
+
- 0.5 = moderate (e.g. typical process lesson or preference the assistant should follow).
|
|
109
|
+
- 1 = critical (e.g. safety-related rule, explicit "always/never" from user, recurring error pattern).
|
|
110
|
+
- Use decimals as needed (e.g. 0.3, 0.7, 0.9). Do not omit this field.
|
|
111
|
+
|
|
112
|
+
Reply with ONLY a single JSON object, no other text or markdown:
|
|
113
|
+
{"extractions":[{"category":"self_improving_learnings"|"self_improving_errors"|"self_improving_feature_requests","text":"one short statement","importance":0.0 to 1.0}]}
|
|
114
|
+
Every extraction MUST include "importance" (number 0–1). If nothing to extract, return: {"extractions":[]}
|
|
115
|
+
|
|
116
|
+
Conversation (extract from this):
|
|
117
|
+
`;
|