@sentry/junior-memory 0.81.0 → 0.83.0
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/dist/index.js +60 -9
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts +2 -0
- package/dist/recall.d.ts +2 -0
- package/dist/store.d.ts +2 -0
- package/package.json +2 -2
- package/src/plugin.ts +19 -0
- package/src/recall.ts +10 -3
- package/src/store.ts +53 -3
package/dist/plugin.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export interface MemoryPluginOptions {
|
|
2
2
|
modelId?: string;
|
|
3
|
+
/** Maximum cosine distance for vector recall candidates. Defaults to MEMORY_RECALL_MAX_VECTOR_DISTANCE env or 0.45. */
|
|
4
|
+
recallMaxVectorDistance?: number;
|
|
3
5
|
}
|
|
4
6
|
/** Create Junior's long-term memory plugin registration. */
|
|
5
7
|
export declare function createMemoryPlugin(options?: MemoryPluginOptions): import("@sentry/junior-plugin-api").PluginRegistration;
|
package/dist/recall.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export interface MemoryRecallContext {
|
|
|
4
4
|
conversationId?: string;
|
|
5
5
|
db: MemoryDb;
|
|
6
6
|
embedder?: MemoryEmbeddingProvider;
|
|
7
|
+
/** Maximum cosine distance for vector recall. Passed through to the memory store. */
|
|
8
|
+
maxVectorDistance?: number;
|
|
7
9
|
requester?: Requester;
|
|
8
10
|
source: Source;
|
|
9
11
|
text: string;
|
package/dist/store.d.ts
CHANGED
|
@@ -107,6 +107,8 @@ export interface MemorySupersessionDecider {
|
|
|
107
107
|
}
|
|
108
108
|
export interface MemoryStoreOptions {
|
|
109
109
|
embedder?: MemoryEmbeddingProvider;
|
|
110
|
+
/** Maximum cosine distance for vector recall candidates. Model-dependent; tune when changing AI_EMBEDDING_MODEL. */
|
|
111
|
+
maxVectorDistance?: number;
|
|
110
112
|
now?: () => number;
|
|
111
113
|
supersessionDecider?: MemorySupersessionDecider;
|
|
112
114
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.83.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"commander": "^14.0.3",
|
|
28
28
|
"drizzle-orm": "^0.45.2",
|
|
29
29
|
"zod": "^4.4.3",
|
|
30
|
-
"@sentry/junior-plugin-api": "0.
|
|
30
|
+
"@sentry/junior-plugin-api": "0.83.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^25.9.1",
|
package/src/plugin.ts
CHANGED
|
@@ -15,9 +15,13 @@ import { createMemoryPromptMessages } from "./recall";
|
|
|
15
15
|
import type { MemoryDb } from "./store";
|
|
16
16
|
|
|
17
17
|
const MEMORY_MODEL_ENV = "AI_MEMORY_MODEL";
|
|
18
|
+
const MEMORY_RECALL_MAX_VECTOR_DISTANCE_ENV = "MEMORY_RECALL_MAX_VECTOR_DISTANCE";
|
|
19
|
+
const DEFAULT_RECALL_MAX_VECTOR_DISTANCE = 0.45;
|
|
18
20
|
|
|
19
21
|
export interface MemoryPluginOptions {
|
|
20
22
|
modelId?: string;
|
|
23
|
+
/** Maximum cosine distance for vector recall candidates. Defaults to MEMORY_RECALL_MAX_VECTOR_DISTANCE env or 0.45. */
|
|
24
|
+
recallMaxVectorDistance?: number;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
function memoryModelId(options: MemoryPluginOptions): string | undefined {
|
|
@@ -29,6 +33,20 @@ function memoryModelId(options: MemoryPluginOptions): string | undefined {
|
|
|
29
33
|
return envModelId || undefined;
|
|
30
34
|
}
|
|
31
35
|
|
|
36
|
+
function recallMaxVectorDistance(options: MemoryPluginOptions): number {
|
|
37
|
+
if (options.recallMaxVectorDistance !== undefined) {
|
|
38
|
+
return options.recallMaxVectorDistance;
|
|
39
|
+
}
|
|
40
|
+
const raw = process.env[MEMORY_RECALL_MAX_VECTOR_DISTANCE_ENV]?.trim();
|
|
41
|
+
if (raw) {
|
|
42
|
+
const parsed = Number(raw);
|
|
43
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
44
|
+
return Math.min(parsed, 1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return DEFAULT_RECALL_MAX_VECTOR_DISTANCE;
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
function memoryToolContext(ctx: {
|
|
33
51
|
agent: MemoryReviewer;
|
|
34
52
|
conversationId?: string;
|
|
@@ -118,6 +136,7 @@ export function createMemoryPlugin(options: MemoryPluginOptions = {}) {
|
|
|
118
136
|
...(ctx.requester ? { requester: ctx.requester } : {}),
|
|
119
137
|
db: ctx.db as MemoryDb,
|
|
120
138
|
embedder: ctx.embedder,
|
|
139
|
+
maxVectorDistance: recallMaxVectorDistance(options),
|
|
121
140
|
source: ctx.source,
|
|
122
141
|
text: ctx.text,
|
|
123
142
|
});
|
package/src/recall.ts
CHANGED
|
@@ -12,13 +12,15 @@ import {
|
|
|
12
12
|
import { memoryRuntimeContextSchema } from "./types";
|
|
13
13
|
|
|
14
14
|
const DEFAULT_RECALL_LIMIT = 5;
|
|
15
|
-
const MAX_PROMPT_CHARS =
|
|
16
|
-
const MAX_MEMORY_LINE_CHARS =
|
|
15
|
+
const MAX_PROMPT_CHARS = 4_000;
|
|
16
|
+
const MAX_MEMORY_LINE_CHARS = 600;
|
|
17
17
|
|
|
18
18
|
export interface MemoryRecallContext {
|
|
19
19
|
conversationId?: string;
|
|
20
20
|
db: MemoryDb;
|
|
21
21
|
embedder?: MemoryEmbeddingProvider;
|
|
22
|
+
/** Maximum cosine distance for vector recall. Passed through to the memory store. */
|
|
23
|
+
maxVectorDistance?: number;
|
|
22
24
|
requester?: Requester;
|
|
23
25
|
source: Source;
|
|
24
26
|
text: string;
|
|
@@ -78,7 +80,12 @@ export async function createMemoryPromptMessages(
|
|
|
78
80
|
const memories = await createMemoryStore(
|
|
79
81
|
context.db,
|
|
80
82
|
runtimeContext,
|
|
81
|
-
|
|
83
|
+
{
|
|
84
|
+
...(context.embedder ? { embedder: context.embedder } : {}),
|
|
85
|
+
...(context.maxVectorDistance !== undefined
|
|
86
|
+
? { maxVectorDistance: context.maxVectorDistance }
|
|
87
|
+
: {}),
|
|
88
|
+
},
|
|
82
89
|
).searchMemories({
|
|
83
90
|
query: context.text,
|
|
84
91
|
limit: DEFAULT_RECALL_LIMIT,
|
package/src/store.ts
CHANGED
|
@@ -56,6 +56,7 @@ const MAX_MEMORY_CONTENT_CHARS = 4_000;
|
|
|
56
56
|
const EMBEDDING_METRIC = "cosine";
|
|
57
57
|
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
58
58
|
const RELEVANCE_NEAR_TIE_DELTA = 0.01;
|
|
59
|
+
const SOURCE_CHANNEL_BOOST = 0.05;
|
|
59
60
|
const SUPERSESSION_STOP_TERMS = new Set([
|
|
60
61
|
"and",
|
|
61
62
|
"for",
|
|
@@ -73,6 +74,7 @@ export type MemoryDb = PgDatabase<PgQueryResultHKT, typeof memorySqlSchema>;
|
|
|
73
74
|
interface SearchCandidate {
|
|
74
75
|
memory: MemoryRecord;
|
|
75
76
|
score: number;
|
|
77
|
+
sourceKey: string;
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
interface MemoryEmbedding {
|
|
@@ -274,6 +276,8 @@ export interface MemorySupersessionDecider {
|
|
|
274
276
|
|
|
275
277
|
export interface MemoryStoreOptions {
|
|
276
278
|
embedder?: MemoryEmbeddingProvider;
|
|
279
|
+
/** Maximum cosine distance for vector recall candidates. Model-dependent; tune when changing AI_EMBEDDING_MODEL. */
|
|
280
|
+
maxVectorDistance?: number;
|
|
277
281
|
now?: () => number;
|
|
278
282
|
supersessionDecider?: MemorySupersessionDecider;
|
|
279
283
|
}
|
|
@@ -343,6 +347,14 @@ function sourceKey(ctx: MemoryRuntimeContext): string {
|
|
|
343
347
|
return `slack:${ctx.source.teamId}:${ctx.source.channelId}:${threadKey}`;
|
|
344
348
|
}
|
|
345
349
|
|
|
350
|
+
function sourceChannelPrefix(ctx: MemoryRuntimeContext): string | undefined {
|
|
351
|
+
if (ctx.source.platform !== "slack") {
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
354
|
+
// TODO(v0.82.0): Replace Slack source-key prefix matching with typed source proximity metadata.
|
|
355
|
+
return `slack:${ctx.source.teamId}:${ctx.source.channelId}:`;
|
|
356
|
+
}
|
|
357
|
+
|
|
346
358
|
/** Parse one SQL row into the public memory record projection. */
|
|
347
359
|
function parseMemoryRow(row: unknown): MemoryRecord {
|
|
348
360
|
const parsed = memoryRowSchema.parse(row);
|
|
@@ -959,7 +971,7 @@ async function searchVisibleMemories(args: {
|
|
|
959
971
|
nowMs: number;
|
|
960
972
|
query: string;
|
|
961
973
|
scopes: ResolvedMemoryScope[];
|
|
962
|
-
}): Promise<
|
|
974
|
+
}): Promise<SearchCandidate[]> {
|
|
963
975
|
const terms = searchTerms(args.query);
|
|
964
976
|
if (terms.length === 0) {
|
|
965
977
|
return [];
|
|
@@ -981,7 +993,11 @@ async function searchVisibleMemories(args: {
|
|
|
981
993
|
),
|
|
982
994
|
),
|
|
983
995
|
);
|
|
984
|
-
return rows.map(
|
|
996
|
+
return rows.map((row) => ({
|
|
997
|
+
memory: parseMemoryRow(row),
|
|
998
|
+
score: 0,
|
|
999
|
+
sourceKey: row.sourceKey,
|
|
1000
|
+
}));
|
|
985
1001
|
}
|
|
986
1002
|
|
|
987
1003
|
/** Search active visible records with exact pgvector cosine distance. */
|
|
@@ -989,6 +1005,7 @@ async function searchVisibleVectorMemories(args: {
|
|
|
989
1005
|
db: MemoryDb;
|
|
990
1006
|
embedder: MemoryEmbeddingProvider | undefined;
|
|
991
1007
|
limit: number;
|
|
1008
|
+
maxDistance?: number;
|
|
992
1009
|
nowMs: number;
|
|
993
1010
|
query: string;
|
|
994
1011
|
scopes: ResolvedMemoryScope[];
|
|
@@ -1045,15 +1062,31 @@ async function searchVisibleVectorMemories(args: {
|
|
|
1045
1062
|
) {
|
|
1046
1063
|
return [];
|
|
1047
1064
|
}
|
|
1065
|
+
if (args.maxDistance !== undefined && distanceValue > args.maxDistance) {
|
|
1066
|
+
return [];
|
|
1067
|
+
}
|
|
1048
1068
|
return [
|
|
1049
1069
|
{
|
|
1050
1070
|
memory: parseMemoryRow(row.memory),
|
|
1051
1071
|
score: 1 / (1 + Math.max(0, distanceValue)),
|
|
1072
|
+
sourceKey: row.memory.sourceKey,
|
|
1052
1073
|
},
|
|
1053
1074
|
];
|
|
1054
1075
|
});
|
|
1055
1076
|
}
|
|
1056
1077
|
|
|
1078
|
+
function sourceBoost(
|
|
1079
|
+
candidate: Pick<SearchCandidate, "sourceKey">,
|
|
1080
|
+
currentChannelPrefix: string | undefined,
|
|
1081
|
+
): number {
|
|
1082
|
+
if (!currentChannelPrefix) {
|
|
1083
|
+
return 0;
|
|
1084
|
+
}
|
|
1085
|
+
return candidate.sourceKey.startsWith(currentChannelPrefix)
|
|
1086
|
+
? SOURCE_CHANNEL_BOOST
|
|
1087
|
+
: 0;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1057
1090
|
/** Score only observation age for near-tie reranking; this is not lifecycle decay. */
|
|
1058
1091
|
function observedAgeBoost(memory: MemoryRecord, nowMs: number): number {
|
|
1059
1092
|
const ageMs = Math.max(0, nowMs - memory.observedAtMs);
|
|
@@ -1073,12 +1106,14 @@ function observedAgeBoost(memory: MemoryRecord, nowMs: number): number {
|
|
|
1073
1106
|
function mergeSearchCandidates(
|
|
1074
1107
|
candidates: SearchCandidate[],
|
|
1075
1108
|
nowMs: number,
|
|
1109
|
+
currentChannelKey?: string,
|
|
1076
1110
|
): MemoryRecord[] {
|
|
1077
1111
|
const byId = new Map<
|
|
1078
1112
|
string,
|
|
1079
1113
|
{
|
|
1080
1114
|
memory: MemoryRecord;
|
|
1081
1115
|
score: number;
|
|
1116
|
+
sourceKey: string;
|
|
1082
1117
|
}
|
|
1083
1118
|
>();
|
|
1084
1119
|
for (const candidate of candidates) {
|
|
@@ -1090,6 +1125,7 @@ function mergeSearchCandidates(
|
|
|
1090
1125
|
byId.set(candidate.memory.id, {
|
|
1091
1126
|
memory: candidate.memory,
|
|
1092
1127
|
score: candidate.score,
|
|
1128
|
+
sourceKey: candidate.sourceKey,
|
|
1093
1129
|
});
|
|
1094
1130
|
}
|
|
1095
1131
|
return [...byId.values()]
|
|
@@ -1098,6 +1134,12 @@ function mergeSearchCandidates(
|
|
|
1098
1134
|
if (Math.abs(scoreDelta) > RELEVANCE_NEAR_TIE_DELTA) {
|
|
1099
1135
|
return scoreDelta;
|
|
1100
1136
|
}
|
|
1137
|
+
const sourceDelta =
|
|
1138
|
+
sourceBoost(right, currentChannelKey) -
|
|
1139
|
+
sourceBoost(left, currentChannelKey);
|
|
1140
|
+
if (sourceDelta !== 0) {
|
|
1141
|
+
return sourceDelta;
|
|
1142
|
+
}
|
|
1101
1143
|
return (
|
|
1102
1144
|
observedAgeBoost(right.memory, nowMs) -
|
|
1103
1145
|
observedAgeBoost(left.memory, nowMs) ||
|
|
@@ -1117,6 +1159,7 @@ export function createMemoryStore(
|
|
|
1117
1159
|
const runtimeContext = memoryRuntimeContextSchema.parse(context);
|
|
1118
1160
|
const parsedOptions = memoryStoreOptionsSchema.parse({ now: options.now });
|
|
1119
1161
|
const embedder = options.embedder;
|
|
1162
|
+
const maxVectorDistance = options.maxVectorDistance;
|
|
1120
1163
|
const supersessionDecider = options.supersessionDecider;
|
|
1121
1164
|
const getNowMs = parsedOptions.now ?? Date.now;
|
|
1122
1165
|
|
|
@@ -1404,6 +1447,9 @@ export function createMemoryStore(
|
|
|
1404
1447
|
db,
|
|
1405
1448
|
embedder,
|
|
1406
1449
|
limit: limit * VECTOR_SEARCH_OVERFETCH,
|
|
1450
|
+
...(maxVectorDistance !== undefined
|
|
1451
|
+
? { maxDistance: maxVectorDistance }
|
|
1452
|
+
: {}),
|
|
1407
1453
|
nowMs,
|
|
1408
1454
|
query: input.query,
|
|
1409
1455
|
scopes,
|
|
@@ -1416,11 +1462,15 @@ export function createMemoryStore(
|
|
|
1416
1462
|
});
|
|
1417
1463
|
const terms = searchTerms(input.query);
|
|
1418
1464
|
const lexicalCandidates = candidates
|
|
1419
|
-
.map((
|
|
1465
|
+
.map((candidate) => ({
|
|
1466
|
+
...candidate,
|
|
1467
|
+
score: searchScore(candidate.memory, terms),
|
|
1468
|
+
}))
|
|
1420
1469
|
.filter((item) => item.score > 0);
|
|
1421
1470
|
return mergeSearchCandidates(
|
|
1422
1471
|
[...vectorCandidates, ...lexicalCandidates],
|
|
1423
1472
|
nowMs,
|
|
1473
|
+
sourceChannelPrefix(runtimeContext),
|
|
1424
1474
|
).slice(0, limit);
|
|
1425
1475
|
},
|
|
1426
1476
|
|