feat: Universal Import with intelligent type matching (v2.1.0)
✨ ONE universal import method for everything - Auto-detects files, URLs, and raw data - Intelligent noun/verb type matching using embeddings - Support for JSON, CSV, YAML, and text formats - Zero configuration required 🧠 Intelligent Type Matching - Uses semantic embeddings to match 31 noun types - Automatically detects 40 verb relationship types - Confidence scores for type predictions - Caching for improved performance 📦 Import Manager - Centralized import logic with lazy loading - Integrates NeuralImportAugmentation for AI processing - Proper CSV parsing with quote handling - Basic YAML support 🎯 Simplified API - brain.import() - ONE method that handles everything - Auto-detection of URLs and file paths - Backwards compatible with existing code - Clean, modern, delightful developer experience 📚 Documentation - Comprehensive import guide in docs/guides/import-anything.md - Examples for every format and use case - Philosophy of simplicity and zero config ✅ Tests - Full unit test coverage for import functionality - Type matching tests for all 31 nouns and 40 verbs - Tests for CSV, YAML, JSON, and text formats BREAKING CHANGES: None - fully backward compatible
This commit is contained in:
parent
492267b509
commit
1ef01f394a
10 changed files with 1833 additions and 102 deletions
|
|
@ -11,6 +11,8 @@ import { BaseAugmentation, AugmentationContext } from './brainyAugmentation.js'
|
|||
import { NounType, VerbType } from '../types/graphTypes.js'
|
||||
import * as fs from '../universal/fs.js'
|
||||
import * as path from '../universal/path.js'
|
||||
import { IntelligentTypeMatcher, getTypeMatcher } from './typeMatching/intelligentTypeMatcher.js'
|
||||
import { prodLog } from '../utils/logger.js'
|
||||
|
||||
// Neural Import Analysis Types
|
||||
export interface NeuralAnalysisResult {
|
||||
|
|
@ -68,6 +70,7 @@ export class NeuralImportAugmentation extends BaseAugmentation {
|
|||
|
||||
private config: NeuralImportConfig
|
||||
private analysisCache = new Map<string, NeuralAnalysisResult>()
|
||||
private typeMatcher: IntelligentTypeMatcher | null = null
|
||||
|
||||
constructor(config: Partial<NeuralImportConfig> = {}) {
|
||||
super()
|
||||
|
|
@ -81,7 +84,12 @@ export class NeuralImportAugmentation extends BaseAugmentation {
|
|||
}
|
||||
|
||||
protected async onInitialize(): Promise<void> {
|
||||
this.log('🧠 Neural Import augmentation initialized')
|
||||
try {
|
||||
this.typeMatcher = await getTypeMatcher()
|
||||
this.log('🧠 Neural Import augmentation initialized with intelligent type matching')
|
||||
} catch (error) {
|
||||
this.log('⚠️ Failed to initialize type matcher, falling back to heuristics', 'warn')
|
||||
}
|
||||
}
|
||||
|
||||
protected async onShutdown(): Promise<void> {
|
||||
|
|
@ -195,12 +203,7 @@ export class NeuralImportAugmentation extends BaseAugmentation {
|
|||
|
||||
case 'yaml':
|
||||
case 'yml':
|
||||
// For now, basic YAML support - in full implementation would use yaml parser
|
||||
try {
|
||||
return JSON.parse(content) // Placeholder
|
||||
} catch {
|
||||
return [{ text: content }]
|
||||
}
|
||||
return this.parseYAML(content)
|
||||
|
||||
case 'txt':
|
||||
case 'text':
|
||||
|
|
@ -214,26 +217,179 @@ export class NeuralImportAugmentation extends BaseAugmentation {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parse CSV data
|
||||
* Parse CSV data - handles quoted values, escaped quotes, and edge cases
|
||||
*/
|
||||
private parseCSV(content: string): any[] {
|
||||
const lines = content.split('\n').filter(line => line.trim())
|
||||
const lines = content.split('\n')
|
||||
if (lines.length === 0) return []
|
||||
|
||||
const headers = lines[0].split(',').map(h => h.trim())
|
||||
// Parse a CSV line handling quotes
|
||||
const parseLine = (line: string): string[] => {
|
||||
const result: string[] = []
|
||||
let current = ''
|
||||
let inQuotes = false
|
||||
let i = 0
|
||||
|
||||
while (i < line.length) {
|
||||
const char = line[i]
|
||||
const nextChar = line[i + 1]
|
||||
|
||||
if (char === '"') {
|
||||
if (inQuotes && nextChar === '"') {
|
||||
// Escaped quote
|
||||
current += '"'
|
||||
i += 2
|
||||
} else {
|
||||
// Toggle quote mode
|
||||
inQuotes = !inQuotes
|
||||
i++
|
||||
}
|
||||
} else if (char === ',' && !inQuotes) {
|
||||
// Field separator
|
||||
result.push(current.trim())
|
||||
current = ''
|
||||
i++
|
||||
} else {
|
||||
current += char
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// Add last field
|
||||
result.push(current.trim())
|
||||
return result
|
||||
}
|
||||
|
||||
// Parse headers
|
||||
const headers = parseLine(lines[0])
|
||||
const data = []
|
||||
|
||||
// Parse data rows
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const values = lines[i].split(',').map(v => v.trim())
|
||||
const line = lines[i].trim()
|
||||
if (!line) continue // Skip empty lines
|
||||
|
||||
const values = parseLine(line)
|
||||
const row: any = {}
|
||||
|
||||
headers.forEach((header, index) => {
|
||||
row[header] = values[index] || ''
|
||||
const value = values[index] || ''
|
||||
// Try to parse numbers
|
||||
const num = Number(value)
|
||||
row[header] = !isNaN(num) && value !== '' ? num : value
|
||||
})
|
||||
|
||||
data.push(row)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse YAML data
|
||||
*/
|
||||
private parseYAML(content: string): any[] {
|
||||
try {
|
||||
// Simple YAML parser for basic structures
|
||||
// For full YAML support, we'd use js-yaml library
|
||||
const lines = content.split('\n')
|
||||
const result: any[] = []
|
||||
let currentObject: any = null
|
||||
let currentIndent = 0
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim()
|
||||
if (!trimmed || trimmed.startsWith('#')) continue // Skip empty lines and comments
|
||||
|
||||
// Calculate indentation
|
||||
const indent = line.length - line.trimStart().length
|
||||
|
||||
// Check for array item
|
||||
if (trimmed.startsWith('- ')) {
|
||||
const value = trimmed.substring(2).trim()
|
||||
if (indent === 0) {
|
||||
// Top-level array item
|
||||
if (value.includes(':')) {
|
||||
// Object in array
|
||||
currentObject = {}
|
||||
result.push(currentObject)
|
||||
const [key, val] = value.split(':').map(s => s.trim())
|
||||
currentObject[key] = this.parseYAMLValue(val)
|
||||
} else {
|
||||
result.push(this.parseYAMLValue(value))
|
||||
}
|
||||
} else if (currentObject) {
|
||||
// Nested array
|
||||
const lastKey = Object.keys(currentObject).pop()
|
||||
if (lastKey) {
|
||||
if (!Array.isArray(currentObject[lastKey])) {
|
||||
currentObject[lastKey] = []
|
||||
}
|
||||
currentObject[lastKey].push(this.parseYAMLValue(value))
|
||||
}
|
||||
}
|
||||
} else if (trimmed.includes(':')) {
|
||||
// Key-value pair
|
||||
const colonIndex = trimmed.indexOf(':')
|
||||
const key = trimmed.substring(0, colonIndex).trim()
|
||||
const value = trimmed.substring(colonIndex + 1).trim()
|
||||
|
||||
if (indent === 0) {
|
||||
// Top-level object
|
||||
if (!currentObject) {
|
||||
currentObject = {}
|
||||
result.push(currentObject)
|
||||
}
|
||||
currentObject[key] = this.parseYAMLValue(value)
|
||||
currentIndent = 0
|
||||
} else if (currentObject) {
|
||||
// Nested object
|
||||
if (indent > currentIndent && !value) {
|
||||
// Start of nested object
|
||||
const lastKey = Object.keys(currentObject).pop()
|
||||
if (lastKey) {
|
||||
currentObject[lastKey] = { [key]: '' }
|
||||
}
|
||||
} else {
|
||||
currentObject[key] = this.parseYAMLValue(value)
|
||||
}
|
||||
currentIndent = indent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we built a single object and not an array, wrap it
|
||||
if (result.length === 0 && currentObject) {
|
||||
result.push(currentObject)
|
||||
}
|
||||
|
||||
return result.length > 0 ? result : [{ text: content }]
|
||||
} catch (error) {
|
||||
prodLog.warn('YAML parsing failed, treating as text:', error)
|
||||
return [{ text: content }]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a YAML value (handle strings, numbers, booleans, null)
|
||||
*/
|
||||
private parseYAMLValue(value: string): any {
|
||||
if (!value || value === '~' || value === 'null') return null
|
||||
if (value === 'true') return true
|
||||
if (value === 'false') return false
|
||||
|
||||
// Remove quotes if present
|
||||
if ((value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))) {
|
||||
return value.slice(1, -1)
|
||||
}
|
||||
|
||||
// Try to parse as number
|
||||
const num = Number(value)
|
||||
if (!isNaN(num) && value !== '') return num
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform neural analysis on parsed data
|
||||
|
|
@ -251,7 +407,7 @@ export class NeuralImportAugmentation extends BaseAugmentation {
|
|||
|
||||
detectedEntities.push({
|
||||
originalData: item,
|
||||
nounType: this.inferNounType(item),
|
||||
nounType: await this.inferNounType(item),
|
||||
confidence: 0.85,
|
||||
suggestedId: String(entityId),
|
||||
reasoning: 'Detected from structured data',
|
||||
|
|
@ -259,7 +415,7 @@ export class NeuralImportAugmentation extends BaseAugmentation {
|
|||
})
|
||||
|
||||
// Detect relationships from references
|
||||
this.detectRelationships(item, entityId, detectedRelationships)
|
||||
await this.detectRelationships(item, entityId, detectedRelationships)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -295,32 +451,35 @@ export class NeuralImportAugmentation extends BaseAugmentation {
|
|||
}
|
||||
|
||||
/**
|
||||
* Infer noun type from object structure
|
||||
* Infer noun type from object structure using intelligent type matching
|
||||
*/
|
||||
private inferNounType(obj: any): string {
|
||||
// Simple heuristics for type detection
|
||||
if (obj.email || obj.username) return 'Person'
|
||||
if (obj.title && obj.content) return 'Document'
|
||||
if (obj.price || obj.product) return 'Product'
|
||||
if (obj.date || obj.timestamp) return 'Event'
|
||||
if (obj.url || obj.link) return 'Resource'
|
||||
if (obj.lat || obj.longitude) return 'Location'
|
||||
private async inferNounType(obj: any): Promise<string> {
|
||||
if (!this.typeMatcher) {
|
||||
// Initialize type matcher if not available
|
||||
this.typeMatcher = await getTypeMatcher()
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return 'Entity'
|
||||
const result = await this.typeMatcher.matchNounType(obj)
|
||||
|
||||
// Log if confidence is low for debugging
|
||||
if (result.confidence < 0.5) {
|
||||
this.log(`Low confidence (${result.confidence.toFixed(2)}) for noun type: ${result.type}`, 'warn')
|
||||
}
|
||||
|
||||
return result.type
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect relationships from object references
|
||||
*/
|
||||
private detectRelationships(obj: any, sourceId: string, relationships: DetectedRelationship[]): void {
|
||||
private async detectRelationships(obj: any, sourceId: string, relationships: DetectedRelationship[]): Promise<void> {
|
||||
// Look for reference patterns
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (key.endsWith('Id') || key.endsWith('_id') || key === 'parentId' || key === 'userId') {
|
||||
relationships.push({
|
||||
sourceId,
|
||||
targetId: String(value),
|
||||
verbType: this.inferVerbType(key),
|
||||
verbType: await this.inferVerbType(key, obj, { id: value }),
|
||||
confidence: 0.75,
|
||||
weight: 1,
|
||||
reasoning: `Reference detected in field: ${key}`,
|
||||
|
|
@ -335,7 +494,7 @@ export class NeuralImportAugmentation extends BaseAugmentation {
|
|||
relationships.push({
|
||||
sourceId,
|
||||
targetId: String(targetId),
|
||||
verbType: this.inferVerbType(key),
|
||||
verbType: await this.inferVerbType(key, obj, { id: targetId }),
|
||||
confidence: 0.7,
|
||||
weight: 1,
|
||||
reasoning: `Array reference in field: ${key}`,
|
||||
|
|
@ -348,21 +507,22 @@ export class NeuralImportAugmentation extends BaseAugmentation {
|
|||
}
|
||||
|
||||
/**
|
||||
* Infer verb type from field name
|
||||
* Infer verb type from field name using intelligent type matching
|
||||
*/
|
||||
private inferVerbType(fieldName: string): string {
|
||||
const normalized = fieldName.toLowerCase()
|
||||
private async inferVerbType(fieldName: string, sourceObj?: any, targetObj?: any): Promise<string> {
|
||||
if (!this.typeMatcher) {
|
||||
// Initialize type matcher if not available
|
||||
this.typeMatcher = await getTypeMatcher()
|
||||
}
|
||||
|
||||
if (normalized.includes('parent')) return 'childOf'
|
||||
if (normalized.includes('user')) return 'belongsTo'
|
||||
if (normalized.includes('author')) return 'authoredBy'
|
||||
if (normalized.includes('owner')) return 'ownedBy'
|
||||
if (normalized.includes('creator')) return 'createdBy'
|
||||
if (normalized.includes('member')) return 'memberOf'
|
||||
if (normalized.includes('tag')) return 'taggedWith'
|
||||
if (normalized.includes('category')) return 'categorizedAs'
|
||||
const result = await this.typeMatcher.matchVerbType(sourceObj, targetObj, fieldName)
|
||||
|
||||
return 'relatedTo'
|
||||
// Log if confidence is low for debugging
|
||||
if (result.confidence < 0.5) {
|
||||
this.log(`Low confidence (${result.confidence.toFixed(2)}) for verb type: ${result.type}`, 'warn')
|
||||
}
|
||||
|
||||
return result.type
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
534
src/augmentations/typeMatching/intelligentTypeMatcher.ts
Normal file
534
src/augmentations/typeMatching/intelligentTypeMatcher.ts
Normal file
|
|
@ -0,0 +1,534 @@
|
|||
/**
|
||||
* Intelligent Type Matcher - Uses embeddings for semantic type detection
|
||||
*
|
||||
* This module uses our existing TransformerEmbedding and similarity functions
|
||||
* to intelligently match data to our 31 noun types and 40 verb types.
|
||||
*
|
||||
* Features:
|
||||
* - Semantic similarity matching using embeddings
|
||||
* - Context-aware type detection
|
||||
* - Confidence scoring
|
||||
* - Caching for performance
|
||||
*/
|
||||
|
||||
import { NounType, VerbType } from '../../types/graphTypes.js'
|
||||
import { TransformerEmbedding } from '../../utils/embedding.js'
|
||||
import { cosineDistance } from '../../utils/distance.js'
|
||||
import { Vector } from '../../coreTypes.js'
|
||||
|
||||
/**
|
||||
* Type descriptions for semantic matching
|
||||
* These descriptions are used to generate embeddings for each type
|
||||
*/
|
||||
const NOUN_TYPE_DESCRIPTIONS: Record<string, string> = {
|
||||
// Core Entity Types
|
||||
[NounType.Person]: 'person human individual user employee customer citizen member author creator agent actor participant',
|
||||
[NounType.Organization]: 'organization company business corporation institution agency department team group committee board',
|
||||
[NounType.Location]: 'location place address city country region area zone coordinate position site venue building',
|
||||
[NounType.Thing]: 'thing object item product device equipment tool instrument asset artifact material physical tangible',
|
||||
[NounType.Concept]: 'concept idea theory principle philosophy belief value abstract intangible notion thought',
|
||||
[NounType.Event]: 'event occurrence incident activity happening meeting conference celebration milestone timestamp date',
|
||||
|
||||
// Digital/Content Types
|
||||
[NounType.Document]: 'document file report article paper text pdf word contract agreement record documentation',
|
||||
[NounType.Media]: 'media image photo video audio music podcast multimedia graphic visualization animation',
|
||||
[NounType.File]: 'file digital data binary code script program software archive package bundle',
|
||||
[NounType.Message]: 'message email chat communication notification alert announcement broadcast transmission',
|
||||
[NounType.Content]: 'content information data text material resource publication post blog webpage',
|
||||
|
||||
// Collection Types
|
||||
[NounType.Collection]: 'collection group set list array category folder directory catalog inventory database',
|
||||
[NounType.Dataset]: 'dataset data table spreadsheet database records statistics metrics measurements analysis',
|
||||
|
||||
// Business/Application Types
|
||||
[NounType.Product]: 'product item merchandise offering service feature application software solution package',
|
||||
[NounType.Service]: 'service offering subscription support maintenance utility function capability',
|
||||
[NounType.User]: 'user account profile member subscriber customer client participant identity credentials',
|
||||
[NounType.Task]: 'task action todo item job assignment duty responsibility activity step procedure',
|
||||
[NounType.Project]: 'project initiative program campaign effort endeavor plan scheme venture undertaking',
|
||||
|
||||
// Descriptive Types
|
||||
[NounType.Process]: 'process workflow procedure method algorithm sequence pipeline operation routine protocol',
|
||||
[NounType.State]: 'state status condition phase stage mode situation circumstance configuration setting',
|
||||
[NounType.Role]: 'role position title function responsibility duty job capacity designation authority',
|
||||
[NounType.Topic]: 'topic subject theme category tag keyword area domain field discipline specialty',
|
||||
[NounType.Language]: 'language dialect locale tongue vernacular communication speech linguistics vocabulary',
|
||||
[NounType.Currency]: 'currency money dollar euro pound yen bitcoin payment financial monetary unit',
|
||||
[NounType.Measurement]: 'measurement metric quantity value amount size dimension weight height volume distance',
|
||||
|
||||
// Scientific/Research Types
|
||||
[NounType.Hypothesis]: 'hypothesis theory proposition thesis assumption premise conjecture speculation prediction',
|
||||
[NounType.Experiment]: 'experiment test trial study research investigation analysis observation examination',
|
||||
|
||||
// Legal/Regulatory Types
|
||||
[NounType.Contract]: 'contract agreement deal treaty pact covenant license terms conditions policy',
|
||||
[NounType.Regulation]: 'regulation law rule policy standard compliance requirement guideline ordinance statute',
|
||||
|
||||
// Technical Infrastructure Types
|
||||
[NounType.Interface]: 'interface API endpoint protocol specification contract schema definition connection',
|
||||
[NounType.Resource]: 'resource infrastructure server database storage compute memory bandwidth capacity asset'
|
||||
}
|
||||
|
||||
const VERB_TYPE_DESCRIPTIONS: Record<string, string> = {
|
||||
// Core Relationship Types
|
||||
[VerbType.RelatedTo]: 'related connected associated linked correlated relevant pertinent applicable',
|
||||
[VerbType.Contains]: 'contains includes holds stores encompasses comprises consists incorporates',
|
||||
[VerbType.PartOf]: 'part component element member piece portion section segment constituent',
|
||||
[VerbType.LocatedAt]: 'located situated positioned placed found exists resides occupies',
|
||||
[VerbType.References]: 'references cites mentions points links refers quotes sources',
|
||||
|
||||
// Temporal/Causal Types
|
||||
[VerbType.Precedes]: 'precedes before earlier prior previous antecedent preliminary foregoing',
|
||||
[VerbType.Succeeds]: 'succeeds follows after later subsequent next ensuing succeeding',
|
||||
[VerbType.Causes]: 'causes triggers induces produces generates results influences affects',
|
||||
[VerbType.DependsOn]: 'depends requires needs relies necessitates contingent prerequisite',
|
||||
[VerbType.Requires]: 'requires needs demands necessitates mandates obliges compels entails',
|
||||
|
||||
// Creation/Transformation Types
|
||||
[VerbType.Creates]: 'creates makes produces generates builds constructs forms establishes',
|
||||
[VerbType.Transforms]: 'transforms converts changes modifies alters transitions morphs evolves',
|
||||
[VerbType.Becomes]: 'becomes turns evolves transforms changes transitions develops grows',
|
||||
[VerbType.Modifies]: 'modifies changes updates alters edits revises adjusts adapts',
|
||||
[VerbType.Consumes]: 'consumes uses utilizes depletes expends absorbs takes processes',
|
||||
|
||||
// Ownership/Attribution Types
|
||||
[VerbType.Owns]: 'owns possesses holds controls manages administers governs maintains',
|
||||
[VerbType.AttributedTo]: 'attributed credited assigned ascribed authored written composed',
|
||||
[VerbType.CreatedBy]: 'created made produced generated built developed authored written',
|
||||
[VerbType.BelongsTo]: 'belongs property possession part member affiliate associated owned',
|
||||
|
||||
// Social/Organizational Types
|
||||
[VerbType.MemberOf]: 'member participant affiliate associate belongs joined enrolled registered',
|
||||
[VerbType.WorksWith]: 'works collaborates cooperates partners teams assists helps supports',
|
||||
[VerbType.FriendOf]: 'friend companion buddy pal acquaintance associate connection relationship',
|
||||
[VerbType.Follows]: 'follows subscribes tracks monitors watches observes trails pursues',
|
||||
[VerbType.Likes]: 'likes enjoys appreciates favors prefers admires values endorses',
|
||||
[VerbType.ReportsTo]: 'reports answers subordinate accountable responsible supervised managed',
|
||||
[VerbType.Supervises]: 'supervises manages oversees directs leads controls guides administers',
|
||||
[VerbType.Mentors]: 'mentors teaches guides coaches instructs trains advises counsels',
|
||||
[VerbType.Communicates]: 'communicates talks speaks messages contacts interacts corresponds exchanges',
|
||||
|
||||
// Descriptive/Functional Types
|
||||
[VerbType.Describes]: 'describes explains details documents specifies outlines depicts characterizes',
|
||||
[VerbType.Defines]: 'defines specifies establishes determines sets declares identifies designates',
|
||||
[VerbType.Categorizes]: 'categorizes classifies groups sorts organizes arranges labels tags',
|
||||
[VerbType.Measures]: 'measures quantifies gauges assesses evaluates calculates determines counts',
|
||||
[VerbType.Evaluates]: 'evaluates assesses analyzes reviews examines appraises judges rates',
|
||||
[VerbType.Uses]: 'uses utilizes employs applies operates handles manipulates exploits',
|
||||
[VerbType.Implements]: 'implements executes realizes performs accomplishes carries delivers completes',
|
||||
[VerbType.Extends]: 'extends expands enhances augments amplifies broadens enlarges develops',
|
||||
|
||||
// Enhanced Relationships
|
||||
[VerbType.Inherits]: 'inherits derives extends receives obtains acquires succeeds legacy',
|
||||
[VerbType.Conflicts]: 'conflicts contradicts opposes clashes disputes disagrees incompatible inconsistent',
|
||||
[VerbType.Synchronizes]: 'synchronizes coordinates aligns harmonizes matches corresponds parallels coincides',
|
||||
[VerbType.Competes]: 'competes rivals contends contests challenges opposes vies struggles'
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of type matching with confidence scores
|
||||
*/
|
||||
export interface TypeMatchResult {
|
||||
type: string
|
||||
confidence: number
|
||||
reasoning: string
|
||||
alternatives: Array<{
|
||||
type: string
|
||||
confidence: number
|
||||
}>
|
||||
}
|
||||
|
||||
/**
|
||||
* Intelligent Type Matcher using semantic embeddings
|
||||
*/
|
||||
export class IntelligentTypeMatcher {
|
||||
private embedder: TransformerEmbedding
|
||||
private nounEmbeddings: Map<string, Vector> = new Map()
|
||||
private verbEmbeddings: Map<string, Vector> = new Map()
|
||||
private initialized = false
|
||||
private cache: Map<string, TypeMatchResult> = new Map()
|
||||
|
||||
constructor() {
|
||||
this.embedder = new TransformerEmbedding({ verbose: false })
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the type matcher by generating embeddings for all types
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
if (this.initialized) return
|
||||
|
||||
await this.embedder.init()
|
||||
|
||||
// Generate embeddings for noun types
|
||||
for (const [type, description] of Object.entries(NOUN_TYPE_DESCRIPTIONS)) {
|
||||
const embedding = await this.embedder.embed(description)
|
||||
this.nounEmbeddings.set(type, embedding)
|
||||
}
|
||||
|
||||
// Generate embeddings for verb types
|
||||
for (const [type, description] of Object.entries(VERB_TYPE_DESCRIPTIONS)) {
|
||||
const embedding = await this.embedder.embed(description)
|
||||
this.verbEmbeddings.set(type, embedding)
|
||||
}
|
||||
|
||||
this.initialized = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Match an object to the most appropriate noun type
|
||||
*/
|
||||
async matchNounType(obj: any): Promise<TypeMatchResult> {
|
||||
await this.init()
|
||||
|
||||
// Create a text representation of the object for embedding
|
||||
const textRepresentation = this.createTextRepresentation(obj)
|
||||
|
||||
// Check cache
|
||||
const cacheKey = `noun:${textRepresentation}`
|
||||
if (this.cache.has(cacheKey)) {
|
||||
return this.cache.get(cacheKey)!
|
||||
}
|
||||
|
||||
// Generate embedding for the input
|
||||
const inputEmbedding = await this.embedder.embed(textRepresentation)
|
||||
|
||||
// Calculate similarities to all noun types
|
||||
const similarities: Array<{ type: string; similarity: number }> = []
|
||||
|
||||
for (const [type, typeEmbedding] of this.nounEmbeddings.entries()) {
|
||||
// Convert cosine distance to similarity (1 - distance)
|
||||
const similarity = 1 - cosineDistance(inputEmbedding, typeEmbedding)
|
||||
similarities.push({ type, similarity })
|
||||
}
|
||||
|
||||
// Sort by similarity (highest first)
|
||||
similarities.sort((a, b) => b.similarity - a.similarity)
|
||||
|
||||
// Apply heuristic rules for common patterns
|
||||
const heuristicType = this.applyNounHeuristics(obj)
|
||||
if (heuristicType) {
|
||||
// Boost the heuristic type's confidence
|
||||
const heuristicIndex = similarities.findIndex(s => s.type === heuristicType)
|
||||
if (heuristicIndex > 0) {
|
||||
similarities[heuristicIndex].similarity *= 1.2 // 20% boost
|
||||
similarities.sort((a, b) => b.similarity - a.similarity)
|
||||
}
|
||||
}
|
||||
|
||||
// Create result
|
||||
const result: TypeMatchResult = {
|
||||
type: similarities[0].type,
|
||||
confidence: similarities[0].similarity,
|
||||
reasoning: this.generateReasoning(obj, similarities[0].type, 'noun'),
|
||||
alternatives: similarities.slice(1, 4).map(s => ({
|
||||
type: s.type,
|
||||
confidence: s.similarity
|
||||
}))
|
||||
}
|
||||
|
||||
// Cache result
|
||||
this.cache.set(cacheKey, result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Match a relationship to the most appropriate verb type
|
||||
*/
|
||||
async matchVerbType(
|
||||
sourceObj: any,
|
||||
targetObj: any,
|
||||
relationshipHint?: string
|
||||
): Promise<TypeMatchResult> {
|
||||
await this.init()
|
||||
|
||||
// Create text representation of the relationship
|
||||
const textRepresentation = this.createRelationshipText(sourceObj, targetObj, relationshipHint)
|
||||
|
||||
// Check cache
|
||||
const cacheKey = `verb:${textRepresentation}`
|
||||
if (this.cache.has(cacheKey)) {
|
||||
return this.cache.get(cacheKey)!
|
||||
}
|
||||
|
||||
// Generate embedding
|
||||
const inputEmbedding = await this.embedder.embed(textRepresentation)
|
||||
|
||||
// Calculate similarities to all verb types
|
||||
const similarities: Array<{ type: string; similarity: number }> = []
|
||||
|
||||
for (const [type, typeEmbedding] of this.verbEmbeddings.entries()) {
|
||||
const similarity = 1 - cosineDistance(inputEmbedding, typeEmbedding)
|
||||
similarities.push({ type, similarity })
|
||||
}
|
||||
|
||||
// Sort by similarity
|
||||
similarities.sort((a, b) => b.similarity - a.similarity)
|
||||
|
||||
// Apply heuristic rules
|
||||
const heuristicType = this.applyVerbHeuristics(sourceObj, targetObj, relationshipHint)
|
||||
if (heuristicType) {
|
||||
const heuristicIndex = similarities.findIndex(s => s.type === heuristicType)
|
||||
if (heuristicIndex > 0) {
|
||||
similarities[heuristicIndex].similarity *= 1.2
|
||||
similarities.sort((a, b) => b.similarity - a.similarity)
|
||||
}
|
||||
}
|
||||
|
||||
// Create result
|
||||
const result: TypeMatchResult = {
|
||||
type: similarities[0].type,
|
||||
confidence: similarities[0].similarity,
|
||||
reasoning: this.generateReasoning(
|
||||
{ source: sourceObj, target: targetObj, hint: relationshipHint },
|
||||
similarities[0].type,
|
||||
'verb'
|
||||
),
|
||||
alternatives: similarities.slice(1, 4).map(s => ({
|
||||
type: s.type,
|
||||
confidence: s.similarity
|
||||
}))
|
||||
}
|
||||
|
||||
// Cache result
|
||||
this.cache.set(cacheKey, result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Create text representation of an object for embedding
|
||||
*/
|
||||
private createTextRepresentation(obj: any): string {
|
||||
const parts: string[] = []
|
||||
|
||||
// Add type if available
|
||||
if (typeof obj === 'object' && obj !== null) {
|
||||
// Add field names and values
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
parts.push(key)
|
||||
if (typeof value === 'string') {
|
||||
parts.push(value.slice(0, 100)) // Limit string length
|
||||
} else if (typeof value === 'number' || typeof value === 'boolean') {
|
||||
parts.push(String(value))
|
||||
}
|
||||
}
|
||||
|
||||
// Add special fields with higher weight
|
||||
const importantFields = ['type', 'kind', 'category', 'class', 'name', 'title', 'description']
|
||||
for (const field of importantFields) {
|
||||
if (obj[field]) {
|
||||
parts.push(String(obj[field]))
|
||||
parts.push(String(obj[field])) // Double weight for important fields
|
||||
}
|
||||
}
|
||||
} else if (typeof obj === 'string') {
|
||||
parts.push(obj)
|
||||
} else {
|
||||
parts.push(String(obj))
|
||||
}
|
||||
|
||||
return parts.join(' ')
|
||||
}
|
||||
|
||||
/**
|
||||
* Create text representation of a relationship
|
||||
*/
|
||||
private createRelationshipText(
|
||||
sourceObj: any,
|
||||
targetObj: any,
|
||||
relationshipHint?: string
|
||||
): string {
|
||||
const parts: string[] = []
|
||||
|
||||
if (relationshipHint) {
|
||||
parts.push(relationshipHint)
|
||||
parts.push(relationshipHint) // Double weight for explicit hint
|
||||
}
|
||||
|
||||
// Add source context
|
||||
if (sourceObj) {
|
||||
parts.push('source:')
|
||||
parts.push(this.getObjectSummary(sourceObj))
|
||||
}
|
||||
|
||||
// Add target context
|
||||
if (targetObj) {
|
||||
parts.push('target:')
|
||||
parts.push(this.getObjectSummary(targetObj))
|
||||
}
|
||||
|
||||
return parts.join(' ')
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a brief summary of an object
|
||||
*/
|
||||
private getObjectSummary(obj: any): string {
|
||||
if (typeof obj === 'string') return obj.slice(0, 50)
|
||||
if (typeof obj !== 'object' || obj === null) return String(obj)
|
||||
|
||||
const summary: string[] = []
|
||||
const fields = ['type', 'name', 'title', 'id', 'category', 'kind']
|
||||
|
||||
for (const field of fields) {
|
||||
if (obj[field]) {
|
||||
summary.push(String(obj[field]))
|
||||
}
|
||||
}
|
||||
|
||||
return summary.join(' ').slice(0, 100)
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply heuristic rules for noun type detection
|
||||
*/
|
||||
private applyNounHeuristics(obj: any): string | null {
|
||||
if (typeof obj !== 'object' || obj === null) return null
|
||||
|
||||
// Person heuristics
|
||||
if (obj.email || obj.firstName || obj.lastName || obj.username || obj.age || obj.gender) {
|
||||
return NounType.Person
|
||||
}
|
||||
|
||||
// Organization heuristics
|
||||
if (obj.companyName || obj.organizationId || obj.employees || obj.industry) {
|
||||
return NounType.Organization
|
||||
}
|
||||
|
||||
// Location heuristics
|
||||
if (obj.latitude || obj.longitude || obj.address || obj.city || obj.country || obj.coordinates) {
|
||||
return NounType.Location
|
||||
}
|
||||
|
||||
// Document heuristics
|
||||
if (obj.content && (obj.title || obj.author) || obj.documentType || obj.pages) {
|
||||
return NounType.Document
|
||||
}
|
||||
|
||||
// Event heuristics
|
||||
if (obj.startTime || obj.endTime || obj.date || obj.eventType || obj.attendees) {
|
||||
return NounType.Event
|
||||
}
|
||||
|
||||
// Product heuristics
|
||||
if (obj.price || obj.sku || obj.inventory || obj.productId) {
|
||||
return NounType.Product
|
||||
}
|
||||
|
||||
// Task heuristics
|
||||
if (obj.status && (obj.assignee || obj.dueDate) || obj.priority || obj.completed !== undefined) {
|
||||
return NounType.Task
|
||||
}
|
||||
|
||||
// Media heuristics
|
||||
if (obj.url && (obj.url.match(/\.(jpg|jpeg|png|gif|mp4|mp3|wav)/i))) {
|
||||
return NounType.Media
|
||||
}
|
||||
|
||||
// Dataset heuristics
|
||||
if (Array.isArray(obj.data) || obj.rows || obj.columns || obj.schema) {
|
||||
return NounType.Dataset
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply heuristic rules for verb type detection
|
||||
*/
|
||||
private applyVerbHeuristics(
|
||||
sourceObj: any,
|
||||
targetObj: any,
|
||||
relationshipHint?: string
|
||||
): string | null {
|
||||
if (!relationshipHint) return null
|
||||
|
||||
const hint = relationshipHint.toLowerCase()
|
||||
|
||||
// Ownership patterns
|
||||
if (hint.includes('own') || hint.includes('possess') || hint.includes('has')) {
|
||||
return VerbType.Owns
|
||||
}
|
||||
|
||||
// Creation patterns
|
||||
if (hint.includes('create') || hint.includes('made') || hint.includes('authored')) {
|
||||
return VerbType.Creates
|
||||
}
|
||||
|
||||
// Containment patterns
|
||||
if (hint.includes('contain') || hint.includes('include') || hint.includes('has')) {
|
||||
return VerbType.Contains
|
||||
}
|
||||
|
||||
// Membership patterns
|
||||
if (hint.includes('member') || hint.includes('belong') || hint.includes('part')) {
|
||||
return VerbType.MemberOf
|
||||
}
|
||||
|
||||
// Reference patterns
|
||||
if (hint.includes('refer') || hint.includes('cite') || hint.includes('link')) {
|
||||
return VerbType.References
|
||||
}
|
||||
|
||||
// Dependency patterns
|
||||
if (hint.includes('depend') || hint.includes('require') || hint.includes('need')) {
|
||||
return VerbType.DependsOn
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate human-readable reasoning for the type selection
|
||||
*/
|
||||
private generateReasoning(
|
||||
obj: any,
|
||||
selectedType: string,
|
||||
typeKind: 'noun' | 'verb'
|
||||
): string {
|
||||
const descriptions = typeKind === 'noun' ? NOUN_TYPE_DESCRIPTIONS : VERB_TYPE_DESCRIPTIONS
|
||||
const typeDesc = descriptions[selectedType]
|
||||
|
||||
if (typeKind === 'noun') {
|
||||
const fields = Object.keys(obj).slice(0, 3).join(', ')
|
||||
return `Matched to ${selectedType} based on semantic similarity to "${typeDesc.split(' ').slice(0, 5).join(' ')}..." and object fields: ${fields}`
|
||||
} else {
|
||||
return `Matched to ${selectedType} based on semantic similarity to "${typeDesc.split(' ').slice(0, 5).join(' ')}..." and relationship context`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache
|
||||
*/
|
||||
clearCache(): void {
|
||||
this.cache.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of resources
|
||||
*/
|
||||
async dispose(): Promise<void> {
|
||||
await this.embedder.dispose()
|
||||
this.cache.clear()
|
||||
this.nounEmbeddings.clear()
|
||||
this.verbEmbeddings.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton instance for efficient reuse
|
||||
*/
|
||||
let globalMatcher: IntelligentTypeMatcher | null = null
|
||||
|
||||
/**
|
||||
* Get or create the global type matcher instance
|
||||
*/
|
||||
export async function getTypeMatcher(): Promise<IntelligentTypeMatcher> {
|
||||
if (!globalMatcher) {
|
||||
globalMatcher = new IntelligentTypeMatcher()
|
||||
await globalMatcher.init()
|
||||
}
|
||||
return globalMatcher
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue