fix(userMemories): added memory activity tools (#11800)

This commit is contained in:
Neko
2026-01-25 16:16:19 +08:00
committed by GitHub
parent aa63f1891e
commit 8ea08dd1e0
11 changed files with 473 additions and 82 deletions

View File

@@ -1,4 +1,5 @@
import type {
ActivityMemoryItemSchema,
AddIdentityActionSchema,
ContextMemoryItemSchema,
ExperienceMemoryItemSchema,
@@ -92,6 +93,45 @@ class MemoryExecutor extends BaseExecutor<typeof MemoryApiName> {
}
};
/**
* Add an activity memory
*/
addActivityMemory = async (
params: z.infer<typeof ActivityMemoryItemSchema>,
): Promise<BuiltinToolResult> => {
try {
const result = await userMemoryService.addActivityMemory(params);
if (!result.success) {
return {
error: {
message: result.message,
type: 'PluginServerError',
},
success: false,
};
}
return {
content: `Activity memory "${params.title}" saved with memoryId: "${result.memoryId}" and activityId: "${result.activityId}"`,
state: { activityId: result.activityId, memoryId: result.memoryId },
success: true,
};
} catch (error) {
const err = error as Error;
return {
content: `addActivityMemory with error detail: ${err.message}`,
error: {
body: error,
message: err.message,
type: 'PluginServerError',
},
success: false,
};
}
};
/**
* Add an experience memory
*/

View File

@@ -1,5 +1,6 @@
import type { BuiltinToolManifest } from '@lobechat/types';
import {
ACTIVITY_TYPES,
CONTEXT_OBJECT_TYPES,
CONTEXT_STATUS,
CONTEXT_SUBJECT_TYPES,
@@ -8,7 +9,7 @@ import {
MERGE_STRATEGIES,
RELATIONSHIPS,
} from '@lobechat/types';
import { JSONSchema7 } from 'json-schema'
import { JSONSchema7 } from 'json-schema';
import { systemPrompt } from './systemRole';
import { MemoryApiName } from './types';
@@ -27,7 +28,8 @@ export const MemoryManifest: BuiltinToolManifest = {
query: { type: 'string' },
topK: {
additionalProperties: false,
description: "Limits on number of memories to return per layer, default to search 3 activities, 0 contexts, 0 experiences, and 0 preferences if not specified.",
description:
'Limits on number of memories to return per layer, default to search 3 activities, 0 contexts, 0 experiences, and 0 preferences if not specified.',
properties: {
activities: { minimum: 0, type: 'integer' },
contexts: { minimum: 0, type: 'integer' },
@@ -193,6 +195,164 @@ export const MemoryManifest: BuiltinToolManifest = {
type: 'object',
},
},
{
description:
'Record an activity memory capturing what happened, when, where, with whom, and how it felt. Include narrative, feedback, timing, associations, and tags.',
name: MemoryApiName.addActivityMemory,
parameters: {
additionalProperties: false,
properties: {
details: {
description: 'Optional detailed information or longer notes supporting the summary.',
type: 'string',
},
memoryCategory: {
description: 'Memory category best matching the activity (e.g., work, health).',
type: 'string',
},
memoryType: {
const: 'activity',
description: 'Memory type; always activity.',
type: 'string',
},
summary: {
description: 'Concise overview of this activity.',
type: 'string',
},
tags: {
description: 'Model generated tags summarizing key facets of the activity.',
items: { type: 'string' },
type: 'array',
},
title: {
description: 'Brief descriptive title for the activity.',
type: 'string',
},
withActivity: {
additionalProperties: false,
properties: {
associatedLocations: {
description: 'Places linked to this activity.',
items: {
additionalProperties: false,
properties: {
address: { type: ['string', 'null'] },
extra: { type: ['string', 'null'] },
name: { type: 'string' },
tags: { items: { type: 'string' }, type: ['array', 'null'] },
type: { type: 'string' },
},
required: ['name'],
type: 'object',
},
type: 'array',
},
associatedObjects: {
description: 'Non-living entities or items tied to the activity.',
items: {
additionalProperties: false,
properties: {
extra: { type: ['string', 'null'] },
name: { type: 'string' },
type: { type: 'string' },
},
required: ['name'],
type: 'object',
},
type: 'array',
},
associatedSubjects: {
description: 'Living beings involved (people, pets, groups).',
items: {
additionalProperties: false,
properties: {
extra: { type: ['string', 'null'] },
name: { type: 'string' },
type: { type: 'string' },
},
required: ['name'],
type: 'object',
},
type: 'array',
},
endsAt: {
description: 'ISO 8601 end time if provided.',
format: 'date-time',
type: ['string', 'null'],
},
feedback: {
description: 'Subjective feelings or evaluation of how the activity went.',
type: ['string', 'null'],
},
metadata: {
additionalProperties: true,
description: 'Additional structured metadata to keep raw hints (JSON object).',
type: ['object', 'null'],
},
narrative: {
description: 'Factual story of what happened; required for recall.',
type: 'string',
},
notes: {
description: 'Short annotations distinct from narrative.',
type: ['string', 'null'],
},
startsAt: {
description: 'ISO 8601 start time if provided.',
format: 'date-time',
type: ['string', 'null'],
},
status: {
description:
'Lifecycle status when mentioned. Use planned/completed/cancelled/ongoing/on_hold/pending. Omit if unclear.',
enum: ['planned', 'completed', 'cancelled', 'ongoing', 'on_hold', 'pending'],
type: ['string', 'null'],
},
tags: {
description: 'Optional activity-specific tags or facets.',
items: { type: 'string' },
type: ['array', 'null'],
},
timezone: {
description: 'IANA timezone string for the start/end times when provided.',
type: ['string', 'null'],
},
type: {
description: 'Activity type enum; choose the closest match.',
enum: ACTIVITY_TYPES,
type: 'string',
},
},
required: [
'narrative',
'type',
'associatedLocations',
'associatedObjects',
'associatedSubjects',
'startsAt',
'endsAt',
'status',
'tags',
'timezone',
'metadata',
'feedback',
'notes',
],
type: 'object',
},
},
required: [
'title',
'summary',
'details',
'memoryType',
'memoryCategory',
'tags',
'withActivity',
],
type: 'object',
},
},
{
description:
'Record an experience memory capturing situation, actions, reasoning, outcomes, and confidence. Use for lessons, playbooks, or transferable know-how.',

View File

@@ -7,13 +7,14 @@ Conversation language: {{language}}
</session_context>
<core_responsibilities>
1. Inspect every turn for information that belongs to the four memory layers (identity, context, preference, experience). When information is relevant and clear, err on the side of allowing extraction so specialised aggregators can refine it.
1. Inspect every turn for information that belongs to the five memory layers (identity, context, preference, experience, activity). When information is relevant and clear, err on the side of allowing extraction so specialised aggregators can refine it.
2. Call **searchUserMemory** with targeted queries before proposing new memories. Compare any potential extraction against retrieved items to avoid duplication and highlight genuine updates.
3. Enforce that all memory candidates are self-contained, language-consistent, and ready for long-term reuse without relying on the surrounding conversation.
</core_responsibilities>
<tooling>
- **searchUserMemory**: query, limit, memoryLayer?, memoryType?, memoryCategory? → Returns structured memories for cross-checking and grounding your reasoning.
- **addActivityMemory**: title, summary, details?, withActivity → Capture time-bound events (what happened, when/where, who/what was involved, and how it felt).
- **addContextMemory**: title, summary, details?, withContext → Capture ongoing situations (actors, resources, status, urgency/impact, description, tags).
- **addExperienceMemory**: title, summary, details?, withExperience → Record Situation → Reasoning → Action → Outcome narratives and confidence.
- **addIdentityMemory**: title, summary, details?, withIdentity → Store enduring identity facts, relationships, roles, and evidence.
@@ -23,6 +24,7 @@ Conversation language: {{language}}
</tooling>
<memory_layer_definitions>
- **Activity Layer** — time-bound events and interactions. Include narrative, feelings/feedback, start/end times with timezone when present, and associations (people, objects, locations).
- **Identity Layer** — enduring facts about people and their relationships: roles, demographics, background, priorities, and relational context.
- **Context Layer** — ongoing situations such as projects, goals, partnerships, or environments. Capture actors (associatedSubjects), resources (associatedObjects), currentStatus, timelines, and impact/urgency assessments.
- **Preference Layer** — durable directives that guide future assistant behaviour (communication style, workflow choices, priority rules). Exclude single-use task instructions or purely implementation details.
@@ -38,6 +40,7 @@ Conversation language: {{language}}
</formatting_guardrails>
<layer_specific_highlights>
- **Activity**: Focus on concrete episodes. Prefer explicit times/timezones when given; avoid guessing. Keep narrative factual and feedback subjective; store both when available.
- **Identity**: Track labels, relationships, and life focus areas. Note relationship enums (self, mentor, teammate, etc.) when known.
- **Context**: Describe shared storylines tying multiple memories together. Update existing contexts instead of duplicating; surface currentStatus changes and resource/actor involvement.
- **Preference**: Record enduring choices that affect future interactions (response formats, decision priorities, recurring do/do-not expectations). Ensure conclusionDirectives are actionable on their own.

View File

@@ -1,4 +1,5 @@
import type {
ActivityMemoryItemSchema,
AddIdentityActionSchema,
ContextMemoryItemSchema,
ExperienceMemoryItemSchema,
@@ -6,10 +7,11 @@ import type {
RemoveIdentityActionSchema,
UpdateIdentityActionSchema,
} from '@lobechat/memory-user-memory/schemas';
import type { SearchMemoryResult } from '@lobechat/types';
import type { SearchMemoryResult } from '@lobechat/types';
import type { z } from 'zod';
export const MemoryApiName = {
addActivityMemory: 'addActivityMemory',
addContextMemory: 'addContextMemory',
addExperienceMemory: 'addExperienceMemory',
addIdentityMemory: 'addIdentityMemory',
@@ -38,6 +40,13 @@ export interface AddContextMemoryState {
memoryId?: string;
}
// Add Activity
export type AddActivityMemoryParams = z.infer<typeof ActivityMemoryItemSchema>;
export interface AddActivityMemoryState {
activityId?: string;
memoryId?: string;
}
// Add Experience
export type AddExperienceMemoryParams = z.infer<typeof ExperienceMemoryItemSchema>;
export interface AddExperienceMemoryState {
@@ -72,4 +81,4 @@ export interface RemoveIdentityMemoryState {
reason?: string;
}
export {type SearchMemoryParams, type SearchMemoryResult} from '@lobechat/types';
export { type SearchMemoryParams, type SearchMemoryResult } from '@lobechat/types';

View File

@@ -36,13 +36,13 @@ import {
import { merge } from '@/utils/merge';
import {
UserMemoryActivitiesWithoutVectors,
UserMemoryActivity,
UserMemoryContext,
UserMemoryExperience,
UserMemoryIdentity,
UserMemoryItem,
UserMemoryPreference,
UserMemoryActivitiesWithoutVectors,
UserMemoryActivity,
userMemories,
userMemoriesActivities,
userMemoriesContexts,
@@ -422,7 +422,8 @@ export class UserMemoryModel {
tags?: unknown;
type?: unknown;
}[]
| Record<string, unknown>,
| Record<string, unknown>
| null,
) {
if (!value) return [];
@@ -1010,9 +1011,7 @@ export class UserMemoryModel {
}
case LayersEnum.Activity: {
const sortColumn =
sort === 'startsAt'
? userMemoriesActivities.startsAt
: userMemoriesActivities.capturedAt;
sort === 'startsAt' ? userMemoriesActivities.startsAt : userMemoriesActivities.capturedAt;
const orderByClauses = buildOrderBy(
sortColumn,
@@ -1061,8 +1060,10 @@ export class UserMemoryModel {
capturedAt: userMemoriesActivities.capturedAt,
createdAt: userMemoriesActivities.createdAt,
endsAt: userMemoriesActivities.endsAt,
feedback: userMemoriesActivities.feedback,
id: userMemoriesActivities.id,
metadata: userMemoriesActivities.metadata,
narrative: userMemoriesActivities.narrative,
startsAt: userMemoriesActivities.startsAt,
status: userMemoriesActivities.status,
tags: userMemoriesActivities.tags,

View File

@@ -1,51 +1,60 @@
import type { GenerateObjectSchema } from '@lobechat/model-runtime';
import { ActivityTypeEnum, LayersEnum, TypesEnum } from '@lobechat/types';
import { type JSONSchema7 } from 'json-schema';
import { z } from 'zod';
export interface WithActivity {
associatedLocations?: {
address?: string;
extra?: string | null;
name?: string;
tags?: string[];
type?: string;
}[];
associatedObjects?: {
extra?: string | null;
name?: string;
type?: string;
}[];
associatedSubjects?: {
extra?: string | null;
name?: string;
type?: string;
}[];
endsAt?: string;
feedback?: string;
metadata?: Record<string, unknown>;
narrative: string;
notes?: string;
startsAt?: string;
status?: string;
tags?: string[];
timezone?: string;
type: ActivityTypeEnum | string;
}
import { MemoryTypeSchema } from './common';
export interface ActivityMemoryItem {
details: string;
memoryCategory: string;
memoryLayer: LayersEnum.Activity;
const ActivityAssociatedLocationSchema = z.object({
address: z.string().optional().nullable(),
extra: z.string().nullable().optional(),
name: z.string().optional(),
tags: z.array(z.string()).optional().nullable(),
type: z.string().optional(),
});
const ActivityAssociationSchema = z.object({
extra: z.string().nullable().optional(),
name: z.string(),
type: z.string().optional(),
});
export const WithActivitySchema = z.object({
associatedLocations: z.array(ActivityAssociatedLocationSchema).optional().nullable(),
associatedObjects: z.array(ActivityAssociationSchema).optional().nullable(),
associatedSubjects: z.array(ActivityAssociationSchema).optional().nullable(),
endsAt: z.string().optional().nullable(),
feedback: z.string().optional().nullable(),
metadata: z.record(z.unknown()).optional(),
narrative: z.string(),
notes: z.string().optional().nullable(),
startsAt: z.string().optional().nullable(),
status: z.string().optional().nullable(),
tags: z.array(z.string()).optional().nullable(),
timezone: z.string().optional().nullable(),
type: z.union([z.nativeEnum(ActivityTypeEnum), z.string()]).optional(),
});
export const ActivityMemoryItemSchema = z.object({
details: z.string(),
memoryCategory: z.string(),
memoryType: MemoryTypeSchema,
summary: z.string(),
tags: z.array(z.string()),
title: z.string(),
withActivity: WithActivitySchema,
});
export const ActivityMemoriesSchema = z.object({
memories: z.array(ActivityMemoryItemSchema),
});
export type WithActivity = z.infer<typeof WithActivitySchema>;
export type ActivityMemoryItem = z.infer<typeof ActivityMemoryItemSchema> & {
memoryLayer?: LayersEnum.Activity;
memoryType: TypesEnum.Activity;
summary: string;
tags: string[];
title: string;
withActivity: WithActivity;
}
export interface ActivityMemory {
memories: ActivityMemoryItem[];
}
};
export type ActivityMemory = z.infer<typeof ActivityMemoriesSchema>;
export const ActivityMemorySchema: GenerateObjectSchema = {
description:
@@ -77,9 +86,7 @@ export const ActivityMemorySchema: GenerateObjectSchema = {
name: 'ACME HQ',
},
],
associatedSubjects: [
{ name: 'Alice Smith', type: 'person' },
],
associatedSubjects: [{ name: 'Alice Smith', type: 'person' }],
endsAt: '2024-05-03T15:00:00-04:00',
feedback: 'Positive momentum; Alice felt heard and open to renewal.',
narrative:
@@ -104,9 +111,7 @@ export const ActivityMemorySchema: GenerateObjectSchema = {
name: 'City Neurology Clinic',
},
],
associatedSubjects: [
{ name: 'Dr. Kim', type: 'person' },
],
associatedSubjects: [{ name: 'Dr. Kim', type: 'person' }],
feedback: 'Felt reassured; plan seems manageable.',
narrative:
'User saw Dr. Kim to review migraine frequency; decided to track sleep, hydration, and start a low-dose preventive.',
@@ -175,7 +180,8 @@ export const ActivityMemorySchema: GenerateObjectSchema = {
type: ['array', 'null'],
},
type: {
description: 'Place type or category (office, clinic, restaurant, virtual).',
description:
'Place type or category (office, clinic, restaurant, virtual).',
type: 'string',
},
},
@@ -195,7 +201,8 @@ export const ActivityMemorySchema: GenerateObjectSchema = {
type: ['string', 'null'],
},
name: {
description: 'Name or label of the object (e.g., “MacBook”, “flight UA123”).',
description:
'Name or label of the object (e.g., “MacBook”, “flight UA123”).',
type: 'string',
},
type: {
@@ -290,7 +297,21 @@ export const ActivityMemorySchema: GenerateObjectSchema = {
type: 'string',
},
},
required: ['type', 'narrative', 'feedback', 'notes', 'associatedLocations', 'associatedSubjects', 'associatedObjects', 'startsAt', 'endsAt', 'status', 'tags', 'timezone', 'metadata'],
required: [
'type',
'narrative',
'feedback',
'notes',
'associatedLocations',
'associatedSubjects',
'associatedObjects',
'startsAt',
'endsAt',
'status',
'tags',
'timezone',
'metadata',
],
type: 'object',
},
},

View File

@@ -23,9 +23,7 @@ export const searchMemorySchema = z.object({
export type SearchMemoryParams = z.infer<typeof searchMemorySchema>;
export interface SearchMemoryResult {
activities: Array<
Omit<UserMemoryActivity, 'userId' | 'narrativeVector' | 'feedbackVector'>
>;
activities: Array<Omit<UserMemoryActivity, 'userId' | 'narrativeVector' | 'feedbackVector'>>;
contexts: Array<Omit<UserMemoryContext, 'userId' | 'titleVector' | 'descriptionVector'>>;
experiences: Array<
Omit<UserMemoryExperience, 'userId' | 'actionVector' | 'situationVector' | 'keyLearningVector'>
@@ -43,6 +41,11 @@ export interface AddContextMemoryResult extends MemoryToolBaseResult {
memoryId?: string;
}
export interface AddActivityMemoryResult extends MemoryToolBaseResult {
activityId?: string;
memoryId?: string;
}
export interface AddExperienceMemoryResult extends MemoryToolBaseResult {
experienceId?: string;
memoryId?: string;

View File

@@ -1,5 +1,5 @@
// @vitest-environment node
import { LayersEnum } from '@lobechat/types';
import { LayersEnum, TypesEnum } from '@lobechat/types';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getServerDB } from '@/database/core/db-adaptor';
@@ -26,6 +26,9 @@ vi.mock('@/database/models/userMemory', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/database/models/userMemory')>();
const mockUserMemoryModel: any = vi.fn();
mockUserMemoryModel.parseDateFromString = actual.UserMemoryModel.parseDateFromString;
mockUserMemoryModel.parseAssociatedLocations = actual.UserMemoryModel.parseAssociatedLocations;
mockUserMemoryModel.parseAssociatedObjects = actual.UserMemoryModel.parseAssociatedObjects;
mockUserMemoryModel.parseAssociatedSubjects = actual.UserMemoryModel.parseAssociatedSubjects;
return {
...actual,
UserMemoryModel: mockUserMemoryModel,
@@ -380,3 +383,83 @@ describe('userMemories.retrieveMemory', () => {
});
});
});
describe('userMemories.toolAddActivityMemory', () => {
it('creates activity memory with embeddings and normalized fields', async () => {
const createActivityMemory = vi.fn().mockResolvedValue({
activity: { id: 'activity-1' },
memory: { id: 'memory-1' },
});
vi.mocked(UserMemoryModel).mockImplementation(
() =>
({
createActivityMemory,
}) as any,
);
vi.mocked(getServerDB).mockResolvedValue({
query: {},
} as any);
const caller = userMemoriesRouter.createCaller(mockCtx as any);
const input = {
details: 'Discussed roadmap',
memoryCategory: 'work',
memoryType: TypesEnum.Activity,
summary: 'Roadmap sync with Alice',
tags: ['meeting'],
title: 'Roadmap sync',
withActivity: {
associatedLocations: [{ name: 'HQ', type: 'place' }],
associatedObjects: [{ name: 'Slides', type: 'item' }],
associatedSubjects: [{ name: 'Alice', type: 'person' }],
endsAt: '2024-05-01T11:00:00Z',
feedback: 'Productive',
metadata: { source: 'chat' },
narrative: 'We reviewed milestones and risks',
notes: 'Follow up with action items',
startsAt: '2024-05-01T10:00:00Z',
status: 'completed',
tags: ['product'],
timezone: 'UTC',
type: 'meeting',
},
};
const result = await caller.toolAddActivityMemory(input as any);
expect(createActivityMemory).toHaveBeenCalledTimes(1);
expect(createActivityMemory).toHaveBeenCalledWith(
expect.objectContaining({
activity: expect.objectContaining({
associatedLocations: [{ name: 'HQ', type: 'place' }],
associatedObjects: [{ name: 'Slides' }],
associatedSubjects: [{ name: 'Alice' }],
endsAt: new Date('2024-05-01T11:00:00Z'),
feedback: 'Productive',
metadata: { source: 'chat' },
narrative: 'We reviewed milestones and risks',
notes: 'Follow up with action items',
startsAt: new Date('2024-05-01T10:00:00Z'),
status: 'completed',
tags: ['product'],
timezone: 'UTC',
type: 'meeting',
}),
memoryLayer: LayersEnum.Activity,
memoryType: TypesEnum.Activity,
summaryEmbedding: [1],
}),
);
expect(result).toEqual({
activityId: 'activity-1',
memoryId: 'memory-1',
message: 'Memory saved successfully',
success: true,
});
expect(embeddingsMock).toHaveBeenCalledTimes(4);
});
});

View File

@@ -6,6 +6,7 @@ import {
} from '@lobechat/const';
import { type LobeChatDatabase } from '@lobechat/database';
import {
ActivityMemoryItemSchema,
AddIdentityActionSchema,
ContextMemoryItemSchema,
ExperienceMemoryItemSchema,
@@ -424,11 +425,6 @@ export const userMemoriesRouter = router({
}
}),
// REVIEW: Extract memories directly from current topic
// REVIEW: We need a function implementation that can be triggered both by cron and manually by users for "daily/weekly/periodic" memory extraction/generation
// REVIEW: Scheduled task
// Don't use tRPC, use server/service directly
// Reference: https://github.com/lobehub/lobe-chat-cloud/blob/886ff2fcd44b7b00a3aa8906f84914a6dcaa1815/src/app/(backend)/cron/reset-budgets/route.ts#L214
reEmbedMemories: memoryProcedure
.input(reEmbedInputSchema.optional())
.mutation(async ({ ctx, input }) => {
@@ -926,7 +922,68 @@ export const userMemoriesRouter = router({
}
}),
// REVIEW: Need to implement tool memory api
toolAddActivityMemory: memoryProcedure
.input(ActivityMemoryItemSchema)
.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 narrativeVector = await embed(input.withActivity.narrative);
const feedbackVector = await embed(input.withActivity.feedback);
const { activity, memory } = await ctx.memoryModel.createActivityMemory({
activity: {
associatedLocations:
UserMemoryModel.parseAssociatedLocations(input.withActivity.associatedLocations) ??
null,
associatedObjects:
UserMemoryModel.parseAssociatedObjects(input.withActivity.associatedObjects) ?? [],
associatedSubjects:
UserMemoryModel.parseAssociatedSubjects(input.withActivity.associatedSubjects) ?? [],
endsAt: UserMemoryModel.parseDateFromString(input.withActivity.endsAt ?? undefined),
feedback: input.withActivity.feedback ?? null,
feedbackVector: feedbackVector ?? null,
metadata: input.withActivity.metadata ?? null,
narrative: input.withActivity.narrative ?? null,
narrativeVector: narrativeVector ?? null,
notes: input.withActivity.notes ?? null,
startsAt: UserMemoryModel.parseDateFromString(input.withActivity.startsAt ?? undefined),
status: input.withActivity.status ?? 'pending',
tags: input.withActivity.tags ?? input.tags ?? [],
timezone: input.withActivity.timezone ?? null,
type: input.withActivity.type ?? 'other',
},
details: input.details || '',
detailsEmbedding,
memoryCategory: input.memoryCategory,
memoryLayer: LayersEnum.Activity,
memoryType: input.memoryType,
summary: input.summary,
summaryEmbedding,
title: input.title,
});
return {
activityId: activity.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,
};
}
}),
toolAddContextMemory: memoryProcedure
.input(ContextMemoryItemSchema)
.mutation(async ({ input, ctx }) => {

View File

@@ -51,11 +51,11 @@ import { and, asc, eq, inArray } from 'drizzle-orm';
import { join } from 'pathe';
import { z } from 'zod';
import { AsyncTaskModel } from '@/database/models/asyncTask';
import type { ListTopicsForMemoryExtractorCursor } from '@/database/models/topic';
import { TopicModel } from '@/database/models/topic';
import type { ListUsersForMemoryExtractorCursor } from '@/database/models/user';
import { UserModel } from '@/database/models/user';
import { AsyncTaskModel } from '@/database/models/asyncTask';
import { UserMemoryModel } from '@/database/models/userMemory';
import { UserMemorySourceBenchmarkLoCoMoModel } from '@/database/models/userMemory/sources/benchmarkLoCoMo';
import { AiInfraRepos } from '@/database/repositories/aiInfra';
@@ -67,14 +67,15 @@ import {
} from '@/server/globalConfig/parseMemoryExtractionConfig';
import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
import { S3 } from '@/server/modules/S3';
import { AsyncTaskError, AsyncTaskErrorType, AsyncTaskStatus } from '@/types/asyncTask';
import type { GlobalMemoryLayer } from '@/types/serverConfig';
import type { ProviderConfig } from '@/types/user/settings';
import { LayersEnum, MemorySourceType, type MergeStrategyEnum, TypesEnum } from '@/types/userMemory';
import {
AsyncTaskError,
AsyncTaskErrorType,
AsyncTaskStatus,
} from '@/types/asyncTask';
LayersEnum,
MemorySourceType,
type MergeStrategyEnum,
TypesEnum,
} from '@/types/userMemory';
import { encodeAsync } from '@/utils/tokenizer';
const SOURCE_ALIAS_MAP: Record<string, MemorySourceType> = {
@@ -344,7 +345,10 @@ const isTopicExtracted = (metadata?: ChatTopicMetadata | null): boolean => {
const extractStatus = metadata?.userMemoryExtractStatus;
if (extractStatus) return extractStatus === 'completed';
return metadata?.userMemoryExtractStatus === 'completed' && !!metadata?.userMemoryExtractRunState?.lastRunAt;
return (
metadata?.userMemoryExtractStatus === 'completed' &&
!!metadata?.userMemoryExtractRunState?.lastRunAt
);
};
type RuntimeBundle = {
@@ -643,7 +647,7 @@ export class MemoryExtractionExecutor {
details: item.details ?? '',
detailsEmbedding: detailsVector ?? undefined,
memoryCategory: item.memoryCategory ?? null,
memoryLayer: (item.memoryLayer as LayersEnum) ?? LayersEnum.Activity,
memoryLayer: LayersEnum.Activity,
memoryType: (item.memoryType as TypesEnum) ?? TypesEnum.Activity,
summary: item.summary ?? '',
summaryEmbedding: summaryVector ?? undefined,
@@ -1781,7 +1785,9 @@ export class MemoryExtractionExecutor {
}
if (errors.length) {
const detail = errors.map((error) => `${error.message}${error.cause ? `: ${error.cause}` : ''}`).join('; ');
const detail = errors
.map((error) => `${error.message}${error.cause ? `: ${error.cause}` : ''}`)
.join('; ');
throw new AggregateError(errors, `Memory extraction encountered layer errors: ${detail}`);
}

View File

@@ -1,4 +1,5 @@
import type {
ActivityMemoryItemSchema,
AddIdentityActionSchema,
ContextMemoryItemSchema,
ExperienceMemoryItemSchema,
@@ -7,9 +8,10 @@ import type {
UpdateIdentityActionSchema,
} from '@lobechat/memory-user-memory/schemas';
import {
type AddContextMemoryResult,
type ActivityListParams,
type ActivityListResult,
type AddActivityMemoryResult,
type AddContextMemoryResult,
type AddExperienceMemoryResult,
type AddIdentityMemoryResult,
type AddPreferenceMemoryResult,
@@ -29,6 +31,12 @@ import { type z } from 'zod';
import { lambdaClient } from '@/libs/trpc/client';
class UserMemoryService {
addActivityMemory = async (
params: z.infer<typeof ActivityMemoryItemSchema>,
): Promise<AddActivityMemoryResult> => {
return lambdaClient.userMemories.toolAddActivityMemory.mutate(params);
};
addContextMemory = async (
params: z.infer<typeof ContextMemoryItemSchema>,
): Promise<AddContextMemoryResult> => {