♻️ refactor(userMemories): removed un-used code (#11713)

This commit is contained in:
Neko
2026-01-22 23:59:51 +08:00
committed by GitHub
parent 8f7fe537d3
commit 89750fcae9
5 changed files with 0 additions and 1043 deletions

View File

@@ -1,13 +0,0 @@
import { router } from './shared';
import { reembedRouter } from './reembed';
import { searchRouter } from './search';
import { toolsRouter } from './tools';
// Re-export searchUserMemories for potential external use
export { searchUserMemories } from './search';
export const userMemoriesRouter = router({
...reembedRouter._def.procedures,
...searchRouter._def.procedures,
tools: toolsRouter,
});

View File

@@ -1,440 +0,0 @@
import { asc, eq, gte, lte } from 'drizzle-orm';
import pMap from 'p-map';
import { z } from 'zod';
import {
userMemories,
userMemoriesContexts,
userMemoriesExperiences,
userMemoriesIdentities,
userMemoriesPreferences,
} from '@/database/schemas';
import {
EMBEDDING_VECTOR_DIMENSION,
combineConditions,
getEmbeddingRuntime,
memoryProcedure,
normalizeEmbeddable,
router,
} from './shared';
const REEMBED_TABLE_KEYS = [
'userMemories',
'contexts',
'preferences',
'identities',
'experiences',
] as const;
type ReEmbedTableKey = (typeof REEMBED_TABLE_KEYS)[number];
const reEmbedInputSchema = z.object({
concurrency: z.coerce.number().int().min(1).max(50).optional(),
endDate: z.coerce.date().optional(),
limit: z.coerce.number().int().min(1).optional(),
only: z.array(z.enum(REEMBED_TABLE_KEYS)).optional(),
startDate: z.coerce.date().optional(),
});
interface ReEmbedStats {
failed: number;
skipped: number;
succeeded: number;
total: number;
}
export const reembedRouter = router({
reEmbedMemories: memoryProcedure
.input(reEmbedInputSchema.optional())
.mutation(async ({ ctx, input }) => {
try {
const options = input ?? {};
const { agentRuntime, embeddingModel } = await getEmbeddingRuntime(
ctx.serverDB,
ctx.userId,
);
const concurrency = options.concurrency ?? 10;
const shouldProcess = (key: ReEmbedTableKey) =>
!options.only || options.only.length === 0 || options.only.includes(key);
const embedTexts = async (texts: string[]): Promise<number[][]> => {
if (texts.length === 0) return [];
const response = await agentRuntime.embeddings({
dimensions: EMBEDDING_VECTOR_DIMENSION,
input: texts,
model: embeddingModel,
});
if (!response || response.length !== texts.length) {
throw new Error('Embedding response length mismatch');
}
return response;
};
const results: Partial<Record<ReEmbedTableKey, ReEmbedStats>> = {};
const run = async (key: ReEmbedTableKey, handler: () => Promise<ReEmbedStats>) => {
if (!shouldProcess(key)) return;
results[key] = await handler();
};
// Re-embed userMemories
await run('userMemories', async () => {
const where = combineConditions([
eq(userMemories.userId, ctx.userId),
options.startDate ? gte(userMemories.createdAt, options.startDate) : undefined,
options.endDate ? lte(userMemories.createdAt, options.endDate) : undefined,
]);
const rows = await ctx.serverDB.query.userMemories.findMany({
columns: { details: true, id: true, summary: true },
limit: options.limit,
orderBy: [asc(userMemories.createdAt)],
where,
});
let succeeded = 0;
let failed = 0;
let skipped = 0;
await pMap(
rows,
async (row) => {
const summaryText = normalizeEmbeddable(row.summary);
const detailsText = normalizeEmbeddable(row.details);
try {
if (!summaryText && !detailsText) {
await ctx.memoryModel.updateUserMemoryVectors(row.id, {
detailsVector1024: null,
summaryVector1024: null,
});
skipped += 1;
return;
}
const inputs: string[] = [];
if (summaryText) inputs.push(summaryText);
if (detailsText) inputs.push(detailsText);
const embeddings = await embedTexts(inputs);
let embedIndex = 0;
const summaryVector = summaryText ? (embeddings[embedIndex++] ?? null) : null;
const detailsVector = detailsText ? (embeddings[embedIndex++] ?? null) : null;
await ctx.memoryModel.updateUserMemoryVectors(row.id, {
detailsVector1024: detailsVector,
summaryVector1024: summaryVector,
});
succeeded += 1;
} catch (err) {
failed += 1;
console.error(
`[memoryRouter.reEmbed] Failed to re-embed user memory ${row.id}`,
err,
);
}
},
{ concurrency },
);
return {
failed,
skipped,
succeeded,
total: rows.length,
} satisfies ReEmbedStats;
});
// Re-embed contexts
await run('contexts', async () => {
const where = combineConditions([
eq(userMemoriesContexts.userId, ctx.userId),
options.startDate ? gte(userMemoriesContexts.createdAt, options.startDate) : undefined,
options.endDate ? lte(userMemoriesContexts.createdAt, options.endDate) : undefined,
]);
const rows = await ctx.serverDB.query.userMemoriesContexts.findMany({
columns: { description: true, id: true },
limit: options.limit,
orderBy: [asc(userMemoriesContexts.createdAt)],
where,
});
let succeeded = 0;
let failed = 0;
let skipped = 0;
await pMap(
rows,
async (row) => {
const description = normalizeEmbeddable(row.description);
try {
if (!description) {
await ctx.memoryModel.updateContextVectors(row.id, {
descriptionVector: null,
});
skipped += 1;
return;
}
const [embedding] = await embedTexts([description]);
await ctx.memoryModel.updateContextVectors(row.id, {
descriptionVector: embedding ?? null,
});
succeeded += 1;
} catch (err) {
failed += 1;
console.error(`[memoryRouter.reEmbed] Failed to re-embed context ${row.id}`, err);
}
},
{ concurrency },
);
return {
failed,
skipped,
succeeded,
total: rows.length,
} satisfies ReEmbedStats;
});
// Re-embed preferences
await run('preferences', async () => {
const where = combineConditions([
eq(userMemoriesPreferences.userId, ctx.userId),
options.startDate
? gte(userMemoriesPreferences.createdAt, options.startDate)
: undefined,
options.endDate ? lte(userMemoriesPreferences.createdAt, options.endDate) : undefined,
]);
const rows = await ctx.serverDB.query.userMemoriesPreferences.findMany({
columns: { conclusionDirectives: true, id: true },
limit: options.limit,
orderBy: [asc(userMemoriesPreferences.createdAt)],
where,
});
let succeeded = 0;
let failed = 0;
let skipped = 0;
await pMap(
rows,
async (row) => {
const directives = normalizeEmbeddable(row.conclusionDirectives);
try {
if (!directives) {
await ctx.memoryModel.updatePreferenceVectors(row.id, {
conclusionDirectivesVector: null,
});
skipped += 1;
return;
}
const [embedding] = await embedTexts([directives]);
await ctx.memoryModel.updatePreferenceVectors(row.id, {
conclusionDirectivesVector: embedding ?? null,
});
succeeded += 1;
} catch (err) {
failed += 1;
console.error(
`[memoryRouter.reEmbed] Failed to re-embed preference ${row.id}`,
err,
);
}
},
{ concurrency },
);
return {
failed,
skipped,
succeeded,
total: rows.length,
} satisfies ReEmbedStats;
});
// Re-embed identities
await run('identities', async () => {
const where = combineConditions([
eq(userMemoriesIdentities.userId, ctx.userId),
options.startDate
? gte(userMemoriesIdentities.createdAt, options.startDate)
: undefined,
options.endDate ? lte(userMemoriesIdentities.createdAt, options.endDate) : undefined,
]);
const rows = await ctx.serverDB.query.userMemoriesIdentities.findMany({
columns: { description: true, id: true },
limit: options.limit,
orderBy: [asc(userMemoriesIdentities.createdAt)],
where,
});
let succeeded = 0;
let failed = 0;
let skipped = 0;
await pMap(
rows,
async (row) => {
const description = normalizeEmbeddable(row.description);
try {
if (!description) {
await ctx.memoryModel.updateIdentityVectors(row.id, {
descriptionVector: null,
});
skipped += 1;
return;
}
const [embedding] = await embedTexts([description]);
await ctx.memoryModel.updateIdentityVectors(row.id, {
descriptionVector: embedding ?? null,
});
succeeded += 1;
} catch (err) {
failed += 1;
console.error(`[memoryRouter.reEmbed] Failed to re-embed identity ${row.id}`, err);
}
},
{ concurrency },
);
return {
failed,
skipped,
succeeded,
total: rows.length,
} satisfies ReEmbedStats;
});
// Re-embed experiences
await run('experiences', async () => {
const where = combineConditions([
eq(userMemoriesExperiences.userId, ctx.userId),
options.startDate
? gte(userMemoriesExperiences.createdAt, options.startDate)
: undefined,
options.endDate ? lte(userMemoriesExperiences.createdAt, options.endDate) : undefined,
]);
const rows = await ctx.serverDB.query.userMemoriesExperiences.findMany({
columns: { action: true, id: true, keyLearning: true, situation: true },
limit: options.limit,
orderBy: [asc(userMemoriesExperiences.createdAt)],
where,
});
let succeeded = 0;
let failed = 0;
let skipped = 0;
await pMap(
rows,
async (row) => {
const situation = normalizeEmbeddable(row.situation);
const action = normalizeEmbeddable(row.action);
const keyLearning = normalizeEmbeddable(row.keyLearning);
try {
if (!situation && !action && !keyLearning) {
await ctx.memoryModel.updateExperienceVectors(row.id, {
actionVector: null,
keyLearningVector: null,
situationVector: null,
});
skipped += 1;
return;
}
const inputs: string[] = [];
if (situation) inputs.push(situation);
if (action) inputs.push(action);
if (keyLearning) inputs.push(keyLearning);
const embeddings = await embedTexts(inputs);
let embedIndex = 0;
const situationVector = situation ? (embeddings[embedIndex++] ?? null) : null;
const actionVector = action ? (embeddings[embedIndex++] ?? null) : null;
const keyLearningVector = keyLearning ? (embeddings[embedIndex++] ?? null) : null;
await ctx.memoryModel.updateExperienceVectors(row.id, {
actionVector,
keyLearningVector,
situationVector,
});
succeeded += 1;
} catch (err) {
failed += 1;
console.error(
`[memoryRouter.reEmbed] Failed to re-embed experience ${row.id}`,
err,
);
}
},
{ concurrency },
);
return {
failed,
skipped,
succeeded,
total: rows.length,
} satisfies ReEmbedStats;
});
const processedEntries = Object.entries(results) as Array<[ReEmbedTableKey, ReEmbedStats]>;
if (processedEntries.length === 0) {
return {
message: 'No memory records matched re-embed criteria',
results,
success: true,
};
}
const aggregate = processedEntries.reduce(
(acc, [, stats]) => {
acc.failed += stats.failed;
acc.skipped += stats.skipped;
acc.succeeded += stats.succeeded;
acc.total += stats.total;
return acc;
},
{ failed: 0, skipped: 0, succeeded: 0, total: 0 },
);
const message =
aggregate.total === 0
? 'No memory records required re-embedding'
: `Re-embedded ${aggregate.succeeded} of ${aggregate.total} records`;
return {
aggregate,
message,
results,
success: true,
};
} catch (error) {
console.error('Failed to re-embed memories:', error);
return {
message: `Failed to re-embed memories: ${(error as Error).message}`,
success: false,
};
}
}),
});

View File

@@ -1,117 +0,0 @@
import { type LobeChatDatabase } from '@lobechat/database';
import { type z } from 'zod';
import { DEFAULT_FILE_EMBEDDING_MODEL_ITEM } from '@/const/settings/knowledge';
import { type UserMemoryModel } from '@/database/models/userMemory';
import { getServerDefaultFilesConfig } from '@/server/globalConfig';
import { initModelRuntimeFromDB } from '@/server/modules/ModelRuntime';
import { type SearchMemoryResult, searchMemorySchema } from '@/types/userMemory';
import { EMBEDDING_VECTOR_DIMENSION, memoryProcedure, router } from './shared';
type MemorySearchContext = {
memoryModel: UserMemoryModel;
serverDB: LobeChatDatabase;
userId: string;
};
type MemorySearchResult = Awaited<ReturnType<UserMemoryModel['searchWithEmbedding']>>;
const EMPTY_SEARCH_RESULT: SearchMemoryResult = {
contexts: [],
experiences: [],
preferences: [],
};
const mapMemorySearchResult = (layeredResults: MemorySearchResult): SearchMemoryResult => {
return {
contexts: layeredResults.contexts.map((context) => ({
accessedAt: context.accessedAt,
associatedObjects: context.associatedObjects,
associatedSubjects: context.associatedSubjects,
createdAt: context.createdAt,
currentStatus: context.currentStatus,
description: context.description,
id: context.id,
metadata: context.metadata,
scoreImpact: context.scoreImpact,
scoreUrgency: context.scoreUrgency,
tags: context.tags,
title: context.title,
type: context.type,
updatedAt: context.updatedAt,
userMemoryIds: Array.isArray(context.userMemoryIds)
? (context.userMemoryIds as string[])
: null,
})),
experiences: layeredResults.experiences.map((experience) => ({
accessedAt: experience.accessedAt,
action: experience.action,
createdAt: experience.createdAt,
id: experience.id,
keyLearning: experience.keyLearning,
metadata: experience.metadata,
possibleOutcome: experience.possibleOutcome,
reasoning: experience.reasoning,
scoreConfidence: experience.scoreConfidence,
situation: experience.situation,
tags: experience.tags,
type: experience.type,
updatedAt: experience.updatedAt,
userMemoryId: experience.userMemoryId,
})),
preferences: layeredResults.preferences.map((preference) => ({
accessedAt: preference.accessedAt,
conclusionDirectives: preference.conclusionDirectives,
createdAt: preference.createdAt,
id: preference.id,
metadata: preference.metadata,
scorePriority: preference.scorePriority,
suggestions: preference.suggestions,
tags: preference.tags,
type: preference.type,
updatedAt: preference.updatedAt,
userMemoryId: preference.userMemoryId,
})),
} satisfies SearchMemoryResult;
};
export const searchUserMemories = async (
ctx: MemorySearchContext,
input: z.infer<typeof searchMemorySchema>,
): Promise<SearchMemoryResult> => {
const { provider, model: embeddingModel } =
getServerDefaultFilesConfig().embeddingModel || DEFAULT_FILE_EMBEDDING_MODEL_ITEM;
// Read user's provider config from database
const agentRuntime = await initModelRuntimeFromDB(ctx.serverDB, ctx.userId, provider);
const queryEmbeddings = await agentRuntime.embeddings({
dimensions: EMBEDDING_VECTOR_DIMENSION,
input: input.query,
model: embeddingModel,
});
const limits = {
contexts: input.topK?.contexts,
experiences: input.topK?.experiences,
preferences: input.topK?.preferences,
};
const layeredResults = await ctx.memoryModel.searchWithEmbedding({
embedding: queryEmbeddings?.[0],
limits,
});
return mapMemorySearchResult(layeredResults);
};
export const searchRouter = router({
searchMemory: memoryProcedure.input(searchMemorySchema).query(async ({ input, ctx }) => {
try {
return await searchUserMemories(ctx, input);
} catch (error) {
console.error('Failed to retrieve memories:', error);
return EMPTY_SEARCH_RESULT;
}
}),
});

View File

@@ -1,63 +0,0 @@
import { type LobeChatDatabase } from '@lobechat/database';
import { type SQL, and } from 'drizzle-orm';
import { DEFAULT_FILE_EMBEDDING_MODEL_ITEM } from '@/const/settings/knowledge';
import { UserMemoryModel } from '@/database/models/userMemory';
import { authedProcedure } from '@/libs/trpc/lambda';
import { keyVaults, serverDatabase } from '@/libs/trpc/lambda/middleware';
import { getServerDefaultFilesConfig } from '@/server/globalConfig';
import { initModelRuntimeFromDB } from '@/server/modules/ModelRuntime';
export const EMBEDDING_VECTOR_DIMENSION = 1024;
export const memoryProcedure = authedProcedure
.use(serverDatabase)
.use(keyVaults)
.use(async (opts) => {
const { ctx } = opts;
return opts.next({
ctx: {
memoryModel: new UserMemoryModel(ctx.serverDB, ctx.userId),
},
});
});
export const getEmbeddingRuntime = async (serverDB: LobeChatDatabase, userId: string) => {
const { provider, model: embeddingModel } =
getServerDefaultFilesConfig().embeddingModel || DEFAULT_FILE_EMBEDDING_MODEL_ITEM;
// Read user's provider config from database
const agentRuntime = await initModelRuntimeFromDB(serverDB, userId, provider);
return { agentRuntime, embeddingModel };
};
export const createEmbedder = (agentRuntime: any, embeddingModel: string) => {
return async (value?: string | null): Promise<number[] | undefined> => {
if (!value || value.trim().length === 0) return undefined;
const embeddings = await agentRuntime.embeddings({
dimensions: EMBEDDING_VECTOR_DIMENSION,
input: value,
model: embeddingModel,
});
return embeddings?.[0];
};
};
export const combineConditions = (conditions: Array<SQL | undefined>): SQL | undefined => {
const filtered = conditions.filter((condition): condition is SQL => condition !== undefined);
if (filtered.length === 0) return undefined;
if (filtered.length === 1) return filtered[0];
return and(...filtered);
};
export const normalizeEmbeddable = (value?: string | null): string | undefined => {
if (typeof value !== 'string') return undefined;
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : undefined;
};
export { router } from '@/libs/trpc/lambda';

View File

@@ -1,410 +0,0 @@
import {
AddIdentityActionSchema,
ContextMemoryItemSchema,
ExperienceMemoryItemSchema,
PreferenceMemoryItemSchema,
RemoveIdentityActionSchema,
UpdateIdentityActionSchema,
} from '@lobechat/memory-user-memory';
import { LayersEnum } from '@lobechat/types';
import {
type IdentityEntryBasePayload,
type IdentityEntryPayload,
UserMemoryModel,
} from '@/database/models/userMemory';
import { searchMemorySchema } from '@/types/userMemory';
import { searchUserMemories } from './search';
import { createEmbedder, getEmbeddingRuntime, memoryProcedure, router } from './shared';
export const toolsRouter = router({
addContextMemory: memoryProcedure
.input(ContextMemoryItemSchema)
.mutation(async ({ input, ctx }) => {
try {
const { agentRuntime, embeddingModel } = await getEmbeddingRuntime(
ctx.serverDB,
ctx.userId,
);
const embed = createEmbedder(agentRuntime, embeddingModel);
const summaryEmbedding = await embed(input.summary);
const detailsEmbedding = await embed(input.details);
const contextDescriptionEmbedding = await embed(input.withContext.description);
const { context, memory } = await ctx.memoryModel.createContextMemory({
context: {
associatedObjects:
UserMemoryModel.parseAssociatedObjects(input.withContext.associatedObjects) ?? null,
associatedSubjects:
UserMemoryModel.parseAssociatedSubjects(input.withContext.associatedSubjects) ?? null,
currentStatus: input.withContext.currentStatus ?? null,
description: input.withContext.description ?? null,
descriptionVector: contextDescriptionEmbedding ?? null,
metadata: {},
scoreImpact: input.withContext.scoreImpact ?? null,
scoreUrgency: input.withContext.scoreUrgency ?? null,
tags: input.tags ?? [],
title: input.withContext.title ?? null,
type: input.withContext.type ?? null,
},
details: input.details || '',
detailsEmbedding,
memoryCategory: input.memoryCategory,
memoryLayer: LayersEnum.Context,
memoryType: input.memoryType,
summary: input.summary,
summaryEmbedding,
title: input.title,
});
return {
contextId: context.id,
memoryId: memory.id,
message: 'Memory saved successfully',
success: true,
};
} catch (error) {
console.error('Failed to save memory:', error);
return {
message: `Failed to save memory: ${(error as Error).message}`,
success: false,
};
}
}),
addExperienceMemory: memoryProcedure
.input(ExperienceMemoryItemSchema)
.mutation(async ({ input, ctx }) => {
try {
const { agentRuntime, embeddingModel } = await getEmbeddingRuntime(
ctx.serverDB,
ctx.userId,
);
const embed = createEmbedder(agentRuntime, embeddingModel);
const summaryEmbedding = await embed(input.summary);
const detailsEmbedding = await embed(input.details);
const situationVector = await embed(input.withExperience.situation);
const actionVector = await embed(input.withExperience.action);
const keyLearningVector = await embed(input.withExperience.keyLearning);
const { experience, memory } = await ctx.memoryModel.createExperienceMemory({
details: input.details || '',
detailsEmbedding,
experience: {
action: input.withExperience.action ?? null,
actionVector: actionVector ?? null,
keyLearning: input.withExperience.keyLearning ?? null,
keyLearningVector: keyLearningVector ?? null,
metadata: {},
possibleOutcome: input.withExperience.possibleOutcome ?? null,
reasoning: input.withExperience.reasoning ?? null,
scoreConfidence: input.withExperience.scoreConfidence ?? null,
situation: input.withExperience.situation ?? null,
situationVector: situationVector ?? null,
tags: input.tags ?? [],
type: input.memoryType,
},
memoryCategory: input.memoryCategory,
memoryLayer: LayersEnum.Experience,
memoryType: input.memoryType,
summary: input.summary,
summaryEmbedding,
title: input.title,
});
return {
experienceId: experience.id,
memoryId: memory.id,
message: 'Memory saved successfully',
success: true,
};
} catch (error) {
console.error('Failed to save memory:', error);
return {
message: `Failed to save memory: ${(error as Error).message}`,
success: false,
};
}
}),
addIdentityMemory: memoryProcedure
.input(AddIdentityActionSchema)
.mutation(async ({ input, ctx }) => {
try {
const { agentRuntime, embeddingModel } = await getEmbeddingRuntime(
ctx.serverDB,
ctx.userId,
);
const embed = createEmbedder(agentRuntime, embeddingModel);
const summaryEmbedding = await embed(input.summary);
const detailsEmbedding = await embed(input.details);
const descriptionEmbedding = await embed(input.withIdentity.description);
const identityMetadata: Record<string, unknown> = {};
if (
input.withIdentity.scoreConfidence !== null &&
input.withIdentity.scoreConfidence !== undefined
) {
identityMetadata.scoreConfidence = input.withIdentity.scoreConfidence;
}
if (
input.withIdentity.sourceEvidence !== null &&
input.withIdentity.sourceEvidence !== undefined
) {
identityMetadata.sourceEvidence = input.withIdentity.sourceEvidence;
}
const { identityId, userMemoryId } = await ctx.memoryModel.addIdentityEntry({
base: {
details: input.details,
detailsVector1024: detailsEmbedding ?? null,
memoryCategory: input.memoryCategory,
memoryLayer: LayersEnum.Identity,
memoryType: input.memoryType,
metadata: Object.keys(identityMetadata).length > 0 ? identityMetadata : undefined,
summary: input.summary,
summaryVector1024: summaryEmbedding ?? null,
tags: input.tags,
title: input.title,
},
identity: {
description: input.withIdentity.description,
descriptionVector: descriptionEmbedding ?? null,
episodicDate: input.withIdentity.episodicDate,
metadata: Object.keys(identityMetadata).length > 0 ? identityMetadata : undefined,
relationship: input.withIdentity.relationship,
role: input.withIdentity.role,
tags: input.tags,
type: input.withIdentity.type,
},
});
return {
identityId,
memoryId: userMemoryId,
message: 'Identity memory saved successfully',
success: true,
};
} catch (error) {
console.error('Failed to save identity memory:', error);
return {
message: `Failed to save identity memory: ${(error as Error).message}`,
success: false,
};
}
}),
addPreferenceMemory: memoryProcedure
.input(PreferenceMemoryItemSchema)
.mutation(async ({ input, ctx }) => {
try {
const { agentRuntime, embeddingModel } = await getEmbeddingRuntime(
ctx.serverDB,
ctx.userId,
);
const embed = createEmbedder(agentRuntime, embeddingModel);
const summaryEmbedding = await embed(input.summary);
const detailsEmbedding = await embed(input.details);
const conclusionVector = await embed(input.withPreference.conclusionDirectives);
const suggestionsText =
input.withPreference?.suggestions?.length && input.withPreference?.suggestions?.length > 0
? input.withPreference?.suggestions?.join('\n')
: null;
const metadata = {
appContext: input.withPreference.appContext,
extractedScopes: input.withPreference.extractedScopes,
originContext: input.withPreference.originContext,
} satisfies Record<string, unknown>;
const { memory, preference } = await ctx.memoryModel.createPreferenceMemory({
details: input.details || '',
detailsEmbedding,
memoryCategory: input.memoryCategory,
memoryLayer: LayersEnum.Preference,
memoryType: input.memoryType,
preference: {
conclusionDirectives: input.withPreference.conclusionDirectives || '',
conclusionDirectivesVector: conclusionVector ?? null,
metadata,
scorePriority: input.withPreference.scorePriority ?? null,
suggestions: suggestionsText,
tags: input.tags,
type: input.memoryType,
},
summary: input.summary,
summaryEmbedding,
title: input.title,
});
return {
memoryId: memory.id,
message: 'Memory saved successfully',
preferenceId: preference.id,
success: true,
};
} catch (error) {
console.error('Failed to save memory:', error);
return {
message: `Failed to save memory: ${(error as Error).message}`,
success: false,
};
}
}),
removeIdentityMemory: memoryProcedure
.input(RemoveIdentityActionSchema)
.mutation(async ({ input, ctx }) => {
try {
const removed = await ctx.memoryModel.removeIdentityEntry(input.id);
if (!removed) {
return {
message: 'Identity memory not found',
success: false,
};
}
return {
identityId: input.id,
message: 'Identity memory removed successfully',
reason: input.reason,
success: true,
};
} catch (error) {
console.error('Failed to remove identity memory:', error);
return {
message: `Failed to remove identity memory: ${(error as Error).message}`,
success: false,
};
}
}),
searchMemory: memoryProcedure.input(searchMemorySchema).query(async ({ input, ctx }) => {
try {
return await searchUserMemories(ctx, input);
} catch (error) {
console.error('Failed to retrieve memories:', error);
return { contexts: [], experiences: [], preferences: [] };
}
}),
updateIdentityMemory: memoryProcedure
.input(UpdateIdentityActionSchema)
.mutation(async ({ input, ctx }) => {
try {
const { agentRuntime, embeddingModel } = await getEmbeddingRuntime(
ctx.serverDB,
ctx.userId,
);
const embed = createEmbedder(agentRuntime, embeddingModel);
let summaryVector1024: number[] | null | undefined;
if (input.set.summary !== undefined) {
const vector = await embed(input.set.summary);
summaryVector1024 = vector ?? null;
}
let detailsVector1024: number[] | null | undefined;
if (input.set.details !== undefined) {
const vector = await embed(input.set.details);
detailsVector1024 = vector ?? null;
}
let descriptionVector: number[] | null | undefined;
if (input.set.withIdentity.description !== undefined) {
const vector = await embed(input.set.withIdentity.description);
descriptionVector = vector ?? null;
}
const metadataUpdates: Record<string, unknown> = {};
if (Object.hasOwn(input.set.withIdentity, 'scoreConfidence')) {
metadataUpdates.scoreConfidence = input.set.withIdentity.scoreConfidence ?? null;
}
if (Object.hasOwn(input.set.withIdentity, 'sourceEvidence')) {
metadataUpdates.sourceEvidence = input.set.withIdentity.sourceEvidence ?? null;
}
const identityPayload: Partial<IdentityEntryPayload> = {};
if (input.set.withIdentity.description !== undefined) {
identityPayload.description = input.set.withIdentity.description;
identityPayload.descriptionVector = descriptionVector;
}
if (input.set.withIdentity.episodicDate !== undefined) {
identityPayload.episodicDate = input.set.withIdentity.episodicDate;
}
if (input.set.withIdentity.relationship !== undefined) {
identityPayload.relationship = input.set.withIdentity.relationship;
}
if (input.set.withIdentity.role !== undefined) {
identityPayload.role = input.set.withIdentity.role;
}
if (input.set.tags !== undefined) {
identityPayload.tags = input.set.tags;
}
if (input.set.withIdentity.type !== undefined) {
identityPayload.type = input.set.withIdentity.type;
}
if (Object.keys(metadataUpdates).length > 0) {
identityPayload.metadata = metadataUpdates;
}
const basePayload: Partial<IdentityEntryBasePayload> = {};
if (input.set.details !== undefined) {
basePayload.details = input.set.details;
basePayload.detailsVector1024 = detailsVector1024;
}
if (input.set.memoryCategory !== undefined) {
basePayload.memoryCategory = input.set.memoryCategory;
}
if (input.set.memoryType !== undefined) {
basePayload.memoryType = input.set.memoryType;
}
if (input.set.summary !== undefined) {
basePayload.summary = input.set.summary;
basePayload.summaryVector1024 = summaryVector1024;
}
if (input.set.tags !== undefined) {
basePayload.tags = input.set.tags;
}
if (input.set.title !== undefined) {
basePayload.title = input.set.title;
}
if (Object.keys(metadataUpdates).length > 0) {
basePayload.metadata = metadataUpdates;
}
const updated = await ctx.memoryModel.updateIdentityEntry({
base: Object.keys(basePayload).length > 0 ? basePayload : undefined,
identity: Object.keys(identityPayload).length > 0 ? identityPayload : undefined,
identityId: input.id,
mergeStrategy: input.mergeStrategy,
});
if (!updated) {
return {
message: 'Identity memory not found',
success: false,
};
}
return {
identityId: input.id,
message: 'Identity memory updated successfully',
success: true,
};
} catch (error) {
console.error('Failed to update identity memory:', error);
return {
message: `Failed to update identity memory: ${(error as Error).message}`,
success: false,
};
}
}),
});