@aaswe/codebase-ai
Version:
AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs
871 lines (867 loc) • 35.8 kB
JavaScript
"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