@lobehub/lobehub 2.0.0-next.340 → 2.0.0-next.342

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.
@@ -1,440 +0,0 @@
1
- import { asc, eq, gte, lte } from 'drizzle-orm';
2
- import pMap from 'p-map';
3
- import { z } from 'zod';
4
-
5
- import {
6
- userMemories,
7
- userMemoriesContexts,
8
- userMemoriesExperiences,
9
- userMemoriesIdentities,
10
- userMemoriesPreferences,
11
- } from '@/database/schemas';
12
-
13
- import {
14
- EMBEDDING_VECTOR_DIMENSION,
15
- combineConditions,
16
- getEmbeddingRuntime,
17
- memoryProcedure,
18
- normalizeEmbeddable,
19
- router,
20
- } from './shared';
21
-
22
- const REEMBED_TABLE_KEYS = [
23
- 'userMemories',
24
- 'contexts',
25
- 'preferences',
26
- 'identities',
27
- 'experiences',
28
- ] as const;
29
- type ReEmbedTableKey = (typeof REEMBED_TABLE_KEYS)[number];
30
-
31
- const reEmbedInputSchema = z.object({
32
- concurrency: z.coerce.number().int().min(1).max(50).optional(),
33
- endDate: z.coerce.date().optional(),
34
- limit: z.coerce.number().int().min(1).optional(),
35
- only: z.array(z.enum(REEMBED_TABLE_KEYS)).optional(),
36
- startDate: z.coerce.date().optional(),
37
- });
38
-
39
- interface ReEmbedStats {
40
- failed: number;
41
- skipped: number;
42
- succeeded: number;
43
- total: number;
44
- }
45
-
46
- export const reembedRouter = router({
47
- reEmbedMemories: memoryProcedure
48
- .input(reEmbedInputSchema.optional())
49
- .mutation(async ({ ctx, input }) => {
50
- try {
51
- const options = input ?? {};
52
- const { agentRuntime, embeddingModel } = await getEmbeddingRuntime(
53
- ctx.serverDB,
54
- ctx.userId,
55
- );
56
- const concurrency = options.concurrency ?? 10;
57
- const shouldProcess = (key: ReEmbedTableKey) =>
58
- !options.only || options.only.length === 0 || options.only.includes(key);
59
-
60
- const embedTexts = async (texts: string[]): Promise<number[][]> => {
61
- if (texts.length === 0) return [];
62
-
63
- const response = await agentRuntime.embeddings({
64
- dimensions: EMBEDDING_VECTOR_DIMENSION,
65
- input: texts,
66
- model: embeddingModel,
67
- });
68
-
69
- if (!response || response.length !== texts.length) {
70
- throw new Error('Embedding response length mismatch');
71
- }
72
-
73
- return response;
74
- };
75
-
76
- const results: Partial<Record<ReEmbedTableKey, ReEmbedStats>> = {};
77
-
78
- const run = async (key: ReEmbedTableKey, handler: () => Promise<ReEmbedStats>) => {
79
- if (!shouldProcess(key)) return;
80
- results[key] = await handler();
81
- };
82
-
83
- // Re-embed userMemories
84
- await run('userMemories', async () => {
85
- const where = combineConditions([
86
- eq(userMemories.userId, ctx.userId),
87
- options.startDate ? gte(userMemories.createdAt, options.startDate) : undefined,
88
- options.endDate ? lte(userMemories.createdAt, options.endDate) : undefined,
89
- ]);
90
-
91
- const rows = await ctx.serverDB.query.userMemories.findMany({
92
- columns: { details: true, id: true, summary: true },
93
- limit: options.limit,
94
- orderBy: [asc(userMemories.createdAt)],
95
- where,
96
- });
97
-
98
- let succeeded = 0;
99
- let failed = 0;
100
- let skipped = 0;
101
-
102
- await pMap(
103
- rows,
104
- async (row) => {
105
- const summaryText = normalizeEmbeddable(row.summary);
106
- const detailsText = normalizeEmbeddable(row.details);
107
-
108
- try {
109
- if (!summaryText && !detailsText) {
110
- await ctx.memoryModel.updateUserMemoryVectors(row.id, {
111
- detailsVector1024: null,
112
- summaryVector1024: null,
113
- });
114
- skipped += 1;
115
- return;
116
- }
117
-
118
- const inputs: string[] = [];
119
- if (summaryText) inputs.push(summaryText);
120
- if (detailsText) inputs.push(detailsText);
121
-
122
- const embeddings = await embedTexts(inputs);
123
- let embedIndex = 0;
124
-
125
- const summaryVector = summaryText ? (embeddings[embedIndex++] ?? null) : null;
126
- const detailsVector = detailsText ? (embeddings[embedIndex++] ?? null) : null;
127
-
128
- await ctx.memoryModel.updateUserMemoryVectors(row.id, {
129
- detailsVector1024: detailsVector,
130
- summaryVector1024: summaryVector,
131
- });
132
-
133
- succeeded += 1;
134
- } catch (err) {
135
- failed += 1;
136
- console.error(
137
- `[memoryRouter.reEmbed] Failed to re-embed user memory ${row.id}`,
138
- err,
139
- );
140
- }
141
- },
142
- { concurrency },
143
- );
144
-
145
- return {
146
- failed,
147
- skipped,
148
- succeeded,
149
- total: rows.length,
150
- } satisfies ReEmbedStats;
151
- });
152
-
153
- // Re-embed contexts
154
- await run('contexts', async () => {
155
- const where = combineConditions([
156
- eq(userMemoriesContexts.userId, ctx.userId),
157
- options.startDate ? gte(userMemoriesContexts.createdAt, options.startDate) : undefined,
158
- options.endDate ? lte(userMemoriesContexts.createdAt, options.endDate) : undefined,
159
- ]);
160
-
161
- const rows = await ctx.serverDB.query.userMemoriesContexts.findMany({
162
- columns: { description: true, id: true },
163
- limit: options.limit,
164
- orderBy: [asc(userMemoriesContexts.createdAt)],
165
- where,
166
- });
167
-
168
- let succeeded = 0;
169
- let failed = 0;
170
- let skipped = 0;
171
-
172
- await pMap(
173
- rows,
174
- async (row) => {
175
- const description = normalizeEmbeddable(row.description);
176
-
177
- try {
178
- if (!description) {
179
- await ctx.memoryModel.updateContextVectors(row.id, {
180
- descriptionVector: null,
181
- });
182
- skipped += 1;
183
- return;
184
- }
185
-
186
- const [embedding] = await embedTexts([description]);
187
-
188
- await ctx.memoryModel.updateContextVectors(row.id, {
189
- descriptionVector: embedding ?? null,
190
- });
191
- succeeded += 1;
192
- } catch (err) {
193
- failed += 1;
194
- console.error(`[memoryRouter.reEmbed] Failed to re-embed context ${row.id}`, err);
195
- }
196
- },
197
- { concurrency },
198
- );
199
-
200
- return {
201
- failed,
202
- skipped,
203
- succeeded,
204
- total: rows.length,
205
- } satisfies ReEmbedStats;
206
- });
207
-
208
- // Re-embed preferences
209
- await run('preferences', async () => {
210
- const where = combineConditions([
211
- eq(userMemoriesPreferences.userId, ctx.userId),
212
- options.startDate
213
- ? gte(userMemoriesPreferences.createdAt, options.startDate)
214
- : undefined,
215
- options.endDate ? lte(userMemoriesPreferences.createdAt, options.endDate) : undefined,
216
- ]);
217
-
218
- const rows = await ctx.serverDB.query.userMemoriesPreferences.findMany({
219
- columns: { conclusionDirectives: true, id: true },
220
- limit: options.limit,
221
- orderBy: [asc(userMemoriesPreferences.createdAt)],
222
- where,
223
- });
224
-
225
- let succeeded = 0;
226
- let failed = 0;
227
- let skipped = 0;
228
-
229
- await pMap(
230
- rows,
231
- async (row) => {
232
- const directives = normalizeEmbeddable(row.conclusionDirectives);
233
-
234
- try {
235
- if (!directives) {
236
- await ctx.memoryModel.updatePreferenceVectors(row.id, {
237
- conclusionDirectivesVector: null,
238
- });
239
- skipped += 1;
240
- return;
241
- }
242
-
243
- const [embedding] = await embedTexts([directives]);
244
- await ctx.memoryModel.updatePreferenceVectors(row.id, {
245
- conclusionDirectivesVector: embedding ?? null,
246
- });
247
- succeeded += 1;
248
- } catch (err) {
249
- failed += 1;
250
- console.error(
251
- `[memoryRouter.reEmbed] Failed to re-embed preference ${row.id}`,
252
- err,
253
- );
254
- }
255
- },
256
- { concurrency },
257
- );
258
-
259
- return {
260
- failed,
261
- skipped,
262
- succeeded,
263
- total: rows.length,
264
- } satisfies ReEmbedStats;
265
- });
266
-
267
- // Re-embed identities
268
- await run('identities', async () => {
269
- const where = combineConditions([
270
- eq(userMemoriesIdentities.userId, ctx.userId),
271
- options.startDate
272
- ? gte(userMemoriesIdentities.createdAt, options.startDate)
273
- : undefined,
274
- options.endDate ? lte(userMemoriesIdentities.createdAt, options.endDate) : undefined,
275
- ]);
276
-
277
- const rows = await ctx.serverDB.query.userMemoriesIdentities.findMany({
278
- columns: { description: true, id: true },
279
- limit: options.limit,
280
- orderBy: [asc(userMemoriesIdentities.createdAt)],
281
- where,
282
- });
283
-
284
- let succeeded = 0;
285
- let failed = 0;
286
- let skipped = 0;
287
-
288
- await pMap(
289
- rows,
290
- async (row) => {
291
- const description = normalizeEmbeddable(row.description);
292
-
293
- try {
294
- if (!description) {
295
- await ctx.memoryModel.updateIdentityVectors(row.id, {
296
- descriptionVector: null,
297
- });
298
- skipped += 1;
299
- return;
300
- }
301
-
302
- const [embedding] = await embedTexts([description]);
303
- await ctx.memoryModel.updateIdentityVectors(row.id, {
304
- descriptionVector: embedding ?? null,
305
- });
306
- succeeded += 1;
307
- } catch (err) {
308
- failed += 1;
309
- console.error(`[memoryRouter.reEmbed] Failed to re-embed identity ${row.id}`, err);
310
- }
311
- },
312
- { concurrency },
313
- );
314
-
315
- return {
316
- failed,
317
- skipped,
318
- succeeded,
319
- total: rows.length,
320
- } satisfies ReEmbedStats;
321
- });
322
-
323
- // Re-embed experiences
324
- await run('experiences', async () => {
325
- const where = combineConditions([
326
- eq(userMemoriesExperiences.userId, ctx.userId),
327
- options.startDate
328
- ? gte(userMemoriesExperiences.createdAt, options.startDate)
329
- : undefined,
330
- options.endDate ? lte(userMemoriesExperiences.createdAt, options.endDate) : undefined,
331
- ]);
332
-
333
- const rows = await ctx.serverDB.query.userMemoriesExperiences.findMany({
334
- columns: { action: true, id: true, keyLearning: true, situation: true },
335
- limit: options.limit,
336
- orderBy: [asc(userMemoriesExperiences.createdAt)],
337
- where,
338
- });
339
-
340
- let succeeded = 0;
341
- let failed = 0;
342
- let skipped = 0;
343
-
344
- await pMap(
345
- rows,
346
- async (row) => {
347
- const situation = normalizeEmbeddable(row.situation);
348
- const action = normalizeEmbeddable(row.action);
349
- const keyLearning = normalizeEmbeddable(row.keyLearning);
350
-
351
- try {
352
- if (!situation && !action && !keyLearning) {
353
- await ctx.memoryModel.updateExperienceVectors(row.id, {
354
- actionVector: null,
355
- keyLearningVector: null,
356
- situationVector: null,
357
- });
358
- skipped += 1;
359
- return;
360
- }
361
-
362
- const inputs: string[] = [];
363
- if (situation) inputs.push(situation);
364
- if (action) inputs.push(action);
365
- if (keyLearning) inputs.push(keyLearning);
366
-
367
- const embeddings = await embedTexts(inputs);
368
- let embedIndex = 0;
369
-
370
- const situationVector = situation ? (embeddings[embedIndex++] ?? null) : null;
371
- const actionVector = action ? (embeddings[embedIndex++] ?? null) : null;
372
- const keyLearningVector = keyLearning ? (embeddings[embedIndex++] ?? null) : null;
373
-
374
- await ctx.memoryModel.updateExperienceVectors(row.id, {
375
- actionVector,
376
- keyLearningVector,
377
- situationVector,
378
- });
379
- succeeded += 1;
380
- } catch (err) {
381
- failed += 1;
382
- console.error(
383
- `[memoryRouter.reEmbed] Failed to re-embed experience ${row.id}`,
384
- err,
385
- );
386
- }
387
- },
388
- { concurrency },
389
- );
390
-
391
- return {
392
- failed,
393
- skipped,
394
- succeeded,
395
- total: rows.length,
396
- } satisfies ReEmbedStats;
397
- });
398
-
399
- const processedEntries = Object.entries(results) as Array<[ReEmbedTableKey, ReEmbedStats]>;
400
-
401
- if (processedEntries.length === 0) {
402
- return {
403
- message: 'No memory records matched re-embed criteria',
404
- results,
405
- success: true,
406
- };
407
- }
408
-
409
- const aggregate = processedEntries.reduce(
410
- (acc, [, stats]) => {
411
- acc.failed += stats.failed;
412
- acc.skipped += stats.skipped;
413
- acc.succeeded += stats.succeeded;
414
- acc.total += stats.total;
415
-
416
- return acc;
417
- },
418
- { failed: 0, skipped: 0, succeeded: 0, total: 0 },
419
- );
420
-
421
- const message =
422
- aggregate.total === 0
423
- ? 'No memory records required re-embedding'
424
- : `Re-embedded ${aggregate.succeeded} of ${aggregate.total} records`;
425
-
426
- return {
427
- aggregate,
428
- message,
429
- results,
430
- success: true,
431
- };
432
- } catch (error) {
433
- console.error('Failed to re-embed memories:', error);
434
- return {
435
- message: `Failed to re-embed memories: ${(error as Error).message}`,
436
- success: false,
437
- };
438
- }
439
- }),
440
- });
@@ -1,117 +0,0 @@
1
- import { type LobeChatDatabase } from '@lobechat/database';
2
- import { type z } from 'zod';
3
-
4
- import { DEFAULT_FILE_EMBEDDING_MODEL_ITEM } from '@/const/settings/knowledge';
5
- import { type UserMemoryModel } from '@/database/models/userMemory';
6
- import { getServerDefaultFilesConfig } from '@/server/globalConfig';
7
- import { initModelRuntimeFromDB } from '@/server/modules/ModelRuntime';
8
- import { type SearchMemoryResult, searchMemorySchema } from '@/types/userMemory';
9
-
10
- import { EMBEDDING_VECTOR_DIMENSION, memoryProcedure, router } from './shared';
11
-
12
- type MemorySearchContext = {
13
- memoryModel: UserMemoryModel;
14
- serverDB: LobeChatDatabase;
15
- userId: string;
16
- };
17
-
18
- type MemorySearchResult = Awaited<ReturnType<UserMemoryModel['searchWithEmbedding']>>;
19
-
20
- const EMPTY_SEARCH_RESULT: SearchMemoryResult = {
21
- contexts: [],
22
- experiences: [],
23
- preferences: [],
24
- };
25
-
26
- const mapMemorySearchResult = (layeredResults: MemorySearchResult): SearchMemoryResult => {
27
- return {
28
- contexts: layeredResults.contexts.map((context) => ({
29
- accessedAt: context.accessedAt,
30
- associatedObjects: context.associatedObjects,
31
- associatedSubjects: context.associatedSubjects,
32
- createdAt: context.createdAt,
33
- currentStatus: context.currentStatus,
34
- description: context.description,
35
- id: context.id,
36
- metadata: context.metadata,
37
- scoreImpact: context.scoreImpact,
38
- scoreUrgency: context.scoreUrgency,
39
- tags: context.tags,
40
- title: context.title,
41
- type: context.type,
42
- updatedAt: context.updatedAt,
43
- userMemoryIds: Array.isArray(context.userMemoryIds)
44
- ? (context.userMemoryIds as string[])
45
- : null,
46
- })),
47
- experiences: layeredResults.experiences.map((experience) => ({
48
- accessedAt: experience.accessedAt,
49
- action: experience.action,
50
- createdAt: experience.createdAt,
51
- id: experience.id,
52
- keyLearning: experience.keyLearning,
53
- metadata: experience.metadata,
54
- possibleOutcome: experience.possibleOutcome,
55
- reasoning: experience.reasoning,
56
- scoreConfidence: experience.scoreConfidence,
57
- situation: experience.situation,
58
- tags: experience.tags,
59
- type: experience.type,
60
- updatedAt: experience.updatedAt,
61
- userMemoryId: experience.userMemoryId,
62
- })),
63
- preferences: layeredResults.preferences.map((preference) => ({
64
- accessedAt: preference.accessedAt,
65
- conclusionDirectives: preference.conclusionDirectives,
66
- createdAt: preference.createdAt,
67
- id: preference.id,
68
- metadata: preference.metadata,
69
- scorePriority: preference.scorePriority,
70
- suggestions: preference.suggestions,
71
- tags: preference.tags,
72
- type: preference.type,
73
- updatedAt: preference.updatedAt,
74
- userMemoryId: preference.userMemoryId,
75
- })),
76
- } satisfies SearchMemoryResult;
77
- };
78
-
79
- export const searchUserMemories = async (
80
- ctx: MemorySearchContext,
81
- input: z.infer<typeof searchMemorySchema>,
82
- ): Promise<SearchMemoryResult> => {
83
- const { provider, model: embeddingModel } =
84
- getServerDefaultFilesConfig().embeddingModel || DEFAULT_FILE_EMBEDDING_MODEL_ITEM;
85
- // Read user's provider config from database
86
- const agentRuntime = await initModelRuntimeFromDB(ctx.serverDB, ctx.userId, provider);
87
-
88
- const queryEmbeddings = await agentRuntime.embeddings({
89
- dimensions: EMBEDDING_VECTOR_DIMENSION,
90
- input: input.query,
91
- model: embeddingModel,
92
- });
93
-
94
- const limits = {
95
- contexts: input.topK?.contexts,
96
- experiences: input.topK?.experiences,
97
- preferences: input.topK?.preferences,
98
- };
99
-
100
- const layeredResults = await ctx.memoryModel.searchWithEmbedding({
101
- embedding: queryEmbeddings?.[0],
102
- limits,
103
- });
104
-
105
- return mapMemorySearchResult(layeredResults);
106
- };
107
-
108
- export const searchRouter = router({
109
- searchMemory: memoryProcedure.input(searchMemorySchema).query(async ({ input, ctx }) => {
110
- try {
111
- return await searchUserMemories(ctx, input);
112
- } catch (error) {
113
- console.error('Failed to retrieve memories:', error);
114
- return EMPTY_SEARCH_RESULT;
115
- }
116
- }),
117
- });
@@ -1,63 +0,0 @@
1
- import { type LobeChatDatabase } from '@lobechat/database';
2
- import { type SQL, and } from 'drizzle-orm';
3
-
4
- import { DEFAULT_FILE_EMBEDDING_MODEL_ITEM } from '@/const/settings/knowledge';
5
- import { UserMemoryModel } from '@/database/models/userMemory';
6
- import { authedProcedure } from '@/libs/trpc/lambda';
7
- import { keyVaults, serverDatabase } from '@/libs/trpc/lambda/middleware';
8
- import { getServerDefaultFilesConfig } from '@/server/globalConfig';
9
- import { initModelRuntimeFromDB } from '@/server/modules/ModelRuntime';
10
-
11
- export const EMBEDDING_VECTOR_DIMENSION = 1024;
12
-
13
- export const memoryProcedure = authedProcedure
14
- .use(serverDatabase)
15
- .use(keyVaults)
16
- .use(async (opts) => {
17
- const { ctx } = opts;
18
- return opts.next({
19
- ctx: {
20
- memoryModel: new UserMemoryModel(ctx.serverDB, ctx.userId),
21
- },
22
- });
23
- });
24
-
25
- export const getEmbeddingRuntime = async (serverDB: LobeChatDatabase, userId: string) => {
26
- const { provider, model: embeddingModel } =
27
- getServerDefaultFilesConfig().embeddingModel || DEFAULT_FILE_EMBEDDING_MODEL_ITEM;
28
- // Read user's provider config from database
29
- const agentRuntime = await initModelRuntimeFromDB(serverDB, userId, provider);
30
-
31
- return { agentRuntime, embeddingModel };
32
- };
33
-
34
- export const createEmbedder = (agentRuntime: any, embeddingModel: string) => {
35
- return async (value?: string | null): Promise<number[] | undefined> => {
36
- if (!value || value.trim().length === 0) return undefined;
37
-
38
- const embeddings = await agentRuntime.embeddings({
39
- dimensions: EMBEDDING_VECTOR_DIMENSION,
40
- input: value,
41
- model: embeddingModel,
42
- });
43
-
44
- return embeddings?.[0];
45
- };
46
- };
47
-
48
- export const combineConditions = (conditions: Array<SQL | undefined>): SQL | undefined => {
49
- const filtered = conditions.filter((condition): condition is SQL => condition !== undefined);
50
- if (filtered.length === 0) return undefined;
51
- if (filtered.length === 1) return filtered[0];
52
-
53
- return and(...filtered);
54
- };
55
-
56
- export const normalizeEmbeddable = (value?: string | null): string | undefined => {
57
- if (typeof value !== 'string') return undefined;
58
- const trimmed = value.trim();
59
-
60
- return trimmed.length > 0 ? trimmed : undefined;
61
- };
62
-
63
- export { router } from '@/libs/trpc/lambda';