UNPKG

semem

Version:

Semantic Memory for Intelligent Agents

307 lines (271 loc) 10.7 kB
/** * RDFGraphManager.js - Core RDF-Ext integration for Ragno * * This class provides the foundation for RDF-based graph operations in Ragno, * replacing the simple object-oriented approach with proper RDF semantics * following the ragno ontology specification. * * Key Features: * - RDF-Ext dataset management * - Namespace resolution for ragno ontology * - SPARQL integration with existing SPARQLStore * - Graph serialization/deserialization * - URI generation for graph elements */ import rdf from 'rdf-ext' import namespace from '@rdfjs/namespace' import { logger } from '../../Utils.js' export default class RDFGraphManager { constructor(options = {}) { this.dataset = rdf.dataset() this.uriBase = options.uriBase || 'http://example.org/ragno/' this.counters = new Map() // For URI generation // Initialize namespaces following ragno ontology this.ns = { rdf: namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#'), rdfs: namespace('http://www.w3.org/2000/01/rdf-schema#'), owl: namespace('http://www.w3.org/2002/07/owl#'), xsd: namespace('http://www.w3.org/2001/XMLSchema#'), skos: namespace('http://www.w3.org/2004/02/skos/core#'), prov: namespace('http://www.w3.org/ns/prov#'), dcterms: namespace('http://purl.org/dc/terms/'), ragno: namespace('http://purl.org/stuff/ragno/'), ex: namespace(this.uriBase) } logger.info('RDFGraphManager initialized with RDF-Ext dataset') } /** * Generate a unique URI for a given resource type * @param {string} type - Resource type (entity, unit, relationship, etc.) * @returns {string} Generated URI */ generateURI(type) { const count = this.counters.get(type) || 0 this.counters.set(type, count + 1) return `${this.uriBase}${type}/${count + 1}` } /** * Create a new named node with generated URI * @param {string} type - Resource type * @returns {NamedNode} RDF named node */ createNamedNode(type) { const uri = this.generateURI(type) return rdf.namedNode(uri) } /** * Add a triple to the dataset * @param {NamedNode} subject - Subject node * @param {NamedNode} predicate - Predicate node * @param {NamedNode|Literal} object - Object node or literal * @param {NamedNode} [graph] - Optional graph context */ addTriple(subject, predicate, object, graph) { const quad = graph ? rdf.quad(subject, predicate, object, graph) : rdf.quad(subject, predicate, object) this.dataset.add(quad) } /** * Add type declaration for a resource * @param {NamedNode} subject - Resource to type * @param {NamedNode} type - RDF type (e.g., ragno:Entity) */ addType(subject, type) { this.addTriple(subject, this.ns.rdf.type, type) } /** * Add content property to a resource * @param {NamedNode} subject - Resource * @param {string} content - Text content */ addContent(subject, content) { this.addTriple(subject, this.ns.ragno.content, rdf.literal(content)) } /** * Add SKOS preferred label * @param {NamedNode} subject - Resource * @param {string} label - Label text * @param {string} [lang='en'] - Language tag */ addLabel(subject, label, lang = 'en') { this.addTriple(subject, this.ns.skos.prefLabel, rdf.literal(label, lang)) } /** * Create a ragno:Entity with proper RDF structure * @param {string} name - Entity name * @param {boolean} [isEntryPoint=true] - Whether entity is an entry point * @returns {NamedNode} Created entity node */ createEntity(name, isEntryPoint = true) { const entity = this.createNamedNode('entity') // Add required RDF properties this.addType(entity, this.ns.ragno.Entity) this.addLabel(entity, name) this.addTriple(entity, this.ns.ragno.isEntryPoint, rdf.literal(isEntryPoint)) logger.debug(`Created ragno:Entity: ${entity.value} with name "${name}"`) return entity } /** * Create a ragno:Unit with proper RDF structure * @param {string} content - Unit content * @param {string} [source] - Source document reference * @returns {NamedNode} Created unit node */ createUnit(content, source) { const unit = this.createNamedNode('unit') // Add required RDF properties this.addType(unit, this.ns.ragno.Unit) this.addContent(unit, content) if (source) { this.addTriple(unit, this.ns.ragno.hasSourceDocument, rdf.namedNode(source)) } logger.debug(`Created ragno:Unit: ${unit.value}`) return unit } /** * Create a ragno:Relationship as first-class RDF resource * @param {NamedNode} sourceEntity - Source entity * @param {NamedNode} targetEntity - Target entity * @param {string} description - Relationship description * @param {number} [weight=1.0] - Relationship weight * @returns {NamedNode} Created relationship node */ createRelationship(sourceEntity, targetEntity, description, weight = 1.0) { const relationship = this.createNamedNode('relationship') // Add required RDF properties following ragno ontology this.addType(relationship, this.ns.ragno.Relationship) this.addTriple(relationship, this.ns.ragno.hasSourceEntity, sourceEntity) this.addTriple(relationship, this.ns.ragno.hasTargetEntity, targetEntity) this.addContent(relationship, description) this.addTriple(relationship, this.ns.ragno.hasWeight, rdf.literal(weight)) logger.debug(`Created ragno:Relationship: ${relationship.value} between ${sourceEntity.value} and ${targetEntity.value}`) return relationship } /** * Create a ragno:Attribute with entity connection * @param {NamedNode} entity - Entity this attribute describes * @param {string} content - Attribute content * @param {string} [subType] - Attribute subtype (e.g., "Overview") * @returns {NamedNode} Created attribute node */ createAttribute(entity, content, subType) { const attribute = this.createNamedNode('attribute') // Add required RDF properties this.addType(attribute, this.ns.ragno.Attribute) this.addContent(attribute, content) if (subType) { this.addTriple(attribute, this.ns.ragno.subType, this.ns.ex(subType)) } // Link to entity this.addTriple(entity, this.ns.ragno.hasAttribute, attribute) logger.debug(`Created ragno:Attribute: ${attribute.value} for entity ${entity.value}`) return attribute } /** * Connect two elements with ragno:connectsTo * @param {NamedNode} source - Source element * @param {NamedNode} target - Target element * @param {number} [weight] - Optional connection weight */ connectElements(source, target, weight) { this.addTriple(source, this.ns.ragno.connectsTo, target) if (weight !== undefined) { // Create a reified statement for the weight const connection = this.createNamedNode('connection') this.addTriple(connection, this.ns.rdf.subject, source) this.addTriple(connection, this.ns.rdf.predicate, this.ns.ragno.connectsTo) this.addTriple(connection, this.ns.rdf.object, target) this.addTriple(connection, this.ns.ragno.hasWeight, rdf.literal(weight)) } } /** * Query the dataset using SPARQL-like patterns * @param {NamedNode} [subject] - Subject pattern (null for any) * @param {NamedNode} [predicate] - Predicate pattern (null for any) * @param {NamedNode|Literal} [object] - Object pattern (null for any) * @returns {Array} Array of matching quads */ query(subject = null, predicate = null, object = null) { return [...this.dataset.match(subject, predicate, object)] } /** * Get all entities in the dataset * @returns {Array<NamedNode>} Array of entity nodes */ getEntities() { return this.query(null, this.ns.rdf.type, this.ns.ragno.Entity) .map(quad => quad.subject) } /** * Get all relationships in the dataset * @returns {Array<NamedNode>} Array of relationship nodes */ getRelationships() { return this.query(null, this.ns.rdf.type, this.ns.ragno.Relationship) .map(quad => quad.subject) } /** * Get all units in the dataset * @returns {Array<NamedNode>} Array of unit nodes */ getUnits() { return this.query(null, this.ns.rdf.type, this.ns.ragno.Unit) .map(quad => quad.subject) } /** * Export dataset to N-Triples format * @returns {string} N-Triples serialization */ toNTriples() { return [...this.dataset].map(quad => { const subject = quad.subject.value const predicate = quad.predicate.value const object = quad.object.termType === 'Literal' ? `"${quad.object.value}"` : quad.object.value return `<${subject}> <${predicate}> <${object}> .` }).join('\n') } /** * Get dataset statistics * @returns {Object} Statistics about the graph */ getStats() { const entities = this.getEntities().length const relationships = this.getRelationships().length const units = this.getUnits().length const totalTriples = this.dataset.size return { entities, relationships, units, totalTriples, types: { 'ragno:Entity': entities, 'ragno:Relationship': relationships, 'ragno:Unit': units } } } /** * Clear the dataset */ clear() { this.dataset = rdf.dataset() this.counters.clear() logger.info('RDF dataset cleared') } /** * Clone the current dataset * @returns {RDFGraphManager} New instance with cloned data */ clone() { const cloned = new RDFGraphManager({ uriBase: this.uriBase }) for (const quad of this.dataset) { cloned.dataset.add(quad) } cloned.counters = new Map(this.counters) return cloned } }