rust-kgdb 0.8.21 → 0.8.23

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.
@@ -46,26 +46,42 @@ function loadNativeBindingDirect() {
46
46
  }
47
47
 
48
48
  const native = loadNativeBindingDirect()
49
+
50
+ // Native Rust bindings - SDK is THIN, only pure functions
51
+ // GraphDB handles all RDF/SPARQL operations
52
+ // tokenizeIdentifier and computeSimilarity are pure utility functions
49
53
  const {
50
- OlogSchema,
51
- PredicateResolverService,
52
- SchemaValidatorService,
53
- ThinkingReasoner: NativeThinkingReasoner,
54
- computeSimilarity,
55
54
  tokenizeIdentifier,
56
- stemWord,
57
- extractKeywords: nativeExtractKeywords
55
+ computeSimilarity
58
56
  } = native
59
57
 
60
58
  /**
61
- * Extract keywords from natural language prompt using native Rust
62
- * Delegates entirely to Rust KeywordExtractor - no JavaScript stop words
59
+ * Extract keywords from natural language prompt
60
+ * Uses schema predicates for domain-aware extraction
63
61
  * @param {string} prompt - Natural language prompt
62
+ * @param {string[]} schemaPredicates - Optional schema predicates for domain hints
64
63
  * @returns {string[]} Extracted keywords
65
64
  */
66
- function extractKeywords(prompt) {
65
+ function extractKeywords(prompt, schemaPredicates = []) {
67
66
  if (!prompt) return []
68
- return nativeExtractKeywords(prompt)
67
+ // Split on whitespace and filter short words
68
+ const words = prompt.toLowerCase()
69
+ .replace(/[^\w\s]/g, ' ')
70
+ .split(/\s+/)
71
+ .filter(w => w.length > 2)
72
+
73
+ // If schema predicates provided, boost domain-relevant keywords
74
+ if (schemaPredicates.length > 0) {
75
+ const predicateWords = new Set()
76
+ for (const pred of schemaPredicates) {
77
+ const tokens = tokenizeIdentifier ? tokenizeIdentifier(pred.split('/').pop().split('#').pop()) : []
78
+ tokens.forEach(t => predicateWords.add(t.toLowerCase()))
79
+ }
80
+ // Return words that match schema or are content words
81
+ return words.filter(w => predicateWords.has(w) || w.length > 3)
82
+ }
83
+
84
+ return words
69
85
  }
70
86
 
71
87
  // ============================================================================
@@ -838,12 +854,12 @@ class SchemaContext {
838
854
  // STRATEGY 2: Extract RDFS/OWL explicit schema (if VoID incomplete)
839
855
  if (ctx.classes.size < 10) {
840
856
  const classQuery = `
841
- PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
842
- PREFIX owl: <http://www.w3.org/2002/07/owl#>
843
857
  SELECT DISTINCT ?class ?super ?label WHERE {
844
- { ?class a rdfs:Class } UNION { ?class a owl:Class }
845
- OPTIONAL { ?class rdfs:subClassOf ?super }
846
- OPTIONAL { ?class rdfs:label ?label }
858
+ { ?class <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2000/01/rdf-schema#Class> }
859
+ UNION
860
+ { ?class <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2002/07/owl#Class> }
861
+ OPTIONAL { ?class <http://www.w3.org/2000/01/rdf-schema#subClassOf> ?super }
862
+ OPTIONAL { ?class <http://www.w3.org/2000/01/rdf-schema#label> ?label }
847
863
  } LIMIT ${config.maxClasses}
848
864
  `
849
865
  const classResults = kg.querySelect(classQuery)
@@ -860,14 +876,15 @@ class SchemaContext {
860
876
  // STRATEGY 3: Extract property morphisms with domain/range
861
877
  if (ctx.properties.size < 10) {
862
878
  const propQuery = `
863
- PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
864
- PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
865
- PREFIX owl: <http://www.w3.org/2002/07/owl#>
866
879
  SELECT DISTINCT ?prop ?domain ?range ?label WHERE {
867
- { ?prop a rdf:Property } UNION { ?prop a owl:ObjectProperty } UNION { ?prop a owl:DatatypeProperty }
868
- OPTIONAL { ?prop rdfs:domain ?domain }
869
- OPTIONAL { ?prop rdfs:range ?range }
870
- OPTIONAL { ?prop rdfs:label ?label }
880
+ { ?prop <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/1999/02/22-rdf-syntax-ns#Property> }
881
+ UNION
882
+ { ?prop <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2002/07/owl#ObjectProperty> }
883
+ UNION
884
+ { ?prop <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2002/07/owl#DatatypeProperty> }
885
+ OPTIONAL { ?prop <http://www.w3.org/2000/01/rdf-schema#domain> ?domain }
886
+ OPTIONAL { ?prop <http://www.w3.org/2000/01/rdf-schema#range> ?range }
887
+ OPTIONAL { ?prop <http://www.w3.org/2000/01/rdf-schema#label> ?label }
871
888
  } LIMIT ${config.maxProperties}
872
889
  `
873
890
  const propResults = kg.querySelect(propQuery)
@@ -922,11 +939,9 @@ class SchemaContext {
922
939
  //
923
940
  // ALWAYS extract entities - they are essential for entity resolution
924
941
  const entityQuery = `
925
- PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
926
- PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
927
942
  SELECT DISTINCT ?entity ?label ?type WHERE {
928
- ?entity rdfs:label ?label .
929
- OPTIONAL { ?entity a ?type }
943
+ ?entity <http://www.w3.org/2000/01/rdf-schema#label> ?label .
944
+ OPTIONAL { ?entity <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> ?type }
930
945
  } LIMIT ${config.maxEntities || 1000}
931
946
  `
932
947
  try {
@@ -1037,15 +1052,13 @@ class SchemaContext {
1037
1052
  try {
1038
1053
  // Extract classes (Objects in schema category)
1039
1054
  const classQuery = `
1040
- PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
1041
- PREFIX owl: <http://www.w3.org/2002/07/owl#>
1042
- PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
1043
1055
  SELECT DISTINCT ?class ?super ?label ?comment WHERE {
1044
- { ?class a rdfs:Class }
1045
- UNION { ?class a owl:Class }
1046
- OPTIONAL { ?class rdfs:subClassOf ?super }
1047
- OPTIONAL { ?class rdfs:label ?label }
1048
- OPTIONAL { ?class rdfs:comment ?comment }
1056
+ { ?class <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2000/01/rdf-schema#Class> }
1057
+ UNION
1058
+ { ?class <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2002/07/owl#Class> }
1059
+ OPTIONAL { ?class <http://www.w3.org/2000/01/rdf-schema#subClassOf> ?super }
1060
+ OPTIONAL { ?class <http://www.w3.org/2000/01/rdf-schema#label> ?label }
1061
+ OPTIONAL { ?class <http://www.w3.org/2000/01/rdf-schema#comment> ?comment }
1049
1062
  } LIMIT ${CONFIG.schema.maxClasses}
1050
1063
  `
1051
1064
  const classResults = loadedKg.querySelect(classQuery)
@@ -1069,17 +1082,15 @@ class SchemaContext {
1069
1082
 
1070
1083
  // Extract properties (Morphisms with domain/range)
1071
1084
  const propQuery = `
1072
- PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
1073
- PREFIX owl: <http://www.w3.org/2002/07/owl#>
1074
- PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
1075
- SELECT DISTINCT ?prop ?domain ?range ?label ?functional WHERE {
1076
- { ?prop a rdf:Property }
1077
- UNION { ?prop a owl:ObjectProperty }
1078
- UNION { ?prop a owl:DatatypeProperty }
1079
- OPTIONAL { ?prop rdfs:domain ?domain }
1080
- OPTIONAL { ?prop rdfs:range ?range }
1081
- OPTIONAL { ?prop rdfs:label ?label }
1082
- OPTIONAL { ?prop a owl:FunctionalProperty . BIND(true AS ?functional) }
1085
+ SELECT DISTINCT ?prop ?domain ?range ?label WHERE {
1086
+ { ?prop <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/1999/02/22-rdf-syntax-ns#Property> }
1087
+ UNION
1088
+ { ?prop <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2002/07/owl#ObjectProperty> }
1089
+ UNION
1090
+ { ?prop <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2002/07/owl#DatatypeProperty> }
1091
+ OPTIONAL { ?prop <http://www.w3.org/2000/01/rdf-schema#domain> ?domain }
1092
+ OPTIONAL { ?prop <http://www.w3.org/2000/01/rdf-schema#range> ?range }
1093
+ OPTIONAL { ?prop <http://www.w3.org/2000/01/rdf-schema#label> ?label }
1083
1094
  } LIMIT ${CONFIG.schema.maxProperties}
1084
1095
  `
1085
1096
  const propResults = loadedKg.querySelect(propQuery)
@@ -1088,14 +1099,13 @@ class SchemaContext {
1088
1099
  const domain = r.bindings?.domain || r.domain
1089
1100
  const range = r.bindings?.range || r.range
1090
1101
  const label = r.bindings?.label || r.label
1091
- const functional = r.bindings?.functional || r.functional
1092
1102
  if (prop) {
1093
1103
  ctx.properties.set(prop, {
1094
1104
  uri: prop,
1095
1105
  domain: domain || null,
1096
1106
  range: range || null,
1097
1107
  label: label || null,
1098
- functional: !!functional,
1108
+ functional: false,
1099
1109
  source
1100
1110
  })
1101
1111
  }
@@ -2942,100 +2952,18 @@ class LLMPlanner {
2942
2952
  o: r.bindings?.o || r.o
2943
2953
  }))
2944
2954
 
2945
- // Initialize predicate resolver (native Rust - NO JavaScript fallback per NO FALLBACKS principle)
2946
- const threshold = CONFIG.scoring?.similarityThreshold || 0.3
2947
- if (native?.OlogSchema && native?.PredicateResolverService) {
2948
- try {
2949
- // Build OlogSchema from extracted schema
2950
- const olog = new native.OlogSchema()
2951
- olog.withNamespace('http://schema.org/')
2952
-
2953
- // Add classes
2954
- for (const cls of (schema.classes || [])) {
2955
- try {
2956
- const localName = cls.split('/').pop().split('#').pop()
2957
- olog.addClass(localName)
2958
- } catch (e) { /* skip invalid class */ }
2959
- }
2960
-
2961
- // Add properties with aliases extracted from local names
2962
- for (const prop of (schema.predicates || [])) {
2963
- try {
2964
- const localName = prop.split('/').pop().split('#').pop()
2965
- // Generate aliases from tokenized form
2966
- const tokens = native.tokenizeIdentifier(localName)
2967
- const aliases = tokens.length > 1 ? [tokens.join(''), tokens.join('_')] : []
2968
- olog.addProperty(prop, 'Thing', 'Thing', [localName, ...aliases])
2969
- } catch (e) { /* skip invalid property */ }
2970
- }
2971
-
2972
- olog.build()
2973
-
2974
- // ============================================================
2975
- // ENTITY RESOLUTION: Populate Olog with entities from RDF data
2976
- // This enables NL entity references like "Fifth Amendment" to
2977
- // resolve to canonical URIs like "legal:FifthAmendment"
2978
- //
2979
- // CRITICAL FIX (2025-12-23): Use SchemaContext's extracted entities
2980
- // The entities extracted with Strategy 6 (rdfs:label) are now used
2981
- // to populate the Olog's label_to_entity map for O(1) lookup.
2982
- // ============================================================
2983
- try {
2984
- let entityCount = 0
2985
-
2986
- // PRIMARY: Use SchemaContext entities (extracted via Strategy 6)
2987
- // These are the entities with rdfs:label that we need for resolution
2988
- // Use this._schemaContext if available (from getSchemaContext())
2989
- const schemaCtx = this._schemaContext || await this.getSchemaContext?.()
2990
- if (schemaCtx && schemaCtx.entities && schemaCtx.entities.size > 0) {
2991
- const entityTriples = []
2992
- for (const [uri, info] of schemaCtx.entities) {
2993
- // Create triples for entity labels
2994
- if (info.label) {
2995
- entityTriples.push([uri, 'http://www.w3.org/2000/01/rdf-schema#label', info.label])
2996
- }
2997
- if (info.type) {
2998
- entityTriples.push([uri, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', info.type])
2999
- }
3000
- }
3001
- if (entityTriples.length > 0 && olog.populateEntitiesFromTriples) {
3002
- entityCount = olog.populateEntitiesFromTriples(JSON.stringify(entityTriples))
3003
- schema._entityCount = entityCount
3004
- schema._entitySource = 'schemaContext'
3005
- }
3006
- }
3007
-
3008
- // FALLBACK: Query triples if SchemaContext has no entities
3009
- if (entityCount === 0 && this.kg && typeof this.kg.querySelect === 'function') {
3010
- const allTriples = this.kg.querySelect('SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 10000')
3011
- if (allTriples && allTriples.length > 0) {
3012
- // Convert to triple array format expected by Rust
3013
- const triplesArray = allTriples.map(result => [
3014
- result.bindings?.s || result.bindings?.subject || '',
3015
- result.bindings?.p || result.bindings?.predicate || '',
3016
- result.bindings?.o || result.bindings?.object || ''
3017
- ]).filter(t => t[0] && t[1] && t[2])
3018
-
3019
- // Populate entities in Olog using Rust extraction
3020
- if (triplesArray.length > 0 && olog.populateEntitiesFromTriples) {
3021
- entityCount = olog.populateEntitiesFromTriples(JSON.stringify(triplesArray))
3022
- schema._entityCount = entityCount
3023
- schema._entitySource = 'triples'
3024
- }
3025
- }
3026
- }
3027
- } catch (entityErr) {
3028
- // Entity extraction is optional - continue without it
3029
- schema._entityExtractionError = entityErr.message
2955
+ // Build predicate index for fast keyword→URI lookup
2956
+ // NO STATE - computed fresh from schema.predicates
2957
+ schema._predicateIndex = new Map()
2958
+ for (const pred of (schema.predicates || [])) {
2959
+ const localName = pred.split('/').pop().split('#').pop()
2960
+ const tokens = tokenizeIdentifier ? tokenizeIdentifier(localName) : [localName.toLowerCase()]
2961
+ // Index by local name and tokens
2962
+ schema._predicateIndex.set(localName.toLowerCase(), pred)
2963
+ for (const token of tokens) {
2964
+ if (token.length > 2) {
2965
+ schema._predicateIndex.set(token.toLowerCase(), pred)
3030
2966
  }
3031
-
3032
- schema._nativeResolver = new native.PredicateResolverService(olog, threshold)
3033
- schema._nativeOlog = olog
3034
- } catch (e) {
3035
- // NO FALLBACKS - propagate error with context
3036
- console.error('[extractSchema] Native resolver initialization failed:', e.message)
3037
- schema._nativeResolverError = e.message
3038
- schema._nativeResolver = null
3039
2967
  }
3040
2968
  }
3041
2969
 
@@ -3214,10 +3142,15 @@ ${schemaText}
3214
3142
  ${memoryText}
3215
3143
 
3216
3144
  RULES:
3217
- - ONLY use predicates from the schema above
3218
- - NEVER invent predicate names
3219
- - If schema doesn't match user's request, set intent to "schema_mismatch"
3220
- - Use proper SPARQL syntax
3145
+ - Use predicates from the schema to construct SPARQL queries
3146
+ - For pattern queries (fraud rings, collusion, networks, relationships):
3147
+ - Map semantic intent to relationship predicates (e.g., 'knows', 'referredBy', 'claimsWith')
3148
+ - Generate triangle/cycle patterns: ?a :knows ?b . ?b :knows ?c . ?c :knows ?a
3149
+ - A "fraud ring" = entities connected in cycles via relationship predicates
3150
+ - For risk queries, use 'riskScore' with FILTER (e.g., FILTER(?score > 0.7))
3151
+ - For similarity queries, look for shared attributes (same address, overlapping claims)
3152
+ - Always return valid SPARQL using actual schema predicates
3153
+ - Use proper SPARQL 1.1 syntax with correct prefixes
3221
3154
 
3222
3155
  Respond in JSON:
3223
3156
  {
@@ -3332,14 +3265,26 @@ Intent types: detect_fraud, find_similar, explain, find_patterns, aggregate, gen
3332
3265
 
3333
3266
  // Generate SPARQL based on intent and schema
3334
3267
  if (intent.query || intent.compliance || intent.aggregate) {
3335
- const sparql = this._generateSchemaSparql(intent, schema, context)
3336
- steps.push({
3337
- id: stepId++,
3338
- tool: 'kg.sparql.query',
3339
- input_type: 'Query',
3340
- output_type: 'BindingSet',
3341
- args: { sparql }
3342
- })
3268
+ let sparql = null
3269
+
3270
+ // Try schema-driven SPARQL generation first (fast, deterministic)
3271
+ try {
3272
+ sparql = this._generateSchemaSparql(intent, schema, context)
3273
+ } catch (schemaErr) {
3274
+ // Keyword matching failed - return empty steps (let LLM handle in call())
3275
+ // This is NOT a fallback - complex queries go through LLM path
3276
+ sparql = null
3277
+ }
3278
+
3279
+ if (sparql) {
3280
+ steps.push({
3281
+ id: stepId++,
3282
+ tool: 'kg.sparql.query',
3283
+ input_type: 'Query',
3284
+ output_type: 'BindingSet',
3285
+ args: { sparql }
3286
+ })
3287
+ }
3343
3288
  }
3344
3289
 
3345
3290
  if (intent.pattern) {
@@ -3426,39 +3371,77 @@ Intent types: detect_fraud, find_similar, explain, find_patterns, aggregate, gen
3426
3371
  }
3427
3372
 
3428
3373
  // ============================================================
3429
- // DELEGATE TO RUST: HyperFederate PredicateResolverService
3430
- //
3431
- // ALL query generation delegated to Rust. NO JavaScript fallbacks.
3432
- // The Rust PredicateResolver.generate_federated_sql() handles:
3433
- // - Entity resolution (NL → URI)
3434
- // - Predicate resolution (NL → schema morphism)
3435
- // - SPARQL pattern generation (category theory composition)
3436
- // - SQL wrapping with HyperFederate UDFs
3374
+ // SCHEMA-DRIVEN SPARQL GENERATION
3375
+ // Uses schema._predicateIndex built during extractSchema
3376
+ // NO FALLBACKS - requires valid schema match
3437
3377
  // ============================================================
3438
3378
 
3439
- if (!schema._nativeResolver || typeof schema._nativeResolver.generateFederatedSql !== 'function') {
3379
+ const predicateIndex = schema._predicateIndex || new Map()
3380
+ const predicates = schema.predicates || []
3381
+
3382
+ if (predicates.length === 0) {
3440
3383
  throw new Error(JSON.stringify({
3441
- type: 'ConfigurationError',
3442
- message: 'Native HyperFederate resolver not configured.',
3443
- suggestion: 'Ensure schema is built with OlogSchema and PredicateResolverService initialized.',
3444
- recoverable: false
3384
+ type: 'SchemaError',
3385
+ message: 'No schema predicates available.',
3386
+ suggestion: 'Load data with valid RDF predicates first.',
3387
+ recoverable: true
3445
3388
  }))
3446
3389
  }
3447
3390
 
3448
- const result = JSON.parse(schema._nativeResolver.generateFederatedSql(prompt, limit))
3449
- context._federatedSqlResult = result
3391
+ // Extract keywords from prompt
3392
+ const keywords = extractKeywords(prompt, predicates)
3393
+
3394
+ // Find ALL matching predicates using tokenized comparison
3395
+ const matches = []
3396
+ for (const keyword of keywords) {
3397
+ const kwLower = keyword.toLowerCase()
3450
3398
 
3451
- if (result.confidence < 0.3) {
3452
- context._resolutionWarning = {
3453
- type: 'LowConfidence',
3454
- confidence: result.confidence,
3455
- pattern: result.pattern,
3456
- resolved_predicates: result.resolved_predicates,
3457
- suggestion: 'Entities/predicates may not exist in schema. Check data population.'
3399
+ // Direct index lookup (index has tokenized predicate names)
3400
+ if (predicateIndex.has(kwLower)) {
3401
+ matches.push({ predicate: predicateIndex.get(kwLower), score: 1.0, keyword })
3402
+ continue
3458
3403
  }
3404
+
3405
+ // Token-based matching: tokenize predicate and check for substring/exact match
3406
+ for (const pred of predicates) {
3407
+ const localName = pred.split('/').pop().split('#').pop()
3408
+ const tokens = tokenizeIdentifier ? tokenizeIdentifier(localName) : [localName.toLowerCase()]
3409
+
3410
+ for (const token of tokens) {
3411
+ if (token === kwLower) {
3412
+ matches.push({ predicate: pred, score: 1.0, keyword })
3413
+ break
3414
+ } else if (token.includes(kwLower) || kwLower.includes(token)) {
3415
+ matches.push({ predicate: pred, score: 0.7, keyword })
3416
+ break
3417
+ }
3418
+ }
3419
+ }
3420
+ }
3421
+
3422
+ if (matches.length === 0) {
3423
+ throw new Error(JSON.stringify({
3424
+ type: 'NoMatchError',
3425
+ message: `No schema predicates match prompt: "${prompt}"`,
3426
+ keywords,
3427
+ availablePredicates: predicates.slice(0, 10),
3428
+ suggestion: 'Rephrase query using predicates from schema.',
3429
+ recoverable: true
3430
+ }))
3459
3431
  }
3460
3432
 
3461
- return result.sparql
3433
+ // Sort by score and get best match
3434
+ matches.sort((a, b) => b.score - a.score)
3435
+ const bestMatch = matches[0]
3436
+
3437
+ // Build SPARQL from schema predicate
3438
+ const sparql = `SELECT ?subject ?object WHERE { ?subject <${bestMatch.predicate}> ?object } LIMIT ${limit}`
3439
+ context._matchedPredicate = bestMatch.predicate
3440
+ context._matchConfidence = bestMatch.score
3441
+ context._matchKeyword = bestMatch.keyword
3442
+ context._allMatches = matches.slice(0, 5)
3443
+
3444
+ return sparql
3462
3445
  }
3463
3446
 
3464
3447
  /**
@@ -4491,19 +4474,9 @@ class ThinkingReasoner {
4491
4474
  this.contextId = config.contextId || `thinking-${Date.now()}`
4492
4475
  this.actorId = config.actorId || 'hypermind-agent'
4493
4476
 
4494
- // NATIVE RUST DELEGATION: Use native ThinkingReasoner for real reasoning
4495
- // The JavaScript class is a thin wrapper - all heavy lifting in Rust
4496
- // NativeThinkingReasoner is imported at module level from the native binding
4497
- if (NativeThinkingReasoner) {
4498
- try {
4499
- this._native = new NativeThinkingReasoner()
4500
- this._hasNative = true
4501
- } catch (e) {
4502
- this._hasNative = false
4503
- }
4504
- } else {
4505
- this._hasNative = false
4506
- }
4477
+ // Native reasoning not available in thin SDK
4478
+ // All reasoning handled via SPARQL and schema-driven approach
4479
+ this._hasNative = false
4507
4480
 
4508
4481
  // Fallback stores (only used if native not available)
4509
4482
  this.events = []
@@ -5774,7 +5747,37 @@ class HyperMindAgent {
5774
5747
  trace.addStep({ type: 'intent_classification', intent })
5775
5748
 
5776
5749
  // 3. Generate typed execution plan
5777
- const plan = this._generatePlan(intent, prompt)
5750
+ let plan
5751
+ try {
5752
+ plan = this._generatePlan(intent, prompt)
5753
+ } catch (planErr) {
5754
+ // Schema-based SPARQL generation failed - try LLM for semantic understanding
5755
+ // This is NOT a fallback, it's the proper path for complex queries
5756
+ if (this.apiKey && this.planner && this.planner.model) {
5757
+ const schema = this.planner._schemaCache || { predicates: [], classes: [] }
5758
+ const llmResult = await this.planner._planWithLLM(prompt, schema, memories)
5759
+ if (llmResult && llmResult.sparql) {
5760
+ // Create plan with LLM-generated SPARQL
5761
+ plan = {
5762
+ id: `plan_llm_${Date.now()}`,
5763
+ intent: llmResult.type || intent.type,
5764
+ steps: [{
5765
+ id: 1,
5766
+ tool: 'kg.sparql.query',
5767
+ args: { sparql: llmResult.sparql }
5768
+ }],
5769
+ type_chain: 'kg.sparql.query',
5770
+ _llmGenerated: true
5771
+ }
5772
+ trace.addStep({ type: 'llm_sparql_generation', sparql: llmResult.sparql })
5773
+ }
5774
+ }
5775
+
5776
+ // If still no plan, throw the original error
5777
+ if (!plan) {
5778
+ throw planErr
5779
+ }
5780
+ }
5778
5781
  trace.addStep({ type: 'execution_plan', plan })
5779
5782
 
5780
5783
  // 4. Execute plan in WASM sandbox
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rust-kgdb",
3
- "version": "0.8.21",
3
+ "version": "0.8.23",
4
4
  "description": "High-performance RDF/SPARQL database with AI agent framework and cross-database federation. GraphDB (449ns lookups, 5-11x faster than RDFox), HyperFederate (KGDB + Snowflake + BigQuery), GraphFrames analytics, Datalog reasoning, HNSW vector embeddings. HyperMindAgent for schema-aware query generation with audit trails. W3C SPARQL 1.1 compliant. Native performance via Rust + NAPI-RS.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
Binary file
Binary file
Binary file