rust-kgdb 0.8.14 → 0.8.16

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.
Files changed (2) hide show
  1. package/hypermind-agent.js +167 -40
  2. package/package.json +1 -1
@@ -4924,6 +4924,7 @@ class HyperMindAgent {
4924
4924
  this.rules = config.rules || new DatalogRuleSet()
4925
4925
  this.sandbox = new WasmSandbox(config.sandbox || {})
4926
4926
  this.name = config.name || 'hypermind-agent'
4927
+ this.answerFormat = config.answerFormat || 'text' // 'text' | 'table' | 'json'
4927
4928
 
4928
4929
  // ThinkingReasoner for deductive reasoning with proof-carrying outputs
4929
4930
  // Enabled by default - every HyperMindAgent has deductive reasoning
@@ -5025,33 +5026,75 @@ class HyperMindAgent {
5025
5026
  }
5026
5027
 
5027
5028
  // 4. Train RDF2Vec embeddings if requested
5028
- let embeddings = null
5029
+ // Uses native Rust loadTtlWithEmbeddings() - all embedding logic in Rust
5030
+ let embeddingsEnabled = false
5029
5031
  if (options.rdf2vec) {
5030
5032
  try {
5031
- const { Rdf2VecEngine, EmbeddingService } = loadNativeBindingDirect()
5032
- const rdf2vec = new Rdf2VecEngine()
5033
- embeddings = new EmbeddingService()
5034
-
5035
- // Train RDF2Vec on the graph using native GraphDb
5036
- rdf2vec.train(db._db, {
5037
- dimensions: options.rdf2vecDimensions || 128,
5038
- walkLength: options.rdf2vecWalkLength || 10,
5039
- walksPerEntity: options.rdf2vecWalksPerEntity || 10,
5040
- windowSize: options.rdf2vecWindowSize || 5,
5041
- minCount: 1,
5042
- epochs: options.rdf2vecEpochs || 5
5043
- })
5033
+ // Get RDF2Vec config from options (support both boolean and object)
5034
+ const rdf2vecConfig = typeof options.rdf2vec === 'object' ? options.rdf2vec : {}
5035
+ // Use snake_case for NAPI-RS struct field names
5036
+ const config = {
5037
+ vector_size: rdf2vecConfig.dimensions || options.rdf2vecDimensions || 128,
5038
+ window_size: rdf2vecConfig.window || options.rdf2vecWindowSize || 5,
5039
+ walk_length: rdf2vecConfig.walkLength || options.rdf2vecWalkLength || 5,
5040
+ walks_per_node: rdf2vecConfig.walksPerNode || options.rdf2vecWalksPerEntity || 10
5041
+ }
5044
5042
 
5045
- if (rdf2vec.isTrained()) {
5046
- console.log(`[HyperMindAgent.create] Trained RDF2Vec embeddings (${rdf2vec.dimensions()} dimensions)`)
5047
- embeddings._rdf2vec = rdf2vec
5043
+ // Re-load data with embeddings using native Rust (efficient, parallel)
5044
+ const loadResult = JSON.parse(db._db.loadTtlWithEmbeddings(
5045
+ options.data,
5046
+ null,
5047
+ config
5048
+ ))
5049
+
5050
+ if (loadResult.embeddings?.trained) {
5051
+ embeddingsEnabled = true
5052
+ console.log(`[HyperMindAgent.create] Trained RDF2Vec embeddings:`)
5053
+ console.log(` Entities: ${loadResult.embeddings.entities_embedded}`)
5054
+ console.log(` Dimensions: ${loadResult.embeddings.dimensions}`)
5055
+ console.log(` Walks: ${loadResult.embeddings.walks_generated}`)
5056
+ console.log(` Training time: ${loadResult.embeddings.training_time_secs?.toFixed(2)}s`)
5048
5057
  }
5049
5058
  } catch (e) {
5050
5059
  console.warn(`[HyperMindAgent.create] RDF2Vec training skipped: ${e.message}`)
5051
- embeddings = null
5052
5060
  }
5053
5061
  }
5054
5062
 
5063
+ // Create wrapper for embedding operations if enabled
5064
+ const embeddings = embeddingsEnabled ? {
5065
+ // Delegate to native GraphDB embedding methods
5066
+ getEmbedding: (entity) => db._db.getEmbedding(entity),
5067
+ findSimilar: (entity, k, threshold) => {
5068
+ try {
5069
+ const results = JSON.parse(db._db.findSimilar(entity, k || 10))
5070
+ if (threshold) {
5071
+ return results.filter(r => r.similarity >= threshold)
5072
+ }
5073
+ return results
5074
+ } catch (e) {
5075
+ return []
5076
+ }
5077
+ },
5078
+ search: (text, k, threshold) => {
5079
+ // For text search, find entities that match and return similar
5080
+ // This uses the embedding similarity from native Rust
5081
+ const entities = db.querySelect(`SELECT ?s WHERE { ?s ?p ?o } LIMIT ${k || 10}`)
5082
+ const results = []
5083
+ for (const e of entities) {
5084
+ const entity = e.bindings?.s || e.s
5085
+ if (entity) {
5086
+ const similar = JSON.parse(db._db.findSimilar(entity, 1))
5087
+ if (similar.length > 0 && (!threshold || similar[0].similarity >= threshold)) {
5088
+ results.push({ entity, similarity: similar[0]?.similarity || 0 })
5089
+ }
5090
+ }
5091
+ }
5092
+ return results.slice(0, k || 10)
5093
+ },
5094
+ hasEmbeddings: () => db._db.hasEmbeddings(),
5095
+ getStats: () => JSON.parse(db._db.getEmbeddingStats())
5096
+ } : null
5097
+
5055
5098
  // 5. Create agent with all components
5056
5099
  const agent = new HyperMindAgent({
5057
5100
  name: options.name,
@@ -5825,36 +5868,120 @@ LIMIT 100`
5825
5868
 
5826
5869
  _formatAnswer(results, inferences, intent, deduction = null) {
5827
5870
  const sparqlResults = results.filter(r => r.tool === 'kg.sparql.query' && r.success)
5828
- const totalResults = sparqlResults.reduce((sum, r) => sum + (Array.isArray(r.result) ? r.result.length : 0), 0)
5871
+ const allRows = sparqlResults.flatMap(r => Array.isArray(r.result) ? r.result : [])
5872
+ const totalResults = allRows.length
5873
+
5874
+ // Helper to extract readable name from URI
5875
+ const extractName = (uri) => {
5876
+ if (!uri) return null
5877
+ const str = String(uri)
5878
+ const lastSlash = str.lastIndexOf('/')
5879
+ const lastHash = str.lastIndexOf('#')
5880
+ const pos = Math.max(lastSlash, lastHash)
5881
+ const local = pos >= 0 ? str.substring(pos + 1) : str
5882
+ // Convert "lessort__mathias" to "Mathias Lessort"
5883
+ if (local.includes('__')) {
5884
+ const parts = local.split('__')
5885
+ return parts.map(p => p.charAt(0).toUpperCase() + p.slice(1)).reverse().join(' ')
5886
+ }
5887
+ return local.charAt(0).toUpperCase() + local.slice(1)
5888
+ }
5889
+
5890
+ // Extract unique entities from results
5891
+ const extractEntities = (rows, maxItems = 10) => {
5892
+ const seen = new Set()
5893
+ const entities = []
5894
+ for (const row of rows) {
5895
+ for (const [key, value] of Object.entries(row)) {
5896
+ const name = extractName(value)
5897
+ if (name && !seen.has(name) && !name.match(/^(none|unknown|integer)$/i)) {
5898
+ seen.add(name)
5899
+ entities.push({ key, name, uri: value })
5900
+ if (entities.length >= maxItems) return entities
5901
+ }
5902
+ }
5903
+ }
5904
+ return entities
5905
+ }
5829
5906
 
5830
- let answer = `Found ${totalResults} results`
5907
+ // Determine output format
5908
+ const format = this.answerFormat || 'text'
5831
5909
 
5832
- if (inferences.length > 0) {
5833
- answer += ` using ${inferences.length} reasoning rules`
5910
+ // JSON format - return structured data
5911
+ if (format === 'json') {
5912
+ return JSON.stringify({
5913
+ count: totalResults,
5914
+ results: allRows.slice(0, 20).map(row => {
5915
+ const formatted = {}
5916
+ for (const [k, v] of Object.entries(row)) {
5917
+ formatted[k] = extractName(v) || v
5918
+ }
5919
+ return formatted
5920
+ }),
5921
+ reasoning: deduction ? {
5922
+ observations: deduction.observations || 0,
5923
+ derivedFacts: (deduction.derivedFacts || []).length,
5924
+ rulesApplied: deduction.rulesFired || 0
5925
+ } : null
5926
+ }, null, 2)
5927
+ }
5928
+
5929
+ // TABLE format - professional tabular output
5930
+ if (format === 'table') {
5931
+ const entities = extractEntities(allRows, 15)
5932
+ if (entities.length === 0) {
5933
+ return `No results found.`
5934
+ }
5935
+
5936
+ // Build table
5937
+ let table = `\n┌${'─'.repeat(40)}┐\n`
5938
+ table += `│ Results (${totalResults} total)${' '.repeat(Math.max(0, 24 - String(totalResults).length))}│\n`
5939
+ table += `├${'─'.repeat(40)}┤\n`
5940
+
5941
+ for (const entity of entities) {
5942
+ const nameStr = entity.name.substring(0, 36).padEnd(36)
5943
+ table += `│ ${nameStr} │\n`
5944
+ }
5945
+
5946
+ if (totalResults > entities.length) {
5947
+ table += `│ ... and ${totalResults - entities.length} more${' '.repeat(Math.max(0, 23 - String(totalResults - entities.length).length))}│\n`
5948
+ }
5949
+ table += `└${'─'.repeat(40)}┘`
5950
+
5951
+ return table
5834
5952
  }
5835
5953
 
5836
- // NEW: Deductive reasoning summary (v0.8.6+)
5837
- if (deduction && deduction.derivedFacts && deduction.derivedFacts.length > 0) {
5838
- answer += `\n\nDeductive Reasoning: Derived ${deduction.derivedFacts.length} facts `
5839
- answer += `via ${deduction.rulesFired || 0} rule applications. `
5840
- answer += `Generated ${(deduction.proofs || []).length} cryptographic proofs.`
5841
-
5842
- // Top 3 conclusions
5843
- const topFacts = deduction.derivedFacts.slice(0, 3)
5844
- if (topFacts.length > 0) {
5845
- answer += '\n\nKey Conclusions:'
5846
- for (const fact of topFacts) {
5847
- const factStr = fact.args
5848
- ? `${fact.predicate}(${fact.args.join(', ')})`
5849
- : `${fact.predicate || 'related'}(${fact.subject || 'unknown'}, ${fact.object || 'unknown'})`
5850
- answer += `\n - ${factStr}`
5851
- }
5954
+ // TEXT format (default) - natural language answer
5955
+ const entities = extractEntities(allRows, 10)
5956
+
5957
+ if (totalResults === 0) {
5958
+ return 'No results found.'
5959
+ }
5960
+
5961
+ // Build natural language answer from actual data
5962
+ let answer = ''
5963
+
5964
+ if (entities.length > 0) {
5965
+ const names = entities.map(e => e.name)
5966
+ if (names.length === 1) {
5967
+ answer = names[0]
5968
+ } else if (names.length <= 5) {
5969
+ answer = names.slice(0, -1).join(', ') + ' and ' + names[names.length - 1]
5970
+ } else {
5971
+ answer = names.slice(0, 5).join(', ') + ` and ${totalResults - 5} more`
5852
5972
  }
5973
+ } else {
5974
+ answer = `Found ${totalResults} results`
5975
+ }
5976
+
5977
+ // Add reasoning context if available
5978
+ if (deduction && deduction.derivedFacts && deduction.derivedFacts.length > 0) {
5979
+ answer += `\n\n[Reasoning: ${deduction.observations || 0} observations → ${deduction.derivedFacts.length} derived facts via ${deduction.rulesFired || 0} OWL rules]`
5853
5980
  }
5854
5981
 
5982
+ // Special handling for fraud detection
5855
5983
  if (intent.type === 'detect_fraud' && totalResults > 0) {
5856
- answer = `Detected ${totalResults} potential fraud cases using ${inferences.length} detection rules` +
5857
- (answer.indexOf('\n\n') >= 0 ? answer.substring(answer.indexOf('\n\n')) : '')
5984
+ answer = `Detected ${totalResults} potential fraud cases: ${answer}`
5858
5985
  }
5859
5986
 
5860
5987
  return answer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rust-kgdb",
3
- "version": "0.8.14",
3
+ "version": "0.8.16",
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",