diff --git a/src/ai/react-agent.ts b/src/ai/react-agent.ts index 0a1f0c893..a41339552 100644 --- a/src/ai/react-agent.ts +++ b/src/ai/react-agent.ts @@ -62,10 +62,12 @@ export class ReActAgent { private cypherGenerator: CypherGenerator; private context: ReActContext | null = null; private chatHistory: LocalStorageChatHistory | null = null; + private graph?: KnowledgeGraph; - constructor(llmService: LLMService, cypherGenerator: CypherGenerator, _kuzuQueryEngine?: any) { + constructor(llmService: LLMService, cypherGenerator: CypherGenerator, graph?: KnowledgeGraph) { this.llmService = llmService; this.cypherGenerator = cypherGenerator; + this.graph = graph; // Store graph reference for KuzuDB access } /** @@ -85,7 +87,33 @@ export class ReActAgent { projectName: context.projectName, sessionId: context.sessionId }; + + // Update graph reference for KuzuDB access + this.graph = context.graph; + this.cypherGenerator.updateSchema(context.graph); + + // Test KuzuDB connectivity + await this.testKuzuDBConnection(); + } + + /** + * Test KuzuDB connection and log results + */ + private async testKuzuDBConnection(): Promise { + try { + console.log('๐Ÿงช Testing KuzuDB connection...'); + const testResult = await this.executeGraphQuery('MATCH (n) RETURN COUNT(n) as nodeCount LIMIT 1'); + + if (testResult.success && testResult.source === 'KuzuDB') { + console.log('โœ… KuzuDB connection test successful!'); + console.log(`๐Ÿ“Š Total nodes in KuzuDB: ${testResult.rows[0]?.[0] || 'unknown'}`); + } else { + console.log('โš ๏ธ KuzuDB connection test failed, using fallback'); + } + } catch (error) { + console.log('โŒ KuzuDB connection test error:', error); + } } /** @@ -352,8 +380,23 @@ CRITICAL: The action field must be exactly one of these four values: query_graph maxRetries: 3 }); - // Execute the query (placeholder - implement based on your graph engine) - const results = await this.executeGraphQuery(cypherQuery.cypher); + // Add LIMIT if not present to prevent JSON truncation issues + let finalCypher = cypherQuery.cypher; + if (!finalCypher.toLowerCase().includes('limit')) { + finalCypher += ' LIMIT 20'; + } + + // Execute the query + const results = await this.executeGraphQuery(finalCypher); + + // Truncate large responses to prevent JSON parsing issues + if (results.rows && results.rows.length > 20) { + results.rows = results.rows.slice(0, 20); + results.rowCount = 20; + results.truncated = true; + results.summary += ' (showing first 20 results)'; + } + output = JSON.stringify(results, null, 2); success = true; } catch (error) { @@ -424,12 +467,93 @@ CRITICAL: The action field must be exactly one of these four values: query_graph } /** - * Execute a graph query (placeholder implementation) + * Execute a graph query using KuzuDB if available, fallback to JSON graph */ private async executeGraphQuery(cypher: string): Promise { - // This is a placeholder - implement based on your graph engine - console.log('Executing Cypher query:', cypher); - return { nodes: [], relationships: [], message: 'Graph query executed (placeholder)' }; + console.log('๐Ÿ” ReActAgent executing Cypher query:', cypher); + + // Try to get KuzuQueryEngine from DualWriteKnowledgeGraph + if (this.graph && 'getKuzuGraph' in this.graph) { + const kuzuGraph = (this.graph as any).getKuzuGraph(); + if (kuzuGraph && 'executeQuery' in kuzuGraph) { + try { + console.log('๐Ÿš€ Using KuzuDB for query execution'); + const result = await kuzuGraph.executeQuery(cypher); + + // Format result for AI consumption + const formattedResult = { + success: true, + source: 'KuzuDB', + columns: result.columns || [], + rows: result.rows || [], + rowCount: result.rowCount || result.rows?.length || 0, + executionTime: result.executionTime || 0, + // Add human-readable summary + summary: `Found ${result.rowCount || result.rows?.length || 0} results in ${result.executionTime || 0}ms` + }; + + console.log(`โœ… KuzuDB query successful: ${formattedResult.rowCount} rows returned`); + return formattedResult; + + } catch (error) { + console.error('โŒ KuzuDB query failed:', error); + console.log('๐Ÿ”„ Falling back to JSON graph query'); + + // Return error info for AI to understand what went wrong + return { + success: false, + source: 'KuzuDB', + error: error instanceof Error ? error.message : 'Unknown error', + fallback: 'Attempting JSON graph query...' + }; + } + } + } + + // Fallback to JSON graph query + console.log('๐Ÿ“Š Using JSON graph fallback'); + return this.fallbackGraphQuery(cypher); + } + + /** + * Fallback graph query using the JSON graph and GraphQueryEngine + */ + private async fallbackGraphQuery(cypher: string): Promise { + if (!this.context?.graph) { + return { + nodes: [], + relationships: [], + message: 'No graph context available', + success: false, + source: 'none' + }; + } + + try { + // Use existing GraphQueryEngine for fallback + const { GraphQueryEngine } = await import('../core/graph/query-engine.ts'); + const queryEngine = new GraphQueryEngine(this.context.graph); + + console.log('๐Ÿ“„ Using JSON graph fallback for query execution'); + const result = queryEngine.executeQuery(cypher); + return { + nodes: result.nodes, + relationships: result.relationships, + data: result.data, + success: true, + source: 'JSON' + }; + } catch (error) { + console.error('โŒ Fallback query failed:', error); + return { + nodes: [], + relationships: [], + message: 'Query execution failed', + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + source: 'error' + }; + } } /** diff --git a/src/core/kuzu/kuzu-loader.ts b/src/core/kuzu/kuzu-loader.ts index 4d5394701..8c3099078 100644 --- a/src/core/kuzu/kuzu-loader.ts +++ b/src/core/kuzu/kuzu-loader.ts @@ -286,9 +286,17 @@ class KuzuInstanceImpl implements KuzuInstance { const columns = result.getColumnNames(); const rows: any[][] = []; - // Fetch all rows + // Fetch all rows and handle BigInt serialization while (result.hasNext()) { - rows.push(result.getNext()); + const row = result.getNext(); + // Convert BigInt values to strings to avoid serialization issues + const processedRow = row.map(cell => { + if (typeof cell === 'bigint') { + return cell.toString(); + } + return cell; + }); + rows.push(processedRow); } result.close(); diff --git a/src/core/kuzu/kuzu-npm-integration.ts b/src/core/kuzu/kuzu-npm-integration.ts index 2d2cb222d..db5cbd227 100644 --- a/src/core/kuzu/kuzu-npm-integration.ts +++ b/src/core/kuzu/kuzu-npm-integration.ts @@ -156,13 +156,23 @@ function createKuzuInstance(): KuzuInstance { // Extract data from kuzu-wasm result const columns = result.getColumnNames(); - const rows = await result.getAllRows(); // Get all rows at once (async) + const rawRows = await result.getAllRows(); // Get all rows at once (async) const rowCount = await result.getNumTuples(); // This might be async too + // Convert BigInt values to strings to avoid serialization issues + const rows = rawRows.map(row => + row.map(cell => { + if (typeof cell === 'bigint') { + return cell.toString(); + } + return cell; + }) + ); + const queryResult: QueryResult = { columns, rows, - rowCount, + rowCount: typeof rowCount === 'bigint' ? Number(rowCount) : rowCount, executionTime: 0 // TODO: Add timing if available }; diff --git a/src/services/ingestion.service.ts b/src/services/ingestion.service.ts index da3856e8c..7d375b004 100644 --- a/src/services/ingestion.service.ts +++ b/src/services/ingestion.service.ts @@ -70,21 +70,11 @@ export class IngestionService { throw new Error(result.error || 'Processing failed'); } + // Recreate the appropriate graph based on worker metadata + const graph = await this.recreateGraphFromResult(result); + return { - graph: { - nodes: result.nodes || [], - relationships: result.relationships || [], - addNode: () => {}, - addRelationship: () => {}, - removeNode: () => {}, - removeRelationship: () => {}, - clear: () => {}, - getNodeById: () => null, - getRelationshipById: () => null, - getNodesByLabel: () => [], - getRelationshipsByType: () => [], - getConnectedNodes: () => ({ incoming: [], outgoing: [] }) - }, + graph, fileContents }; } finally { @@ -137,21 +127,11 @@ export class IngestionService { throw new Error(result.error || 'Processing failed'); } + // Recreate the appropriate graph based on worker metadata + const graph = await this.recreateGraphFromResult(result); + return { - graph: { - nodes: result.nodes || [], - relationships: result.relationships || [], - addNode: () => {}, - addRelationship: () => {}, - removeNode: () => {}, - removeRelationship: () => {}, - clear: () => {}, - getNodeById: () => null, - getRelationshipById: () => null, - getNodesByLabel: () => [], - getRelationshipsByType: () => [], - getConnectedNodes: () => ({ incoming: [], outgoing: [] }) - }, + graph, fileContents }; } finally { @@ -219,4 +199,64 @@ export class IngestionService { return structure; // No normalization if prefix isn't common enough } + + /** + * Recreate the appropriate graph type based on worker result metadata + */ + private async recreateGraphFromResult(result: any): Promise { + console.log(`๐Ÿ“Š Recreating graph: type=${result.graphType}, kuzuEnabled=${result.kuzuEnabled}`); + + if (result.graphType === 'DualWriteKnowledgeGraph' && result.kuzuEnabled) { + try { + // Recreate DualWriteKnowledgeGraph with KuzuDB + console.log('๐Ÿš€ Recreating DualWriteKnowledgeGraph with KuzuDB...'); + + // Initialize KuzuDB query engine + const { KuzuQueryEngine } = await import('../core/graph/kuzu-query-engine.ts'); + const queryEngine = new KuzuQueryEngine({ + enableCache: true, + cacheSize: 1000, + cacheTTL: 5 * 60 * 1000 // 5 minutes + }); + + await queryEngine.initialize(); + + // Create KuzuDB knowledge graph + const { KuzuKnowledgeGraph } = await import('../core/graph/kuzu-knowledge-graph.ts'); + const kuzuGraph = new KuzuKnowledgeGraph(queryEngine, { + enableCache: true, + batchSize: 100, + autoCommit: false + }); + + // Create dual-write graph + const { DualWriteKnowledgeGraph } = await import('../core/graph/dual-write-knowledge-graph.ts'); + const dualWriteGraph = new DualWriteKnowledgeGraph(kuzuGraph); + + // Add the data from worker result + (result.nodes || []).forEach((node: any) => dualWriteGraph.addNode(node)); + (result.relationships || []).forEach((rel: any) => dualWriteGraph.addRelationship(rel)); + + // Commit to KuzuDB + await dualWriteGraph.flushKuzuDB(); + + console.log('โœ… Successfully recreated DualWriteKnowledgeGraph with KuzuDB'); + return dualWriteGraph; + + } catch (error) { + console.warn('โŒ Failed to recreate DualWriteKnowledgeGraph, falling back to SimpleKnowledgeGraph:', error); + } + } + + // Fallback to SimpleKnowledgeGraph + console.log('๐Ÿ“„ Creating SimpleKnowledgeGraph fallback'); + const { SimpleKnowledgeGraph } = await import('../core/graph/graph.ts'); + const simpleGraph = new SimpleKnowledgeGraph(); + + // Add nodes and relationships from result + (result.nodes || []).forEach((node: any) => simpleGraph.addNode(node)); + (result.relationships || []).forEach((rel: any) => simpleGraph.addRelationship(rel)); + + return simpleGraph; + } } \ No newline at end of file diff --git a/src/ui/components/chat/ChatInterface.tsx b/src/ui/components/chat/ChatInterface.tsx index 7da4a4b92..d9c5e1117 100644 --- a/src/ui/components/chat/ChatInterface.tsx +++ b/src/ui/components/chat/ChatInterface.tsx @@ -107,8 +107,8 @@ const ChatInterface: React.FC = ({ // Services const [llmService] = useState(new LLMService()); const [cypherGenerator] = useState(new CypherGenerator(llmService)); - // KuzuDB query engine removed - using graph directly - const [ragOrchestrator] = useState(new ReActAgent(llmService, cypherGenerator, graph)); + // Create ReActAgent with initial graph, will be updated in useEffect + const [ragOrchestrator] = useState(() => new ReActAgent(llmService, cypherGenerator, graph)); const messagesEndRef = useRef(null); const inputRef = useRef(null); @@ -119,6 +119,12 @@ const ChatInterface: React.FC = ({ try { await ragOrchestrator.initialize(); + // Update graph reference in ReActAgent + if (graph) { + (ragOrchestrator as any).graph = graph; + console.log('โœ… Updated graph reference in ReActAgent:', graph.constructor.name); + } + // Only set context if we have valid graph data if (graph && graph.nodes && graph.nodes.length > 0) { // Get or create session diff --git a/src/workers/ingestion.worker.ts b/src/workers/ingestion.worker.ts index ed3ea7337..9cabc0bdd 100644 --- a/src/workers/ingestion.worker.ts +++ b/src/workers/ingestion.worker.ts @@ -22,11 +22,14 @@ export interface IngestionResult { callStats: { totalCalls: number; callTypes: Record }; }; duration: number; + graphType?: 'DualWriteKnowledgeGraph' | 'SimpleKnowledgeGraph'; + kuzuEnabled?: boolean; } export class IngestionWorker { private pipeline: GraphPipeline | ParallelGraphPipeline; private progressCallback?: (progress: IngestionProgress) => void; + private currentGraph: KnowledgeGraph | null = null; constructor() { // Choose pipeline based on feature flag - same logic as main thread @@ -99,6 +102,9 @@ export class IngestionWorker { fileContents: fileContentsMap }); + // Store the graph for later access + this.currentGraph = graph; + // Note: Keeping file contents available for UI components // fileContentsMap.clear(); // Commented out to preserve file contents for SourceViewer @@ -119,6 +125,12 @@ export class IngestionWorker { relationshipStats[rel.type] = (relationshipStats[rel.type] || 0) + 1; }); + // Determine graph type and KuzuDB status + const graphType = graph.constructor.name === 'DualWriteKnowledgeGraph' ? 'DualWriteKnowledgeGraph' : 'SimpleKnowledgeGraph'; + const kuzuEnabled = graphType === 'DualWriteKnowledgeGraph' && 'isKuzuDBEnabled' in graph && (graph as any).isKuzuDBEnabled(); + + console.log(`๐Ÿ“Š Worker returning graph type: ${graphType}, KuzuDB enabled: ${kuzuEnabled}`); + // Return only serializable data - nodes and relationships arrays return { success: true, @@ -129,7 +141,9 @@ export class IngestionWorker { relationshipStats, callStats: { totalCalls: 0, callTypes: {} } }, - duration + duration, + graphType: graphType as 'DualWriteKnowledgeGraph' | 'SimpleKnowledgeGraph', + kuzuEnabled }; } catch (error) { console.error('IngestionWorker: Processing failed:', error); @@ -254,6 +268,7 @@ export class IngestionWorker { // Cleanup resources if needed console.log('Ingestion worker terminated'); } + } // Expose the worker class via Comlink