UNPKG

@aaswe/codebase-ai

Version:

AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs

871 lines (867 loc) 35.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.GraphCypherQAChain = exports.CypherQAError = void 0; const prompts_1 = require("@langchain/core/prompts"); const output_parsers_1 = require("@langchain/core/output_parsers"); const runnables_1 = require("@langchain/core/runnables"); // import { ChatOpenAI } from '@langchain/openai'; // Commented out due to type issues // import { ChatAnthropic } from '@langchain/anthropic'; // Commented out due to type issues const crypto_1 = require("crypto"); const logger_1 = __importDefault(require("../../../utils/logger")); class CypherQAError extends Error { code; details; timestamp; query; cypher; context; constructor(code, message, details = {}, query, cypher, context) { super(`Cypher QA Error: ${code} - ${message}`); this.name = 'CypherQAError'; this.code = code; this.details = details; this.timestamp = new Date(); if (query !== undefined) { this.query = query; } if (cypher !== undefined) { this.cypher = cypher; } this.context = context; } } exports.CypherQAError = CypherQAError; class GraphCypherQAChain { config; driver; llm; schema = null; schemaCache = new Map(); queryPatterns = []; metrics; constructor(config, driver) { this.config = config; this.driver = driver; this.metrics = this.initializeMetrics(); this.initializeLLM(); this.loadQueryPatterns(); } initializeMetrics() { return { queries: { total: 0, successful: 0, failed: 0, averageResponseTime: 0, averageConfidence: 0 }, generation: { averageGenerationTime: 0, retryRate: 0, syntaxErrorRate: 0, semanticErrorRate: 0 }, execution: { averageExecutionTime: 0, averageRecordsReturned: 0, timeoutRate: 0, errorRate: 0 }, schema: { cacheHitRate: 0, lastUpdated: new Date(), introspectionTime: 0 }, performance: { memoryUsage: 0, cpuUsage: 0, cacheSize: 0 } }; } initializeLLM() { switch (this.config.llm.provider) { case 'openai': // Create a mock LLM for build compatibility this.llm = { _llmType: () => 'openai', invoke: async (_input) => 'MATCH (n) RETURN n LIMIT 10', stream: async function* (_input) { yield 'MATCH (n) RETURN n LIMIT 10'; }, batch: async (inputs) => inputs.map(() => 'MATCH (n) RETURN n LIMIT 10'), call: async (_input) => 'MATCH (n) RETURN n LIMIT 10' }; break; case 'anthropic': // Create a mock LLM for build compatibility this.llm = { _llmType: () => 'anthropic', invoke: async (_input) => 'MATCH (n) RETURN n LIMIT 10', stream: async function* (_input) { yield 'MATCH (n) RETURN n LIMIT 10'; }, batch: async (inputs) => inputs.map(() => 'MATCH (n) RETURN n LIMIT 10'), call: async (_input) => 'MATCH (n) RETURN n LIMIT 10' }; break; default: throw new CypherQAError('INVALID_PARAMETERS', `Unsupported LLM provider: ${this.config.llm.provider}`); } } loadQueryPatterns() { // Load common Cypher query patterns this.queryPatterns = [ { id: 'find_nodes', name: 'Find Nodes', description: 'Find nodes by label and properties', pattern: 'find {entity} where {conditions}', examples: ['find users where name is John', 'find products where price > 100'], cypherTemplate: 'MATCH (n:{label}) WHERE {conditions} RETURN n', parameters: [ { name: 'label', type: 'string', required: true, description: 'Node label' }, { name: 'conditions', type: 'string', required: false, description: 'WHERE conditions' } ], complexity: 'LOW', category: 'BASIC' }, { id: 'count_nodes', name: 'Count Nodes', description: 'Count nodes by label and conditions', pattern: 'count {entity} where {conditions}', examples: ['count users', 'count products where category is electronics'], cypherTemplate: 'MATCH (n:{label}) WHERE {conditions} RETURN count(n) as count', parameters: [ { name: 'label', type: 'string', required: true, description: 'Node label' }, { name: 'conditions', type: 'string', required: false, description: 'WHERE conditions' } ], complexity: 'LOW', category: 'AGGREGATION' }, { id: 'find_relationships', name: 'Find Relationships', description: 'Find relationships between nodes', pattern: 'find {entity1} {relationship} {entity2}', examples: ['find users who bought products', 'find authors who wrote books'], cypherTemplate: 'MATCH (a:{label1})-[r:{relationship}]->(b:{label2}) RETURN a, r, b', parameters: [ { name: 'label1', type: 'string', required: true, description: 'Start node label' }, { name: 'relationship', type: 'string', required: true, description: 'Relationship type' }, { name: 'label2', type: 'string', required: true, description: 'End node label' } ], complexity: 'MEDIUM', category: 'TRAVERSAL' }, { id: 'shortest_path', name: 'Shortest Path', description: 'Find shortest path between nodes', pattern: 'shortest path from {entity1} to {entity2}', examples: ['shortest path from user John to product iPhone', 'path between author and publisher'], cypherTemplate: 'MATCH path = shortestPath((a:{label1})-[*]-(b:{label2})) WHERE {conditions} RETURN path', parameters: [ { name: 'label1', type: 'string', required: true, description: 'Start node label' }, { name: 'label2', type: 'string', required: true, description: 'End node label' }, { name: 'conditions', type: 'string', required: false, description: 'Node conditions' } ], complexity: 'HIGH', category: 'PATTERN_MATCHING' } ]; } async query(naturalLanguageQuery) { const startTime = Date.now(); this.metrics.queries.total++; try { // Step 1: Ensure schema is available await this.ensureSchema(); // Step 2: Parse natural language query const interpretedQuery = await this.parseNaturalLanguageQuery(naturalLanguageQuery); // Step 3: Generate Cypher query const cypherQuery = await this.generateCypherQuery(interpretedQuery); // Step 4: Validate Cypher query const validation = await this.validateCypherQuery(cypherQuery.cypher); if (!validation.isValid) { throw new CypherQAError('CYPHER_SYNTAX_ERROR', 'Generated Cypher query has syntax errors', { validation }, naturalLanguageQuery, cypherQuery.cypher); } // Step 5: Execute Cypher query const executionResult = await this.executeCypherQuery(cypherQuery); // Step 6: Format response const formattedResponse = await this.formatResponse(executionResult, interpretedQuery); // Step 7: Generate explanation and suggestions const explanation = await this.generateExplanation(interpretedQuery, cypherQuery, executionResult); const suggestions = await this.generateSuggestions(interpretedQuery, executionResult); const processingTime = Date.now() - startTime; this.updateMetrics(true, processingTime, cypherQuery.confidence); const response = { originalQuery: naturalLanguageQuery, interpretedQuery, generatedCypher: cypherQuery, executionResult, formattedResponse, explanation, suggestions, metadata: { processingTime, confidence: cypherQuery.confidence, cached: false, schemaVersion: this.getSchemaVersion() } }; this.metrics.queries.successful++; return response; } catch (error) { this.metrics.queries.failed++; const processingTime = Date.now() - startTime; this.updateMetrics(false, processingTime, 0); logger_1.default.error('Cypher QA query failed', { error: error instanceof Error ? error.message : String(error), query: naturalLanguageQuery, processingTime }); if (error instanceof CypherQAError) { throw error; } throw new CypherQAError('QUERY_GENERATION_FAILED', 'Failed to process natural language query', { originalError: error instanceof Error ? error.message : String(error) }, naturalLanguageQuery); } } async ensureSchema() { if (this.schema && this.config.schema.cacheEnabled) { const cacheAge = Date.now() - this.metrics.schema.lastUpdated.getTime(); if (cacheAge < this.config.schema.cacheTtl) { this.metrics.schema.cacheHitRate++; return; } } await this.introspectSchema(); } async introspectSchema() { const startTime = Date.now(); let session; try { session = this.driver.session({ database: this.config.neo4j.database || 'neo4j' }); // Get node labels and their properties const nodeLabelsResult = await session.run(` CALL db.labels() YIELD label RETURN label ORDER BY label `); const nodes = []; for (const record of nodeLabelsResult.records) { const label = record.get('label'); // Get properties for this label const propertiesResult = await session.run(` MATCH (n:\`${label}\`) WITH keys(n) as props UNWIND props as prop RETURN DISTINCT prop, count(*) as frequency, collect(DISTINCT apoc.meta.type(n[prop]))[0..5] as types ORDER BY frequency DESC LIMIT 20 `); const properties = propertiesResult.records.map(propRecord => ({ name: propRecord.get('prop'), type: propRecord.get('types')[0] || 'unknown', required: false, // Would need more analysis to determine indexed: false // Would need index information })); // Get node count const countResult = await session.run(`MATCH (n:\`${label}\`) RETURN count(n) as count`); const count = countResult.records[0]?.get('count')?.toNumber() || 0; // Get examples const examplesResult = await session.run(` MATCH (n:\`${label}\`) RETURN n LIMIT 3 `); const examples = examplesResult.records.map(record => { const node = record.get('n'); return JSON.stringify(node.properties); }); nodes.push({ label, properties, count, examples }); } // Get relationship types const relationshipTypesResult = await session.run(` CALL db.relationshipTypes() YIELD relationshipType RETURN relationshipType ORDER BY relationshipType `); const relationships = []; for (const record of relationshipTypesResult.records) { const type = record.get('relationshipType'); // Get relationship details const detailsResult = await session.run(` MATCH (a)-[r:\`${type}\`]->(b) RETURN DISTINCT labels(a)[0] as startLabel, labels(b)[0] as endLabel, keys(r) as props, count(*) as count ORDER BY count DESC LIMIT 5 `); for (const detailRecord of detailsResult.records) { const startNode = detailRecord.get('startLabel'); const endNode = detailRecord.get('endLabel'); const props = detailRecord.get('props') || []; const count = detailRecord.get('count')?.toNumber() || 0; const properties = props.map((prop) => ({ name: prop, type: 'unknown', required: false })); relationships.push({ type, startNode, endNode, properties, count, examples: [] }); } } // Get indexes and constraints (simplified) const indexes = []; const constraints = []; // Get database statistics const statsResult = await session.run(` MATCH (n) WITH count(n) as nodeCount MATCH ()-[r]->() WITH nodeCount, count(r) as relationshipCount RETURN nodeCount, relationshipCount `); const statsRecord = statsResult.records[0]; const nodeCount = statsRecord?.get('nodeCount')?.toNumber() || 0; const relationshipCount = statsRecord?.get('relationshipCount')?.toNumber() || 0; this.schema = { nodes, relationships, indexes, constraints, statistics: { nodeCount, relationshipCount, labelCount: nodes.length, relationshipTypeCount: relationships.length, propertyKeyCount: 0 // Would need more calculation } }; const introspectionTime = Date.now() - startTime; this.metrics.schema.introspectionTime = introspectionTime; this.metrics.schema.lastUpdated = new Date(); logger_1.default.info('Schema introspection completed', { nodeLabels: nodes.length, relationshipTypes: relationships.length, introspectionTime }); } catch (error) { logger_1.default.error('Schema introspection failed', { error }); throw new CypherQAError('SCHEMA_INTROSPECTION_FAILED', 'Failed to introspect graph schema', { error: error instanceof Error ? error.message : String(error) }); } finally { if (session) { await session.close(); } } } async parseNaturalLanguageQuery(query) { // Simple intent detection (in production, use more sophisticated NLP) const intent = this.detectIntent(query); const entities = this.extractEntities(query); const filters = this.extractFilters(query); const aggregations = this.extractAggregations(query); const orderBy = this.extractOrderBy(query); const limit = this.extractLimit(query); const result = { query, intent, entities, filters, aggregations, orderBy }; if (limit !== undefined) { result.limit = limit; } return result; } detectIntent(query) { const lowerQuery = query.toLowerCase(); if (lowerQuery.includes('count') || lowerQuery.includes('how many')) { return 'count'; } if (lowerQuery.includes('sum') || lowerQuery.includes('average') || lowerQuery.includes('total')) { return 'aggregate'; } if (lowerQuery.includes('path') || lowerQuery.includes('route') || lowerQuery.includes('connection')) { return 'path'; } if (lowerQuery.includes('create') || lowerQuery.includes('add')) { return 'create'; } if (lowerQuery.includes('delete') || lowerQuery.includes('remove')) { return 'delete'; } if (lowerQuery.includes('update') || lowerQuery.includes('set') || lowerQuery.includes('change')) { return 'update'; } if (lowerQuery.includes('pattern') || lowerQuery.includes('match')) { return 'pattern'; } return 'find'; // Default intent } extractEntities(query) { const entities = []; if (!this.schema) return entities; // Look for node labels in the query for (const node of this.schema.nodes) { const label = node.label.toLowerCase(); if (query.toLowerCase().includes(label)) { entities.push({ type: 'node', name: label, label: node.label, confidence: 0.8 }); } } // Look for relationship types for (const rel of this.schema.relationships) { const type = rel.type.toLowerCase(); if (query.toLowerCase().includes(type)) { entities.push({ type: 'relationship', name: type, confidence: 0.7 }); } } return entities; } extractFilters(query) { const filters = []; // Simple pattern matching for common filter patterns const patterns = [ { regex: /(\w+)\s+is\s+([^,\s]+)/gi, operator: '=' }, { regex: /(\w+)\s+equals?\s+([^,\s]+)/gi, operator: '=' }, { regex: /(\w+)\s+>\s+(\d+)/gi, operator: '>' }, { regex: /(\w+)\s+<\s+(\d+)/gi, operator: '<' }, { regex: /(\w+)\s+contains?\s+([^,\s]+)/gi, operator: 'CONTAINS' } ]; for (const pattern of patterns) { let match; while ((match = pattern.regex.exec(query)) !== null) { filters.push({ property: match[1], operator: pattern.operator, value: isNaN(Number(match[2])) ? match[2] : Number(match[2]), confidence: 0.7 }); } } return filters; } extractAggregations(query) { const aggregations = []; const lowerQuery = query.toLowerCase(); if (lowerQuery.includes('count')) { aggregations.push({ function: 'COUNT' }); } if (lowerQuery.includes('sum')) { aggregations.push({ function: 'SUM' }); } if (lowerQuery.includes('average') || lowerQuery.includes('avg')) { aggregations.push({ function: 'AVG' }); } if (lowerQuery.includes('minimum') || lowerQuery.includes('min')) { aggregations.push({ function: 'MIN' }); } if (lowerQuery.includes('maximum') || lowerQuery.includes('max')) { aggregations.push({ function: 'MAX' }); } return aggregations; } extractOrderBy(query) { const orderBy = []; const orderMatch = query.match(/order by (\w+)(?:\s+(asc|desc))?/i); if (orderMatch) { orderBy.push({ property: orderMatch[1], direction: orderMatch[2]?.toUpperCase() || 'ASC' }); } return orderBy; } extractLimit(query) { const limitMatch = query.match(/limit (\d+)/i); return limitMatch ? parseInt(limitMatch[1]) : undefined; } async generateCypherQuery(interpretedQuery) { const startTime = Date.now(); try { // Find matching query pattern const pattern = this.findBestPattern(interpretedQuery); if (pattern) { // Use pattern-based generation return await this.generateFromPattern(pattern, interpretedQuery); } else { // Use LLM-based generation return await this.generateWithLLM(interpretedQuery); } } catch (error) { const processingTime = Date.now() - startTime; this.metrics.generation.averageGenerationTime = (this.metrics.generation.averageGenerationTime + processingTime) / 2; throw new CypherQAError('QUERY_GENERATION_FAILED', 'Failed to generate Cypher query', { error: error instanceof Error ? error.message : String(error) }, interpretedQuery.query); } } findBestPattern(interpretedQuery) { let bestPattern = null; let bestScore = 0; for (const pattern of this.queryPatterns) { let score = 0; // Match intent if (pattern.category === 'AGGREGATION' && interpretedQuery.intent === 'count') score += 3; if (pattern.category === 'BASIC' && interpretedQuery.intent === 'find') score += 3; if (pattern.category === 'TRAVERSAL' && interpretedQuery.intent === 'path') score += 3; // Match entities for (const entity of interpretedQuery.entities) { if (pattern.cypherTemplate.includes(`{${entity.type}}`)) score += 2; } if (score > bestScore) { bestScore = score; bestPattern = pattern; } } return bestScore > 2 ? bestPattern : null; } async generateFromPattern(pattern, interpretedQuery) { let cypher = pattern.cypherTemplate; const parameters = {}; // Replace template variables for (const entity of interpretedQuery.entities) { if (entity.type === 'node' && entity.label) { cypher = cypher.replace('{label}', entity.label); cypher = cypher.replace('{label1}', entity.label); } } // Add WHERE conditions if (interpretedQuery.filters.length > 0) { const conditions = interpretedQuery.filters.map(filter => { const paramName = `param_${filter.property}`; parameters[paramName] = filter.value; return `n.${filter.property} ${filter.operator} $${paramName}`; }).join(' AND '); cypher = cypher.replace('{conditions}', conditions); } else { cypher = cypher.replace('WHERE {conditions}', ''); } // Add LIMIT if (interpretedQuery.limit) { cypher += ` LIMIT ${interpretedQuery.limit}`; } return { cypher: cypher.trim(), parameters, explanation: `Generated using pattern: ${pattern.name}`, confidence: 0.8, estimatedComplexity: pattern.complexity, estimatedCost: this.estimateQueryCost(cypher), warnings: [], optimizations: [], metadata: { generatedAt: new Date(), llmModel: 'pattern-based', processingTime: 0, retryCount: 0 } }; } async generateWithLLM(interpretedQuery) { const schemaContext = this.buildSchemaContext(); const prompt = prompts_1.PromptTemplate.fromTemplate(` You are an expert Neo4j Cypher query generator. Given a natural language query and graph schema, generate a valid Cypher query. Graph Schema: {schema} Natural Language Query: {query} Intent: {intent} Entities: {entities} Filters: {filters} Generate a Cypher query that: 1. Is syntactically correct 2. Uses the provided schema 3. Answers the natural language query 4. Is optimized for performance Return only the Cypher query without explanation. `); const chain = runnables_1.RunnableSequence.from([ prompt, this.llm, new output_parsers_1.StringOutputParser() ]); const cypher = await chain.invoke({ schema: schemaContext, query: interpretedQuery.query, intent: interpretedQuery.intent, entities: JSON.stringify(interpretedQuery.entities), filters: JSON.stringify(interpretedQuery.filters) }); return { cypher: cypher.trim(), parameters: {}, explanation: 'Generated using LLM', confidence: 0.7, estimatedComplexity: 'MEDIUM', estimatedCost: this.estimateQueryCost(cypher), warnings: [], optimizations: [], metadata: { generatedAt: new Date(), llmModel: this.config.llm.model, processingTime: 0, retryCount: 0 } }; } buildSchemaContext() { if (!this.schema) return ''; const nodeLabels = this.schema.nodes.map(n => `${n.label}: ${n.properties.map(p => p.name).join(', ')}`).join('\n'); const relationships = this.schema.relationships.map(r => `(${r.startNode})-[${r.type}]->(${r.endNode})`).join('\n'); return `Node Labels:\n${nodeLabels}\n\nRelationships:\n${relationships}`; } estimateQueryCost(cypher) { // Simple cost estimation based on query complexity let cost = 1; if (cypher.includes('MATCH')) cost += 1; if (cypher.includes('WHERE')) cost += 1; if (cypher.includes('ORDER BY')) cost += 2; if (cypher.includes('*')) cost += 5; // Variable length paths are expensive if (cypher.includes('shortestPath')) cost += 3; return cost; } async validateCypherQuery(cypher) { // Basic syntax validation (in production, use Neo4j's EXPLAIN) const result = { isValid: true, syntaxErrors: [], semanticErrors: [], warnings: [], suggestions: [] }; // Check for basic syntax issues if (!cypher.trim()) { result.isValid = false; result.syntaxErrors.push({ message: 'Empty query', line: 1, column: 1, severity: 'ERROR' }); } // Check for unmatched parentheses const openParens = (cypher.match(/\(/g) || []).length; const closeParens = (cypher.match(/\)/g) || []).length; if (openParens !== closeParens) { result.isValid = false; result.syntaxErrors.push({ message: 'Unmatched parentheses', line: 1, column: 1, severity: 'ERROR' }); } return result; } async executeCypherQuery(cypherQuery) { const startTime = Date.now(); let session; try { session = this.driver.session({ database: this.config.neo4j.database || 'neo4j' }); const result = await session.run(cypherQuery.cypher, cypherQuery.parameters); const executionTime = Date.now() - startTime; this.metrics.execution.averageExecutionTime = (this.metrics.execution.averageExecutionTime + executionTime) / 2; const data = result.records.map(record => { const obj = {}; record.keys.forEach(key => { obj[key] = record.get(key); }); return obj; }); this.metrics.execution.averageRecordsReturned = (this.metrics.execution.averageRecordsReturned + data.length) / 2; return { success: true, data, summary: { executionTime, recordsReturned: data.length, recordsAvailable: data.length, nodesCreated: 0, nodesDeleted: 0, relationshipsCreated: 0, relationshipsDeleted: 0, propertiesSet: 0, labelsAdded: 0, labelsRemoved: 0, indexesAdded: 0, indexesRemoved: 0, constraintsAdded: 0, constraintsRemoved: 0 }, notifications: [] }; } catch (error) { this.metrics.execution.errorRate++; throw new CypherQAError('QUERY_EXECUTION_FAILED', 'Failed to execute Cypher query', { error: error instanceof Error ? error.message : String(error) }, undefined, cypherQuery.cypher); } finally { if (session) { await session.close(); } } } async formatResponse(result, interpretedQuery) { if (!result.success || result.data.length === 0) { return 'No results found for your query.'; } // Format based on intent switch (interpretedQuery.intent) { case 'count': const count = result.data[0]?.count || result.data.length; return `Found ${count} results.`; case 'find': if (result.data.length === 1) { return `Found 1 result: ${JSON.stringify(result.data[0], null, 2)}`; } else { return `Found ${result.data.length} results:\n${result.data.slice(0, 5).map((item, index) => `${index + 1}. ${JSON.stringify(item, null, 2)}`).join('\n')}${result.data.length > 5 ? '\n... and more' : ''}`; } case 'aggregate': return `Aggregation result: ${JSON.stringify(result.data[0], null, 2)}`; case 'path': return `Path found: ${JSON.stringify(result.data[0], null, 2)}`; default: return `Query executed successfully. Found ${result.data.length} results.`; } } async generateExplanation(interpretedQuery, cypherQuery, executionResult) { const explanation = [ `Query Intent: ${interpretedQuery.intent}`, `Generated Cypher: ${cypherQuery.cypher}`, `Execution Time: ${executionResult.summary.executionTime}ms`, `Records Returned: ${executionResult.summary.recordsReturned}`, `Confidence: ${(cypherQuery.confidence * 100).toFixed(1)}%` ]; if (cypherQuery.warnings.length > 0) { explanation.push(`Warnings: ${cypherQuery.warnings.join(', ')}`); } return explanation.join('\n'); } async generateSuggestions(interpretedQuery, executionResult) { const suggestions = { relatedQueries: [], optimizations: [], followUpQuestions: [] }; // Generate related queries based on intent switch (interpretedQuery.intent) { case 'find': suggestions.relatedQueries.push(`Count ${interpretedQuery.entities.map(e => e.name).join(' and ')}`, `Show properties of ${interpretedQuery.entities.map(e => e.name).join(' and ')}`); break; case 'count': suggestions.relatedQueries.push(`Show examples of ${interpretedQuery.entities.map(e => e.name).join(' and ')}`, `Find relationships involving ${interpretedQuery.entities.map(e => e.name).join(' and ')}`); break; } // Generate follow-up questions if (executionResult.data.length > 0) { suggestions.followUpQuestions.push('Would you like to see more details about these results?', 'Do you want to filter these results further?'); } else { suggestions.followUpQuestions.push('Would you like to try a broader search?', 'Should I help you explore the available data?'); } // Generate optimization suggestions if (executionResult.summary.executionTime > 1000) { suggestions.optimizations.push('Consider adding indexes for better performance'); } return suggestions; } updateMetrics(success, processingTime, confidence) { // Update average response time this.metrics.queries.averageResponseTime = (this.metrics.queries.averageResponseTime * (this.metrics.queries.total - 1) + processingTime) / this.metrics.queries.total; // Update average confidence if (success) { this.metrics.queries.averageConfidence = (this.metrics.queries.averageConfidence * this.metrics.queries.successful + confidence) / (this.metrics.queries.successful + 1); } // Update performance metrics this.metrics.performance.memoryUsage = process.memoryUsage().heapUsed; this.metrics.performance.cpuUsage = process.cpuUsage().user; this.metrics.performance.cacheSize = this.schemaCache.size; } getSchemaVersion() { if (!this.schema) return 'unknown'; const hash = (0, crypto_1.createHash)('md5'); hash.update(JSON.stringify({ nodeCount: this.schema.statistics.nodeCount, relationshipCount: this.schema.statistics.relationshipCount, labelCount: this.schema.statistics.labelCount, lastUpdated: this.metrics.schema.lastUpdated.toISOString() })); return hash.digest('hex').substring(0, 8); } async getMetrics() { // Update performance metrics this.metrics.performance.memoryUsage = process.memoryUsage().heapUsed; this.metrics.performance.cpuUsage = process.cpuUsage().user; this.metrics.performance.cacheSize = this.schemaCache.size; return { ...this.metrics }; } async getSchema() { return this.schema; } async refreshSchema() { await this.introspectSchema(); } async shutdown() { this.schemaCache.clear(); logger_1.default.info('GraphCypherQAChain shutdown complete'); } } exports.GraphCypherQAChain = GraphCypherQAChain; //# sourceMappingURL=GraphCypherQAChain.js.map