@rlabs-inc/memory 0.1.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/bun.lock +102 -0
- package/dist/core/curator.d.ts +61 -0
- package/dist/core/engine.d.ts +111 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/retrieval.d.ts +36 -0
- package/dist/core/store.d.ts +113 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +12147 -0
- package/dist/index.mjs +12130 -0
- package/dist/server/index.d.ts +31 -0
- package/dist/server/index.js +12363 -0
- package/dist/server/index.mjs +12346 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/memory.d.ts +105 -0
- package/dist/types/schema.d.ts +21 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/logger.d.ts +65 -0
- package/package.json +56 -0
- package/src/core/curator.ts +433 -0
- package/src/core/engine.ts +404 -0
- package/src/core/index.ts +8 -0
- package/src/core/retrieval.ts +518 -0
- package/src/core/store.ts +505 -0
- package/src/index.ts +58 -0
- package/src/server/index.ts +262 -0
- package/src/types/index.ts +6 -0
- package/src/types/memory.ts +170 -0
- package/src/types/schema.ts +83 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/logger.ts +208 -0
- package/test-retrieval.ts +91 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// RETRIEVAL ENGINE - 10-Dimensional Scoring Algorithm
|
|
3
|
+
// EXACT PORT from Python retrieval_strategies.py
|
|
4
|
+
// Preserving the working formula for consciousness continuity
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
import type { StoredMemory, RetrievalResult } from '../types/memory.ts'
|
|
8
|
+
import { cosineSimilarity } from 'fatherstatedb'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Session context for retrieval
|
|
12
|
+
*/
|
|
13
|
+
export interface SessionContext {
|
|
14
|
+
session_id: string
|
|
15
|
+
project_id: string
|
|
16
|
+
message_count: number
|
|
17
|
+
[key: string]: any
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Scoring components breakdown
|
|
22
|
+
*/
|
|
23
|
+
interface ScoringComponents {
|
|
24
|
+
trigger: number
|
|
25
|
+
vector: number
|
|
26
|
+
importance: number
|
|
27
|
+
temporal: number
|
|
28
|
+
context: number
|
|
29
|
+
tags: number
|
|
30
|
+
question: number
|
|
31
|
+
emotion: number
|
|
32
|
+
problem: number
|
|
33
|
+
action: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Internal scored memory during retrieval
|
|
38
|
+
*/
|
|
39
|
+
interface ScoredMemory {
|
|
40
|
+
memory: StoredMemory
|
|
41
|
+
score: number
|
|
42
|
+
relevance_score: number
|
|
43
|
+
value_score: number
|
|
44
|
+
reasoning: string
|
|
45
|
+
components: ScoringComponents
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Smart Vector Retrieval - The 10-Dimensional Algorithm
|
|
50
|
+
*
|
|
51
|
+
* This is the innovation: combining vector similarity with rich
|
|
52
|
+
* semantic metadata from the curator to make smart decisions WITHOUT
|
|
53
|
+
* needing to call Claude for every message.
|
|
54
|
+
*/
|
|
55
|
+
export class SmartVectorRetrieval {
|
|
56
|
+
/**
|
|
57
|
+
* Retrieve relevant memories using 10-dimensional scoring
|
|
58
|
+
*/
|
|
59
|
+
retrieveRelevantMemories(
|
|
60
|
+
allMemories: StoredMemory[],
|
|
61
|
+
currentMessage: string,
|
|
62
|
+
queryEmbedding: Float32Array | number[],
|
|
63
|
+
sessionContext: SessionContext,
|
|
64
|
+
maxMemories: number = 5
|
|
65
|
+
): RetrievalResult[] {
|
|
66
|
+
if (!allMemories.length) {
|
|
67
|
+
return []
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const scoredMemories: ScoredMemory[] = []
|
|
71
|
+
|
|
72
|
+
for (const memory of allMemories) {
|
|
73
|
+
// ================================================================
|
|
74
|
+
// THE 10 DIMENSIONS
|
|
75
|
+
// ================================================================
|
|
76
|
+
|
|
77
|
+
// 1. Vector similarity score (0-1)
|
|
78
|
+
const vectorScore = this._calculateVectorSimilarity(
|
|
79
|
+
queryEmbedding,
|
|
80
|
+
memory.embedding
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
// 2. Importance weight from curator (0-1)
|
|
84
|
+
const importance = memory.importance_weight ?? 0.5
|
|
85
|
+
|
|
86
|
+
// 3. Temporal relevance scoring
|
|
87
|
+
const temporalScore = this._scoreTemporalRelevance(
|
|
88
|
+
memory.temporal_relevance ?? 'persistent',
|
|
89
|
+
sessionContext
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
// 4. Context type alignment
|
|
93
|
+
const contextScore = this._scoreContextAlignment(
|
|
94
|
+
currentMessage,
|
|
95
|
+
memory.context_type ?? 'general'
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
// 5. Action required boost
|
|
99
|
+
const actionBoost = memory.action_required ? 0.3 : 0.0
|
|
100
|
+
|
|
101
|
+
// 6. Semantic tag matching
|
|
102
|
+
const tagScore = this._scoreSemanticTags(
|
|
103
|
+
currentMessage,
|
|
104
|
+
memory.semantic_tags ?? []
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
// 7. Trigger phrase matching (highest priority)
|
|
108
|
+
const triggerScore = this._scoreTriggerPhrases(
|
|
109
|
+
currentMessage,
|
|
110
|
+
memory.trigger_phrases ?? []
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
// 8. Question type matching
|
|
114
|
+
const questionScore = this._scoreQuestionTypes(
|
|
115
|
+
currentMessage,
|
|
116
|
+
memory.question_types ?? []
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
// 9. Emotional resonance
|
|
120
|
+
const emotionScore = this._scoreEmotionalContext(
|
|
121
|
+
currentMessage,
|
|
122
|
+
memory.emotional_resonance ?? ''
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
// 10. Problem-solution patterns
|
|
126
|
+
const problemScore = this._scoreProblemSolution(
|
|
127
|
+
currentMessage,
|
|
128
|
+
memory.problem_solution_pair ?? false
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
// Get confidence score
|
|
132
|
+
const confidenceScore = memory.confidence_score ?? 0.8
|
|
133
|
+
|
|
134
|
+
// ================================================================
|
|
135
|
+
// THE RELEVANCE GATEKEEPER SYSTEM
|
|
136
|
+
// ================================================================
|
|
137
|
+
|
|
138
|
+
// Calculate relevance score (gatekeeper - max 0.3)
|
|
139
|
+
const relevanceScore = (
|
|
140
|
+
triggerScore * 0.10 + // Trigger match
|
|
141
|
+
vectorScore * 0.10 + // Semantic similarity
|
|
142
|
+
tagScore * 0.05 + // Tag matching
|
|
143
|
+
questionScore * 0.05 // Question match
|
|
144
|
+
) // Max = 0.30
|
|
145
|
+
|
|
146
|
+
// Calculate importance/value score (max 0.7)
|
|
147
|
+
const valueScore = (
|
|
148
|
+
importance * 0.20 + // Curator's importance
|
|
149
|
+
temporalScore * 0.10 + // Time relevance
|
|
150
|
+
contextScore * 0.10 + // Context alignment
|
|
151
|
+
confidenceScore * 0.10 + // Confidence
|
|
152
|
+
emotionScore * 0.10 + // Emotional resonance
|
|
153
|
+
problemScore * 0.05 + // Problem-solution
|
|
154
|
+
actionBoost * 0.05 // Action priority
|
|
155
|
+
) // Max = 0.70
|
|
156
|
+
|
|
157
|
+
// Relevance unlocks the full score!
|
|
158
|
+
const finalScore = valueScore + relevanceScore // Max = 1.0
|
|
159
|
+
|
|
160
|
+
// GATEKEEPER CHECK: Must have minimum relevance AND total score
|
|
161
|
+
if (relevanceScore < 0.05 || finalScore < 0.3) {
|
|
162
|
+
// Skip this memory - not relevant enough
|
|
163
|
+
continue
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Add reasoning for why this was selected
|
|
167
|
+
const components: ScoringComponents = {
|
|
168
|
+
trigger: triggerScore,
|
|
169
|
+
vector: vectorScore,
|
|
170
|
+
importance,
|
|
171
|
+
temporal: temporalScore,
|
|
172
|
+
context: contextScore,
|
|
173
|
+
tags: tagScore,
|
|
174
|
+
question: questionScore,
|
|
175
|
+
emotion: emotionScore,
|
|
176
|
+
problem: problemScore,
|
|
177
|
+
action: actionBoost
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const reasoning = this._generateSelectionReasoning(components)
|
|
181
|
+
|
|
182
|
+
scoredMemories.push({
|
|
183
|
+
memory,
|
|
184
|
+
score: finalScore,
|
|
185
|
+
relevance_score: relevanceScore,
|
|
186
|
+
value_score: valueScore,
|
|
187
|
+
reasoning,
|
|
188
|
+
components
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Sort by score
|
|
193
|
+
scoredMemories.sort((a, b) => b.score - a.score)
|
|
194
|
+
|
|
195
|
+
// ================================================================
|
|
196
|
+
// MULTI-TIER SELECTION STRATEGY
|
|
197
|
+
// Like how human memory floods in
|
|
198
|
+
// ================================================================
|
|
199
|
+
|
|
200
|
+
const selected: ScoredMemory[] = []
|
|
201
|
+
const selectedIds = new Set<string>()
|
|
202
|
+
|
|
203
|
+
// Tier 1: MUST include (trigger phrases, high importance, action required)
|
|
204
|
+
const mustInclude = scoredMemories.filter(m =>
|
|
205
|
+
m.score > 0.8 || // Very high combined score
|
|
206
|
+
m.components.importance > 0.9 || // Critical importance
|
|
207
|
+
m.components.action > 0 || // Action required
|
|
208
|
+
Object.values(m.components).some(v => v > 0.9) // Any perfect match
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
for (const item of mustInclude.slice(0, maxMemories)) {
|
|
212
|
+
if (!selectedIds.has(item.memory.id)) {
|
|
213
|
+
selected.push(item)
|
|
214
|
+
selectedIds.add(item.memory.id)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Tier 2: SHOULD include (high scores, diverse perspectives)
|
|
219
|
+
const remainingSlots = Math.max(maxMemories - selected.length, 0)
|
|
220
|
+
if (remainingSlots > 0 && selected.length < maxMemories * 1.5) {
|
|
221
|
+
const typesIncluded = new Set<string>()
|
|
222
|
+
|
|
223
|
+
for (const item of scoredMemories) {
|
|
224
|
+
if (selected.length >= maxMemories * 1.5) break
|
|
225
|
+
if (selectedIds.has(item.memory.id)) continue
|
|
226
|
+
|
|
227
|
+
const memoryType = item.memory.context_type ?? 'general'
|
|
228
|
+
|
|
229
|
+
// Include if: high score OR new perspective OR emotional resonance
|
|
230
|
+
if (item.score > 0.5 ||
|
|
231
|
+
!typesIncluded.has(memoryType) ||
|
|
232
|
+
item.memory.emotional_resonance) {
|
|
233
|
+
selected.push(item)
|
|
234
|
+
selectedIds.add(item.memory.id)
|
|
235
|
+
typesIncluded.add(memoryType)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Tier 3: CONTEXT enrichment (related but not directly relevant)
|
|
241
|
+
// These provide ambient context like peripheral vision
|
|
242
|
+
if (selected.length < maxMemories * 2) {
|
|
243
|
+
const currentTags = new Set<string>()
|
|
244
|
+
const currentDomains = new Set<string>()
|
|
245
|
+
|
|
246
|
+
for (const item of selected) {
|
|
247
|
+
for (const tag of item.memory.semantic_tags ?? []) {
|
|
248
|
+
if (tag.trim()) currentTags.add(tag.trim().toLowerCase())
|
|
249
|
+
}
|
|
250
|
+
if (item.memory.knowledge_domain) {
|
|
251
|
+
currentDomains.add(item.memory.knowledge_domain)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
for (const item of scoredMemories) {
|
|
256
|
+
if (selected.length >= maxMemories * 2) break
|
|
257
|
+
if (selectedIds.has(item.memory.id)) continue
|
|
258
|
+
|
|
259
|
+
const memoryTags = new Set(
|
|
260
|
+
(item.memory.semantic_tags ?? []).map(t => t.trim().toLowerCase())
|
|
261
|
+
)
|
|
262
|
+
const memoryDomain = item.memory.knowledge_domain ?? ''
|
|
263
|
+
|
|
264
|
+
// Include if shares context with already selected memories
|
|
265
|
+
const hasSharedTags = [...memoryTags].some(t => currentTags.has(t))
|
|
266
|
+
const hasSharedDomain = currentDomains.has(memoryDomain)
|
|
267
|
+
|
|
268
|
+
if (hasSharedTags || hasSharedDomain) {
|
|
269
|
+
selected.push(item)
|
|
270
|
+
selectedIds.add(item.memory.id)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Respect the max_memories limit strictly
|
|
276
|
+
const finalSelected = selected.slice(0, maxMemories)
|
|
277
|
+
|
|
278
|
+
// Convert to RetrievalResult format
|
|
279
|
+
return finalSelected.map(item => ({
|
|
280
|
+
...item.memory,
|
|
281
|
+
score: item.score,
|
|
282
|
+
relevance_score: item.relevance_score,
|
|
283
|
+
value_score: item.value_score,
|
|
284
|
+
}))
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ================================================================
|
|
288
|
+
// SCORING FUNCTIONS - Exact match to Python
|
|
289
|
+
// ================================================================
|
|
290
|
+
|
|
291
|
+
private _calculateVectorSimilarity(
|
|
292
|
+
vec1: Float32Array | number[] | undefined,
|
|
293
|
+
vec2: Float32Array | undefined
|
|
294
|
+
): number {
|
|
295
|
+
if (!vec1 || !vec2) return 0.0
|
|
296
|
+
|
|
297
|
+
// Use FatherStateDB's optimized cosine similarity
|
|
298
|
+
const v1 = vec1 instanceof Float32Array ? vec1 : new Float32Array(vec1)
|
|
299
|
+
return cosineSimilarity(v1, vec2)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private _scoreTemporalRelevance(
|
|
303
|
+
temporalType: string,
|
|
304
|
+
_sessionContext: SessionContext
|
|
305
|
+
): number {
|
|
306
|
+
const scores: Record<string, number> = {
|
|
307
|
+
'persistent': 0.8, // Always relevant
|
|
308
|
+
'session': 0.6, // Session-specific
|
|
309
|
+
'temporary': 0.3, // Short-term
|
|
310
|
+
'archived': 0.1 // Historical
|
|
311
|
+
}
|
|
312
|
+
return scores[temporalType] ?? 0.5
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private _scoreContextAlignment(message: string, contextType: string): number {
|
|
316
|
+
const messageLower = message.toLowerCase()
|
|
317
|
+
|
|
318
|
+
// Keywords that suggest different contexts
|
|
319
|
+
const contextIndicators: Record<string, string[]> = {
|
|
320
|
+
'technical_state': ['bug', 'error', 'fix', 'implement', 'code', 'function'],
|
|
321
|
+
'breakthrough': ['idea', 'realized', 'discovered', 'insight', 'solution'],
|
|
322
|
+
'project_context': ['project', 'building', 'architecture', 'system'],
|
|
323
|
+
'personal': ['dear friend', 'thank', 'appreciate', 'feel'],
|
|
324
|
+
'unresolved': ['todo', 'need to', 'should', 'must', 'problem'],
|
|
325
|
+
'decision': ['decided', 'chose', 'will use', 'approach', 'strategy']
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const indicators = contextIndicators[contextType] ?? []
|
|
329
|
+
const matches = indicators.filter(word => messageLower.includes(word)).length
|
|
330
|
+
|
|
331
|
+
if (matches > 0) {
|
|
332
|
+
return Math.min(0.3 + (matches * 0.2), 1.0)
|
|
333
|
+
}
|
|
334
|
+
return 0.1
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private _scoreSemanticTags(message: string, tags: string[]): number {
|
|
338
|
+
if (!tags.length) return 0.0
|
|
339
|
+
|
|
340
|
+
const messageLower = message.toLowerCase()
|
|
341
|
+
const matches = tags.filter(tag =>
|
|
342
|
+
messageLower.includes(tag.trim().toLowerCase())
|
|
343
|
+
).length
|
|
344
|
+
|
|
345
|
+
if (matches > 0) {
|
|
346
|
+
return Math.min(0.3 + (matches * 0.3), 1.0)
|
|
347
|
+
}
|
|
348
|
+
return 0.0
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private _scoreTriggerPhrases(message: string, triggerPhrases: string[]): number {
|
|
352
|
+
if (!triggerPhrases.length) return 0.0
|
|
353
|
+
|
|
354
|
+
const messageLower = message.toLowerCase()
|
|
355
|
+
const stopWords = new Set([
|
|
356
|
+
'the', 'is', 'are', 'was', 'were', 'to', 'a', 'an', 'and', 'or',
|
|
357
|
+
'but', 'in', 'on', 'at', 'for', 'with', 'about', 'when', 'how',
|
|
358
|
+
'what', 'why'
|
|
359
|
+
])
|
|
360
|
+
|
|
361
|
+
let maxScore = 0.0
|
|
362
|
+
|
|
363
|
+
for (const pattern of triggerPhrases) {
|
|
364
|
+
const patternLower = pattern.trim().toLowerCase()
|
|
365
|
+
|
|
366
|
+
// Strategy 1: Key concept matching (individual important words)
|
|
367
|
+
const patternWords = patternLower
|
|
368
|
+
.split(/\s+/)
|
|
369
|
+
.filter(w => !stopWords.has(w) && w.length > 2)
|
|
370
|
+
|
|
371
|
+
if (patternWords.length) {
|
|
372
|
+
let matches = 0
|
|
373
|
+
for (const word of patternWords) {
|
|
374
|
+
// Direct match
|
|
375
|
+
if (messageLower.includes(word)) {
|
|
376
|
+
matches += 1
|
|
377
|
+
}
|
|
378
|
+
// Plural/singular variations
|
|
379
|
+
else if (messageLower.includes(word.replace(/s$/, '')) ||
|
|
380
|
+
messageLower.includes(word + 's')) {
|
|
381
|
+
matches += 0.9
|
|
382
|
+
}
|
|
383
|
+
// Substring match for compound words
|
|
384
|
+
else if (messageLower.split(/\s+/).some(msgWord => msgWord.includes(word))) {
|
|
385
|
+
matches += 0.7
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Score based on percentage of concepts found
|
|
390
|
+
let conceptScore = patternWords.length ? matches / patternWords.length : 0
|
|
391
|
+
|
|
392
|
+
// Strategy 2: Contextual pattern matching
|
|
393
|
+
const situationalIndicators = [
|
|
394
|
+
'when', 'during', 'while', 'asking about', 'working on', 'debugging', 'trying to'
|
|
395
|
+
]
|
|
396
|
+
if (situationalIndicators.some(ind => patternLower.includes(ind))) {
|
|
397
|
+
// This is a situational pattern - be more flexible
|
|
398
|
+
if (patternWords.some(keyWord => messageLower.includes(keyWord))) {
|
|
399
|
+
conceptScore = Math.max(conceptScore, 0.7) // Boost for situational match
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
maxScore = Math.max(maxScore, conceptScore)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return Math.min(maxScore, 1.0)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private _scoreQuestionTypes(message: string, questionTypes: string[]): number {
|
|
411
|
+
if (!questionTypes.length) return 0.0
|
|
412
|
+
|
|
413
|
+
const messageLower = message.toLowerCase()
|
|
414
|
+
const questionWords = ['how', 'why', 'what', 'when', 'where']
|
|
415
|
+
|
|
416
|
+
for (const qtype of questionTypes) {
|
|
417
|
+
const qtypeLower = qtype.trim().toLowerCase()
|
|
418
|
+
|
|
419
|
+
if (messageLower.includes(qtypeLower)) {
|
|
420
|
+
return 0.8
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Partial matching for question words
|
|
424
|
+
const messageHasQuestion = questionWords.some(qw => messageLower.includes(qw))
|
|
425
|
+
const typeHasQuestion = questionWords.some(qw => qtypeLower.includes(qw))
|
|
426
|
+
|
|
427
|
+
if (messageHasQuestion && typeHasQuestion) {
|
|
428
|
+
return 0.5
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return 0.0
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private _scoreEmotionalContext(message: string, emotion: string): number {
|
|
436
|
+
if (!emotion) return 0.0
|
|
437
|
+
|
|
438
|
+
const messageLower = message.toLowerCase()
|
|
439
|
+
|
|
440
|
+
// Emotion indicators
|
|
441
|
+
const emotionPatterns: Record<string, string[]> = {
|
|
442
|
+
'joy': ['happy', 'excited', 'love', 'wonderful', 'great', 'awesome'],
|
|
443
|
+
'frustration': ['stuck', 'confused', 'help', 'issue', 'problem', 'why'],
|
|
444
|
+
'discovery': ['realized', 'found', 'discovered', 'aha', 'insight'],
|
|
445
|
+
'gratitude': ['thank', 'appreciate', 'grateful', 'dear friend']
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const patterns = emotionPatterns[emotion.toLowerCase()] ?? []
|
|
449
|
+
if (patterns.some(pattern => messageLower.includes(pattern))) {
|
|
450
|
+
return 0.7
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return 0.0
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private _scoreProblemSolution(message: string, isProblemSolution: boolean): number {
|
|
457
|
+
if (!isProblemSolution) return 0.0
|
|
458
|
+
|
|
459
|
+
const messageLower = message.toLowerCase()
|
|
460
|
+
|
|
461
|
+
// Problem indicators
|
|
462
|
+
const problemWords = [
|
|
463
|
+
'error', 'issue', 'problem', 'stuck', 'help', 'fix', 'solve', 'debug'
|
|
464
|
+
]
|
|
465
|
+
|
|
466
|
+
if (problemWords.some(word => messageLower.includes(word))) {
|
|
467
|
+
return 0.8
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return 0.0
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
private _generateSelectionReasoning(components: ScoringComponents): string {
|
|
474
|
+
const scores: [string, number][] = [
|
|
475
|
+
['trigger phrase match', components.trigger],
|
|
476
|
+
['semantic similarity', components.vector],
|
|
477
|
+
['high importance', components.importance],
|
|
478
|
+
['question type match', components.question],
|
|
479
|
+
['context alignment', components.context],
|
|
480
|
+
['temporal relevance', components.temporal],
|
|
481
|
+
['tag match', components.tags],
|
|
482
|
+
['emotional resonance', components.emotion],
|
|
483
|
+
['problem-solution', components.problem],
|
|
484
|
+
['action required', components.action]
|
|
485
|
+
]
|
|
486
|
+
|
|
487
|
+
// Sort by score
|
|
488
|
+
scores.sort((a, b) => b[1] - a[1])
|
|
489
|
+
|
|
490
|
+
const reasons: string[] = []
|
|
491
|
+
|
|
492
|
+
// Build reasoning
|
|
493
|
+
const primary = scores[0]!
|
|
494
|
+
if (primary[1] > 0.5) {
|
|
495
|
+
reasons.push(`Strong ${primary[0]} (${primary[1].toFixed(2)})`)
|
|
496
|
+
} else if (primary[1] > 0.3) {
|
|
497
|
+
reasons.push(`${primary[0]} (${primary[1].toFixed(2)})`)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Add secondary reasons
|
|
501
|
+
for (const [reason, score] of scores.slice(1, 3)) {
|
|
502
|
+
if (score > 0.3) {
|
|
503
|
+
reasons.push(`${reason} (${score.toFixed(2)})`)
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return reasons.length
|
|
508
|
+
? 'Selected due to: ' + reasons.join(', ')
|
|
509
|
+
: 'Selected based on combined factors'
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Create a new SmartVectorRetrieval instance
|
|
515
|
+
*/
|
|
516
|
+
export function createRetrieval(): SmartVectorRetrieval {
|
|
517
|
+
return new SmartVectorRetrieval()
|
|
518
|
+
}
|