audrey 0.5.1 → 0.9.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/README.md +217 -134
- package/mcp-server/config.js +1 -1
- package/mcp-server/index.js +74 -4
- package/package.json +1 -1
- package/src/affect.js +64 -0
- package/src/audrey.js +86 -3
- package/src/confidence.js +10 -2
- package/src/consolidate.js +4 -2
- package/src/context.js +15 -0
- package/src/db.js +8 -2
- package/src/decay.js +12 -5
- package/src/encode.js +12 -3
- package/src/forget.js +111 -0
- package/src/index.js +5 -1
- package/src/interference.js +51 -0
- package/src/recall.js +78 -32
package/src/recall.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { computeConfidence, DEFAULT_HALF_LIVES } from './confidence.js';
|
|
1
|
+
import { computeConfidence, DEFAULT_HALF_LIVES, salienceModifier } from './confidence.js';
|
|
2
|
+
import { interferenceModifier } from './interference.js';
|
|
3
|
+
import { contextMatchRatio, contextModifier } from './context.js';
|
|
4
|
+
import { moodCongruenceModifier, affectSimilarity } from './affect.js';
|
|
2
5
|
import { daysBetween, safeJsonParse } from './utils.js';
|
|
3
6
|
|
|
4
7
|
function computeEpisodicConfidence(ep, now, confidenceConfig = {}) {
|
|
5
8
|
const ageDays = daysBetween(ep.created_at, now);
|
|
6
9
|
const halfLives = confidenceConfig.halfLives || DEFAULT_HALF_LIVES;
|
|
7
|
-
|
|
10
|
+
let confidence = computeConfidence({
|
|
8
11
|
sourceType: ep.source,
|
|
9
12
|
supportingCount: 1,
|
|
10
13
|
contradictingCount: 0,
|
|
@@ -15,6 +18,8 @@ function computeEpisodicConfidence(ep, now, confidenceConfig = {}) {
|
|
|
15
18
|
weights: confidenceConfig.weights,
|
|
16
19
|
customSourceReliability: confidenceConfig.sourceReliability,
|
|
17
20
|
});
|
|
21
|
+
confidence *= salienceModifier(ep.salience);
|
|
22
|
+
return Math.max(0, Math.min(1, confidence));
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
function computeSemanticConfidence(sem, now, confidenceConfig = {}) {
|
|
@@ -23,7 +28,7 @@ function computeSemanticConfidence(sem, now, confidenceConfig = {}) {
|
|
|
23
28
|
? daysBetween(sem.last_reinforced_at, now)
|
|
24
29
|
: ageDays;
|
|
25
30
|
const halfLives = confidenceConfig.halfLives || DEFAULT_HALF_LIVES;
|
|
26
|
-
|
|
31
|
+
let confidence = computeConfidence({
|
|
27
32
|
sourceType: 'tool-result',
|
|
28
33
|
supportingCount: sem.supporting_count || 0,
|
|
29
34
|
contradictingCount: sem.contradicting_count || 0,
|
|
@@ -34,6 +39,9 @@ function computeSemanticConfidence(sem, now, confidenceConfig = {}) {
|
|
|
34
39
|
weights: confidenceConfig.weights,
|
|
35
40
|
customSourceReliability: confidenceConfig.sourceReliability,
|
|
36
41
|
});
|
|
42
|
+
confidence *= interferenceModifier(sem.interference_count || 0, confidenceConfig.interferenceWeight);
|
|
43
|
+
confidence *= salienceModifier(sem.salience);
|
|
44
|
+
return Math.max(0, Math.min(1, confidence));
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
function computeProceduralConfidence(proc, now, confidenceConfig = {}) {
|
|
@@ -42,7 +50,7 @@ function computeProceduralConfidence(proc, now, confidenceConfig = {}) {
|
|
|
42
50
|
? daysBetween(proc.last_reinforced_at, now)
|
|
43
51
|
: ageDays;
|
|
44
52
|
const halfLives = confidenceConfig.halfLives || DEFAULT_HALF_LIVES;
|
|
45
|
-
|
|
53
|
+
let confidence = computeConfidence({
|
|
46
54
|
sourceType: 'tool-result',
|
|
47
55
|
supportingCount: proc.success_count || 0,
|
|
48
56
|
contradictingCount: proc.failure_count || 0,
|
|
@@ -53,9 +61,12 @@ function computeProceduralConfidence(proc, now, confidenceConfig = {}) {
|
|
|
53
61
|
weights: confidenceConfig.weights,
|
|
54
62
|
customSourceReliability: confidenceConfig.sourceReliability,
|
|
55
63
|
});
|
|
64
|
+
confidence *= interferenceModifier(proc.interference_count || 0, confidenceConfig.interferenceWeight);
|
|
65
|
+
confidence *= salienceModifier(proc.salience);
|
|
66
|
+
return Math.max(0, Math.min(1, confidence));
|
|
56
67
|
}
|
|
57
68
|
|
|
58
|
-
function buildEpisodicEntry(ep, confidence, score, includeProvenance) {
|
|
69
|
+
function buildEpisodicEntry(ep, confidence, score, includeProvenance, contextMatch, moodCongruence) {
|
|
59
70
|
const entry = {
|
|
60
71
|
id: ep.id,
|
|
61
72
|
content: ep.content,
|
|
@@ -65,6 +76,12 @@ function buildEpisodicEntry(ep, confidence, score, includeProvenance) {
|
|
|
65
76
|
source: ep.source,
|
|
66
77
|
createdAt: ep.created_at,
|
|
67
78
|
};
|
|
79
|
+
if (contextMatch !== undefined) {
|
|
80
|
+
entry.contextMatch = contextMatch;
|
|
81
|
+
}
|
|
82
|
+
if (moodCongruence !== undefined) {
|
|
83
|
+
entry.moodCongruence = moodCongruence;
|
|
84
|
+
}
|
|
68
85
|
if (includeProvenance) {
|
|
69
86
|
entry.provenance = {
|
|
70
87
|
source: ep.source,
|
|
@@ -121,7 +138,19 @@ function buildProceduralEntry(proc, confidence, score, includeProvenance) {
|
|
|
121
138
|
return entry;
|
|
122
139
|
}
|
|
123
140
|
|
|
124
|
-
function
|
|
141
|
+
function stateClause(includeDormant) {
|
|
142
|
+
return includeDormant
|
|
143
|
+
? "AND (v.state = 'active' OR v.state = 'context_dependent' OR v.state = 'dormant')"
|
|
144
|
+
: "AND (v.state = 'active' OR v.state = 'context_dependent')";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function matchesDateFilters(createdAt, filters) {
|
|
148
|
+
if (filters.after && createdAt <= filters.after) return false;
|
|
149
|
+
if (filters.before && createdAt >= filters.before) return false;
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function knnEpisodic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, confidenceConfig, filters = {}) {
|
|
125
154
|
const rows = db.prepare(`
|
|
126
155
|
SELECT e.*, (1.0 - v.distance) AS similarity
|
|
127
156
|
FROM vec_episodes v
|
|
@@ -133,34 +162,51 @@ function knnEpisodic(db, queryBuffer, candidateK, now, minConfidence, includePro
|
|
|
133
162
|
|
|
134
163
|
const results = [];
|
|
135
164
|
for (const row of rows) {
|
|
136
|
-
|
|
165
|
+
if (!matchesDateFilters(row.created_at, filters)) continue;
|
|
166
|
+
if (filters.tags?.length) {
|
|
167
|
+
const rowTags = safeJsonParse(row.tags, []);
|
|
168
|
+
if (!filters.tags.some(t => rowTags.includes(t))) continue;
|
|
169
|
+
}
|
|
170
|
+
if (filters.sources?.length && !filters.sources.includes(row.source)) continue;
|
|
171
|
+
let confidence = computeEpisodicConfidence(row, now, confidenceConfig);
|
|
172
|
+
|
|
173
|
+
let ctxMatch;
|
|
174
|
+
if (confidenceConfig?.retrievalContext) {
|
|
175
|
+
const encodingCtx = safeJsonParse(row.context, {});
|
|
176
|
+
ctxMatch = contextMatchRatio(encodingCtx, confidenceConfig.retrievalContext);
|
|
177
|
+
confidence *= contextModifier(encodingCtx, confidenceConfig.retrievalContext, confidenceConfig.contextWeight);
|
|
178
|
+
confidence = Math.max(0, Math.min(1, confidence));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let moodMatch;
|
|
182
|
+
if (confidenceConfig?.retrievalMood) {
|
|
183
|
+
const encodingAffect = safeJsonParse(row.affect, {});
|
|
184
|
+
moodMatch = affectSimilarity(encodingAffect, confidenceConfig.retrievalMood);
|
|
185
|
+
confidence *= moodCongruenceModifier(encodingAffect, confidenceConfig.retrievalMood, confidenceConfig.affectWeight);
|
|
186
|
+
confidence = Math.max(0, Math.min(1, confidence));
|
|
187
|
+
}
|
|
188
|
+
|
|
137
189
|
if (confidence < minConfidence) continue;
|
|
138
190
|
const score = row.similarity * confidence;
|
|
139
|
-
results.push(buildEpisodicEntry(row, confidence, score, includeProvenance));
|
|
191
|
+
results.push(buildEpisodicEntry(row, confidence, score, includeProvenance, ctxMatch, moodMatch));
|
|
140
192
|
}
|
|
141
193
|
return results;
|
|
142
194
|
}
|
|
143
195
|
|
|
144
|
-
function knnSemantic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig) {
|
|
145
|
-
let stateFilter;
|
|
146
|
-
if (includeDormant) {
|
|
147
|
-
stateFilter = "AND (v.state = 'active' OR v.state = 'context_dependent' OR v.state = 'dormant')";
|
|
148
|
-
} else {
|
|
149
|
-
stateFilter = "AND (v.state = 'active' OR v.state = 'context_dependent')";
|
|
150
|
-
}
|
|
151
|
-
|
|
196
|
+
function knnSemantic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig, filters = {}) {
|
|
152
197
|
const rows = db.prepare(`
|
|
153
198
|
SELECT s.*, (1.0 - v.distance) AS similarity
|
|
154
199
|
FROM vec_semantics v
|
|
155
200
|
JOIN semantics s ON s.id = v.id
|
|
156
201
|
WHERE v.embedding MATCH ?
|
|
157
202
|
AND k = ?
|
|
158
|
-
${
|
|
203
|
+
${stateClause(includeDormant)}
|
|
159
204
|
`).all(queryBuffer, candidateK);
|
|
160
205
|
|
|
161
206
|
const results = [];
|
|
162
207
|
const matchedIds = [];
|
|
163
208
|
for (const row of rows) {
|
|
209
|
+
if (!matchesDateFilters(row.created_at, filters)) continue;
|
|
164
210
|
const confidence = computeSemanticConfidence(row, now, confidenceConfig);
|
|
165
211
|
if (confidence < minConfidence) continue;
|
|
166
212
|
const score = row.similarity * confidence;
|
|
@@ -170,26 +216,20 @@ function knnSemantic(db, queryBuffer, candidateK, now, minConfidence, includePro
|
|
|
170
216
|
return { results, matchedIds };
|
|
171
217
|
}
|
|
172
218
|
|
|
173
|
-
function knnProcedural(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig) {
|
|
174
|
-
let stateFilter;
|
|
175
|
-
if (includeDormant) {
|
|
176
|
-
stateFilter = "AND (v.state = 'active' OR v.state = 'context_dependent' OR v.state = 'dormant')";
|
|
177
|
-
} else {
|
|
178
|
-
stateFilter = "AND (v.state = 'active' OR v.state = 'context_dependent')";
|
|
179
|
-
}
|
|
180
|
-
|
|
219
|
+
function knnProcedural(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig, filters = {}) {
|
|
181
220
|
const rows = db.prepare(`
|
|
182
221
|
SELECT p.*, (1.0 - v.distance) AS similarity
|
|
183
222
|
FROM vec_procedures v
|
|
184
223
|
JOIN procedures p ON p.id = v.id
|
|
185
224
|
WHERE v.embedding MATCH ?
|
|
186
225
|
AND k = ?
|
|
187
|
-
${
|
|
226
|
+
${stateClause(includeDormant)}
|
|
188
227
|
`).all(queryBuffer, candidateK);
|
|
189
228
|
|
|
190
229
|
const results = [];
|
|
191
230
|
const matchedIds = [];
|
|
192
231
|
for (const row of rows) {
|
|
232
|
+
if (!matchesDateFilters(row.created_at, filters)) continue;
|
|
193
233
|
const confidence = computeProceduralConfidence(row, now, confidenceConfig);
|
|
194
234
|
if (confidence < minConfidence) continue;
|
|
195
235
|
const score = row.similarity * confidence;
|
|
@@ -203,7 +243,7 @@ function knnProcedural(db, queryBuffer, candidateK, now, minConfidence, includeP
|
|
|
203
243
|
* @param {import('better-sqlite3').Database} db
|
|
204
244
|
* @param {import('./embedding.js').EmbeddingProvider} embeddingProvider
|
|
205
245
|
* @param {string} query
|
|
206
|
-
* @param {{ minConfidence?: number, types?: string[], limit?: number, includeProvenance?: boolean, includeDormant?: boolean }} [options]
|
|
246
|
+
* @param {{ minConfidence?: number, types?: string[], limit?: number, includeProvenance?: boolean, includeDormant?: boolean, tags?: string[], sources?: string[], after?: string, before?: string }} [options]
|
|
207
247
|
* @returns {AsyncGenerator<{ id: string, content: string, type: string, confidence: number, score: number, source: string, createdAt: string }>}
|
|
208
248
|
*/
|
|
209
249
|
export async function* recallStream(db, embeddingProvider, query, options = {}) {
|
|
@@ -214,24 +254,30 @@ export async function* recallStream(db, embeddingProvider, query, options = {})
|
|
|
214
254
|
includeProvenance = false,
|
|
215
255
|
includeDormant = false,
|
|
216
256
|
confidenceConfig,
|
|
257
|
+
tags,
|
|
258
|
+
sources,
|
|
259
|
+
after,
|
|
260
|
+
before,
|
|
217
261
|
} = options;
|
|
218
262
|
|
|
219
263
|
const queryVector = await embeddingProvider.embed(query);
|
|
220
264
|
const queryBuffer = embeddingProvider.vectorToBuffer(queryVector);
|
|
221
265
|
const searchTypes = types || ['episodic', 'semantic', 'procedural'];
|
|
222
266
|
const now = new Date();
|
|
223
|
-
const
|
|
267
|
+
const hasFilters = tags?.length || sources?.length || after || before;
|
|
268
|
+
const candidateK = hasFilters ? limit * 5 : limit * 3;
|
|
269
|
+
const filters = { tags, sources, after, before };
|
|
224
270
|
|
|
225
271
|
const allResults = [];
|
|
226
272
|
|
|
227
273
|
if (searchTypes.includes('episodic')) {
|
|
228
|
-
const episodic = knnEpisodic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, confidenceConfig);
|
|
274
|
+
const episodic = knnEpisodic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, confidenceConfig, filters);
|
|
229
275
|
allResults.push(...episodic);
|
|
230
276
|
}
|
|
231
277
|
|
|
232
278
|
if (searchTypes.includes('semantic')) {
|
|
233
279
|
const { results: semResults, matchedIds: semIds } =
|
|
234
|
-
knnSemantic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig);
|
|
280
|
+
knnSemantic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig, filters);
|
|
235
281
|
allResults.push(...semResults);
|
|
236
282
|
|
|
237
283
|
if (semIds.length > 0) {
|
|
@@ -247,7 +293,7 @@ export async function* recallStream(db, embeddingProvider, query, options = {})
|
|
|
247
293
|
|
|
248
294
|
if (searchTypes.includes('procedural')) {
|
|
249
295
|
const { results: procResults, matchedIds: procIds } =
|
|
250
|
-
knnProcedural(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig);
|
|
296
|
+
knnProcedural(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig, filters);
|
|
251
297
|
allResults.push(...procResults);
|
|
252
298
|
|
|
253
299
|
if (procIds.length > 0) {
|
|
@@ -272,7 +318,7 @@ export async function* recallStream(db, embeddingProvider, query, options = {})
|
|
|
272
318
|
* @param {import('better-sqlite3').Database} db
|
|
273
319
|
* @param {import('./embedding.js').EmbeddingProvider} embeddingProvider
|
|
274
320
|
* @param {string} query
|
|
275
|
-
* @param {{ minConfidence?: number, types?: string[], limit?: number, includeProvenance?: boolean, includeDormant?: boolean }} [options]
|
|
321
|
+
* @param {{ minConfidence?: number, types?: string[], limit?: number, includeProvenance?: boolean, includeDormant?: boolean, tags?: string[], sources?: string[], after?: string, before?: string }} [options]
|
|
276
322
|
* @returns {Promise<Array<{ id: string, content: string, type: string, confidence: number, score: number, source: string, createdAt: string }>>}
|
|
277
323
|
*/
|
|
278
324
|
export async function recall(db, embeddingProvider, query, options = {}) {
|