chore: recovery checkpoint - v3.0 API successfully recovered
CRITICAL CHECKPOINT - DO NOT PUSH TO GITHUB Recovery Status: - Successfully recovered brainy.ts from compiled JavaScript - All core v3.0 API methods functional (add, get, update, delete, relate, find, etc.) - Neural subsystem intact (562KB embedded patterns, NLP working) - Augmentation pipeline operational (20+ augmentations) - HNSW clustering system complete - Triple Intelligence compiled (needs constructor fix) - Test suite validates functionality Changes preserved: - 898 files with changes from last 3 days - 144,475 insertions - All augmentation improvements - All test coverage enhancements - Complete v3.0 feature set This is a LOCAL checkpoint only - contains recovered work after corruption incident. Created backup in .backups/brainy-full-20250910-151314.tar.gz Branch: recovery-checkpoint-20250910-151433 Date: Wed Sep 10 03:18:04 PM PDT 2025
This commit is contained in:
parent
f65455fb22
commit
8ff382ca3b
895 changed files with 143654 additions and 28268 deletions
|
|
@ -0,0 +1,655 @@
|
|||
/**
|
||||
* 🧠 Pattern Library for Natural Language Processing
|
||||
* Manages pre-computed pattern embeddings and smart matching
|
||||
*
|
||||
* Uses Brainy's own features for self-leveraging intelligence:
|
||||
* - Embeddings for semantic similarity
|
||||
* - Pattern caching for performance
|
||||
* - Progressive learning from usage
|
||||
*/
|
||||
import { EMBEDDED_PATTERNS, getPatternEmbeddings, PATTERNS_METADATA } from './embeddedPatterns.js';
|
||||
export class PatternLibrary {
|
||||
constructor(brain) {
|
||||
this.brain = brain;
|
||||
this.patterns = new Map();
|
||||
this.patternEmbeddings = new Map();
|
||||
this.embeddingCache = new Map();
|
||||
this.successMetrics = new Map();
|
||||
}
|
||||
/**
|
||||
* Initialize pattern library with pre-computed embeddings
|
||||
*/
|
||||
async init() {
|
||||
// Try to load pre-computed embeddings first
|
||||
const precomputedEmbeddings = getPatternEmbeddings();
|
||||
if (precomputedEmbeddings.size > 0) {
|
||||
// Use pre-computed embeddings (instant!)
|
||||
console.debug(`Loading ${precomputedEmbeddings.size} pre-computed pattern embeddings`);
|
||||
for (const pattern of EMBEDDED_PATTERNS) {
|
||||
this.patterns.set(pattern.id, pattern);
|
||||
this.successMetrics.set(pattern.id, pattern.confidence);
|
||||
const embedding = precomputedEmbeddings.get(pattern.id);
|
||||
if (embedding) {
|
||||
this.patternEmbeddings.set(pattern.id, Array.from(embedding));
|
||||
}
|
||||
}
|
||||
console.debug(`Pattern library ready: ${PATTERNS_METADATA.totalPatterns} patterns loaded instantly`);
|
||||
}
|
||||
else {
|
||||
// Fall back to runtime computation
|
||||
console.debug('No pre-computed embeddings found, computing at runtime...');
|
||||
for (const pattern of EMBEDDED_PATTERNS) {
|
||||
this.patterns.set(pattern.id, pattern);
|
||||
this.successMetrics.set(pattern.id, pattern.confidence);
|
||||
}
|
||||
// Compute embeddings for all patterns
|
||||
await this.precomputeEmbeddings();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Pre-compute embeddings for all patterns for fast matching
|
||||
*/
|
||||
async precomputeEmbeddings() {
|
||||
for (const [id, pattern] of this.patterns) {
|
||||
// Average embeddings of all examples for robust representation
|
||||
const embeddings = [];
|
||||
for (const example of pattern.examples) {
|
||||
const embedding = await this.getEmbedding(example);
|
||||
embeddings.push(embedding);
|
||||
}
|
||||
// Average the embeddings
|
||||
const avgEmbedding = this.averageVectors(embeddings);
|
||||
this.patternEmbeddings.set(id, avgEmbedding);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get embedding with caching
|
||||
*/
|
||||
async getEmbedding(text) {
|
||||
if (this.embeddingCache.has(text)) {
|
||||
return this.embeddingCache.get(text);
|
||||
}
|
||||
// Use add/get/delete pattern to get embeddings
|
||||
const id = await this.brain.add({
|
||||
data: text,
|
||||
type: 'document'
|
||||
});
|
||||
const entity = await this.brain.get(id);
|
||||
const embedding = entity?.vector || [];
|
||||
// Clean up temporary entity
|
||||
await this.brain.delete(id);
|
||||
this.embeddingCache.set(text, embedding);
|
||||
return embedding;
|
||||
}
|
||||
/**
|
||||
* Find best matching patterns for a query
|
||||
*/
|
||||
async findBestPatterns(queryEmbedding, k = 3) {
|
||||
const matches = [];
|
||||
// Calculate similarity with all patterns
|
||||
for (const [id, patternEmbedding] of this.patternEmbeddings) {
|
||||
const similarity = this.cosineSimilarity(queryEmbedding, patternEmbedding);
|
||||
const pattern = this.patterns.get(id);
|
||||
// Apply success metric boost
|
||||
const successBoost = this.successMetrics.get(id) || 0.5;
|
||||
const adjustedSimilarity = similarity * (0.7 + 0.3 * successBoost);
|
||||
matches.push({
|
||||
pattern,
|
||||
similarity: adjustedSimilarity
|
||||
});
|
||||
}
|
||||
// Sort by similarity and return top k
|
||||
matches.sort((a, b) => b.similarity - a.similarity);
|
||||
return matches.slice(0, k);
|
||||
}
|
||||
/**
|
||||
* Extract slots from query based on pattern with enhanced fuzzy matching
|
||||
*/
|
||||
extractSlots(query, pattern) {
|
||||
const slots = {};
|
||||
const errors = [];
|
||||
let confidence = pattern.confidence;
|
||||
// If pattern has named slot definitions, use them
|
||||
if (pattern.slots && pattern.slots.length > 0) {
|
||||
return this.extractNamedSlots(query, pattern);
|
||||
}
|
||||
// Try regex extraction first
|
||||
const regex = new RegExp(pattern.pattern, 'i');
|
||||
const match = query.match(regex);
|
||||
if (match) {
|
||||
// Extract captured groups as slots
|
||||
for (let i = 1; i < match.length; i++) {
|
||||
slots[`$${i}`] = match[i];
|
||||
}
|
||||
// High confidence if regex matches
|
||||
confidence = Math.min(confidence * 1.2, 1.0);
|
||||
}
|
||||
else {
|
||||
// Enhanced fuzzy matching with Levenshtein distance
|
||||
const fuzzyResult = this.fuzzyExtractSlots(query, pattern);
|
||||
Object.assign(slots, fuzzyResult.slots);
|
||||
confidence = fuzzyResult.confidence;
|
||||
if (fuzzyResult.errors) {
|
||||
errors.push(...fuzzyResult.errors);
|
||||
}
|
||||
}
|
||||
// Post-process slots
|
||||
this.postProcessSlots(slots, pattern);
|
||||
return { slots, confidence, errors: errors.length > 0 ? errors : undefined };
|
||||
}
|
||||
/**
|
||||
* Extract named slots with type validation
|
||||
*/
|
||||
extractNamedSlots(query, pattern) {
|
||||
const slots = {};
|
||||
const errors = [];
|
||||
let confidence = pattern.confidence;
|
||||
if (!pattern.slots) {
|
||||
return { slots, confidence };
|
||||
}
|
||||
// Create a flexible regex from pattern
|
||||
let flexiblePattern = pattern.pattern;
|
||||
const slotPositions = new Map();
|
||||
// Replace named slots in pattern with capture groups
|
||||
pattern.slots.forEach((slot, index) => {
|
||||
const slotPattern = slot.pattern || this.getDefaultPatternForType(slot.type);
|
||||
flexiblePattern = flexiblePattern.replace(new RegExp(`\\{${slot.name}\\}`, 'g'), `(${slotPattern})`);
|
||||
slotPositions.set(index + 1, slot);
|
||||
});
|
||||
const regex = new RegExp(flexiblePattern, 'i');
|
||||
const match = query.match(regex);
|
||||
if (match) {
|
||||
// Extract and validate each slot
|
||||
slotPositions.forEach((slotDef, position) => {
|
||||
const value = match[position];
|
||||
if (value) {
|
||||
// Apply transformation if defined
|
||||
const transformedValue = slotDef.transform
|
||||
? slotDef.transform(value)
|
||||
: this.transformByType(value, slotDef.type);
|
||||
// Validate the value
|
||||
if (this.validateSlotValue(transformedValue, slotDef)) {
|
||||
slots[slotDef.name] = transformedValue;
|
||||
}
|
||||
else {
|
||||
errors.push(`Invalid value for slot '${slotDef.name}': expected ${slotDef.type}, got '${value}'`);
|
||||
confidence *= 0.8;
|
||||
}
|
||||
}
|
||||
else if (slotDef.required) {
|
||||
if (slotDef.default !== undefined) {
|
||||
slots[slotDef.name] = slotDef.default;
|
||||
}
|
||||
else {
|
||||
errors.push(`Required slot '${slotDef.name}' not found`);
|
||||
confidence *= 0.5;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Try fuzzy matching for named slots
|
||||
const fuzzyResult = this.fuzzyExtractNamedSlots(query, pattern);
|
||||
Object.assign(slots, fuzzyResult.slots);
|
||||
confidence = fuzzyResult.confidence;
|
||||
if (fuzzyResult.errors) {
|
||||
errors.push(...fuzzyResult.errors);
|
||||
}
|
||||
}
|
||||
return { slots, confidence, errors: errors.length > 0 ? errors : undefined };
|
||||
}
|
||||
/**
|
||||
* Fuzzy extraction using Levenshtein distance
|
||||
*/
|
||||
fuzzyExtractSlots(query, pattern) {
|
||||
const slots = {};
|
||||
let bestConfidence = 0;
|
||||
// Try each example with fuzzy matching
|
||||
for (const example of pattern.examples) {
|
||||
const distance = this.levenshteinDistance(query.toLowerCase(), example.toLowerCase());
|
||||
const similarity = 1 - (distance / Math.max(query.length, example.length));
|
||||
if (similarity > 0.6) { // 60% similarity threshold
|
||||
// Extract slots using alignment
|
||||
const aligned = this.alignStrings(query, example);
|
||||
const extractedSlots = this.extractSlotsFromAlignment(aligned, pattern);
|
||||
if (Object.keys(extractedSlots).length > 0) {
|
||||
const currentConfidence = pattern.confidence * similarity;
|
||||
if (currentConfidence > bestConfidence) {
|
||||
Object.assign(slots, extractedSlots);
|
||||
bestConfidence = currentConfidence;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
slots,
|
||||
confidence: bestConfidence,
|
||||
errors: bestConfidence < 0.5 ? ['Low confidence fuzzy match'] : undefined
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Fuzzy extraction for named slots
|
||||
*/
|
||||
fuzzyExtractNamedSlots(query, pattern) {
|
||||
const slots = {};
|
||||
const errors = [];
|
||||
let confidence = pattern.confidence * 0.7; // Lower confidence for fuzzy
|
||||
if (!pattern.slots) {
|
||||
return { slots, confidence };
|
||||
}
|
||||
// Tokenize query for flexible matching
|
||||
const tokens = this.tokenize(query);
|
||||
pattern.slots.forEach(slotDef => {
|
||||
const value = this.findSlotValueInTokens(tokens, slotDef);
|
||||
if (value) {
|
||||
const transformedValue = slotDef.transform
|
||||
? slotDef.transform(value)
|
||||
: this.transformByType(value, slotDef.type);
|
||||
if (this.validateSlotValue(transformedValue, slotDef)) {
|
||||
slots[slotDef.name] = transformedValue;
|
||||
}
|
||||
else {
|
||||
errors.push(`Fuzzy match: uncertain value for '${slotDef.name}'`);
|
||||
confidence *= 0.9;
|
||||
}
|
||||
}
|
||||
else if (slotDef.required && slotDef.default !== undefined) {
|
||||
slots[slotDef.name] = slotDef.default;
|
||||
}
|
||||
});
|
||||
return { slots, confidence, errors: errors.length > 0 ? errors : undefined };
|
||||
}
|
||||
/**
|
||||
* Find slot value in tokens based on type
|
||||
*/
|
||||
findSlotValueInTokens(tokens, slotDef) {
|
||||
const joinedTokens = tokens.join(' ');
|
||||
switch (slotDef.type) {
|
||||
case 'number':
|
||||
const numberMatch = joinedTokens.match(/\d+(\.\d+)?/);
|
||||
return numberMatch ? numberMatch[0] : null;
|
||||
case 'date':
|
||||
const datePatterns = [
|
||||
/\d{4}-\d{2}-\d{2}/,
|
||||
/\d{1,2}\/\d{1,2}\/\d{2,4}/,
|
||||
/(january|february|march|april|may|june|july|august|september|october|november|december)\s+\d{1,2},?\s+\d{4}/i,
|
||||
/(today|tomorrow|yesterday)/i
|
||||
];
|
||||
for (const pattern of datePatterns) {
|
||||
const match = joinedTokens.match(pattern);
|
||||
if (match)
|
||||
return match[0];
|
||||
}
|
||||
return null;
|
||||
case 'person':
|
||||
// Look for capitalized words (proper nouns)
|
||||
const personMatch = joinedTokens.match(/\b[A-Z][a-z]+(\s+[A-Z][a-z]+)*\b/);
|
||||
return personMatch ? personMatch[0] : null;
|
||||
case 'location':
|
||||
// Look for location indicators
|
||||
const locationPatterns = [
|
||||
/\b(in|at|from|to)\s+([A-Z][a-z]+(\s+[A-Z][a-z]+)*)\b/,
|
||||
/\b[A-Z][a-z]+,\s+[A-Z]{2}\b/ // City, STATE format
|
||||
];
|
||||
for (const pattern of locationPatterns) {
|
||||
const match = joinedTokens.match(pattern);
|
||||
if (match)
|
||||
return match[2] || match[0];
|
||||
}
|
||||
return null;
|
||||
case 'entity':
|
||||
case 'text':
|
||||
case 'any':
|
||||
default:
|
||||
// Return first non-common word as potential value
|
||||
const commonWords = new Set(['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for']);
|
||||
const significantToken = tokens.find(t => !commonWords.has(t.toLowerCase()));
|
||||
return significantToken || null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get default regex pattern for slot type
|
||||
*/
|
||||
getDefaultPatternForType(type) {
|
||||
switch (type) {
|
||||
case 'number':
|
||||
return '\\d+(?:\\.\\d+)?';
|
||||
case 'date':
|
||||
return '[\\w\\s,/-]+';
|
||||
case 'person':
|
||||
return '[A-Z][a-z]+(?:\\s+[A-Z][a-z]+)*';
|
||||
case 'location':
|
||||
return '[A-Z][a-z]+(?:[\\s,]+[A-Z][a-z]+)*';
|
||||
case 'entity':
|
||||
return '[\\w\\s-]+';
|
||||
case 'text':
|
||||
case 'any':
|
||||
default:
|
||||
return '.+';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Transform value based on type
|
||||
*/
|
||||
transformByType(value, type) {
|
||||
switch (type) {
|
||||
case 'number':
|
||||
const num = parseFloat(value);
|
||||
return isNaN(num) ? value : num;
|
||||
case 'date':
|
||||
// Simple date parsing
|
||||
if (value.toLowerCase() === 'today') {
|
||||
return new Date().toISOString().split('T')[0];
|
||||
}
|
||||
else if (value.toLowerCase() === 'tomorrow') {
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
return tomorrow.toISOString().split('T')[0];
|
||||
}
|
||||
else if (value.toLowerCase() === 'yesterday') {
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
return yesterday.toISOString().split('T')[0];
|
||||
}
|
||||
return value;
|
||||
case 'person':
|
||||
case 'location':
|
||||
case 'entity':
|
||||
// Capitalize properly
|
||||
return value.split(' ')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(' ');
|
||||
default:
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Validate slot value against definition
|
||||
*/
|
||||
validateSlotValue(value, slotDef) {
|
||||
if (value === null || value === undefined) {
|
||||
return !slotDef.required;
|
||||
}
|
||||
switch (slotDef.type) {
|
||||
case 'number':
|
||||
return typeof value === 'number' && !isNaN(value);
|
||||
case 'date':
|
||||
return typeof value === 'string' && value.length > 0;
|
||||
case 'text':
|
||||
case 'person':
|
||||
case 'location':
|
||||
case 'entity':
|
||||
return typeof value === 'string' && value.length > 0;
|
||||
case 'any':
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Calculate Levenshtein distance between two strings
|
||||
*/
|
||||
levenshteinDistance(s1, s2) {
|
||||
const len1 = s1.length;
|
||||
const len2 = s2.length;
|
||||
const matrix = [];
|
||||
for (let i = 0; i <= len1; i++) {
|
||||
matrix[i] = [i];
|
||||
}
|
||||
for (let j = 0; j <= len2; j++) {
|
||||
matrix[0][j] = j;
|
||||
}
|
||||
for (let i = 1; i <= len1; i++) {
|
||||
for (let j = 1; j <= len2; j++) {
|
||||
const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
|
||||
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, // deletion
|
||||
matrix[i][j - 1] + 1, // insertion
|
||||
matrix[i - 1][j - 1] + cost // substitution
|
||||
);
|
||||
}
|
||||
}
|
||||
return matrix[len1][len2];
|
||||
}
|
||||
/**
|
||||
* Align two strings for slot extraction
|
||||
*/
|
||||
alignStrings(query, example) {
|
||||
const queryTokens = this.tokenize(query);
|
||||
const exampleTokens = this.tokenize(example);
|
||||
const aligned = [];
|
||||
let i = 0, j = 0;
|
||||
while (i < queryTokens.length && j < exampleTokens.length) {
|
||||
if (queryTokens[i] === exampleTokens[j]) {
|
||||
aligned.push([queryTokens[i], exampleTokens[j]]);
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
else {
|
||||
// Try to find best match
|
||||
const bestMatch = this.findBestTokenMatch(queryTokens[i], exampleTokens.slice(j, j + 3));
|
||||
if (bestMatch.index >= 0) {
|
||||
j += bestMatch.index;
|
||||
aligned.push([queryTokens[i], exampleTokens[j]]);
|
||||
}
|
||||
else {
|
||||
aligned.push([queryTokens[i], exampleTokens[j]]);
|
||||
}
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
return aligned;
|
||||
}
|
||||
/**
|
||||
* Find best token match using fuzzy comparison
|
||||
*/
|
||||
findBestTokenMatch(token, candidates) {
|
||||
let bestIndex = -1;
|
||||
let bestSimilarity = 0;
|
||||
candidates.forEach((candidate, index) => {
|
||||
const distance = this.levenshteinDistance(token.toLowerCase(), candidate.toLowerCase());
|
||||
const similarity = 1 - (distance / Math.max(token.length, candidate.length));
|
||||
if (similarity > bestSimilarity && similarity > 0.6) {
|
||||
bestIndex = index;
|
||||
bestSimilarity = similarity;
|
||||
}
|
||||
});
|
||||
return { index: bestIndex, similarity: bestSimilarity };
|
||||
}
|
||||
/**
|
||||
* Extract slots from string alignment
|
||||
*/
|
||||
extractSlotsFromAlignment(aligned, _pattern) {
|
||||
const slots = {};
|
||||
let slotIndex = 1;
|
||||
aligned.forEach(([queryToken, exampleToken]) => {
|
||||
if (exampleToken.startsWith('$')) {
|
||||
slots[`$${slotIndex}`] = queryToken;
|
||||
slotIndex++;
|
||||
}
|
||||
});
|
||||
return slots;
|
||||
}
|
||||
/**
|
||||
* Fill template with extracted slots
|
||||
*/
|
||||
fillTemplate(template, slots) {
|
||||
const filled = JSON.parse(JSON.stringify(template));
|
||||
// Recursively replace slot placeholders
|
||||
const replacePlaceholders = (obj) => {
|
||||
if (typeof obj === 'string') {
|
||||
// Replace ${1}, ${2}, etc. with slot values
|
||||
return obj.replace(/\$\{(\d+)\}/g, (_, num) => {
|
||||
return slots[`$${num}`] || '';
|
||||
});
|
||||
}
|
||||
else if (Array.isArray(obj)) {
|
||||
return obj.map(item => replacePlaceholders(item));
|
||||
}
|
||||
else if (typeof obj === 'object' && obj !== null) {
|
||||
const result = {};
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
const newKey = replacePlaceholders(key);
|
||||
result[newKey] = replacePlaceholders(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
return replacePlaceholders(filled);
|
||||
}
|
||||
/**
|
||||
* Update pattern success metrics based on usage
|
||||
*/
|
||||
updateSuccessMetric(patternId, success) {
|
||||
const current = this.successMetrics.get(patternId) || 0.5;
|
||||
// Exponential moving average
|
||||
const alpha = 0.1;
|
||||
const newMetric = success
|
||||
? current + alpha * (1 - current)
|
||||
: current - alpha * current;
|
||||
this.successMetrics.set(patternId, newMetric);
|
||||
}
|
||||
/**
|
||||
* Learn new pattern from successful query
|
||||
*/
|
||||
async learnPattern(query, result) {
|
||||
// Find similar existing patterns
|
||||
const queryEmbedding = await this.getEmbedding(query);
|
||||
const similar = await this.findBestPatterns(queryEmbedding, 1);
|
||||
if (similar[0]?.similarity < 0.7) {
|
||||
// This is a new pattern type - add it
|
||||
const newPattern = {
|
||||
id: `learned_${Date.now()}`,
|
||||
category: 'learned',
|
||||
examples: [query],
|
||||
pattern: this.generateRegexFromQuery(query),
|
||||
template: result,
|
||||
confidence: 0.6 // Start with moderate confidence
|
||||
};
|
||||
this.patterns.set(newPattern.id, newPattern);
|
||||
this.patternEmbeddings.set(newPattern.id, queryEmbedding);
|
||||
this.successMetrics.set(newPattern.id, 0.6);
|
||||
}
|
||||
else {
|
||||
// Similar pattern exists - add as example
|
||||
const pattern = similar[0].pattern;
|
||||
if (!pattern.examples.includes(query)) {
|
||||
pattern.examples.push(query);
|
||||
// Update pattern embedding with new example
|
||||
const embeddings = await Promise.all(pattern.examples.map(ex => this.getEmbedding(ex)));
|
||||
const newEmbedding = this.averageVectors(embeddings);
|
||||
this.patternEmbeddings.set(pattern.id, newEmbedding);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Helper: Average multiple vectors
|
||||
*/
|
||||
averageVectors(vectors) {
|
||||
if (vectors.length === 0)
|
||||
return [];
|
||||
const dim = vectors[0].length;
|
||||
const avg = new Array(dim).fill(0);
|
||||
for (const vec of vectors) {
|
||||
for (let i = 0; i < dim; i++) {
|
||||
avg[i] += vec[i];
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < dim; i++) {
|
||||
avg[i] /= vectors.length;
|
||||
}
|
||||
return avg;
|
||||
}
|
||||
/**
|
||||
* Helper: Calculate cosine similarity
|
||||
*/
|
||||
cosineSimilarity(a, b) {
|
||||
let dotProduct = 0;
|
||||
let normA = 0;
|
||||
let normB = 0;
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
dotProduct += a[i] * b[i];
|
||||
normA += a[i] * a[i];
|
||||
normB += b[i] * b[i];
|
||||
}
|
||||
normA = Math.sqrt(normA);
|
||||
normB = Math.sqrt(normB);
|
||||
if (normA === 0 || normB === 0)
|
||||
return 0;
|
||||
return dotProduct / (normA * normB);
|
||||
}
|
||||
/**
|
||||
* Helper: Simple tokenization
|
||||
*/
|
||||
tokenize(text) {
|
||||
return text.toLowerCase().split(/\s+/).filter(t => t.length > 0);
|
||||
}
|
||||
/**
|
||||
* Helper: Post-process extracted slots
|
||||
*/
|
||||
postProcessSlots(slots, _pattern) {
|
||||
// Convert string numbers to actual numbers
|
||||
for (const [key, value] of Object.entries(slots)) {
|
||||
if (typeof value === 'string') {
|
||||
// Check if it's a number
|
||||
const num = parseFloat(value);
|
||||
if (!isNaN(num) && value.match(/^\d+(\.\d+)?$/)) {
|
||||
slots[key] = num;
|
||||
}
|
||||
// Parse dates
|
||||
if (value.match(/\d{4}/) || value.match(/(january|february|march|april|may|june|july|august|september|october|november|december)/i)) {
|
||||
// Simple year extraction
|
||||
const year = value.match(/\d{4}/);
|
||||
if (year) {
|
||||
slots[key] = parseInt(year[0]);
|
||||
}
|
||||
}
|
||||
// Clean up captured values
|
||||
slots[key] = value.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Helper: Generate regex pattern from query
|
||||
*/
|
||||
generateRegexFromQuery(query) {
|
||||
// Simple pattern generation - replace variable parts with capture groups
|
||||
let pattern = query.toLowerCase();
|
||||
// Replace numbers with \d+ capture
|
||||
pattern = pattern.replace(/\d+/g, '(\\d+)');
|
||||
// Replace quoted strings with .+ capture
|
||||
pattern = pattern.replace(/"[^"]+"/g, '(.+)');
|
||||
// Replace proper nouns (capitalized words) with capture
|
||||
pattern = pattern.replace(/\b[A-Z]\w+\b/g, '([A-Z][\\w]+)');
|
||||
return pattern;
|
||||
}
|
||||
/**
|
||||
* Get pattern statistics for monitoring
|
||||
*/
|
||||
getStatistics() {
|
||||
const stats = {
|
||||
totalPatterns: this.patterns.size,
|
||||
categories: {},
|
||||
averageConfidence: 0,
|
||||
topPatterns: []
|
||||
};
|
||||
// Count by category
|
||||
for (const pattern of this.patterns.values()) {
|
||||
stats.categories[pattern.category] = (stats.categories[pattern.category] || 0) + 1;
|
||||
}
|
||||
// Calculate average confidence
|
||||
let totalConfidence = 0;
|
||||
for (const confidence of this.successMetrics.values()) {
|
||||
totalConfidence += confidence;
|
||||
}
|
||||
stats.averageConfidence = totalConfidence / this.successMetrics.size;
|
||||
// Get top patterns by success
|
||||
const sortedPatterns = Array.from(this.successMetrics.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 10);
|
||||
stats.topPatterns = sortedPatterns.map(([id, success]) => ({ id, success }));
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=patternLibrary.js.map
|
||||
Loading…
Add table
Add a link
Reference in a new issue