@lobehub/lobehub 2.0.0-next.360 → 2.0.0-next.361
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 +25 -0
- package/changelog/v1.json +5 -0
- package/package.json +1 -1
- package/packages/const/src/userMemory.ts +1 -0
- 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/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/src/libs/next/proxy/define-config.ts +1 -0
- package/src/server/globalConfig/parseMemoryExtractionConfig.ts +7 -1
- package/src/server/services/memory/userMemory/__tests__/extract.runtime.test.ts +2 -0
- package/src/server/services/memory/userMemory/extract.ts +108 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.361](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.360...v2.0.0-next.361)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-24**</sup>
|
|
8
|
+
|
|
9
|
+
#### ✨ Features
|
|
10
|
+
|
|
11
|
+
- **userMemories**: Added memory layer activity.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's improved
|
|
19
|
+
|
|
20
|
+
- **userMemories**: Added memory layer activity, closes [#11747](https://github.com/lobehub/lobe-chat/issues/11747) ([2021b1c](https://github.com/lobehub/lobe-chat/commit/2021b1c))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
## [Version 2.0.0-next.360](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.359...v2.0.0-next.360)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2026-01-24**</sup>
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.361",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AssociatedObjectSchema } from '@lobechat/memory-user-memory';
|
|
2
2
|
import {
|
|
3
|
+
ActivityTypeEnum,
|
|
3
4
|
IdentityTypeEnum,
|
|
4
5
|
LayersEnum,
|
|
5
6
|
MemorySourceType,
|
|
@@ -40,7 +41,10 @@ import {
|
|
|
40
41
|
UserMemoryIdentity,
|
|
41
42
|
UserMemoryItem,
|
|
42
43
|
UserMemoryPreference,
|
|
44
|
+
UserMemoryActivitiesWithoutVectors,
|
|
45
|
+
UserMemoryActivity,
|
|
43
46
|
userMemories,
|
|
47
|
+
userMemoriesActivities,
|
|
44
48
|
userMemoriesContexts,
|
|
45
49
|
userMemoriesExperiences,
|
|
46
50
|
userMemoriesIdentities,
|
|
@@ -104,6 +108,16 @@ export interface CreateUserMemoryContextParams extends BaseCreateUserMemoryParam
|
|
|
104
108
|
>;
|
|
105
109
|
}
|
|
106
110
|
|
|
111
|
+
export interface CreateUserMemoryActivityParams extends BaseCreateUserMemoryParams {
|
|
112
|
+
activity: Optional<
|
|
113
|
+
Omit<
|
|
114
|
+
UserMemoryActivity,
|
|
115
|
+
'id' | 'userId' | 'createdAt' | 'updatedAt' | 'accessedAt' | 'userMemoryId'
|
|
116
|
+
>,
|
|
117
|
+
'capturedAt'
|
|
118
|
+
>;
|
|
119
|
+
}
|
|
120
|
+
|
|
107
121
|
export interface CreateUserMemoryExperienceParams extends BaseCreateUserMemoryParams {
|
|
108
122
|
experience: Optional<
|
|
109
123
|
Omit<
|
|
@@ -135,6 +149,7 @@ export interface CreateUserMemoryPreferenceParams extends BaseCreateUserMemoryPa
|
|
|
135
149
|
}
|
|
136
150
|
|
|
137
151
|
export type CreateUserMemoryParams =
|
|
152
|
+
| CreateUserMemoryActivityParams
|
|
138
153
|
| CreateUserMemoryContextParams
|
|
139
154
|
| CreateUserMemoryExperienceParams
|
|
140
155
|
| CreateUserMemoryIdentityParams
|
|
@@ -143,7 +158,7 @@ export type CreateUserMemoryParams =
|
|
|
143
158
|
export interface SearchUserMemoryParams {
|
|
144
159
|
embedding?: number[];
|
|
145
160
|
limit?: number;
|
|
146
|
-
limits?: Partial<Record<'contexts' | 'experiences' | 'preferences', number>>;
|
|
161
|
+
limits?: Partial<Record<'activities' | 'contexts' | 'experiences' | 'preferences', number>>;
|
|
147
162
|
memoryCategory?: string;
|
|
148
163
|
memoryType?: string;
|
|
149
164
|
query?: string;
|
|
@@ -151,12 +166,13 @@ export interface SearchUserMemoryParams {
|
|
|
151
166
|
|
|
152
167
|
export interface SearchUserMemoryWithEmbeddingParams {
|
|
153
168
|
embedding?: number[];
|
|
154
|
-
limits?: Partial<Record<'contexts' | 'experiences' | 'preferences', number>>;
|
|
169
|
+
limits?: Partial<Record<'activities' | 'contexts' | 'experiences' | 'preferences', number>>;
|
|
155
170
|
memoryCategory?: string;
|
|
156
171
|
memoryType?: string;
|
|
157
172
|
}
|
|
158
173
|
|
|
159
174
|
export interface UserMemorySearchAggregatedResult {
|
|
175
|
+
activities: UserMemoryActivitiesWithoutVectors[];
|
|
160
176
|
contexts: UserMemoryContextWithoutVectors[];
|
|
161
177
|
experiences: UserMemoryExperienceWithoutVectors[];
|
|
162
178
|
preferences: UserMemoryPreferenceWithoutVectors[];
|
|
@@ -380,12 +396,67 @@ export class UserMemoryModel {
|
|
|
380
396
|
const extra = JSON.parse(parsed.data.extra || '{}');
|
|
381
397
|
parsed.data.extra = extra;
|
|
382
398
|
associations.push(parsed.data);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (
|
|
403
|
+
item &&
|
|
404
|
+
typeof item === 'object' &&
|
|
405
|
+
'name' in item &&
|
|
406
|
+
typeof (item as any).name === 'string'
|
|
407
|
+
) {
|
|
408
|
+
associations.push({ name: (item as any).name });
|
|
383
409
|
}
|
|
384
410
|
});
|
|
385
411
|
|
|
386
412
|
return associations.length > 0 ? associations : [];
|
|
387
413
|
}
|
|
388
414
|
|
|
415
|
+
static parseAssociatedLocations(
|
|
416
|
+
value?:
|
|
417
|
+
| {
|
|
418
|
+
address?: unknown;
|
|
419
|
+
name?: unknown;
|
|
420
|
+
tags?: unknown;
|
|
421
|
+
type?: unknown;
|
|
422
|
+
}[]
|
|
423
|
+
| Record<string, unknown>,
|
|
424
|
+
) {
|
|
425
|
+
if (!value) return [];
|
|
426
|
+
|
|
427
|
+
const raw = Array.isArray(value) ? value : [value];
|
|
428
|
+
const locations: {
|
|
429
|
+
address?: string;
|
|
430
|
+
name?: string;
|
|
431
|
+
tags?: string[];
|
|
432
|
+
type?: string;
|
|
433
|
+
}[] = [];
|
|
434
|
+
|
|
435
|
+
raw.forEach((item) => {
|
|
436
|
+
if (!item || typeof item !== 'object') return;
|
|
437
|
+
|
|
438
|
+
const address = typeof (item as any).address === 'string' ? (item as any).address : undefined;
|
|
439
|
+
const name = typeof (item as any).name === 'string' ? (item as any).name : undefined;
|
|
440
|
+
const type = typeof (item as any).type === 'string' ? (item as any).type : undefined;
|
|
441
|
+
const tagsRaw = (item as any).tags;
|
|
442
|
+
const tags =
|
|
443
|
+
Array.isArray(tagsRaw) && tagsRaw.every((tag) => typeof tag === 'string')
|
|
444
|
+
? (tagsRaw as string[])
|
|
445
|
+
: undefined;
|
|
446
|
+
|
|
447
|
+
if (address || name || type || tags) {
|
|
448
|
+
locations.push({
|
|
449
|
+
address,
|
|
450
|
+
name,
|
|
451
|
+
tags,
|
|
452
|
+
type,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
return locations;
|
|
458
|
+
}
|
|
459
|
+
|
|
389
460
|
static parseDateFromString(value?: string | Date | null): Date | null {
|
|
390
461
|
if (!value) return null;
|
|
391
462
|
if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
|
|
@@ -537,6 +608,46 @@ export class UserMemoryModel {
|
|
|
537
608
|
});
|
|
538
609
|
};
|
|
539
610
|
|
|
611
|
+
createActivityMemory = async (
|
|
612
|
+
params: CreateUserMemoryActivityParams,
|
|
613
|
+
): Promise<{ activity: UserMemoryActivity; memory: UserMemoryItem }> => {
|
|
614
|
+
return this.db.transaction(async (tx) => {
|
|
615
|
+
const baseValues = this.buildBaseMemoryInsertValues(params, {
|
|
616
|
+
metadata: params.activity.metadata ?? null,
|
|
617
|
+
status: params.activity.status ?? 'pending',
|
|
618
|
+
tags: params.activity.tags ?? null,
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
const [memory] = await tx.insert(userMemories).values(baseValues).returning();
|
|
622
|
+
if (!memory) throw new Error('Failed to create user memory activity');
|
|
623
|
+
|
|
624
|
+
const activityValues = {
|
|
625
|
+
associatedLocations: params.activity.associatedLocations ?? null,
|
|
626
|
+
associatedObjects: params.activity.associatedObjects ?? [],
|
|
627
|
+
associatedSubjects: params.activity.associatedSubjects ?? [],
|
|
628
|
+
capturedAt: params.activity.capturedAt,
|
|
629
|
+
endsAt: coerceDate(params.activity.endsAt),
|
|
630
|
+
feedback: params.activity.feedback ?? null,
|
|
631
|
+
feedbackVector: params.activity.feedbackVector ?? null,
|
|
632
|
+
metadata: params.activity.metadata ?? null,
|
|
633
|
+
narrative: params.activity.narrative ?? null,
|
|
634
|
+
narrativeVector: params.activity.narrativeVector ?? null,
|
|
635
|
+
notes: params.activity.notes ?? null,
|
|
636
|
+
startsAt: coerceDate(params.activity.startsAt),
|
|
637
|
+
status: params.activity.status ?? null,
|
|
638
|
+
tags: params.activity.tags ?? [],
|
|
639
|
+
timezone: params.activity.timezone ?? null,
|
|
640
|
+
type: params.activity.type ?? ActivityTypeEnum.Other,
|
|
641
|
+
userId: this.userId,
|
|
642
|
+
userMemoryId: memory.id,
|
|
643
|
+
} satisfies typeof userMemoriesActivities.$inferInsert;
|
|
644
|
+
|
|
645
|
+
const [activity] = await tx.insert(userMemoriesActivities).values(activityValues).returning();
|
|
646
|
+
|
|
647
|
+
return { activity, memory };
|
|
648
|
+
});
|
|
649
|
+
};
|
|
650
|
+
|
|
540
651
|
createPreferenceMemory = async (
|
|
541
652
|
params: CreateUserMemoryPreferenceParams,
|
|
542
653
|
): Promise<{ memory: UserMemoryItem; preference: UserMemoryPreference }> => {
|
|
@@ -575,12 +686,13 @@ export class UserMemoryModel {
|
|
|
575
686
|
const { embedding, limits } = params;
|
|
576
687
|
|
|
577
688
|
const resolvedLimits = {
|
|
689
|
+
activities: limits?.activities,
|
|
578
690
|
contexts: limits?.contexts,
|
|
579
691
|
experiences: limits?.experiences,
|
|
580
692
|
preferences: limits?.preferences,
|
|
581
693
|
};
|
|
582
694
|
|
|
583
|
-
const [experiences, contexts, preferences] = await Promise.all([
|
|
695
|
+
const [experiences, contexts, preferences, activities] = await Promise.all([
|
|
584
696
|
this.searchExperiences({
|
|
585
697
|
embedding,
|
|
586
698
|
limit: resolvedLimits.experiences,
|
|
@@ -593,6 +705,10 @@ export class UserMemoryModel {
|
|
|
593
705
|
embedding,
|
|
594
706
|
limit: resolvedLimits.preferences,
|
|
595
707
|
}),
|
|
708
|
+
this.searchActivities({
|
|
709
|
+
embedding,
|
|
710
|
+
limit: resolvedLimits.activities,
|
|
711
|
+
}),
|
|
596
712
|
]);
|
|
597
713
|
|
|
598
714
|
const accessedMemoryIds = new Set<string>();
|
|
@@ -602,6 +718,9 @@ export class UserMemoryModel {
|
|
|
602
718
|
preferences.forEach((preference) => {
|
|
603
719
|
if (preference.userMemoryId) accessedMemoryIds.add(preference.userMemoryId);
|
|
604
720
|
});
|
|
721
|
+
activities.forEach((activity) => {
|
|
722
|
+
if (activity.userMemoryId) accessedMemoryIds.add(activity.userMemoryId);
|
|
723
|
+
});
|
|
605
724
|
const contextLinkIds: string[] = [];
|
|
606
725
|
contexts.forEach((context) => {
|
|
607
726
|
const ids = Array.isArray(context.userMemoryIds) ? (context.userMemoryIds as string[]) : [];
|
|
@@ -617,6 +736,7 @@ export class UserMemoryModel {
|
|
|
617
736
|
}
|
|
618
737
|
|
|
619
738
|
return {
|
|
739
|
+
activities,
|
|
620
740
|
contexts,
|
|
621
741
|
experiences,
|
|
622
742
|
preferences,
|
|
@@ -1795,6 +1915,61 @@ export class UserMemoryModel {
|
|
|
1795
1915
|
await this.db.delete(userMemories).where(eq(userMemories.userId, this.userId));
|
|
1796
1916
|
};
|
|
1797
1917
|
|
|
1918
|
+
searchActivities = async (params: {
|
|
1919
|
+
embedding?: number[];
|
|
1920
|
+
limit?: number;
|
|
1921
|
+
type?: string;
|
|
1922
|
+
}): Promise<UserMemoryActivitiesWithoutVectors[]> => {
|
|
1923
|
+
const { embedding, limit = 5, type } = params;
|
|
1924
|
+
if (limit <= 0) {
|
|
1925
|
+
return [];
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
let query = this.db
|
|
1929
|
+
.select({
|
|
1930
|
+
accessedAt: userMemoriesActivities.accessedAt,
|
|
1931
|
+
associatedLocations: userMemoriesActivities.associatedLocations,
|
|
1932
|
+
associatedObjects: userMemoriesActivities.associatedObjects,
|
|
1933
|
+
associatedSubjects: userMemoriesActivities.associatedSubjects,
|
|
1934
|
+
capturedAt: userMemoriesActivities.capturedAt,
|
|
1935
|
+
createdAt: userMemoriesActivities.createdAt,
|
|
1936
|
+
endsAt: userMemoriesActivities.endsAt,
|
|
1937
|
+
feedback: userMemoriesActivities.feedback,
|
|
1938
|
+
id: userMemoriesActivities.id,
|
|
1939
|
+
metadata: userMemoriesActivities.metadata,
|
|
1940
|
+
narrative: userMemoriesActivities.narrative,
|
|
1941
|
+
notes: userMemoriesActivities.notes,
|
|
1942
|
+
startsAt: userMemoriesActivities.startsAt,
|
|
1943
|
+
status: userMemoriesActivities.status,
|
|
1944
|
+
tags: userMemoriesActivities.tags,
|
|
1945
|
+
timezone: userMemoriesActivities.timezone,
|
|
1946
|
+
type: userMemoriesActivities.type,
|
|
1947
|
+
updatedAt: userMemoriesActivities.updatedAt,
|
|
1948
|
+
userId: userMemoriesActivities.userId,
|
|
1949
|
+
userMemoryId: userMemoriesActivities.userMemoryId,
|
|
1950
|
+
...(embedding && {
|
|
1951
|
+
similarity: sql<number>`1 - (${cosineDistance(userMemoriesActivities.narrativeVector, embedding)}) AS similarity`,
|
|
1952
|
+
}),
|
|
1953
|
+
})
|
|
1954
|
+
.from(userMemoriesActivities)
|
|
1955
|
+
.$dynamic();
|
|
1956
|
+
|
|
1957
|
+
const conditions = [eq(userMemoriesActivities.userId, this.userId)];
|
|
1958
|
+
if (type) {
|
|
1959
|
+
conditions.push(eq(userMemoriesActivities.type, type));
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
query = query.where(and(...conditions));
|
|
1963
|
+
|
|
1964
|
+
if (embedding) {
|
|
1965
|
+
query = query.orderBy(desc(sql`similarity`));
|
|
1966
|
+
} else {
|
|
1967
|
+
query = query.orderBy(desc(userMemoriesActivities.createdAt));
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
return query.limit(limit) as Promise<UserMemoryActivitiesWithoutVectors[]>;
|
|
1971
|
+
};
|
|
1972
|
+
|
|
1798
1973
|
searchContexts = async (params: {
|
|
1799
1974
|
embedding?: number[];
|
|
1800
1975
|
limit?: number;
|
|
@@ -63,7 +63,7 @@ export class UserMemorySourceBenchmarkLoCoMoModel {
|
|
|
63
63
|
return { id };
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
replaceParts(sourceId: string, parts: BenchmarkLoCoMoPart[]) {
|
|
67
67
|
const store = this.getPartStore();
|
|
68
68
|
store.delete(sourceId);
|
|
69
69
|
if (!parts.length) return;
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"@lobechat/context-engine": "workspace:*",
|
|
21
21
|
"@lobechat/model-runtime": "workspace:*",
|
|
22
22
|
"@lobechat/prompts": "workspace:*",
|
|
23
|
-
"dotenv": "^17.2.3",
|
|
24
23
|
"dayjs": "^1.11.11",
|
|
24
|
+
"dotenv": "^17.2.3",
|
|
25
25
|
"ora": "^9.0.0",
|
|
26
26
|
"unist-builder": "^4.0.0",
|
|
27
27
|
"xast-util-to-xml": "^4.0.0",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@lobechat/types": "workspace:*",
|
|
33
|
+
"@types/json-schema": "^7.0.15",
|
|
33
34
|
"@types/xast": "^2.0.4",
|
|
34
35
|
"promptfoo": "^0.118.17",
|
|
35
36
|
"tsx": "^4.20.6"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { renderPlaceholderTemplate } from '@lobechat/context-engine';
|
|
2
|
+
|
|
3
|
+
import { activityPrompt } from '../../../../src/prompts';
|
|
4
|
+
import type { ExtractorTemplateProps } from '../../../../src/types';
|
|
5
|
+
|
|
6
|
+
export interface PromptVars extends ExtractorTemplateProps {
|
|
7
|
+
conversation: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const buildActivityMessages = (vars: PromptVars) => {
|
|
11
|
+
const retrievedContext =
|
|
12
|
+
Array.isArray(vars.retrievedContexts) && vars.retrievedContexts.length > 0
|
|
13
|
+
? vars.retrievedContexts.join('\n\n')
|
|
14
|
+
: typeof vars.retrievedContexts === 'string'
|
|
15
|
+
? vars.retrievedContexts
|
|
16
|
+
: 'No similar memories retrieved.';
|
|
17
|
+
|
|
18
|
+
const rendered = renderPlaceholderTemplate(activityPrompt, {
|
|
19
|
+
availableCategories: vars.availableCategories,
|
|
20
|
+
language: vars.language || 'English',
|
|
21
|
+
retrievedContext,
|
|
22
|
+
sessionDate: vars.sessionDate || new Date().toISOString(),
|
|
23
|
+
topK: vars.topK ?? 5,
|
|
24
|
+
username: vars.username || 'User',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const messages = [
|
|
28
|
+
{ content: rendered, role: 'system' as const },
|
|
29
|
+
{ content: rendered, role: 'user' as const },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
if (vars.conversation) {
|
|
33
|
+
messages.push({
|
|
34
|
+
content: `Conversation:\n${vars.conversation}`,
|
|
35
|
+
role: 'user' as const,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return messages;
|
|
40
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
description: Regression benchmark for activity layer structured extraction
|
|
2
|
+
|
|
3
|
+
providers:
|
|
4
|
+
- id: openai:chat:gpt-5-mini
|
|
5
|
+
config:
|
|
6
|
+
response_format: file://../../../response-formats/activity.json
|
|
7
|
+
temperature: 0
|
|
8
|
+
|
|
9
|
+
prompts:
|
|
10
|
+
- file://./prompt.ts
|
|
11
|
+
|
|
12
|
+
tests:
|
|
13
|
+
- file://./tests/cases.ts
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
type PromptfooAssert =
|
|
2
|
+
| { type: 'javascript'; value: string }
|
|
3
|
+
| { provider?: string; type: 'llm-rubric'; value: string };
|
|
4
|
+
|
|
5
|
+
interface PromptfooTestCase {
|
|
6
|
+
assert: PromptfooAssert[];
|
|
7
|
+
description?: string;
|
|
8
|
+
vars: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const baseSchemaAssert: PromptfooAssert = {
|
|
12
|
+
type: 'javascript',
|
|
13
|
+
value: `
|
|
14
|
+
let parsed;
|
|
15
|
+
try {
|
|
16
|
+
parsed = JSON.parse(output);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error('Failed to parse JSON output', error);
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!parsed || !Array.isArray(parsed.memories)) return false;
|
|
23
|
+
|
|
24
|
+
return parsed.memories.every((memory) => {
|
|
25
|
+
return (
|
|
26
|
+
memory.memoryType === 'activity' &&
|
|
27
|
+
memory.title &&
|
|
28
|
+
memory.summary &&
|
|
29
|
+
memory.withActivity?.type &&
|
|
30
|
+
memory.withActivity?.narrative
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
`,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const baseVars = {
|
|
37
|
+
availableCategories: ['work', 'health', 'personal'],
|
|
38
|
+
language: 'English',
|
|
39
|
+
topK: 5,
|
|
40
|
+
username: 'User',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const testCases: PromptfooTestCase[] = [
|
|
44
|
+
{
|
|
45
|
+
assert: [
|
|
46
|
+
baseSchemaAssert,
|
|
47
|
+
{
|
|
48
|
+
type: 'javascript',
|
|
49
|
+
value: `
|
|
50
|
+
const data = JSON.parse(output);
|
|
51
|
+
const first = data.memories?.[0];
|
|
52
|
+
if (!first) return false;
|
|
53
|
+
|
|
54
|
+
const activity = first.withActivity || {};
|
|
55
|
+
return Boolean(activity.startsAt && activity.endsAt && activity.timezone && activity.associatedLocations?.[0]?.name);
|
|
56
|
+
`,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
provider: 'openai:gpt-5-mini',
|
|
60
|
+
type: 'llm-rubric',
|
|
61
|
+
value:
|
|
62
|
+
'Should extract a meeting activity including timing (start/end/timezone), location name ACME HQ, status completed when implied, and feedback reflecting the positive tone.',
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
description: 'Meeting with explicit time and location',
|
|
66
|
+
vars: {
|
|
67
|
+
...baseVars,
|
|
68
|
+
conversation:
|
|
69
|
+
'User: I met with Alice at ACME HQ on 2024-05-03 from 14:00-15:00 America/New_York. We reviewed Q2 renewal scope and agreed to send revised pricing next week. I felt positive and collaborative about the call.',
|
|
70
|
+
retrievedContexts: ['Previous similar memory: met with Alice about renewal last month.'],
|
|
71
|
+
sessionDate: '2024-05-03',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
assert: [
|
|
76
|
+
baseSchemaAssert,
|
|
77
|
+
{
|
|
78
|
+
type: 'javascript',
|
|
79
|
+
value: `
|
|
80
|
+
const data = JSON.parse(output);
|
|
81
|
+
const first = data.memories?.[0];
|
|
82
|
+
if (!first) return false;
|
|
83
|
+
|
|
84
|
+
const activity = first.withActivity || {};
|
|
85
|
+
return Boolean(activity.narrative && activity.feedback);
|
|
86
|
+
`,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
provider: 'openai:gpt-5-mini',
|
|
90
|
+
type: 'llm-rubric',
|
|
91
|
+
value:
|
|
92
|
+
'Should capture an exercise activity without inventing exact timestamps or timezones; keep the narrative and feedback about the yoga session at home and omit temporal fields that were not provided.',
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
description: 'Exercise without explicit time or timezone',
|
|
96
|
+
vars: {
|
|
97
|
+
...baseVars,
|
|
98
|
+
conversation:
|
|
99
|
+
'User: Over the weekend I did a 30-minute yoga session at home with my roommate. No specific time was set, it was just a casual stretch and it left me feeling calm.',
|
|
100
|
+
retrievedContexts: [],
|
|
101
|
+
sessionDate: '2025-05-05 10:02:00',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
export default testCases;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { renderPlaceholderTemplate } from '@lobechat/context-engine';
|
|
2
|
+
import { MemorySourceType } from '@lobechat/types';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { isAbsolute, join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { BenchmarkLocomoContextProvider, BenchmarkLocomoPart } from '../../../../src/providers';
|
|
7
|
+
import type { IngestPayload } from '../../../../src/converters/locomo';
|
|
8
|
+
import { activityPrompt } from '../../../../src/prompts';
|
|
9
|
+
import type { ExtractorTemplateProps, MemoryExtractionJob } from '../../../../src/types';
|
|
10
|
+
|
|
11
|
+
export interface PromptVars extends ExtractorTemplateProps {
|
|
12
|
+
payloadPath: string;
|
|
13
|
+
sessionId?: string;
|
|
14
|
+
userId?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const resolvePath = (payloadPath: string) =>
|
|
18
|
+
isAbsolute(payloadPath) ? payloadPath : join(process.cwd(), payloadPath);
|
|
19
|
+
|
|
20
|
+
const buildParts = (payload: IngestPayload, sessionId?: string): BenchmarkLocomoPart[] => {
|
|
21
|
+
let partIndex = 0;
|
|
22
|
+
const sessions = payload.sessions.filter(
|
|
23
|
+
(session) => !sessionId || session.sessionId === sessionId,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return sessions.flatMap((session) =>
|
|
27
|
+
session.turns.map((turn) => {
|
|
28
|
+
const metadata = {
|
|
29
|
+
diaId: turn.diaId,
|
|
30
|
+
imageCaption: turn.imageCaption,
|
|
31
|
+
imageUrls: turn.imageUrls,
|
|
32
|
+
sessionId: session.sessionId,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
content: turn.text,
|
|
37
|
+
createdAt: turn.createdAt || session.timestamp,
|
|
38
|
+
metadata,
|
|
39
|
+
partIndex: partIndex++,
|
|
40
|
+
sessionId: session.sessionId,
|
|
41
|
+
speaker: turn.speaker,
|
|
42
|
+
};
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const resolveSessionDate = (payload: IngestPayload, parts: BenchmarkLocomoPart[], sessionId?: string) => {
|
|
48
|
+
const sessionDate =
|
|
49
|
+
payload.sessions.find((session) => session.sessionId === sessionId)?.timestamp ||
|
|
50
|
+
payload.sessions[0]?.timestamp;
|
|
51
|
+
|
|
52
|
+
if (sessionDate) return sessionDate;
|
|
53
|
+
|
|
54
|
+
const latestCreatedAt = parts
|
|
55
|
+
.map((part) => (part.createdAt ? new Date(part.createdAt) : null))
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.sort((a, b) => (a!.getTime() > b!.getTime() ? 1 : -1))
|
|
58
|
+
.at(-1);
|
|
59
|
+
|
|
60
|
+
return latestCreatedAt ? latestCreatedAt.toISOString() : new Date().toISOString();
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const buildLocomoActivityMessages = async (vars: PromptVars) => {
|
|
64
|
+
const payloadPath = resolvePath(vars.payloadPath);
|
|
65
|
+
const payloadRaw = await readFile(payloadPath, 'utf8');
|
|
66
|
+
const payload = JSON.parse(payloadRaw) as IngestPayload;
|
|
67
|
+
|
|
68
|
+
const parts = buildParts(payload, vars.sessionId);
|
|
69
|
+
if (parts.length === 0) {
|
|
70
|
+
throw new Error(`No matching parts found in ${payload.sampleId} for session ${vars.sessionId || 'all'}`);
|
|
71
|
+
}
|
|
72
|
+
const userId = vars.userId || `locomo-user-${payload.sampleId}`;
|
|
73
|
+
const sourceId = payload.topicId || `sample_${payload.sampleId}`;
|
|
74
|
+
const sessionDate = vars.sessionDate || resolveSessionDate(payload, parts, vars.sessionId);
|
|
75
|
+
|
|
76
|
+
const provider = new BenchmarkLocomoContextProvider({
|
|
77
|
+
parts,
|
|
78
|
+
sampleId: payload.sampleId,
|
|
79
|
+
sourceId,
|
|
80
|
+
userId,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const extractionJob: MemoryExtractionJob = {
|
|
84
|
+
source: MemorySourceType.BenchmarkLocomo,
|
|
85
|
+
sourceId,
|
|
86
|
+
userId,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const { context } = await provider.buildContext(extractionJob);
|
|
90
|
+
|
|
91
|
+
const rendered = renderPlaceholderTemplate(activityPrompt, {
|
|
92
|
+
availableCategories: vars.availableCategories,
|
|
93
|
+
language: vars.language || 'English',
|
|
94
|
+
retrievedContext: context || 'No similar memories retrieved.',
|
|
95
|
+
sessionDate,
|
|
96
|
+
topK: vars.topK ?? 5,
|
|
97
|
+
username: vars.username || 'User',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return [
|
|
101
|
+
{ content: rendered, role: 'system' as const },
|
|
102
|
+
{ content: rendered, role: 'user' as const },
|
|
103
|
+
];
|
|
104
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
description: LoCoMo regression for activity layer with relative time resolution
|
|
2
|
+
|
|
3
|
+
providers:
|
|
4
|
+
- id: openai:chat:gpt-5-mini
|
|
5
|
+
config:
|
|
6
|
+
response_format: file://../../../response-formats/activity.json
|
|
7
|
+
temperature: 0
|
|
8
|
+
|
|
9
|
+
prompts:
|
|
10
|
+
- file://./prompt.ts
|
|
11
|
+
|
|
12
|
+
tests:
|
|
13
|
+
- file://./tests/cases.ts
|