UNPKG

graphzep

Version:

GraphZep: A temporal knowledge graph memory system for AI agents based on the Zep paper

428 lines (423 loc) 15.9 kB
/** * Ontology Manager for GraphZep RDF support * Handles loading, validation, and management of domain ontologies */ import fs from 'fs/promises'; import path from 'path'; import { NamespaceManager } from './namespaces.js'; export class OntologyManager { ontologies = new Map(); activeOntology = null; nsManager; validationCache = new Map(); constructor(nsManager) { this.nsManager = nsManager || new NamespaceManager(); } /** * Load ontology from file with format detection */ async loadOntology(filePath, ontologyId) { try { const content = await fs.readFile(filePath, 'utf-8'); const format = this.detectFormat(filePath, content); const id = ontologyId || path.basename(filePath, path.extname(filePath)); const ontology = await this.parseOntology(content, format, id); this.ontologies.set(id, ontology); // Update namespace manager with ontology namespaces for (const [prefix, uri] of ontology.namespaces.entries()) { this.nsManager.addNamespace(prefix, uri); } // Set as active if it's the first one if (!this.activeOntology) { this.activeOntology = id; } return id; } catch (error) { throw new Error(`Failed to load ontology from ${filePath}: ${error}`); } } /** * Load ontology from string content */ async loadOntologyFromString(content, format, ontologyId) { try { const ontology = await this.parseOntology(content, format, ontologyId); this.ontologies.set(ontologyId, ontology); // Update namespace manager for (const [prefix, uri] of ontology.namespaces.entries()) { this.nsManager.addNamespace(prefix, uri); } if (!this.activeOntology) { this.activeOntology = ontologyId; } return ontologyId; } catch (error) { throw new Error(`Failed to parse ontology: ${error}`); } } /** * Set active ontology for validation and extraction */ setActiveOntology(ontologyId) { if (!this.ontologies.has(ontologyId)) { throw new Error(`Ontology not found: ${ontologyId}`); } this.activeOntology = ontologyId; } /** * Get active ontology */ getActiveOntology() { return this.activeOntology ? this.ontologies.get(this.activeOntology) || null : null; } /** * Validate RDF triple against active ontology */ validateTriple(triple) { const cacheKey = `${triple.subject}:${triple.predicate}:${JSON.stringify(triple.object)}`; // Check cache first if (this.validationCache.has(cacheKey)) { return this.validationCache.get(cacheKey); } const result = this.performValidation(triple); // Cache result this.validationCache.set(cacheKey, result); return result; } /** * Validate multiple triples */ validateTriples(triples) { const errors = []; const warnings = []; for (const triple of triples) { const result = this.validateTriple(triple); errors.push(...result.errors); warnings.push(...result.warnings); } return { valid: errors.length === 0, errors, warnings }; } /** * Generate extraction guidance for LLM */ generateExtractionGuidance(content, ontologyId) { const ontology = ontologyId ? this.ontologies.get(ontologyId) : this.getActiveOntology(); if (!ontology) { throw new Error('No ontology available for guidance'); } const entityTypes = Array.from(ontology.classes.values()) .filter(cls => !cls.uri.startsWith('http://www.w3.org/')) .map(cls => cls.label || cls.uri.split('#').pop() || cls.uri.split('/').pop() || cls.uri) .slice(0, 20); // Limit to avoid token overflow const relationTypes = Array.from(ontology.properties.values()) .filter(prop => prop.domain.length > 0 && prop.range.length > 0) .map(prop => prop.label || prop.uri.split('#').pop() || prop.uri.split('/').pop() || prop.uri) .slice(0, 15); const constraints = this.generateConstraintDescriptions(ontology); const examples = this.generateExamples(ontology); const prompt = this.buildExtractionPrompt(content, entityTypes, relationTypes, constraints, examples); return { entityTypes, relationTypes, constraints, examples, prompt }; } /** * Get ontology statistics */ getOntologyStats(ontologyId) { const ontology = ontologyId ? this.ontologies.get(ontologyId) : this.getActiveOntology(); if (!ontology) { throw new Error('No ontology available'); } const depth = this.calculateOntologyDepth(ontology); const complexity = this.calculateComplexity(ontology); return { totalClasses: ontology.classes.size, totalProperties: ontology.properties.size, totalIndividuals: ontology.individuals.size, totalRules: ontology.rules.length, depth, complexity }; } /** * Get class hierarchy */ getClassHierarchy(rootClass, ontologyId) { const ontology = ontologyId ? this.ontologies.get(ontologyId) : this.getActiveOntology(); if (!ontology) { throw new Error('No ontology available'); } const root = rootClass || 'http://www.w3.org/2002/07/owl#Thing'; return this.buildClassHierarchy(ontology, root); } /** * Search classes and properties */ search(query, type = 'both') { const results = []; const queryLower = query.toLowerCase(); for (const ontology of this.ontologies.values()) { if (type === 'class' || type === 'both') { for (const cls of ontology.classes.values()) { if (this.matchesQuery(cls, queryLower)) { results.push({ type: 'class', ontology: ontology.uri, ...cls }); } } } if (type === 'property' || type === 'both') { for (const prop of ontology.properties.values()) { if (this.matchesQuery(prop, queryLower)) { results.push({ type: 'property', ontology: ontology.uri, ...prop }); } } } } return results; } /** * Export ontology to different formats */ async exportOntology(ontologyId, format) { const ontology = this.ontologies.get(ontologyId); if (!ontology) { throw new Error(`Ontology not found: ${ontologyId}`); } // Convert back to RDF format // This is a simplified implementation - real-world would use proper RDF libraries const triples = this.ontologyToTriples(ontology); switch (format) { case 'turtle': return this.triplesToTurtle(triples); case 'rdf-xml': return this.triplesToRDFXML(triples); case 'json-ld': return this.triplesToJSONLD(triples); default: throw new Error(`Unsupported format: ${format}`); } } async parseOntology(content, format, id) { // Simplified ontology parsing for demo console.log(`Parsing ${format} ontology: ${id}`); return { uri: `http://example.com/ontologies/${id}`, classes: new Map([ ['http://graphzep.ai/ontology#Person', { uri: 'http://graphzep.ai/ontology#Person', label: 'Person', comment: 'A human being', superClasses: [], subClasses: [], properties: [], restrictions: [] }] ]), properties: new Map([ ['http://graphzep.ai/ontology#knows', { uri: 'http://graphzep.ai/ontology#knows', label: 'knows', comment: 'Person knows another person', domain: ['http://graphzep.ai/ontology#Person'], range: ['http://graphzep.ai/ontology#Person'], subProperties: [], superProperties: [] }] ]), individuals: new Map(), rules: [], imports: [], namespaces: new Map([ ['zep', 'http://graphzep.ai/ontology#'], ['rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'] ]) }; } // Simplified methods - these would be implemented with proper RDF parsing in production detectFormat(filePath, content) { const extension = path.extname(filePath).toLowerCase(); // Check by file extension first switch (extension) { case '.rdf': case '.owl': return 'application/rdf+xml'; case '.ttl': return 'text/turtle'; case '.n3': return 'text/n3'; case '.nt': return 'application/n-triples'; case '.jsonld': return 'application/ld+json'; } // Check by content if (content.includes('<?xml') && content.includes('rdf:RDF')) { return 'application/rdf+xml'; } else if (content.includes('@prefix') || content.includes('PREFIX')) { return 'text/turtle'; } else if (content.includes('{') && content.includes('@context')) { return 'application/ld+json'; } // Default to RDF/XML return 'application/rdf+xml'; } performValidation(triple) { const ontology = this.getActiveOntology(); if (!ontology) { return { valid: true, errors: [], warnings: [] }; } const errors = []; const warnings = []; // Validate property exists const property = ontology.properties.get(triple.predicate); if (!property && !this.isBuiltinProperty(triple.predicate)) { warnings.push({ type: 'unknown_property', message: `Unknown property: ${triple.predicate}`, predicate: triple.predicate, severity: 'warning' }); } // Validate domain and range constraints if (property) { // Domain validation would require knowing the type of the subject // Range validation would require knowing the type of the object // This is simplified for the example } return { valid: errors.length === 0, errors, warnings }; } isBuiltinProperty(predicate) { return predicate.startsWith('http://www.w3.org/1999/02/22-rdf-syntax-ns#') || predicate.startsWith('http://www.w3.org/2000/01/rdf-schema#') || predicate.startsWith('http://www.w3.org/2002/07/owl#'); } generateConstraintDescriptions(ontology) { const constraints = []; for (const cls of ontology.classes.values()) { if (cls.restrictions.length > 0) { for (const restriction of cls.restrictions) { constraints.push(`${cls.label || cls.uri} must have ${restriction.type} ${restriction.value} for property ${restriction.property}`); } } } return constraints.slice(0, 10); // Limit for prompt size } generateExamples(ontology) { // Generate simple examples based on ontology structure const examples = []; for (const prop of ontology.properties.values()) { if (prop.domain.length > 0 && prop.range.length > 0) { const domainLabel = this.getClassLabel(ontology, prop.domain[0]); const rangeLabel = this.getClassLabel(ontology, prop.range[0]); const propLabel = prop.label || 'related to'; examples.push(`${domainLabel} ${propLabel} ${rangeLabel}`); } } return examples.slice(0, 5); } getClassLabel(ontology, classUri) { const cls = ontology.classes.get(classUri); return cls?.label || classUri.split('#').pop() || classUri.split('/').pop() || classUri; } buildExtractionPrompt(content, entityTypes, relationTypes, constraints, examples) { return ` Extract entities and relationships from the following text using the provided ontology. Available entity types: ${entityTypes.join(', ')} Available relationship types: ${relationTypes.join(', ')} Constraints: ${constraints.map(c => `- ${c}`).join('\n')} Examples: ${examples.map(e => `- ${e}`).join('\n')} Text to analyze: ${content} Please extract entities and their relationships in the format: - Entity: [name] (type: [entity_type]) - Relationship: [entity1] [relationship_type] [entity2] `.trim(); } calculateOntologyDepth(ontology) { let maxDepth = 0; for (const cls of ontology.classes.values()) { const depth = this.calculateClassDepth(ontology, cls.uri, new Set()); maxDepth = Math.max(maxDepth, depth); } return maxDepth; } calculateClassDepth(ontology, classUri, visited) { if (visited.has(classUri)) return 0; visited.add(classUri); const cls = ontology.classes.get(classUri); if (!cls || cls.superClasses.length === 0) return 1; let maxParentDepth = 0; for (const parentUri of cls.superClasses) { const parentDepth = this.calculateClassDepth(ontology, parentUri, visited); maxParentDepth = Math.max(maxParentDepth, parentDepth); } return maxParentDepth + 1; } calculateComplexity(ontology) { // Simple complexity metric based on structure return ontology.classes.size + ontology.properties.size * 2 + ontology.rules.length * 3; } buildClassHierarchy(ontology, rootUri) { const cls = ontology.classes.get(rootUri); if (!cls) return null; const children = []; for (const [uri, otherClass] of ontology.classes.entries()) { if (otherClass.superClasses.includes(rootUri)) { children.push(this.buildClassHierarchy(ontology, uri)); } } return { uri: cls.uri, label: cls.label, comment: cls.comment, children }; } matchesQuery(item, query) { return (item.label?.toLowerCase().includes(query)) || (item.comment?.toLowerCase().includes(query)) || (item.uri.toLowerCase().includes(query)); } ontologyToTriples(ontology) { // Simplified conversion - would need proper implementation return []; } triplesToTurtle(triples) { // Simplified conversion return ''; } triplesToRDFXML(triples) { // Simplified conversion return ''; } triplesToJSONLD(triples) { // Simplified conversion return ''; } } //# sourceMappingURL=ontology-manager.js.map