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/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
- return computeConfidence({
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
- return computeConfidence({
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
- return computeConfidence({
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 knnEpisodic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, confidenceConfig) {
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
- const confidence = computeEpisodicConfidence(row, now, confidenceConfig);
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
- ${stateFilter}
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
- ${stateFilter}
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 candidateK = limit * 3;
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 = {}) {