@aaswe/codebase-ai
Version:
AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs
998 lines (993 loc) • 37.8 kB
JavaScript
"use strict";
/**
* SPARQL Query Engine
*
* Production-ready natural language to SPARQL translation engine with
* intelligent query generation, optimization, and execution capabilities.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SPARQLQueryEngine = void 0;
const events_1 = require("events");
const uuid_1 = require("uuid");
const openai_1 = require("@langchain/openai");
// Temporarily disable Anthropic to avoid dependency issues
// import { ChatAnthropic } from '@langchain/anthropic';
const prompts_1 = require("@langchain/core/prompts");
const output_parsers_1 = require("@langchain/core/output_parsers");
const runnables_1 = require("@langchain/core/runnables");
const logger_1 = __importDefault(require("../../../utils/logger"));
const types_1 = require("./types");
/**
* SPARQL Query Engine for natural language to SPARQL translation
*/
class SPARQLQueryEngine extends events_1.EventEmitter {
config;
llm;
rdfStore;
queryCache = new Map();
metrics;
queryPatterns = [];
ontologyInfo = null;
isInitialized = false;
cacheCleanupInterval;
constructor(config, rdfStore) {
super();
this.config = this.mergeWithDefaults(config);
this.rdfStore = rdfStore;
this.metrics = this.initializeMetrics();
// Initialize LLM based on provider
this.llm = this.initializeLLM();
// Initialize query patterns
this.initializeQueryPatterns();
logger_1.default.info('SPARQLQueryEngine initialized', {
provider: this.config.llm.provider,
model: this.config.llm.model,
caching: this.config.caching?.enabled
});
}
/**
* Merge user config with defaults
*/
mergeWithDefaults(config) {
return {
rdf: {
...config.rdf,
timeout: config.rdf.timeout || 30000,
maxResults: config.rdf.maxResults || 1000
},
llm: {
...config.llm,
temperature: config.llm.temperature || 0.1,
maxTokens: config.llm.maxTokens || 1000
},
queryGeneration: {
maxRetries: config.queryGeneration?.maxRetries || 3,
timeoutMs: config.queryGeneration?.timeoutMs || 30000,
validateSyntax: config.queryGeneration?.validateSyntax !== false,
optimizeQuery: config.queryGeneration?.optimizeQuery !== false,
usePatterns: config.queryGeneration?.usePatterns !== false
},
prefixes: config.prefixes || {
'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
'owl': 'http://www.w3.org/2002/07/owl#',
'xsd': 'http://www.w3.org/2001/XMLSchema#'
},
response: {
includeQuery: config.response?.includeQuery !== false,
includeExplanation: config.response?.includeExplanation !== false,
formatResults: config.response?.formatResults !== false,
maxResults: config.response?.maxResults || 100
},
caching: {
enabled: config.caching?.enabled !== false,
ttl: config.caching?.ttl || 300000, // 5 minutes
maxSize: config.caching?.maxSize || 1000
}
};
}
/**
* Initialize metrics
*/
initializeMetrics() {
return {
queries: {
total: 0,
successful: 0,
failed: 0,
cached: 0,
averageResponseTime: 0,
averageConfidence: 0
},
generation: {
patternBased: 0,
llmBased: 0,
hybrid: 0,
averageGenerationTime: 0,
retryRate: 0,
syntaxErrorRate: 0
},
execution: {
averageExecutionTime: 0,
averageResultCount: 0,
timeoutRate: 0,
errorRate: 0
},
cache: {
hits: 0,
misses: 0,
hitRate: 0,
size: 0
}
};
}
/**
* Initialize LLM based on configuration
*/
initializeLLM() {
const apiKey = this.config.llm.apiKey;
switch (this.config.llm.provider) {
case 'openai':
return new openai_1.ChatOpenAI({
modelName: this.config.llm.model,
temperature: this.config.llm.temperature || 0.1,
maxTokens: this.config.llm.maxTokens || 1000,
openAIApiKey: apiKey || process.env.OPENAI_API_KEY || '',
...(this.config.llm.baseURL && {
configuration: {
baseURL: this.config.llm.baseURL
}
})
});
case 'anthropic':
// Temporarily disabled - use OpenAI interface instead
throw new Error('Anthropic provider temporarily disabled. Use OpenAI or local provider.');
case 'local':
// Use OpenAI-compatible interface for local models
return new openai_1.ChatOpenAI({
modelName: this.config.llm.model,
temperature: this.config.llm.temperature || 0.1,
maxTokens: this.config.llm.maxTokens || 1000,
openAIApiKey: 'local-key', // Placeholder for local models
configuration: {
baseURL: this.config.llm.baseURL || 'http://localhost:1234/v1'
}
});
default:
throw new Error(`Unsupported LLM provider: ${this.config.llm.provider}`);
}
}
/**
* Initialize query patterns for pattern-based generation
*/
initializeQueryPatterns() {
this.queryPatterns = [
{
id: 'simple-select',
name: 'Simple Select',
description: 'Basic SELECT query for finding entities',
intent: 'select',
patterns: ['find', 'show', 'get', 'list'],
template: 'SELECT ?x WHERE { ?x rdf:type {CLASS} }',
variables: ['CLASS'],
confidence: 0.9,
examples: ['find all users', 'show products', 'list companies']
},
{
id: 'count-entities',
name: 'Count Entities',
description: 'COUNT query for counting entities',
intent: 'count',
patterns: ['how many', 'count', 'number of'],
template: 'SELECT (COUNT(?x) as ?count) WHERE { ?x rdf:type {CLASS} }',
variables: ['CLASS'],
confidence: 0.95,
examples: ['how many users', 'count products', 'number of companies']
},
{
id: 'ask-existence',
name: 'Ask Existence',
description: 'ASK query for checking existence',
intent: 'ask',
patterns: ['is there', 'does', 'exists'],
template: 'ASK { ?x rdf:type {CLASS} }',
variables: ['CLASS'],
confidence: 0.9,
examples: ['is there a user', 'does product exist']
}
];
}
/**
* Load ontology information from RDF store
*/
async loadOntologyInfo() {
try {
// Simple ontology loading - in production, use more sophisticated methods
this.ontologyInfo = {
classes: [
{ uri: 'http://example.org/User', label: 'User', instances: 0 },
{ uri: 'http://example.org/Product', label: 'Product', instances: 0 },
{ uri: 'http://example.org/Company', label: 'Company', instances: 0 }
],
properties: [
{ uri: 'http://example.org/name', label: 'name', type: 'DatatypeProperty', domain: [], range: [] },
{ uri: 'http://example.org/age', label: 'age', type: 'DatatypeProperty', domain: [], range: [] },
{ uri: 'http://example.org/worksFor', label: 'works for', type: 'ObjectProperty', domain: [], range: [] }
],
individuals: [],
namespaces: this.config.prefixes
};
logger_1.default.info('Ontology information loaded');
}
catch (error) {
logger_1.default.warn('Failed to load ontology information', { error });
this.ontologyInfo = null;
}
}
/**
* Start cache cleanup interval
*/
startCacheCleanup() {
this.cacheCleanupInterval = setInterval(() => {
this.cleanupCache();
}, 60000); // Clean every minute
}
/**
* Clean up expired cache entries
*/
cleanupCache() {
const now = Date.now();
for (const [key, entry] of this.queryCache.entries()) {
if (now - entry.timestamp > entry.ttl) {
this.queryCache.delete(key);
}
}
this.metrics.cache.size = this.queryCache.size;
}
/**
* Generate cache key for query
*/
generateCacheKey(query) {
return Buffer.from(query.toLowerCase().trim()).toString('base64');
}
/**
* Get cached result
*/
getCachedResult(cacheKey) {
const entry = this.queryCache.get(cacheKey);
if (!entry) {
this.metrics.cache.misses++;
return null;
}
const now = Date.now();
if (now - entry.timestamp > entry.ttl) {
this.queryCache.delete(cacheKey);
this.metrics.cache.misses++;
return null;
}
entry.hits++;
this.metrics.cache.hits++;
this.updateCacheHitRate();
return entry.result;
}
/**
* Cache query result
*/
cacheResult(cacheKey, result) {
const maxSize = this.config.caching?.maxSize || 1000;
if (this.queryCache.size >= maxSize) {
// Remove oldest entry
const oldestKey = this.queryCache.keys().next().value;
if (oldestKey) {
this.queryCache.delete(oldestKey);
}
}
this.queryCache.set(cacheKey, {
key: cacheKey,
query: result.originalQuery,
result,
timestamp: Date.now(),
ttl: this.config.caching?.ttl || 300000,
hits: 0
});
this.metrics.cache.size = this.queryCache.size;
}
/**
* Update cache hit rate
*/
updateCacheHitRate() {
const total = this.metrics.cache.hits + this.metrics.cache.misses;
this.metrics.cache.hitRate = total > 0 ? this.metrics.cache.hits / total : 0;
}
/**
* Update metrics
*/
updateMetrics(response, success) {
this.metrics.queries.total++;
if (success && response) {
this.metrics.queries.successful++;
this.metrics.queries.averageResponseTime =
(this.metrics.queries.averageResponseTime * (this.metrics.queries.successful - 1) +
response.metadata.processingTime) / this.metrics.queries.successful;
this.metrics.queries.averageConfidence =
(this.metrics.queries.averageConfidence * (this.metrics.queries.successful - 1) +
response.interpretedQuery.confidence) / this.metrics.queries.successful;
// Update generation metrics
switch (response.generatedSPARQL.generationMethod) {
case 'pattern':
this.metrics.generation.patternBased++;
break;
case 'llm':
this.metrics.generation.llmBased++;
break;
case 'hybrid':
this.metrics.generation.hybrid++;
break;
}
// Update execution metrics
if (response.executionResult.success) {
this.metrics.execution.averageExecutionTime =
(this.metrics.execution.averageExecutionTime * (this.metrics.queries.successful - 1) +
response.executionResult.summary.executionTime) / this.metrics.queries.successful;
this.metrics.execution.averageResultCount =
(this.metrics.execution.averageResultCount * (this.metrics.queries.successful - 1) +
response.executionResult.summary.resultCount) / this.metrics.queries.successful;
}
}
else {
this.metrics.queries.failed++;
}
}
/**
* Initialize the SPARQL Query Engine
*/
async initialize() {
try {
// Load ontology information from RDF store
await this.loadOntologyInfo();
// Warm up cache if enabled
if (this.config.caching?.enabled) {
this.startCacheCleanup();
}
this.isInitialized = true;
logger_1.default.info('SPARQLQueryEngine initialization complete');
}
catch (error) {
logger_1.default.error('Failed to initialize SPARQLQueryEngine', { error });
throw new types_1.SPARQLError('INVALID_PARAMETERS', 'Failed to initialize SPARQL engine', { details: error });
}
}
/**
* Process natural language query and return SPARQL results
*/
async query(naturalLanguageQuery) {
if (!this.isInitialized) {
await this.initialize();
}
const queryId = (0, uuid_1.v4)();
const startTime = Date.now();
this.emit('query_start', {
type: 'query_start',
queryId,
query: naturalLanguageQuery,
timestamp: startTime
});
try {
// Check cache first
const cacheKey = this.generateCacheKey(naturalLanguageQuery);
const cachedResult = this.getCachedResult(cacheKey);
if (cachedResult) {
this.emit('cache_hit', {
type: 'cache_hit',
queryId,
query: naturalLanguageQuery,
timestamp: Date.now()
});
this.metrics.queries.cached++;
return { ...cachedResult, metadata: { ...cachedResult.metadata, cacheHit: true } };
}
this.emit('cache_miss', {
type: 'cache_miss',
queryId,
query: naturalLanguageQuery,
timestamp: Date.now()
});
// Parse natural language query
const interpretedQuery = await this.parseNaturalLanguageQuery(naturalLanguageQuery);
// Generate SPARQL query
const generatedSPARQL = await this.generateSPARQLQuery(interpretedQuery);
// Validate generated SPARQL
if (this.config.queryGeneration?.validateSyntax) {
const validation = this.validateSPARQL(generatedSPARQL.sparql);
if (!validation.valid) {
throw new types_1.SPARQLError('SPARQL_SYNTAX_ERROR', 'Generated SPARQL has syntax errors', {
query: naturalLanguageQuery,
sparql: generatedSPARQL.sparql,
details: validation.errors
});
}
}
// Execute SPARQL query
const executionResult = await this.executeSPARQLQuery(generatedSPARQL);
// Format response
const formattedResponse = this.formatResponse(executionResult, interpretedQuery.intent);
// Generate suggestions
const suggestions = await this.generateSuggestions(interpretedQuery, executionResult);
// Create response
const response = {
originalQuery: naturalLanguageQuery,
interpretedQuery,
generatedSPARQL,
executionResult,
formattedResponse,
explanation: this.generateExplanation(interpretedQuery, generatedSPARQL),
suggestions,
metadata: {
queryId,
timestamp: startTime,
processingTime: Date.now() - startTime,
cacheHit: false
}
};
// Cache result if enabled
if (this.config.caching?.enabled) {
this.cacheResult(cacheKey, response);
}
// Update metrics
this.updateMetrics(response, true);
this.emit('query_complete', {
type: 'query_complete',
queryId,
query: naturalLanguageQuery,
timestamp: Date.now(),
duration: Date.now() - startTime
});
return response;
}
catch (error) {
const sparqlError = error instanceof types_1.SPARQLError ? error :
new types_1.SPARQLError('QUERY_EXECUTION_FAILED', 'Query processing failed', {
query: naturalLanguageQuery,
details: error
});
this.emit('query_error', {
type: 'query_error',
queryId,
query: naturalLanguageQuery,
timestamp: Date.now(),
duration: Date.now() - startTime,
error: sparqlError
});
this.updateMetrics(null, false);
throw sparqlError;
}
}
/**
* Get current metrics
*/
getMetrics() {
return { ...this.metrics };
}
/**
* Shutdown the engine
*/
async shutdown() {
if (this.cacheCleanupInterval) {
clearInterval(this.cacheCleanupInterval);
}
this.queryCache.clear();
this.removeAllListeners();
logger_1.default.info('SPARQLQueryEngine shutdown complete');
}
/**
* Parse natural language query into structured format
*/
async parseNaturalLanguageQuery(query) {
try {
// Detect query intent
const intent = this.detectQueryIntent(query);
// Extract entities
const entities = await this.extractEntities(query);
// Extract triple patterns
const triplePatterns = await this.extractTriplePatterns(query, entities);
// Extract filters
const filters = this.extractFilters(query);
// Extract aggregations
const aggregations = this.extractAggregations(query);
// Calculate confidence
const confidence = this.calculateParsingConfidence(intent, entities, triplePatterns);
return {
intent,
entities,
triplePatterns,
filters,
aggregations,
confidence
};
}
catch (error) {
logger_1.default.error('Failed to parse natural language query', { query, error });
throw new types_1.SPARQLError('QUERY_PARSING_FAILED', 'Failed to parse natural language query', {
query,
details: error
});
}
}
/**
* Detect query intent from natural language
*/
detectQueryIntent(query) {
const lowerQuery = query.toLowerCase();
// Intent detection patterns
if (lowerQuery.includes('how many') || lowerQuery.includes('count')) {
return 'count';
}
if (lowerQuery.includes('average') || lowerQuery.includes('sum') || lowerQuery.includes('max') || lowerQuery.includes('min')) {
return 'aggregate';
}
if (lowerQuery.includes('is there') || lowerQuery.includes('does') || lowerQuery.includes('exists')) {
return 'ask';
}
if (lowerQuery.includes('describe') || lowerQuery.includes('tell me about')) {
return 'describe';
}
if (lowerQuery.includes('path') || lowerQuery.includes('connection') || lowerQuery.includes('related')) {
return 'path';
}
if (lowerQuery.includes('construct') || lowerQuery.includes('build') || lowerQuery.includes('create graph')) {
return 'construct';
}
// Default to select
return 'select';
}
/**
* Extract entities from natural language query
*/
async extractEntities(query) {
const entities = [];
// Simple entity extraction - in production, use NER models
const words = query.toLowerCase().split(/\s+/);
// Look for known classes and properties from ontology
if (this.ontologyInfo) {
for (const cls of this.ontologyInfo.classes) {
if (cls.label && words.some(word => word.includes(cls.label.toLowerCase()))) {
entities.push({
type: 'class',
value: cls.label,
uri: cls.uri,
confidence: 0.8
});
}
}
for (const prop of this.ontologyInfo.properties) {
if (prop.label && words.some(word => word.includes(prop.label.toLowerCase()))) {
entities.push({
type: 'property',
value: prop.label,
uri: prop.uri,
confidence: 0.8
});
}
}
}
return entities;
}
/**
* Extract triple patterns from query and entities
*/
async extractTriplePatterns(_query, entities) {
const patterns = [];
// Simple pattern extraction - in production, use more sophisticated NLP
for (const entity of entities) {
if (entity.type === 'class') {
patterns.push({
subject: { type: 'variable', value: '?x', confidence: 1.0 },
predicate: { type: 'property', value: 'rdf:type', uri: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', confidence: 1.0 },
object: entity
});
}
}
return patterns;
}
/**
* Extract filters from natural language query
*/
extractFilters(query) {
const filters = [];
const lowerQuery = query.toLowerCase();
// Simple filter extraction
const numberMatch = lowerQuery.match(/(\w+)\s+(greater|less|more|fewer)\s+than\s+(\d+)/);
if (numberMatch) {
filters.push({
type: numberMatch[2].includes('greater') || numberMatch[2].includes('more') ? 'greater' : 'less',
property: numberMatch[1],
value: parseInt(numberMatch[3])
});
}
return filters;
}
/**
* Extract aggregations from natural language query
*/
extractAggregations(query) {
const aggregations = [];
const lowerQuery = query.toLowerCase();
if (lowerQuery.includes('count')) {
aggregations.push({
function: 'COUNT',
variable: '?x',
alias: 'count'
});
}
if (lowerQuery.includes('average')) {
aggregations.push({
function: 'AVG',
variable: '?value',
alias: 'average'
});
}
return aggregations;
}
/**
* Calculate parsing confidence
*/
calculateParsingConfidence(intent, entities, patterns) {
let confidence = 0.5; // Base confidence
// Boost confidence based on entities found
if (entities.length > 0) {
confidence += 0.2;
}
// Boost confidence based on patterns
if (patterns.length > 0) {
confidence += 0.2;
}
// Intent-specific adjustments
if (intent !== 'select') {
confidence += 0.1; // Specific intents are more confident
}
return Math.min(confidence, 1.0);
}
/**
* Generate SPARQL query from parsed structure
*/
async generateSPARQLQuery(parsedQuery) {
try {
// Try pattern-based generation first
if (this.config.queryGeneration?.usePatterns) {
const patternResult = this.tryPatternBasedGeneration(parsedQuery);
if (patternResult) {
return patternResult;
}
}
// Fall back to LLM-based generation
return await this.generateWithLLM(parsedQuery);
}
catch (error) {
logger_1.default.error('Failed to generate SPARQL query', { parsedQuery, error });
throw new types_1.SPARQLError('SPARQL_GENERATION_FAILED', 'Failed to generate SPARQL query', {
details: error
});
}
}
/**
* Try pattern-based SPARQL generation
*/
tryPatternBasedGeneration(parsedQuery) {
const matchingPatterns = this.queryPatterns.filter(pattern => pattern.intent === parsedQuery.intent && pattern.confidence >= 0.7);
if (matchingPatterns.length === 0) {
return null;
}
// Use the highest confidence pattern
const bestPattern = matchingPatterns.reduce((best, current) => current.confidence > best.confidence ? current : best);
try {
const sparql = this.applyPattern(bestPattern, parsedQuery);
return {
sparql,
prefixes: this.config.prefixes,
parameters: {},
confidence: bestPattern.confidence,
explanation: `Generated using pattern: ${bestPattern.name}`,
generationMethod: 'pattern',
optimizations: [],
estimatedComplexity: 'low'
};
}
catch (error) {
logger_1.default.warn('Pattern-based generation failed', { pattern: bestPattern.id, error });
return null;
}
}
/**
* Apply pattern template to generate SPARQL
*/
applyPattern(pattern, parsedQuery) {
let sparql = pattern.template;
// Replace variables in template
for (const variable of pattern.variables) {
const value = this.getVariableValue(variable, parsedQuery);
sparql = sparql.replace(new RegExp(`\\{${variable}\\}`, 'g'), value);
}
return sparql;
}
/**
* Get variable value for pattern template
*/
getVariableValue(variable, parsedQuery) {
switch (variable) {
case 'CLASS':
const classEntity = parsedQuery.entities.find(e => e.type === 'class');
return classEntity?.uri || classEntity?.value || '?class';
case 'PROPERTY':
const propEntity = parsedQuery.entities.find(e => e.type === 'property');
return propEntity?.uri || propEntity?.value || '?property';
default:
return `?${variable.toLowerCase()}`;
}
}
/**
* Generate SPARQL using LLM
*/
async generateWithLLM(parsedQuery) {
const prompt = prompts_1.PromptTemplate.fromTemplate(`
You are an expert SPARQL query generator. Convert the following natural language query structure into a valid SPARQL query.
Query Intent: {intent}
Entities: {entities}
Triple Patterns: {triplePatterns}
Filters: {filters}
Aggregations: {aggregations}
Available Prefixes:
{prefixes}
Ontology Information:
{ontologyInfo}
Generate a valid SPARQL query that:
1. Uses appropriate prefixes
2. Includes proper WHERE clauses
3. Applies filters correctly
4. Handles aggregations if needed
5. Is optimized for performance
Return only the SPARQL query without explanations.
`);
const chain = runnables_1.RunnableSequence.from([
prompt,
this.llm,
new output_parsers_1.StringOutputParser()
]);
try {
const sparql = await chain.invoke({
intent: parsedQuery.intent,
entities: JSON.stringify(parsedQuery.entities, null, 2),
triplePatterns: JSON.stringify(parsedQuery.triplePatterns, null, 2),
filters: JSON.stringify(parsedQuery.filters, null, 2),
aggregations: JSON.stringify(parsedQuery.aggregations, null, 2),
prefixes: Object.entries(this.config.prefixes)
.map(([prefix, uri]) => `PREFIX ${prefix}: <${uri}>`)
.join('\n'),
ontologyInfo: this.ontologyInfo ? JSON.stringify(this.ontologyInfo, null, 2) : 'Not available'
});
return {
sparql: sparql.trim(),
prefixes: this.config.prefixes,
parameters: {},
confidence: Math.min(parsedQuery.confidence + 0.1, 1.0),
explanation: 'Generated using LLM-based natural language processing',
generationMethod: 'llm',
optimizations: [],
estimatedComplexity: this.estimateQueryComplexity(sparql)
};
}
catch (error) {
logger_1.default.error('LLM-based SPARQL generation failed', { error });
throw new types_1.SPARQLError('SPARQL_GENERATION_FAILED', 'LLM generation failed', { details: error });
}
}
/**
* Estimate query complexity
*/
estimateQueryComplexity(sparql) {
const lowerSparql = sparql.toLowerCase();
let complexity = 0;
// Count complexity indicators
if (lowerSparql.includes('union'))
complexity += 2;
if (lowerSparql.includes('optional'))
complexity += 1;
if (lowerSparql.includes('filter'))
complexity += 1;
if (lowerSparql.includes('group by'))
complexity += 2;
if (lowerSparql.includes('order by'))
complexity += 1;
if ((lowerSparql.match(/\{/g) || []).length > 2)
complexity += 1;
if (complexity >= 4)
return 'high';
if (complexity >= 2)
return 'medium';
return 'low';
}
/**
* Execute SPARQL query against RDF store
*/
async executeSPARQLQuery(generatedQuery) {
const startTime = Date.now();
try {
// Add prefixes to query
const fullQuery = this.addPrefixesToQuery(generatedQuery.sparql, generatedQuery.prefixes);
// Execute query with timeout
const timeout = this.config.rdf.timeout || 30000;
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Query timeout')), timeout);
});
const queryPromise = this.rdfStore.query(fullQuery);
const result = await Promise.race([queryPromise, timeoutPromise]);
const executionTime = Date.now() - startTime;
return {
success: true,
data: Array.isArray(result) ? result : [result],
summary: {
executionTime,
resultCount: Array.isArray(result) ? result.length : 1,
hasMore: false
}
};
}
catch (error) {
const executionTime = Date.now() - startTime;
logger_1.default.error('SPARQL query execution failed', {
query: generatedQuery.sparql,
error,
executionTime
});
return {
success: false,
data: [],
summary: {
executionTime,
resultCount: 0
},
error: {
code: 'EXECUTION_FAILED',
message: error instanceof Error ? error.message : String(error),
details: error
}
};
}
}
/**
* Add prefixes to SPARQL query
*/
addPrefixesToQuery(sparql, prefixes) {
const prefixLines = Object.entries(prefixes)
.map(([prefix, uri]) => `PREFIX ${prefix}: <${uri}>`)
.join('\n');
return prefixLines + '\n\n' + sparql;
}
/**
* Validate SPARQL syntax
*/
validateSPARQL(sparql) {
const errors = [];
const warnings = [];
// Basic syntax validation
if (!sparql.trim()) {
errors.push({
type: 'syntax',
message: 'Empty SPARQL query'
});
}
// Check for balanced braces
const openBraces = (sparql.match(/\{/g) || []).length;
const closeBraces = (sparql.match(/\}/g) || []).length;
if (openBraces !== closeBraces) {
errors.push({
type: 'syntax',
message: 'Unbalanced braces in SPARQL query'
});
}
// Check for required keywords
const lowerSparql = sparql.toLowerCase();
if (!lowerSparql.includes('select') && !lowerSparql.includes('ask') &&
!lowerSparql.includes('construct') && !lowerSparql.includes('describe')) {
errors.push({
type: 'syntax',
message: 'Missing query type (SELECT, ASK, CONSTRUCT, or DESCRIBE)'
});
}
return {
valid: errors.length === 0,
errors,
warnings
};
}
/**
* Format execution results for display
*/
formatResponse(result, intent) {
if (!result.success) {
return `Query execution failed: ${result.error?.message || 'Unknown error'}`;
}
if (result.data.length === 0) {
return 'No results found.';
}
switch (intent) {
case 'ask':
return result.data[0] ? 'Yes' : 'No';
case 'count':
return `Count: ${result.data[0]?.count || result.data.length}`;
case 'aggregate':
const aggResult = result.data[0];
if (aggResult) {
const keys = Object.keys(aggResult);
return keys.map(key => `${key}: ${aggResult[key]?.value || aggResult[key]}`).join(', ');
}
return 'No aggregation results';
default:
// Format as table for select queries
if (result.data.length === 1) {
const item = result.data[0];
return Object.entries(item)
.map(([key, value]) => `${key}: ${value?.value || value}`)
.join('\n');
}
else {
return `Found ${result.data.length} results:\n` +
result.data.slice(0, 5).map((item, index) => `${index + 1}. ${Object.values(item).map(v => v?.value || v).join(', ')}`).join('\n') +
(result.data.length > 5 ? `\n... and ${result.data.length - 5} more` : '');
}
}
}
/**
* Generate query explanation
*/
generateExplanation(parsed, generated) {
const parts = [
`This query searches for ${parsed.intent} operations`,
`Found ${parsed.entities.length} entities in the query`,
`Generated using ${generated.generationMethod} method`,
`Estimated complexity: ${generated.estimatedComplexity}`
];
if (parsed.filters.length > 0) {
parts.push(`Applied ${parsed.filters.length} filters`);
}
if (parsed.aggregations.length > 0) {
parts.push(`Includes ${parsed.aggregations.length} aggregations`);
}
return parts.join('. ') + '.';
}
/**
* Generate query suggestions
*/
async generateSuggestions(parsed, result) {
const suggestions = {
relatedQueries: [],
followUpQuestions: [],
optimizations: [],
alternativeFormulations: []
};
// Generate related queries based on intent
switch (parsed.intent) {
case 'select':
suggestions.relatedQueries.push('Count the results', 'Show more details');
break;
case 'count':
suggestions.relatedQueries.push('Show the actual items', 'Group by category');
break;
case 'ask':
suggestions.relatedQueries.push('Show examples', 'Count how many');
break;
}
// Performance suggestions
if (result.summary.executionTime > 1000) {
suggestions.optimizations.push('Consider adding more specific filters to improve performance');
}
if (result.summary.resultCount > 100) {
suggestions.optimizations.push('Large result set - consider using LIMIT clause');
}
return suggestions;
}
}
exports.SPARQLQueryEngine = SPARQLQueryEngine;
//# sourceMappingURL=SPARQLQueryEngine.js.map