fix(memory): sync new memories into pgvector
Memind CI / Test, build, and release guards (push) Successful in 2m31s
Memind CI / Test, build, and release guards (push) Successful in 2m31s
This commit is contained in:
@@ -127,6 +127,172 @@ test('createMemoryV2Runtime fails open when candidate schema initialization fail
|
||||
await memory.close();
|
||||
});
|
||||
|
||||
test('createMemoryV2Runtime syncs a successful user memory write into pgvector before returning', async () => {
|
||||
const mysqlCalls = [];
|
||||
const pgCalls = [];
|
||||
class FakePool {
|
||||
async query(sql, params) {
|
||||
pgCalls.push({ sql, params });
|
||||
return { rows: [] };
|
||||
}
|
||||
async end() {}
|
||||
}
|
||||
const mysqlPool = {
|
||||
async query(sql, params) {
|
||||
mysqlCalls.push({ sql, params });
|
||||
if (sql.includes('FROM h5_user_memory_items')) {
|
||||
return [[{
|
||||
id: 'mem-new',
|
||||
user_id: 'u-1',
|
||||
label: 'fact',
|
||||
memory_text: '灰度代号 MEM-NEW',
|
||||
evidence_message_id: 'msg-1',
|
||||
source_session_id: 's-new',
|
||||
confidence: 0.9,
|
||||
status: 'active',
|
||||
created_at: 100,
|
||||
updated_at: 200,
|
||||
}]];
|
||||
}
|
||||
throw new Error(`Unexpected MySQL query: ${sql}`);
|
||||
},
|
||||
};
|
||||
const memory = await createMemoryV2Runtime({
|
||||
logger: silentLogger(),
|
||||
mysqlPool,
|
||||
legacyMemoryService: legacyService(),
|
||||
env: {
|
||||
MEMORY_ENABLED: '1',
|
||||
MEMORY_EVENT_LOG_ENABLED: '1',
|
||||
MEMORY_BACKEND: 'pgvector',
|
||||
MEMORY_VECTOR_ENABLED: '1',
|
||||
MEMORY_PGVECTOR_DATABASE_URL: 'postgresql://local/memory',
|
||||
MEMORY_PGVECTOR_EMBEDDING_MODULE: './fake-embed.mjs',
|
||||
},
|
||||
async importPg() { return { Pool: FakePool }; },
|
||||
async importModule() { return { embedQuery: async () => [0.1, 0.2] }; },
|
||||
});
|
||||
|
||||
const result = await memory.write({ userId: 'u-1', sessionId: 's-new', messages: [] });
|
||||
assert.equal(result.memories, 1);
|
||||
assert.match(mysqlCalls[0].sql, /user_id = \?/);
|
||||
assert.match(mysqlCalls[0].sql, /source_session_id = \?/);
|
||||
assert.deepEqual(mysqlCalls[0].params, ['u-1', 's-new', 50]);
|
||||
const insert = pgCalls.find((call) => call.sql.includes('INSERT INTO "memory_embeddings"'));
|
||||
assert.ok(insert);
|
||||
assert.equal(insert.params[1], '灰度代号 MEM-NEW');
|
||||
await memory.close();
|
||||
});
|
||||
|
||||
test('createMemoryV2Runtime retries scoped pgvector sync after a deduplicated memory write', async () => {
|
||||
const pgCalls = [];
|
||||
class FakePool {
|
||||
async query(sql, params) {
|
||||
pgCalls.push({ sql, params });
|
||||
return { rows: [] };
|
||||
}
|
||||
async end() {}
|
||||
}
|
||||
const mysqlPool = {
|
||||
async query(sql) {
|
||||
if (sql.includes('FROM h5_user_memory_items')) {
|
||||
return [[{
|
||||
id: 'mem-existing',
|
||||
user_id: 'u-1',
|
||||
label: 'fact',
|
||||
memory_text: '需要重试同步的既有记忆',
|
||||
source_session_id: 's-existing',
|
||||
confidence: 0.9,
|
||||
created_at: 100,
|
||||
updated_at: 200,
|
||||
}]];
|
||||
}
|
||||
throw new Error(`Unexpected MySQL query: ${sql}`);
|
||||
},
|
||||
};
|
||||
const memory = await createMemoryV2Runtime({
|
||||
logger: silentLogger(),
|
||||
mysqlPool,
|
||||
legacyMemoryService: {
|
||||
...legacyService(),
|
||||
async saveAndAnalyze() {
|
||||
return { saved: 0, analyzed: 0, memories: 0 };
|
||||
},
|
||||
},
|
||||
env: {
|
||||
MEMORY_ENABLED: '1',
|
||||
MEMORY_BACKEND: 'pgvector',
|
||||
MEMORY_VECTOR_ENABLED: '1',
|
||||
MEMORY_PGVECTOR_DATABASE_URL: 'postgresql://local/memory',
|
||||
MEMORY_PGVECTOR_EMBEDDING_MODULE: './fake-embed.mjs',
|
||||
},
|
||||
async importPg() { return { Pool: FakePool }; },
|
||||
async importModule() { return { embedQuery: async () => [0.1, 0.2] }; },
|
||||
});
|
||||
|
||||
const result = await memory.write({ userId: 'u-1', sessionId: 's-existing', messages: [] });
|
||||
assert.equal(result.memories, 0);
|
||||
const insert = pgCalls.find((call) => call.sql.includes('INSERT INTO "memory_embeddings"'));
|
||||
assert.ok(insert);
|
||||
assert.equal(insert.params[1], '需要重试同步的既有记忆');
|
||||
await memory.close();
|
||||
});
|
||||
|
||||
test('createMemoryV2Runtime syncs promoted canary candidates into pgvector', async () => {
|
||||
const pgCalls = [];
|
||||
class FakePool {
|
||||
async query(sql, params) {
|
||||
pgCalls.push({ sql, params });
|
||||
return { rows: [] };
|
||||
}
|
||||
async end() {}
|
||||
}
|
||||
const mysqlPool = {
|
||||
async query(sql) {
|
||||
if (sql.startsWith('SELECT * FROM h5_memory_v2_candidates')) {
|
||||
return [[{
|
||||
user_id: 'u-1', memory_type: 'episodic', content: '候选灰度代号',
|
||||
session_id: 's-1', confidence: 0.9, evidence_json: '{}', created_at: 100,
|
||||
}]];
|
||||
}
|
||||
if (sql.startsWith('INSERT IGNORE INTO h5_user_memory_items')) return [{ affectedRows: 1 }];
|
||||
if (sql.includes('FROM h5_user_memory_items')) {
|
||||
return [[{
|
||||
id: 'mem-promoted', user_id: 'u-1', label: 'episodic', memory_text: '候选灰度代号',
|
||||
source_session_id: 's-1', confidence: 0.9, status: 'active', created_at: 100, updated_at: 200,
|
||||
}]];
|
||||
}
|
||||
throw new Error(`Unexpected MySQL query: ${sql}`);
|
||||
},
|
||||
};
|
||||
const memory = await createMemoryV2Runtime({
|
||||
logger: silentLogger(),
|
||||
mysqlPool,
|
||||
legacyMemoryService: legacyService(),
|
||||
env: {
|
||||
MEMORY_ENABLED: '1',
|
||||
MEMORY_BACKEND: 'pgvector',
|
||||
MEMORY_VECTOR_ENABLED: '1',
|
||||
MEMORY_PGVECTOR_DATABASE_URL: 'postgresql://local/memory',
|
||||
MEMORY_PGVECTOR_EMBEDDING_MODULE: './fake-embed.mjs',
|
||||
MEMORY_LIFECYCLE_ENABLED: '1',
|
||||
MEMORY_PROMOTION_ENABLED: '1',
|
||||
MEMORY_LIFECYCLE_ROLLOUT_MODE: 'canary',
|
||||
MEMORY_LIFECYCLE_ROLLOUT_USER_IDS: 'u-1',
|
||||
},
|
||||
async importPg() { return { Pool: FakePool }; },
|
||||
async importModule() { return { embedQuery: async () => [0.3, 0.4] }; },
|
||||
});
|
||||
|
||||
const result = await memory.lifecycle.promote({ userId: 'u-1' });
|
||||
assert.equal(result.promoted, 1);
|
||||
assert.deepEqual(result.promotedUserIds, ['u-1']);
|
||||
const insert = pgCalls.find((call) => call.sql.includes('INSERT INTO "memory_embeddings"'));
|
||||
assert.ok(insert);
|
||||
assert.equal(insert.params[1], '候选灰度代号');
|
||||
await memory.close();
|
||||
});
|
||||
|
||||
test('createMemoryV2Runtime selects pgvector only when pool and embedding are configured', async () => {
|
||||
const queries = [];
|
||||
let poolEnded = false;
|
||||
|
||||
Reference in New Issue
Block a user