@lobehub/lobehub 2.0.0-next.360 → 2.0.0-next.362
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/CHANGELOG.md +50 -0
- package/Dockerfile +2 -1
- package/changelog/v1.json +14 -0
- package/locales/en-US/chat.json +3 -1
- package/locales/zh-CN/chat.json +2 -0
- package/package.json +1 -1
- package/packages/const/src/userMemory.ts +1 -0
- package/packages/context-engine/src/base/BaseEveryUserContentProvider.ts +204 -0
- package/packages/context-engine/src/base/BaseLastUserContentProvider.ts +1 -8
- package/packages/context-engine/src/base/__tests__/BaseEveryUserContentProvider.test.ts +354 -0
- package/packages/context-engine/src/base/constants.ts +20 -0
- package/packages/context-engine/src/engine/messages/MessagesEngine.ts +27 -23
- package/packages/context-engine/src/engine/messages/__tests__/MessagesEngine.test.ts +364 -0
- package/packages/context-engine/src/providers/PageEditorContextInjector.ts +17 -13
- package/packages/context-engine/src/providers/PageSelectionsInjector.ts +65 -0
- package/packages/context-engine/src/providers/__tests__/PageSelectionsInjector.test.ts +333 -0
- package/packages/context-engine/src/providers/index.ts +3 -1
- package/packages/database/src/models/userMemory/model.ts +178 -3
- package/packages/database/src/models/userMemory/sources/benchmarkLoCoMo.ts +1 -1
- package/packages/memory-user-memory/package.json +2 -1
- package/packages/memory-user-memory/promptfoo/evals/activity/basic/buildMessages.ts +40 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/basic/eval.yaml +13 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/basic/prompt.ts +5 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/basic/tests/cases.ts +106 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/locomo/buildMessages.ts +104 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/locomo/eval.yaml +13 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/locomo/prompt.ts +5 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/locomo/tests/benchmark-locomo-payload-conv-26.json +149 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/locomo/tests/cases.ts +72 -0
- package/packages/memory-user-memory/promptfoo/response-formats/activity.json +370 -0
- package/packages/memory-user-memory/promptfoo/response-formats/experience.json +14 -0
- package/packages/memory-user-memory/promptfoo/response-formats/identity.json +281 -255
- package/packages/memory-user-memory/promptfooconfig.yaml +1 -0
- package/packages/memory-user-memory/scripts/generate-response-formats.ts +26 -2
- package/packages/memory-user-memory/src/extractors/activity.ts +44 -0
- package/packages/memory-user-memory/src/extractors/gatekeeper.test.ts +2 -1
- package/packages/memory-user-memory/src/extractors/gatekeeper.ts +2 -1
- package/packages/memory-user-memory/src/extractors/index.ts +1 -0
- package/packages/memory-user-memory/src/prompts/gatekeeper.ts +3 -3
- package/packages/memory-user-memory/src/prompts/index.ts +7 -1
- package/packages/memory-user-memory/src/prompts/layers/activity.ts +90 -0
- package/packages/memory-user-memory/src/prompts/layers/index.ts +1 -0
- package/packages/memory-user-memory/src/providers/existingUserMemory.test.ts +25 -1
- package/packages/memory-user-memory/src/providers/existingUserMemory.ts +113 -0
- package/packages/memory-user-memory/src/schemas/activity.ts +315 -0
- package/packages/memory-user-memory/src/schemas/experience.ts +5 -5
- package/packages/memory-user-memory/src/schemas/gatekeeper.ts +1 -0
- package/packages/memory-user-memory/src/schemas/index.ts +1 -0
- package/packages/memory-user-memory/src/services/extractExecutor.ts +29 -0
- package/packages/memory-user-memory/src/types.ts +7 -0
- package/packages/prompts/src/agents/index.ts +1 -0
- package/packages/prompts/src/agents/pageSelectionContext.ts +28 -0
- package/packages/types/src/aiChat.ts +4 -0
- package/packages/types/src/message/common/index.ts +1 -0
- package/packages/types/src/message/common/metadata.ts +8 -0
- package/packages/types/src/message/common/pageSelection.ts +36 -0
- package/packages/types/src/message/ui/params.ts +16 -0
- package/packages/types/src/serverConfig.ts +1 -1
- package/packages/types/src/userMemory/layers.ts +52 -0
- package/packages/types/src/userMemory/list.ts +20 -2
- package/packages/types/src/userMemory/shared.ts +22 -1
- package/packages/types/src/userMemory/trace.ts +1 -0
- package/packages/types/src/util.ts +9 -1
- package/scripts/prebuild.mts +1 -0
- package/src/features/ChatInput/Desktop/ContextContainer/ContextList.tsx +1 -1
- package/src/features/Conversation/ChatInput/index.tsx +9 -1
- package/src/features/Conversation/Messages/User/components/MessageContent.tsx +7 -1
- package/src/features/Conversation/Messages/User/components/PageSelections.tsx +62 -0
- package/src/features/PageEditor/EditorCanvas/useAskCopilotItem.tsx +5 -1
- package/src/libs/next/proxy/define-config.ts +1 -0
- package/src/locales/default/chat.ts +3 -2
- package/src/server/globalConfig/parseMemoryExtractionConfig.ts +7 -1
- package/src/server/routers/lambda/aiChat.ts +7 -0
- package/src/server/services/memory/userMemory/__tests__/extract.runtime.test.ts +2 -0
- package/src/server/services/memory/userMemory/extract.ts +108 -7
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +5 -19
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { renderPlaceholderTemplate } from '@lobechat/context-engine';
|
|
2
|
+
|
|
3
|
+
import { activityPrompt } from '../prompts';
|
|
4
|
+
import { ActivityMemory, ActivityMemorySchema } from '../schemas';
|
|
5
|
+
import { ExtractorTemplateProps } from '../types';
|
|
6
|
+
import { BaseMemoryExtractor } from './base';
|
|
7
|
+
|
|
8
|
+
export class ActivityExtractor extends BaseMemoryExtractor<ActivityMemory> {
|
|
9
|
+
getPrompt(): string {
|
|
10
|
+
return activityPrompt;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
protected getPromptName(): string {
|
|
14
|
+
return 'layer-activity';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getSchema() {
|
|
18
|
+
return ActivityMemorySchema;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Activity schema uses JSON Schema directly; no zod validation is applied here.
|
|
22
|
+
protected getResultSchema() {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getTemplateProps(options: ExtractorTemplateProps) {
|
|
27
|
+
return {
|
|
28
|
+
availableCategories: options.availableCategories,
|
|
29
|
+
language: options.language,
|
|
30
|
+
retrievedContext: options.retrievedContexts?.join('\n\n') || 'No similar memories retrieved.',
|
|
31
|
+
sessionDate: options.sessionDate,
|
|
32
|
+
topK: options.topK,
|
|
33
|
+
username: options.username,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
buildUserPrompt(options: ExtractorTemplateProps): string {
|
|
38
|
+
if (!this.promptTemplate) {
|
|
39
|
+
throw new Error('Prompt template not loaded');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return renderPlaceholderTemplate(this.promptTemplate!, this.getTemplateProps(options));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -31,7 +31,7 @@ describe('UserMemoryGateKeeper', () => {
|
|
|
31
31
|
expect(schema?.strict).toBe(true);
|
|
32
32
|
|
|
33
33
|
const properties = (schema?.schema as any).properties;
|
|
34
|
-
const requiredLayers = ['context', 'experience', 'identity', 'preference'];
|
|
34
|
+
const requiredLayers = ['activity', 'context', 'experience', 'identity', 'preference'];
|
|
35
35
|
|
|
36
36
|
requiredLayers.forEach((layer) => {
|
|
37
37
|
const layerSchema = properties[layer];
|
|
@@ -78,6 +78,7 @@ describe('UserMemoryGateKeeper', () => {
|
|
|
78
78
|
const extractor = new UserMemoryGateKeeper(extractorConfig);
|
|
79
79
|
|
|
80
80
|
const llmResult = {
|
|
81
|
+
activity: { reasoning: 'reasoning', shouldExtract: true },
|
|
81
82
|
context: { reasoning: 'reasoning', shouldExtract: true },
|
|
82
83
|
experience: { reasoning: 'reasoning', shouldExtract: false },
|
|
83
84
|
identity: { reasoning: 'reasoning', shouldExtract: true },
|
|
@@ -27,12 +27,13 @@ export class UserMemoryGateKeeper extends BaseMemoryExtractor<GatekeeperResult,
|
|
|
27
27
|
schema: {
|
|
28
28
|
additionalProperties: false,
|
|
29
29
|
properties: {
|
|
30
|
+
activity: layerDecision,
|
|
30
31
|
context: layerDecision,
|
|
31
32
|
experience: layerDecision,
|
|
32
33
|
identity: layerDecision,
|
|
33
34
|
preference: layerDecision,
|
|
34
35
|
},
|
|
35
|
-
required: ['context', 'experience', 'identity', 'preference'],
|
|
36
|
+
required: ['activity', 'context', 'experience', 'identity', 'preference'],
|
|
36
37
|
type: 'object' as const,
|
|
37
38
|
},
|
|
38
39
|
strict: true,
|
|
@@ -9,11 +9,11 @@ export const gatekeeperPrompt = [
|
|
|
9
9
|
'',
|
|
10
10
|
'Evaluate the conversation for these memory layers:',
|
|
11
11
|
'',
|
|
12
|
-
|
|
12
|
+
'**Activity Layer** - Episodic events with clear timelines and participants:',
|
|
13
13
|
'',
|
|
14
14
|
'- Meetings, calls, errands, and appointments with start/end times',
|
|
15
|
-
'- Locations (physical or virtual) and timezones',
|
|
16
|
-
'- Status (planned, completed) and follow-up actions',
|
|
15
|
+
'- Locations (physical or virtual) and timezones when provided (omit if absent)',
|
|
16
|
+
'- Status (planned, completed, cancelled, ongoing, on_hold, pending) and follow-up actions',
|
|
17
17
|
'- Narratives summarizing what happened and feedback/notes',
|
|
18
18
|
'',
|
|
19
19
|
'**Identity Layer** - Information about actors, relationships, and personal attributes:',
|
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
export { gatekeeperPrompt } from './gatekeeper';
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
activityPrompt,
|
|
4
|
+
contextPrompt,
|
|
5
|
+
experiencePrompt,
|
|
6
|
+
identityPrompt,
|
|
7
|
+
preferencePrompt,
|
|
8
|
+
} from './layers';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export const activityPrompt = [
|
|
2
|
+
'You are a focused memory extraction assistant specialized in the **activity** layer.',
|
|
3
|
+
'When extracting, ensure all the content is using {{ language }}.',
|
|
4
|
+
'',
|
|
5
|
+
'\\<user_context>',
|
|
6
|
+
'Current user: {{ username }}',
|
|
7
|
+
'Session date: {{ sessionDate }}',
|
|
8
|
+
'Available memory categories: {{ availableCategories }}',
|
|
9
|
+
'Target layer: activity',
|
|
10
|
+
'\\</user_context>',
|
|
11
|
+
'',
|
|
12
|
+
'## Retrieved Memory (Top {{ topK }})',
|
|
13
|
+
'',
|
|
14
|
+
'Use the list below to de-duplicate and decide whether you need to extract anything new.',
|
|
15
|
+
'Do not copy these verbatim; treat them as comparison references.',
|
|
16
|
+
'',
|
|
17
|
+
'{{ retrievedContext }}',
|
|
18
|
+
'',
|
|
19
|
+
'## Your Task',
|
|
20
|
+
'',
|
|
21
|
+
'Extract **ALL** activity layer information from the conversation. Activities are time-bound events',
|
|
22
|
+
'with people, places, status, and a factual narrative of what happened. Capture how it felt via',
|
|
23
|
+
'feedback. Temporal (startsAt, endsAt, timezone) and association fields are OPTIONAL—only include',
|
|
24
|
+
'them when explicitly provided. If missing, **omit the fields entirely** (do not set null/empty) instead of guessing.',
|
|
25
|
+
'',
|
|
26
|
+
'**CRITICAL**: Return an **array** of activity memory items. Extract each distinct activity as a separate item.',
|
|
27
|
+
'Before extracting, review the retrieved similar memories (top {{ topK }} items shown above). Extract items',
|
|
28
|
+
'that are NEW or MATERIALLY UPDATED compared to retrieved entries. Avoid duplicates or near-duplicates.',
|
|
29
|
+
'',
|
|
30
|
+
'## Name Handling and Neutrality',
|
|
31
|
+
'- Always refer to the user with the exact placeholder token "User".',
|
|
32
|
+
"- Do not infer, invent, or translate the user's real name.",
|
|
33
|
+
'- Avoid gendered terms or honorifics; keep references neutral.',
|
|
34
|
+
'',
|
|
35
|
+
'## Output Format',
|
|
36
|
+
'',
|
|
37
|
+
'Return structured JSON data according to the provided schema:',
|
|
38
|
+
'- Basic fields: title, summary, details, memoryLayer, memoryType, memoryCategory, tags',
|
|
39
|
+
'- Activity-specific fields in withActivity: type (enum), status (planned/completed/cancelled/ongoing/on_hold/pending),',
|
|
40
|
+
' optional timezone, startsAt, endsAt (ISO 8601), associatedSubjects (self-aware entities: people, pets, groups, orgs), associatedObjects',
|
|
41
|
+
' (non-living items like transportation, devices),',
|
|
42
|
+
' associatedLocations (places; capture any mentioned venue/address), notes (prep/agenda), narrative (factual story),',
|
|
43
|
+
' feedback (how it felt), metadata (JSON), tags (activity facets).',
|
|
44
|
+
'- Do not fabricate dates, timezones, or locations. Only include them when explicitly mentioned or derivable.',
|
|
45
|
+
'',
|
|
46
|
+
'## Memory Formatting Guidelines',
|
|
47
|
+
'',
|
|
48
|
+
'> ALL MEMORY ITEMS MUST BE SELF-CONTAINED',
|
|
49
|
+
'- Use concrete names/entities—avoid pronouns.',
|
|
50
|
+
'- Preserve the original language from user input—do not translate.',
|
|
51
|
+
'- Include relevant details, participants, places, timing if present, and outcomes.',
|
|
52
|
+
'- Keep narratives factual; avoid speculative embellishment.',
|
|
53
|
+
'',
|
|
54
|
+
'✓ **Good Examples:**',
|
|
55
|
+
'- "User met with Alice at ACME HQ on 2024-05-03 14:00-15:00 America/New_York to review the Q2 renewal,',
|
|
56
|
+
' aligned on reduced scope, and agreed to send revised pricing; User felt the call was positive and collaborative."',
|
|
57
|
+
'- "User visited Dr. Kim for a migraine check-up, discussed triggers and sleep, and started a low-dose preventive;',
|
|
58
|
+
' User felt reassured."',
|
|
59
|
+
'',
|
|
60
|
+
'✗ **Bad Examples:**',
|
|
61
|
+
'- "They had a meeting" → Missing who, when, where, and outcome.',
|
|
62
|
+
'- "Felt good" → Missing context of the activity.',
|
|
63
|
+
'- "Probably in New York" → Do not guess locations or timezones.',
|
|
64
|
+
'',
|
|
65
|
+
'## Layer-Specific Guidance',
|
|
66
|
+
'- Focus on discrete events (meetings, calls, trips, workouts, meals, appointments).',
|
|
67
|
+
'- Always capture mentioned locations in associatedLocations. If none are stated, omit the field.',
|
|
68
|
+
'- If the activity is a trip, record transportation modes or specific rides/tickets in associatedObjects.',
|
|
69
|
+
'- Route any self-aware entity without a known identity ID (people, pets, groups, institutions/orgs) to associatedSubjects.',
|
|
70
|
+
'- type choices: meeting, appointment, exercise, meal, social, trip, task, workshop, conference, call, errand, celebration, class, project-session, other.',
|
|
71
|
+
'- Status handling: use provided status markers (planned, completed, cancelled, ongoing, on_hold, pending). If no status is stated, omit.',
|
|
72
|
+
'- Do not add the user as an associatedSubject; only include other mentioned living entities.',
|
|
73
|
+
'- Feedback captures how the activity felt (effort, mood, satisfaction); narrative captures the factual sequence.',
|
|
74
|
+
'- Location and temporal fields are optional—only include when stated.',
|
|
75
|
+
'- Temporal anchoring: when the conversation uses relative time phrases (yesterday, tomorrow, last week), anchor calculations to the specific message timestamp (created_at) if present; otherwise fall back to sessionDate. Keep the anchor timezone/time-of-day when converting relative references and do not invent new times if none are given.',
|
|
76
|
+
'',
|
|
77
|
+
'## Few-shot Hints',
|
|
78
|
+
'- Meeting with known person, time stated → include type=meeting, startsAt/endsAt/timezone, associatedSubjects for participants, location name if given.',
|
|
79
|
+
'- Workout described without time/place → type=exercise, omit time/timezone/location, keep narrative/feedback.',
|
|
80
|
+
'- Dinner with friends mentioned without exact time → type=meal or social, include attendees if named, omit time/place if absent.',
|
|
81
|
+
'',
|
|
82
|
+
'## Security Considerations',
|
|
83
|
+
'**NEVER extract or store sensitive information:** passwords, API keys, financial account numbers, or private encryption keys.',
|
|
84
|
+
'',
|
|
85
|
+
'---',
|
|
86
|
+
'Final instructions:',
|
|
87
|
+
'1. Extract each distinct activity memory as a separate item.',
|
|
88
|
+
'2. Ensure memories are self-contained and in {{ language }}.',
|
|
89
|
+
'3. Return JSON matching the schema. Use [] when nothing should be extracted.',
|
|
90
|
+
].join('\\n');
|
|
@@ -19,6 +19,30 @@ describe('RetrievalUserMemoryContextProvider', () => {
|
|
|
19
19
|
const provider = new RetrievalUserMemoryContextProvider({
|
|
20
20
|
fetchedAt,
|
|
21
21
|
retrievedMemories: {
|
|
22
|
+
activities: [
|
|
23
|
+
{
|
|
24
|
+
accessedAt: new Date(),
|
|
25
|
+
type: 'meeting',
|
|
26
|
+
associatedLocations: [{ name: 'Zoom' }],
|
|
27
|
+
associatedSubjects: [{ name: 'Alice', type: 'person' }],
|
|
28
|
+
capturedAt: new Date(),
|
|
29
|
+
createdAt: new Date(),
|
|
30
|
+
endsAt: new Date('2024-02-01T02:00:00.000Z'),
|
|
31
|
+
feedback: 'Felt good',
|
|
32
|
+
id: 'act-1',
|
|
33
|
+
metadata: {},
|
|
34
|
+
narrative: 'Weekly sync about roadmap',
|
|
35
|
+
notes: 'Agenda: roadmap',
|
|
36
|
+
startsAt: new Date('2024-02-01T01:00:00.000Z'),
|
|
37
|
+
status: 'completed',
|
|
38
|
+
tags: ['meeting'],
|
|
39
|
+
timezone: 'UTC',
|
|
40
|
+
updatedAt: new Date(),
|
|
41
|
+
userId: 'user-1',
|
|
42
|
+
userMemoryId: 'mem-act-1',
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
+
} as any,
|
|
45
|
+
],
|
|
22
46
|
contexts: [
|
|
23
47
|
{
|
|
24
48
|
accessedAt: new Date(),
|
|
@@ -91,7 +115,7 @@ describe('RetrievalUserMemoryContextProvider', () => {
|
|
|
91
115
|
expect(result.userId).toBe('user-1');
|
|
92
116
|
expect(result.metadata).toEqual({});
|
|
93
117
|
expect(result.context).equal(
|
|
94
|
-
'<user_memories contexts="1" experiences="1" memory_fetched_at="2024-02-01T00:00:00.000Z" preferences="1"><user_memories_context id="ctx-1" type="project"><context_title>LobeHub</context_title><context_description>Weekly syncs for LobeHub</context_description><context_current_status>active</context_current_status><context_tags>project, team</context_tags></user_memories_context><user_memories_experience id="exp-1" type="product"><experience_situation>Release planning</experience_situation><experience_key_learning>Ship smaller increments</experience_key_learning><experience_action>Shipped feature</experience_action><experience_reasoning>Better agility</experience_reasoning><experience_possible_outcome>Faster releases</experience_possible_outcome><experience_tags>release</experience_tags></user_memories_experience><user_memories_preference id="pref-1" type="style"><preference_conclusion_directives>Always keep updates concise</preference_conclusion_directives><preference_suggestions>Use bullet points</preference_suggestions><preference_tags>communication</preference_tags></user_memories_preference></user_memories>',
|
|
118
|
+
'<user_memories activities="1" contexts="1" experiences="1" memory_fetched_at="2024-02-01T00:00:00.000Z" preferences="1"><user_memories_activity id="act-1" activity_type="meeting" status="completed" timezone="UTC" starts_at="2024-02-01T01:00:00.000Z" ends_at="2024-02-01T02:00:00.000Z"><activity_narrative>Weekly sync about roadmap</activity_narrative><activity_notes>Agenda: roadmap</activity_notes><activity_feedback>Felt good</activity_feedback><activity_associated_location>Zoom</activity_associated_location><activity_associated_subject>Alice | type: person</activity_associated_subject><activity_tags>meeting</activity_tags></user_memories_activity><user_memories_context id="ctx-1" type="project"><context_title>LobeHub</context_title><context_description>Weekly syncs for LobeHub</context_description><context_current_status>active</context_current_status><context_tags>project, team</context_tags></user_memories_context><user_memories_experience id="exp-1" type="product"><experience_situation>Release planning</experience_situation><experience_key_learning>Ship smaller increments</experience_key_learning><experience_action>Shipped feature</experience_action><experience_reasoning>Better agility</experience_reasoning><experience_possible_outcome>Faster releases</experience_possible_outcome><experience_tags>release</experience_tags></user_memories_experience><user_memories_preference id="pref-1" type="style"><preference_conclusion_directives>Always keep updates concise</preference_conclusion_directives><preference_suggestions>Use bullet points</preference_suggestions><preference_tags>communication</preference_tags></user_memories_preference></user_memories>',
|
|
95
119
|
);
|
|
96
120
|
});
|
|
97
121
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
IdentityMemoryDetail,
|
|
3
|
+
UserMemoryActivityWithoutVectors,
|
|
3
4
|
UserMemoryContextWithoutVectors,
|
|
4
5
|
UserMemoryExperienceWithoutVectors,
|
|
5
6
|
UserMemoryPreferenceWithoutVectors,
|
|
@@ -12,6 +13,7 @@ import { x } from 'xastscript';
|
|
|
12
13
|
import type { BuiltContext, MemoryContextProvider, MemoryExtractionJob } from '../types';
|
|
13
14
|
|
|
14
15
|
interface RetrievedMemories {
|
|
16
|
+
activities: UserMemoryActivityWithoutVectors[];
|
|
15
17
|
contexts: UserMemoryContextWithoutVectors[];
|
|
16
18
|
experiences: UserMemoryExperienceWithoutVectors[];
|
|
17
19
|
preferences: UserMemoryPreferenceWithoutVectors[];
|
|
@@ -31,13 +33,123 @@ export class RetrievalUserMemoryContextProvider implements MemoryContextProvider
|
|
|
31
33
|
this.fetchedAt = options.fetchedAt;
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
private formatTags(tags?: unknown) {
|
|
37
|
+
return Array.isArray(tags) && tags.length ? `tags: ${tags.join(', ')}` : '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private formatLocation(location: any) {
|
|
41
|
+
if (!location || typeof location !== 'object') return '';
|
|
42
|
+
const parts: string[] = [];
|
|
43
|
+
|
|
44
|
+
if (typeof location.name === 'string' && location.name) parts.push(location.name);
|
|
45
|
+
if (typeof location.type === 'string' && location.type) parts.push(`type: ${location.type}`);
|
|
46
|
+
if (typeof location.address === 'string' && location.address) parts.push(location.address);
|
|
47
|
+
|
|
48
|
+
const tags = this.formatTags(location.tags);
|
|
49
|
+
if (tags) parts.push(tags);
|
|
50
|
+
|
|
51
|
+
return parts.join(' | ');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private formatAssociation(value: any) {
|
|
55
|
+
if (!value || typeof value !== 'object') return '';
|
|
56
|
+
const parts: string[] = [];
|
|
57
|
+
|
|
58
|
+
if (typeof value.name === 'string' && value.name) parts.push(value.name);
|
|
59
|
+
if (typeof value.type === 'string' && value.type) parts.push(`type: ${value.type}`);
|
|
60
|
+
|
|
61
|
+
if (value.extra && typeof value.extra === 'object') {
|
|
62
|
+
try {
|
|
63
|
+
parts.push(`extra: ${JSON.stringify(value.extra)}`);
|
|
64
|
+
} catch {
|
|
65
|
+
parts.push('extra: [unserializable]');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return parts.join(' | ');
|
|
70
|
+
}
|
|
71
|
+
|
|
34
72
|
async buildContext(job: MemoryExtractionJob): Promise<BuiltContext> {
|
|
73
|
+
const activities = this.retrievedMemories.activities || [];
|
|
35
74
|
const contexts = this.retrievedMemories.contexts || [];
|
|
36
75
|
const experiences = this.retrievedMemories.experiences || [];
|
|
37
76
|
const preferences = this.retrievedMemories.preferences || [];
|
|
38
77
|
|
|
78
|
+
|
|
79
|
+
|
|
39
80
|
const userMemoriesChildren: Child[] = [];
|
|
40
81
|
|
|
82
|
+
activities.forEach((activity) => {
|
|
83
|
+
const attributes: Record<string, string> = { id: activity.id ?? '' };
|
|
84
|
+
const similarity = (activity as { similarity?: number }).similarity;
|
|
85
|
+
|
|
86
|
+
if (typeof similarity === 'number') {
|
|
87
|
+
attributes.similarity = similarity.toFixed(3);
|
|
88
|
+
}
|
|
89
|
+
if (activity.type) {
|
|
90
|
+
attributes.activity_type = activity.type;
|
|
91
|
+
}
|
|
92
|
+
if (activity.status) {
|
|
93
|
+
attributes.status = activity.status;
|
|
94
|
+
}
|
|
95
|
+
if (activity.timezone) {
|
|
96
|
+
attributes.timezone = activity.timezone;
|
|
97
|
+
}
|
|
98
|
+
if (activity.startsAt) {
|
|
99
|
+
attributes.starts_at = new Date(activity.startsAt).toISOString();
|
|
100
|
+
}
|
|
101
|
+
if (activity.endsAt) {
|
|
102
|
+
attributes.ends_at = new Date(activity.endsAt).toISOString();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const children: Child[] = [];
|
|
106
|
+
const legacyLocation = (activity as { location?: unknown }).location;
|
|
107
|
+
const associatedLocations = Array.isArray(activity.associatedLocations)
|
|
108
|
+
? activity.associatedLocations
|
|
109
|
+
: legacyLocation
|
|
110
|
+
? [legacyLocation]
|
|
111
|
+
: [];
|
|
112
|
+
const associatedObjects = Array.isArray(activity.associatedObjects)
|
|
113
|
+
? activity.associatedObjects
|
|
114
|
+
: [];
|
|
115
|
+
const associatedSubjects = Array.isArray(activity.associatedSubjects)
|
|
116
|
+
? activity.associatedSubjects
|
|
117
|
+
: [];
|
|
118
|
+
|
|
119
|
+
if (activity.narrative) {
|
|
120
|
+
children.push(x('activity_narrative', activity.narrative));
|
|
121
|
+
}
|
|
122
|
+
if (activity.notes) {
|
|
123
|
+
children.push(x('activity_notes', activity.notes));
|
|
124
|
+
}
|
|
125
|
+
if (activity.feedback) {
|
|
126
|
+
children.push(x('activity_feedback', activity.feedback));
|
|
127
|
+
}
|
|
128
|
+
associatedLocations.forEach((location) => {
|
|
129
|
+
const value = this.formatLocation(location);
|
|
130
|
+
if (value) {
|
|
131
|
+
children.push(x('activity_associated_location', value));
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
associatedObjects.forEach((object) => {
|
|
135
|
+
const value = this.formatAssociation(object);
|
|
136
|
+
if (value) {
|
|
137
|
+
children.push(x('activity_associated_object', value));
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
associatedSubjects.forEach((subject) => {
|
|
141
|
+
const value = this.formatAssociation(subject);
|
|
142
|
+
if (value) {
|
|
143
|
+
children.push(x('activity_associated_subject', value));
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
if (Array.isArray(activity.tags) && activity.tags.length > 0) {
|
|
147
|
+
children.push(x('activity_tags', activity.tags.join(', ')));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
userMemoriesChildren.push(x('user_memories_activity', attributes, ...children));
|
|
151
|
+
});
|
|
152
|
+
|
|
41
153
|
contexts.forEach((context) => {
|
|
42
154
|
const attributes: Record<string, string> = { id: context.id ?? '' };
|
|
43
155
|
const similarity = (context as { similarity?: number }).similarity;
|
|
@@ -126,6 +238,7 @@ export class RetrievalUserMemoryContextProvider implements MemoryContextProvider
|
|
|
126
238
|
x(
|
|
127
239
|
'user_memories',
|
|
128
240
|
{
|
|
241
|
+
activities: activities.length.toString(),
|
|
129
242
|
contexts: contexts.length.toString(),
|
|
130
243
|
experiences: experiences.length.toString(),
|
|
131
244
|
memory_fetched_at: new Date(this.fetchedAt ?? Date.now()).toISOString(),
|