agentdb 1.5.8 → 1.6.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 +11 -11
- package/dist/agentdb.min.js +4 -4
- package/dist/cli/agentdb-cli.d.ts +29 -0
- package/dist/cli/agentdb-cli.d.ts.map +1 -1
- package/dist/cli/agentdb-cli.js +1009 -34
- package/dist/cli/agentdb-cli.js.map +1 -1
- package/dist/controllers/ContextSynthesizer.d.ts +65 -0
- package/dist/controllers/ContextSynthesizer.d.ts.map +1 -0
- package/dist/controllers/ContextSynthesizer.js +208 -0
- package/dist/controllers/ContextSynthesizer.js.map +1 -0
- package/dist/controllers/MMRDiversityRanker.d.ts +50 -0
- package/dist/controllers/MMRDiversityRanker.d.ts.map +1 -0
- package/dist/controllers/MMRDiversityRanker.js +130 -0
- package/dist/controllers/MMRDiversityRanker.js.map +1 -0
- package/dist/controllers/MetadataFilter.d.ts +70 -0
- package/dist/controllers/MetadataFilter.d.ts.map +1 -0
- package/dist/controllers/MetadataFilter.js +243 -0
- package/dist/controllers/MetadataFilter.js.map +1 -0
- package/dist/controllers/QUICClient.d.ts +109 -0
- package/dist/controllers/QUICClient.d.ts.map +1 -0
- package/dist/controllers/QUICClient.js +299 -0
- package/dist/controllers/QUICClient.js.map +1 -0
- package/dist/controllers/QUICServer.d.ts +121 -0
- package/dist/controllers/QUICServer.d.ts.map +1 -0
- package/dist/controllers/QUICServer.js +383 -0
- package/dist/controllers/QUICServer.js.map +1 -0
- package/dist/controllers/SyncCoordinator.d.ts +120 -0
- package/dist/controllers/SyncCoordinator.d.ts.map +1 -0
- package/dist/controllers/SyncCoordinator.js +441 -0
- package/dist/controllers/SyncCoordinator.js.map +1 -0
- package/dist/controllers/WASMVectorSearch.d.ts.map +1 -1
- package/dist/controllers/WASMVectorSearch.js +10 -2
- package/dist/controllers/WASMVectorSearch.js.map +1 -1
- package/dist/controllers/index.d.ts +12 -0
- package/dist/controllers/index.d.ts.map +1 -1
- package/dist/controllers/index.js +6 -0
- package/dist/controllers/index.js.map +1 -1
- package/dist/db-fallback.d.ts.map +1 -1
- package/dist/db-fallback.js +14 -11
- package/dist/db-fallback.js.map +1 -1
- package/dist/examples/quic-sync-example.d.ts +9 -0
- package/dist/examples/quic-sync-example.d.ts.map +1 -0
- package/dist/examples/quic-sync-example.js +169 -0
- package/dist/examples/quic-sync-example.js.map +1 -0
- package/dist/types/quic.d.ts +518 -0
- package/dist/types/quic.d.ts.map +1 -0
- package/dist/types/quic.js +272 -0
- package/dist/types/quic.js.map +1 -0
- package/package.json +9 -3
- package/src/browser-entry.js +41 -6
- package/src/cli/agentdb-cli.ts +1114 -33
- package/src/controllers/ContextSynthesizer.ts +285 -0
- package/src/controllers/MMRDiversityRanker.ts +187 -0
- package/src/controllers/MetadataFilter.ts +280 -0
- package/src/controllers/QUICClient.ts +413 -0
- package/src/controllers/QUICServer.ts +498 -0
- package/src/controllers/SyncCoordinator.ts +597 -0
- package/src/controllers/WASMVectorSearch.ts +11 -2
- package/src/controllers/index.ts +12 -0
- package/src/db-fallback.ts +13 -10
- package/src/examples/quic-sync-example.ts +198 -0
- package/src/types/quic.ts +772 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Synthesis - Generate coherent narratives from multiple memories
|
|
3
|
+
*
|
|
4
|
+
* Takes a collection of retrieved episodes/patterns and synthesizes
|
|
5
|
+
* a coherent context summary with extracted patterns, success rates,
|
|
6
|
+
* and actionable insights.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface MemoryPattern {
|
|
10
|
+
task: string;
|
|
11
|
+
reward: number;
|
|
12
|
+
success: boolean;
|
|
13
|
+
critique?: string;
|
|
14
|
+
input?: string;
|
|
15
|
+
output?: string;
|
|
16
|
+
similarity?: number;
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SynthesizedContext {
|
|
21
|
+
summary: string;
|
|
22
|
+
patterns: string[];
|
|
23
|
+
successRate: number;
|
|
24
|
+
averageReward: number;
|
|
25
|
+
recommendations: string[];
|
|
26
|
+
keyInsights: string[];
|
|
27
|
+
totalMemories: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class ContextSynthesizer {
|
|
31
|
+
/**
|
|
32
|
+
* Synthesize context from multiple memories
|
|
33
|
+
*
|
|
34
|
+
* @param memories - Retrieved episodes/patterns
|
|
35
|
+
* @param options - Synthesis options
|
|
36
|
+
* @returns Synthesized context with insights
|
|
37
|
+
*/
|
|
38
|
+
static synthesize(
|
|
39
|
+
memories: MemoryPattern[],
|
|
40
|
+
options: {
|
|
41
|
+
minPatternFrequency?: number;
|
|
42
|
+
includeRecommendations?: boolean;
|
|
43
|
+
maxSummaryLength?: number;
|
|
44
|
+
} = {}
|
|
45
|
+
): SynthesizedContext {
|
|
46
|
+
const minPatternFrequency = options.minPatternFrequency ?? 2;
|
|
47
|
+
const includeRecommendations = options.includeRecommendations ?? true;
|
|
48
|
+
|
|
49
|
+
if (memories.length === 0) {
|
|
50
|
+
return {
|
|
51
|
+
summary: 'No relevant memories found.',
|
|
52
|
+
patterns: [],
|
|
53
|
+
successRate: 0,
|
|
54
|
+
averageReward: 0,
|
|
55
|
+
recommendations: [],
|
|
56
|
+
keyInsights: [],
|
|
57
|
+
totalMemories: 0,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Calculate statistics
|
|
62
|
+
const successCount = memories.filter(m => m.success).length;
|
|
63
|
+
const successRate = successCount / memories.length;
|
|
64
|
+
const averageReward = memories.reduce((sum, m) => sum + (m.reward || 0), 0) / memories.length;
|
|
65
|
+
|
|
66
|
+
// Extract common patterns from critiques
|
|
67
|
+
const patterns = this.extractPatterns(memories, minPatternFrequency);
|
|
68
|
+
|
|
69
|
+
// Generate key insights
|
|
70
|
+
const keyInsights = this.generateKeyInsights(memories, successRate, averageReward);
|
|
71
|
+
|
|
72
|
+
// Generate recommendations
|
|
73
|
+
const recommendations = includeRecommendations
|
|
74
|
+
? this.generateRecommendations(memories, patterns, successRate)
|
|
75
|
+
: [];
|
|
76
|
+
|
|
77
|
+
// Generate summary narrative
|
|
78
|
+
const summary = this.generateSummary(memories, patterns, successRate, averageReward);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
summary,
|
|
82
|
+
patterns,
|
|
83
|
+
successRate,
|
|
84
|
+
averageReward,
|
|
85
|
+
recommendations,
|
|
86
|
+
keyInsights,
|
|
87
|
+
totalMemories: memories.length,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Extract common patterns from memory critiques
|
|
93
|
+
*/
|
|
94
|
+
private static extractPatterns(memories: MemoryPattern[], minFrequency: number): string[] {
|
|
95
|
+
const phraseCount = new Map<string, number>();
|
|
96
|
+
|
|
97
|
+
// Extract meaningful phrases from critiques
|
|
98
|
+
for (const memory of memories) {
|
|
99
|
+
if (memory.critique) {
|
|
100
|
+
const phrases = this.extractPhrases(memory.critique);
|
|
101
|
+
for (const phrase of phrases) {
|
|
102
|
+
phraseCount.set(phrase, (phraseCount.get(phrase) || 0) + 1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Filter by minimum frequency
|
|
108
|
+
const patterns: string[] = [];
|
|
109
|
+
for (const [phrase, count] of phraseCount.entries()) {
|
|
110
|
+
if (count >= minFrequency) {
|
|
111
|
+
patterns.push(`${phrase} (${count}/${memories.length} times)`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return patterns.slice(0, 10); // Top 10 patterns
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Extract meaningful phrases from text
|
|
120
|
+
*/
|
|
121
|
+
private static extractPhrases(text: string): string[] {
|
|
122
|
+
// Simple phrase extraction (can be enhanced with NLP)
|
|
123
|
+
const sentences = text.split(/[.!?]/).filter(s => s.trim().length > 0);
|
|
124
|
+
const phrases: string[] = [];
|
|
125
|
+
|
|
126
|
+
for (const sentence of sentences) {
|
|
127
|
+
const trimmed = sentence.trim();
|
|
128
|
+
// Extract phrases with key action words
|
|
129
|
+
if (
|
|
130
|
+
trimmed.match(/\b(use|implement|fix|improve|add|create|design|test)\b/i) &&
|
|
131
|
+
trimmed.length > 10 &&
|
|
132
|
+
trimmed.length < 100
|
|
133
|
+
) {
|
|
134
|
+
phrases.push(trimmed.toLowerCase());
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return phrases;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Generate key insights from memories
|
|
143
|
+
*/
|
|
144
|
+
private static generateKeyInsights(
|
|
145
|
+
memories: MemoryPattern[],
|
|
146
|
+
successRate: number,
|
|
147
|
+
averageReward: number
|
|
148
|
+
): string[] {
|
|
149
|
+
const insights: string[] = [];
|
|
150
|
+
|
|
151
|
+
// Success rate insight
|
|
152
|
+
if (successRate >= 0.8) {
|
|
153
|
+
insights.push(`High success rate (${(successRate * 100).toFixed(0)}%) indicates strong pattern match`);
|
|
154
|
+
} else if (successRate < 0.5) {
|
|
155
|
+
insights.push(`Low success rate (${(successRate * 100).toFixed(0)}%) suggests exploring alternative approaches`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Reward insight
|
|
159
|
+
if (averageReward >= 0.8) {
|
|
160
|
+
insights.push(`High average reward (${averageReward.toFixed(2)}) shows effective past solutions`);
|
|
161
|
+
} else if (averageReward < 0.6) {
|
|
162
|
+
insights.push(`Moderate reward (${averageReward.toFixed(2)}) indicates room for improvement`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Identify high-performing memories
|
|
166
|
+
const topMemories = memories
|
|
167
|
+
.filter(m => m.success && (m.reward || 0) >= 0.9)
|
|
168
|
+
.slice(0, 3);
|
|
169
|
+
|
|
170
|
+
if (topMemories.length > 0) {
|
|
171
|
+
insights.push(`${topMemories.length} exemplary solution(s) found with reward ≥0.9`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Task diversity
|
|
175
|
+
const uniqueTasks = new Set(memories.map(m => m.task)).size;
|
|
176
|
+
if (uniqueTasks > 1) {
|
|
177
|
+
insights.push(`${uniqueTasks} different task types provide diverse perspectives`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return insights;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Generate actionable recommendations
|
|
185
|
+
*/
|
|
186
|
+
private static generateRecommendations(
|
|
187
|
+
memories: MemoryPattern[],
|
|
188
|
+
patterns: string[],
|
|
189
|
+
successRate: number
|
|
190
|
+
): string[] {
|
|
191
|
+
const recommendations: string[] = [];
|
|
192
|
+
|
|
193
|
+
// Extract successful strategies
|
|
194
|
+
const successfulMemories = memories.filter(m => m.success && (m.reward || 0) >= 0.8);
|
|
195
|
+
|
|
196
|
+
if (successfulMemories.length > 0) {
|
|
197
|
+
recommendations.push('Apply strategies from high-reward solutions');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Pattern-based recommendations
|
|
201
|
+
if (patterns.length > 0) {
|
|
202
|
+
recommendations.push(`Follow common patterns: ${patterns[0].split(' (')[0]}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Success rate recommendations
|
|
206
|
+
if (successRate >= 0.7) {
|
|
207
|
+
recommendations.push('Previous approaches were effective - follow similar methodology');
|
|
208
|
+
} else {
|
|
209
|
+
recommendations.push('Consider alternative approaches given mixed past results');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Add general recommendations
|
|
213
|
+
if (memories.length >= 5) {
|
|
214
|
+
recommendations.push('Sufficient historical data available for confident decision-making');
|
|
215
|
+
} else {
|
|
216
|
+
recommendations.push('Limited data - proceed with caution and validate assumptions');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return recommendations.slice(0, 5);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Generate narrative summary
|
|
224
|
+
*/
|
|
225
|
+
private static generateSummary(
|
|
226
|
+
memories: MemoryPattern[],
|
|
227
|
+
patterns: string[],
|
|
228
|
+
successRate: number,
|
|
229
|
+
averageReward: number
|
|
230
|
+
): string {
|
|
231
|
+
const parts: string[] = [];
|
|
232
|
+
|
|
233
|
+
// Opening
|
|
234
|
+
parts.push(`Based on ${memories.length} similar past ${memories.length === 1 ? 'experience' : 'experiences'}`);
|
|
235
|
+
|
|
236
|
+
// Success rate
|
|
237
|
+
const successPercent = (successRate * 100).toFixed(0);
|
|
238
|
+
if (successRate >= 0.7) {
|
|
239
|
+
parts.push(`with a high success rate of ${successPercent}%`);
|
|
240
|
+
} else {
|
|
241
|
+
parts.push(`with a ${successPercent}% success rate`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Average reward
|
|
245
|
+
parts.push(`and average reward of ${averageReward.toFixed(2)}`);
|
|
246
|
+
|
|
247
|
+
// Key patterns
|
|
248
|
+
if (patterns.length > 0) {
|
|
249
|
+
parts.push('. Common effective approaches include:');
|
|
250
|
+
const topPatterns = patterns.slice(0, 3).map(p => p.split(' (')[0]);
|
|
251
|
+
parts.push(topPatterns.map((p, i) => `${i + 1}) ${p}`).join(', '));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// High performers
|
|
255
|
+
const topMemories = memories.filter(m => m.success && (m.reward || 0) >= 0.9);
|
|
256
|
+
if (topMemories.length > 0) {
|
|
257
|
+
parts.push(`. ${topMemories.length} exemplary solution(s) achieved reward ≥0.9`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
parts.push('.');
|
|
261
|
+
|
|
262
|
+
return parts.join(' ');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Extract actionable steps from successful memories
|
|
267
|
+
*/
|
|
268
|
+
static extractActionableSteps(memories: MemoryPattern[]): string[] {
|
|
269
|
+
const steps: string[] = [];
|
|
270
|
+
const successfulMemories = memories.filter(m => m.success && (m.reward || 0) >= 0.8);
|
|
271
|
+
|
|
272
|
+
for (const memory of successfulMemories) {
|
|
273
|
+
if (memory.critique) {
|
|
274
|
+
// Extract step-like phrases
|
|
275
|
+
const matches = memory.critique.match(/\b\d+\.\s+([^.!?]+)/g);
|
|
276
|
+
if (matches) {
|
|
277
|
+
steps.push(...matches.map(m => m.trim()));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Remove duplicates and return top 5
|
|
283
|
+
return Array.from(new Set(steps)).slice(0, 5);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MMR (Maximal Marginal Relevance) Diversity Ranking
|
|
3
|
+
*
|
|
4
|
+
* Implements MMR algorithm to select diverse results that balance
|
|
5
|
+
* relevance to query with diversity from already-selected results.
|
|
6
|
+
*
|
|
7
|
+
* Formula: MMR = argmax [λ × Sim(Di, Q) - (1-λ) × max Sim(Di, Dj)]
|
|
8
|
+
* Di∈R\S Dj∈S
|
|
9
|
+
*
|
|
10
|
+
* Where:
|
|
11
|
+
* - Di = candidate document
|
|
12
|
+
* - Q = query
|
|
13
|
+
* - S = already selected documents
|
|
14
|
+
* - λ = balance parameter (0 = max diversity, 1 = max relevance)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface MMROptions {
|
|
18
|
+
lambda?: number; // Balance between relevance and diversity (default: 0.5)
|
|
19
|
+
k?: number; // Number of results to return (default: 10)
|
|
20
|
+
metric?: 'cosine' | 'euclidean' | 'dot'; // Similarity metric
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface MMRCandidate {
|
|
24
|
+
id: number;
|
|
25
|
+
embedding: number[];
|
|
26
|
+
similarity: number; // Similarity to query
|
|
27
|
+
[key: string]: any; // Additional data
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class MMRDiversityRanker {
|
|
31
|
+
/**
|
|
32
|
+
* Select diverse results using MMR algorithm
|
|
33
|
+
*
|
|
34
|
+
* @param candidates - All candidate results with embeddings
|
|
35
|
+
* @param queryEmbedding - Query vector
|
|
36
|
+
* @param options - MMR configuration
|
|
37
|
+
* @returns Diverse subset of candidates
|
|
38
|
+
*/
|
|
39
|
+
static selectDiverse(
|
|
40
|
+
candidates: MMRCandidate[],
|
|
41
|
+
queryEmbedding: number[],
|
|
42
|
+
options: MMROptions = {}
|
|
43
|
+
): MMRCandidate[] {
|
|
44
|
+
const lambda = options.lambda ?? 0.5;
|
|
45
|
+
const k = options.k ?? 10;
|
|
46
|
+
const metric = options.metric ?? 'cosine';
|
|
47
|
+
|
|
48
|
+
if (candidates.length === 0) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (candidates.length <= k) {
|
|
53
|
+
return candidates;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Calculate initial similarities to query
|
|
57
|
+
const candidatesWithSim = candidates.map(c => ({
|
|
58
|
+
...c,
|
|
59
|
+
similarity: c.similarity ?? this.calculateSimilarity(
|
|
60
|
+
queryEmbedding,
|
|
61
|
+
c.embedding,
|
|
62
|
+
metric
|
|
63
|
+
),
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
const selected: MMRCandidate[] = [];
|
|
67
|
+
const remaining = [...candidatesWithSim];
|
|
68
|
+
|
|
69
|
+
// Select first item (highest relevance)
|
|
70
|
+
remaining.sort((a, b) => b.similarity - a.similarity);
|
|
71
|
+
selected.push(remaining.shift()!);
|
|
72
|
+
|
|
73
|
+
// Iteratively select items with highest MMR score
|
|
74
|
+
while (selected.length < k && remaining.length > 0) {
|
|
75
|
+
let maxMMR = -Infinity;
|
|
76
|
+
let maxIdx = 0;
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
79
|
+
const candidate = remaining[i];
|
|
80
|
+
|
|
81
|
+
// Calculate max similarity to already-selected items
|
|
82
|
+
let maxSimToSelected = -Infinity;
|
|
83
|
+
for (const selectedItem of selected) {
|
|
84
|
+
const sim = this.calculateSimilarity(
|
|
85
|
+
candidate.embedding,
|
|
86
|
+
selectedItem.embedding,
|
|
87
|
+
metric
|
|
88
|
+
);
|
|
89
|
+
maxSimToSelected = Math.max(maxSimToSelected, sim);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Calculate MMR score
|
|
93
|
+
const mmrScore = lambda * candidate.similarity - (1 - lambda) * maxSimToSelected;
|
|
94
|
+
|
|
95
|
+
if (mmrScore > maxMMR) {
|
|
96
|
+
maxMMR = mmrScore;
|
|
97
|
+
maxIdx = i;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Add item with highest MMR score
|
|
102
|
+
selected.push(remaining.splice(maxIdx, 1)[0]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return selected;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Calculate similarity between two vectors
|
|
110
|
+
*/
|
|
111
|
+
private static calculateSimilarity(
|
|
112
|
+
vec1: number[],
|
|
113
|
+
vec2: number[],
|
|
114
|
+
metric: 'cosine' | 'euclidean' | 'dot'
|
|
115
|
+
): number {
|
|
116
|
+
if (vec1.length !== vec2.length) {
|
|
117
|
+
throw new Error(`Vector dimension mismatch: ${vec1.length} vs ${vec2.length}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
switch (metric) {
|
|
121
|
+
case 'cosine': {
|
|
122
|
+
let dot = 0, mag1 = 0, mag2 = 0;
|
|
123
|
+
for (let i = 0; i < vec1.length; i++) {
|
|
124
|
+
dot += vec1[i] * vec2[i];
|
|
125
|
+
mag1 += vec1[i] * vec1[i];
|
|
126
|
+
mag2 += vec2[i] * vec2[i];
|
|
127
|
+
}
|
|
128
|
+
return dot / (Math.sqrt(mag1) * Math.sqrt(mag2));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
case 'euclidean': {
|
|
132
|
+
let sum = 0;
|
|
133
|
+
for (let i = 0; i < vec1.length; i++) {
|
|
134
|
+
const diff = vec1[i] - vec2[i];
|
|
135
|
+
sum += diff * diff;
|
|
136
|
+
}
|
|
137
|
+
return 1 / (1 + Math.sqrt(sum)); // Normalized to 0-1
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
case 'dot': {
|
|
141
|
+
let dot = 0;
|
|
142
|
+
for (let i = 0; i < vec1.length; i++) {
|
|
143
|
+
dot += vec1[i] * vec2[i];
|
|
144
|
+
}
|
|
145
|
+
return dot;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
default:
|
|
149
|
+
throw new Error(`Unknown metric: ${metric}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Calculate diversity score for a set of results
|
|
155
|
+
*
|
|
156
|
+
* @param results - Results to analyze
|
|
157
|
+
* @param metric - Similarity metric
|
|
158
|
+
* @returns Average pairwise distance (higher = more diverse)
|
|
159
|
+
*/
|
|
160
|
+
static calculateDiversityScore(
|
|
161
|
+
results: MMRCandidate[],
|
|
162
|
+
metric: 'cosine' | 'euclidean' | 'dot' = 'cosine'
|
|
163
|
+
): number {
|
|
164
|
+
if (results.length < 2) {
|
|
165
|
+
return 1.0; // Single result is maximally diverse
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let totalDistance = 0;
|
|
169
|
+
let comparisons = 0;
|
|
170
|
+
|
|
171
|
+
for (let i = 0; i < results.length; i++) {
|
|
172
|
+
for (let j = i + 1; j < results.length; j++) {
|
|
173
|
+
const similarity = this.calculateSimilarity(
|
|
174
|
+
results[i].embedding,
|
|
175
|
+
results[j].embedding,
|
|
176
|
+
metric
|
|
177
|
+
);
|
|
178
|
+
// Convert similarity to distance
|
|
179
|
+
const distance = metric === 'cosine' ? 1 - similarity : similarity;
|
|
180
|
+
totalDistance += distance;
|
|
181
|
+
comparisons++;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return comparisons > 0 ? totalDistance / comparisons : 0;
|
|
186
|
+
}
|
|
187
|
+
}
|