mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-30 06:42:43 +00:00
Graph RAG agent is able to execute cyfers, but needs more testing, something is wrong ;-;
This commit is contained in:
parent
b154d6c2ef
commit
4bcb9fbc74
6 changed files with 263 additions and 60 deletions
|
|
@ -105,8 +105,9 @@ export const SettingsPanel = ({ isOpen, onClose, onSettingsSaved }: SettingsPane
|
|||
// Fetch models when API key is entered (debounced)
|
||||
useEffect(() => {
|
||||
if (settings.activeProvider === 'gemini' && settings.gemini?.apiKey) {
|
||||
const apiKey = settings.gemini.apiKey ?? '';
|
||||
const timer = setTimeout(() => {
|
||||
fetchModels(settings.gemini!.apiKey);
|
||||
fetchModels(apiKey);
|
||||
}, 500);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
|
|
@ -260,7 +261,7 @@ export const SettingsPanel = ({ isOpen, onClose, onSettingsSaved }: SettingsPane
|
|||
{settings.gemini?.apiKey && !isLoadingModels && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => fetchModels(settings.gemini!.apiKey)}
|
||||
onClick={() => fetchModels(settings.gemini?.apiKey ?? '')}
|
||||
className="p-1 text-text-muted hover:text-text-primary transition-colors"
|
||||
title="Refresh models"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -275,10 +275,12 @@ export const semanticSearch = async (
|
|||
const queryVecStr = `[${queryVec.join(',')}]`;
|
||||
|
||||
// Query the vector index on CodeEmbedding, then JOIN with CodeNode for metadata
|
||||
// Note: KuzuDB requires WITH after YIELD before using WHERE
|
||||
const cypher = `
|
||||
CALL QUERY_VECTOR_INDEX('CodeEmbedding', 'code_embedding_idx',
|
||||
CAST(${queryVecStr} AS FLOAT[384]), ${k})
|
||||
YIELD node AS emb, distance
|
||||
WITH emb, distance
|
||||
WHERE distance < ${maxDistance}
|
||||
MATCH (n:CodeNode {id: emb.nodeId})
|
||||
RETURN n.id AS nodeId, n.name AS name, n.label AS label,
|
||||
|
|
@ -301,22 +303,24 @@ export const semanticSearch = async (
|
|||
};
|
||||
|
||||
/**
|
||||
* Semantic search with graph expansion
|
||||
* Finds similar nodes AND their connections
|
||||
* Semantic search with graph expansion (flattened results)
|
||||
* Finds similar nodes AND their direct connections with relationship types
|
||||
*
|
||||
* Uses separate CodeEmbedding table and JOINs with CodeNode
|
||||
* Uses separate CodeEmbedding table and JOINs with CodeNode.
|
||||
* Returns flattened results: one row per (match, connected) pair.
|
||||
* This format works with KuzuDB and preserves relationship type information.
|
||||
*
|
||||
* @param executeQuery - Function to execute Cypher queries
|
||||
* @param query - Search query text
|
||||
* @param k - Number of initial results
|
||||
* @param hops - Number of hops to expand (default: 2)
|
||||
* @returns Search results with connected nodes
|
||||
* @param k - Number of initial semantic matches (default: 5)
|
||||
* @param _hops - Unused (kept for API compatibility). Use execute_vector_cypher for multi-hop.
|
||||
* @returns Flattened results: each row is a (match → connected) pair with relationship type
|
||||
*/
|
||||
export const semanticSearchWithContext = async (
|
||||
executeQuery: (cypher: string) => Promise<any[]>,
|
||||
query: string,
|
||||
k: number = 5,
|
||||
hops: number = 2
|
||||
_hops: number = 1 // Currently only single-hop supported; multi-hop via execute_vector_cypher
|
||||
): Promise<any[]> => {
|
||||
if (!isEmbedderReady()) {
|
||||
throw new Error('Embedding model not initialized. Run embedding pipeline first.');
|
||||
|
|
@ -327,23 +331,22 @@ export const semanticSearchWithContext = async (
|
|||
const queryVec = embeddingToArray(queryEmbedding);
|
||||
const queryVecStr = `[${queryVec.join(',')}]`;
|
||||
|
||||
// Query embedding table, JOIN with CodeNode, then expand graph
|
||||
// Query embedding table, JOIN with CodeNode, then expand to direct connections
|
||||
// Using single-hop so we can access r.type (variable-length paths don't support this in KuzuDB)
|
||||
// Note: KuzuDB requires WITH after YIELD before using WHERE
|
||||
const cypher = `
|
||||
CALL QUERY_VECTOR_INDEX('CodeEmbedding', 'code_embedding_idx',
|
||||
CAST(${queryVecStr} AS FLOAT[384]), ${k})
|
||||
YIELD node AS emb, distance
|
||||
WITH emb, distance
|
||||
WHERE distance < 0.5
|
||||
MATCH (match:CodeNode {id: emb.nodeId})
|
||||
MATCH (match)-[r:CodeRelation*1..${hops}]-(connected:CodeNode)
|
||||
MATCH (match)-[r:CodeRelation]-(connected:CodeNode)
|
||||
RETURN match.id AS matchId, match.name AS matchName, match.label AS matchLabel,
|
||||
match.filePath AS matchPath, distance,
|
||||
collect(DISTINCT {
|
||||
id: connected.id,
|
||||
name: connected.name,
|
||||
label: connected.label,
|
||||
relationType: [rel IN r | rel.type]
|
||||
}) AS connections
|
||||
ORDER BY distance
|
||||
connected.id AS connectedId, connected.name AS connectedName,
|
||||
connected.label AS connectedLabel, r.type AS relationType
|
||||
ORDER BY distance, matchId
|
||||
`;
|
||||
|
||||
return executeQuery(cypher);
|
||||
|
|
|
|||
|
|
@ -21,11 +21,12 @@ import type {
|
|||
/**
|
||||
* System prompt for the Graph RAG agent
|
||||
*/
|
||||
const SYSTEM_PROMPT = `You are Nexus AI, an intelligent code analysis assistant. You help developers understand codebases by querying a knowledge graph that contains code structure, relationships, and semantic embeddings.
|
||||
const SYSTEM_PROMPT = `You are Nexus AI, an intelligent code analysis assistant. You help developers understand codebases by querying a knowledge graph (KuzuDB) that contains code structure, relationships, and semantic embeddings.
|
||||
|
||||
CAPABILITIES:
|
||||
- Execute Cypher queries to explore code structure (functions, classes, files, imports, call graphs)
|
||||
- Perform semantic search to find code by meaning (when embeddings are available)
|
||||
- Combine semantic search + graph traversal in a SINGLE Cypher query via the vector index (when embeddings are available)
|
||||
- Trace dependencies and relationships between code elements
|
||||
- Explain code architecture and patterns
|
||||
|
||||
|
|
@ -33,12 +34,37 @@ APPROACH:
|
|||
1. Start by understanding what the user wants to know
|
||||
2. Choose the right tool(s) for the task:
|
||||
- Use 'get_codebase_stats' first if you need an overview
|
||||
- Use 'semantic_search' for concept-based queries ("find authentication logic")
|
||||
- Use 'execute_cypher' for structural queries ("what functions does X call?")
|
||||
- Use 'semantic_search' for simple concept-based lookup (find relevant nodes)
|
||||
- Use 'semantic_search_with_context' for simple semantic + neighborhood expansion (prebuilt 1-3 hop expansion)
|
||||
- Use 'execute_vector_cypher' when you need semantic search + CUSTOM traversal/filters/returns in ONE query
|
||||
- Your Cypher MUST include the placeholder {{QUERY_VECTOR}} where a FLOAT[384] vector belongs
|
||||
- The vector index is on CodeEmbedding (code_embedding_idx). You must JOIN back to CodeNode via emb.nodeId
|
||||
- Use 'execute_cypher' for pure structural queries (no vector search)
|
||||
- Use 'get_code_content' to show actual source code
|
||||
3. Interpret results and explain them clearly
|
||||
4. Suggest follow-up explorations when relevant
|
||||
|
||||
IMPORTANT NOTES ABOUT THE DATABASE:
|
||||
- Nodes are stored in CodeNode(id, label, name, filePath, startLine, endLine, content)
|
||||
- Edges are stored in CodeRelation(FROM CodeNode TO CodeNode, type) where type ∈ {CALLS, IMPORTS, CONTAINS, DEFINES}
|
||||
- Embeddings are stored separately in CodeEmbedding(nodeId, embedding) for memory efficiency
|
||||
- Vector index: code_embedding_idx on CodeEmbedding.embedding (cosine distance; smaller distance = more similar)
|
||||
|
||||
UNIFIED VECTOR + GRAPH QUERY PATTERN (ONE QUERY):
|
||||
1) Vector search to get closest embeddings
|
||||
2) JOIN to CodeNode
|
||||
3) Traverse relationships / collect context
|
||||
|
||||
Example skeleton (note: WITH after YIELD is required in KuzuDB before WHERE):
|
||||
CALL QUERY_VECTOR_INDEX('CodeEmbedding','code_embedding_idx', {{QUERY_VECTOR}}, 10)
|
||||
YIELD node AS emb, distance
|
||||
WITH emb, distance
|
||||
WHERE distance < 0.5
|
||||
MATCH (match:CodeNode {id: emb.nodeId})
|
||||
MATCH (match)-[r:CodeRelation*1..2]-(ctx:CodeNode)
|
||||
RETURN match.name, match.label, match.filePath, distance, collect(DISTINCT ctx.name) AS context
|
||||
ORDER BY distance
|
||||
|
||||
STYLE:
|
||||
- Be concise but thorough
|
||||
- Use code formatting when showing results
|
||||
|
|
@ -163,15 +189,16 @@ export async function* streamAgentResponse(
|
|||
|
||||
for await (const event of stream) {
|
||||
const { event: eventType, data } = event;
|
||||
const dataAny = data as any;
|
||||
|
||||
// Handle tool calls start
|
||||
if (eventType === 'on_tool_start') {
|
||||
yield {
|
||||
type: 'tool_call',
|
||||
toolCall: {
|
||||
id: data.tool_call_id || Date.now().toString(), // fallback if ID missing
|
||||
name: event.name,
|
||||
args: data.input,
|
||||
id: dataAny?.tool_call_id || dataAny?.toolCallId || Date.now().toString(), // fallback if ID missing
|
||||
name: (event as any).name,
|
||||
args: dataAny?.input ?? {},
|
||||
status: 'running',
|
||||
},
|
||||
};
|
||||
|
|
@ -182,10 +209,10 @@ export async function* streamAgentResponse(
|
|||
yield {
|
||||
type: 'tool_result',
|
||||
toolCall: {
|
||||
id: data.tool_call_id || '', // we might need to match by name if ID missing
|
||||
name: event.name,
|
||||
id: dataAny?.tool_call_id || dataAny?.toolCallId || '', // we might need to match by name if ID missing
|
||||
name: (event as any).name,
|
||||
args: {},
|
||||
result: typeof data.output === 'string' ? data.output : JSON.stringify(data.output),
|
||||
result: typeof dataAny?.output === 'string' ? dataAny.output : JSON.stringify(dataAny?.output),
|
||||
status: 'completed',
|
||||
},
|
||||
};
|
||||
|
|
@ -193,7 +220,7 @@ export async function* streamAgentResponse(
|
|||
|
||||
// Handle streamed LLM content
|
||||
if (eventType === 'on_chat_model_stream') {
|
||||
const content = data.chunk?.content;
|
||||
const content = dataAny?.chunk?.content;
|
||||
if (content && typeof content === 'string') {
|
||||
yield {
|
||||
type: 'content',
|
||||
|
|
|
|||
|
|
@ -69,26 +69,56 @@ export const saveSettings = (settings: LLMSettings): void => {
|
|||
export const updateProviderSettings = <T extends LLMProvider>(
|
||||
provider: T,
|
||||
updates: Partial<
|
||||
T extends 'azure-openai' ? Omit<AzureOpenAIConfig, 'provider'> :
|
||||
T extends 'gemini' ? Omit<GeminiConfig, 'provider'> :
|
||||
T extends 'ollama' ? Omit<OllamaConfig, 'provider'> :
|
||||
T extends 'azure-openai' ? Partial<Omit<AzureOpenAIConfig, 'provider'>> :
|
||||
T extends 'gemini' ? Partial<Omit<GeminiConfig, 'provider'>> :
|
||||
T extends 'ollama' ? Partial<Omit<OllamaConfig, 'provider'>> :
|
||||
never
|
||||
>
|
||||
): LLMSettings => {
|
||||
const current = loadSettings();
|
||||
|
||||
const providerKey = provider === 'azure-openai' ? 'azureOpenAI' : provider;
|
||||
|
||||
const updated: LLMSettings = {
|
||||
...current,
|
||||
[providerKey]: {
|
||||
...current[providerKey as keyof LLMSettings],
|
||||
...updates,
|
||||
},
|
||||
};
|
||||
|
||||
saveSettings(updated);
|
||||
return updated;
|
||||
|
||||
// Avoid spreading unions like LLMSettings[keyof LLMSettings] (can be string/undefined)
|
||||
switch (provider) {
|
||||
case 'azure-openai': {
|
||||
const updated: LLMSettings = {
|
||||
...current,
|
||||
azureOpenAI: {
|
||||
...(current.azureOpenAI ?? {}),
|
||||
...(updates as Partial<Omit<AzureOpenAIConfig, 'provider'>>),
|
||||
},
|
||||
};
|
||||
saveSettings(updated);
|
||||
return updated;
|
||||
}
|
||||
case 'gemini': {
|
||||
const updated: LLMSettings = {
|
||||
...current,
|
||||
gemini: {
|
||||
...(current.gemini ?? {}),
|
||||
...(updates as Partial<Omit<GeminiConfig, 'provider'>>),
|
||||
},
|
||||
};
|
||||
saveSettings(updated);
|
||||
return updated;
|
||||
}
|
||||
case 'ollama': {
|
||||
const updated: LLMSettings = {
|
||||
...current,
|
||||
ollama: {
|
||||
...(current.ollama ?? {}),
|
||||
...(updates as Partial<Omit<OllamaConfig, 'provider'>>),
|
||||
},
|
||||
};
|
||||
saveSettings(updated);
|
||||
return updated;
|
||||
}
|
||||
default: {
|
||||
// Should be unreachable due to T extends LLMProvider, but keep a safe fallback
|
||||
const updated: LLMSettings = { ...current };
|
||||
saveSettings(updated);
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import { tool } from '@langchain/core/tools';
|
||||
import { z } from 'zod';
|
||||
import { GRAPH_SCHEMA_DESCRIPTION } from './types';
|
||||
import { WebGPUNotAvailableError, embedText, embeddingToArray, initEmbedder, isEmbedderReady } from '../embeddings/embedder';
|
||||
|
||||
/**
|
||||
* Tool factory - creates tools bound to the KuzuDB query functions
|
||||
|
|
@ -59,6 +60,92 @@ export const createGraphRAGTools = (
|
|||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Tool: Execute Vector Cypher Query (Unified Vector + Graph in ONE query)
|
||||
*
|
||||
* Lets the LLM write a Cypher query that includes a vector index call,
|
||||
* while this tool handles embedding the natural-language query and injecting
|
||||
* the vector into the Cypher safely.
|
||||
*
|
||||
* IMPORTANT:
|
||||
* - The provided Cypher MUST include the placeholder {{QUERY_VECTOR}}
|
||||
* - The placeholder will be replaced with: CAST([..384 floats..] AS FLOAT[384])
|
||||
* - KuzuDB requires WITH after YIELD before using WHERE
|
||||
*
|
||||
* Example:
|
||||
* CALL QUERY_VECTOR_INDEX('CodeEmbedding','code_embedding_idx', {{QUERY_VECTOR}}, 10)
|
||||
* YIELD node AS emb, distance
|
||||
* WITH emb, distance
|
||||
* WHERE distance < 0.5
|
||||
* MATCH (match:CodeNode {id: emb.nodeId}) ...
|
||||
*/
|
||||
const executeVectorCypherTool = tool(
|
||||
async ({ query, cypher }: { query: string; cypher: string }) => {
|
||||
if (!isEmbeddingReady()) {
|
||||
return 'Vector Cypher is not available. Embeddings have not been generated yet.';
|
||||
}
|
||||
|
||||
if (!cypher.includes('{{QUERY_VECTOR}}')) {
|
||||
return "Invalid input: your Cypher must include the placeholder '{{QUERY_VECTOR}}' where a FLOAT[384] vector should go.";
|
||||
}
|
||||
|
||||
try {
|
||||
// Ensure embedder is loaded. If WebGPU isn't available, fall back to WASM.
|
||||
if (!isEmbedderReady()) {
|
||||
try {
|
||||
await initEmbedder();
|
||||
} catch (err) {
|
||||
if (err instanceof WebGPUNotAvailableError) {
|
||||
await initEmbedder(undefined, {}, 'wasm');
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Embed the natural language query and inject into Cypher
|
||||
const queryEmbedding = await embedText(query);
|
||||
const queryVec = embeddingToArray(queryEmbedding);
|
||||
const queryVecStr = `CAST([${queryVec.join(',')}] AS FLOAT[384])`;
|
||||
|
||||
const finalCypher = cypher.replace(/\{\{\s*QUERY_VECTOR\s*\}\}/g, queryVecStr);
|
||||
const results = await executeQuery(finalCypher);
|
||||
|
||||
if (results.length === 0) {
|
||||
return 'Query returned no results.';
|
||||
}
|
||||
|
||||
const formatted = results.slice(0, 50).map((row, i) => {
|
||||
if (Array.isArray(row)) {
|
||||
return `[${i + 1}] ${row.join(', ')}`;
|
||||
}
|
||||
return `[${i + 1}] ${JSON.stringify(row)}`;
|
||||
});
|
||||
|
||||
const resultText = formatted.join('\n');
|
||||
const truncated = results.length > 50 ? `\n... (${results.length - 50} more results truncated)` : '';
|
||||
|
||||
return `Query returned ${results.length} results:\n${resultText}${truncated}`;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return `Vector Cypher error: ${message}\n\nTip: Ensure you're querying the vector index on CodeEmbedding and JOINing back to CodeNode via emb.nodeId.`;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'execute_vector_cypher',
|
||||
description:
|
||||
"Execute a single Cypher query that combines vector similarity search and graph traversal. Provide a natural-language 'query' to embed, and a 'cypher' string containing the placeholder {{QUERY_VECTOR}}. Use this to do semantic search + traversal in ONE Cypher query. Remember: KuzuDB requires 'WITH emb, distance' after 'YIELD node AS emb, distance' before you can use WHERE.",
|
||||
schema: z.object({
|
||||
query: z.string().describe('Natural language query to embed (used to produce a FLOAT[384] vector)'),
|
||||
cypher: z
|
||||
.string()
|
||||
.describe(
|
||||
"Cypher query to execute. MUST contain {{QUERY_VECTOR}}. Pattern: CALL QUERY_VECTOR_INDEX('CodeEmbedding','code_embedding_idx', {{QUERY_VECTOR}}, 10) YIELD node AS emb, distance WITH emb, distance WHERE distance < 0.5 MATCH (n:CodeNode {id: emb.nodeId}) ..."
|
||||
),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Tool: Semantic Code Search
|
||||
* Find code by meaning using vector embeddings
|
||||
|
|
@ -99,36 +186,70 @@ export const createGraphRAGTools = (
|
|||
|
||||
/**
|
||||
* Tool: Semantic Search with Graph Context
|
||||
* Find similar code AND expand to connected nodes
|
||||
* Find similar code AND expand to connected nodes (flattened format with relationship types)
|
||||
*/
|
||||
const semanticSearchWithContextTool = tool(
|
||||
async ({ query, limit, hops }: { query: string; limit?: number; hops?: number }) => {
|
||||
async ({ query, limit }: { query: string; limit?: number }) => {
|
||||
if (!isEmbeddingReady()) {
|
||||
return 'Semantic search is not available. Embeddings have not been generated yet. Please use execute_cypher tool for structured queries instead.';
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await semanticSearchWithContext(query, limit ?? 5, hops ?? 2);
|
||||
const results = await semanticSearchWithContext(query, limit ?? 5);
|
||||
|
||||
if (results.length === 0) {
|
||||
return `No code found matching "${query}". Try a different search term.`;
|
||||
}
|
||||
|
||||
const formatted = results.map((r, i) => {
|
||||
// Results are flattened: one row per (match → connected) pair
|
||||
// Group by match for cleaner output
|
||||
const grouped = new Map<string, {
|
||||
matchName: string;
|
||||
matchLabel: string;
|
||||
matchPath: string;
|
||||
distance: number;
|
||||
connections: Array<{ name: string; label: string; relType: string }>;
|
||||
}>();
|
||||
|
||||
for (const r of results) {
|
||||
const matchId = r.matchId ?? r[0];
|
||||
const matchName = r.matchName ?? r[1];
|
||||
const matchLabel = r.matchLabel ?? r[2];
|
||||
const matchPath = r.matchPath ?? r[3];
|
||||
const distance = r.distance ?? r[4];
|
||||
const connections = r.connections ?? r[5] ?? [];
|
||||
const connectedName = r.connectedName ?? r[6];
|
||||
const connectedLabel = r.connectedLabel ?? r[7];
|
||||
const relationType = r.relationType ?? r[8];
|
||||
|
||||
const connectedNames = Array.isArray(connections)
|
||||
? connections.slice(0, 10).map((c: any) => c.name || c).join(', ')
|
||||
: 'none';
|
||||
if (!grouped.has(matchId)) {
|
||||
grouped.set(matchId, {
|
||||
matchName,
|
||||
matchLabel,
|
||||
matchPath,
|
||||
distance,
|
||||
connections: [],
|
||||
});
|
||||
}
|
||||
|
||||
return `[${i + 1}] ${matchLabel}: ${matchName}\n File: ${matchPath}\n Relevance: ${(1 - distance).toFixed(2)}\n Connected to: ${connectedNames}`;
|
||||
grouped.get(matchId)!.connections.push({
|
||||
name: connectedName,
|
||||
label: connectedLabel,
|
||||
relType: relationType,
|
||||
});
|
||||
}
|
||||
|
||||
// Format grouped results
|
||||
const formatted = Array.from(grouped.values()).map((g, i) => {
|
||||
const connectionsList = g.connections
|
||||
.slice(0, 15)
|
||||
.map(c => `${c.name} (${c.label}) via ${c.relType}`)
|
||||
.join('\n ');
|
||||
const more = g.connections.length > 15 ? `\n ... and ${g.connections.length - 15} more` : '';
|
||||
|
||||
return `[${i + 1}] ${g.matchLabel}: ${g.matchName}\n File: ${g.matchPath}\n Relevance: ${(1 - g.distance).toFixed(2)}\n Connections:\n ${connectionsList}${more}`;
|
||||
});
|
||||
|
||||
return `Found ${results.length} code elements with context:\n\n${formatted.join('\n\n')}`;
|
||||
return `Found ${grouped.size} code elements with ${results.length} total connections:\n\n${formatted.join('\n\n')}`;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return `Search with context error: ${message}`;
|
||||
|
|
@ -136,11 +257,10 @@ export const createGraphRAGTools = (
|
|||
},
|
||||
{
|
||||
name: 'semantic_search_with_context',
|
||||
description: 'Search for code semantically AND expand to show connected code elements (callers, callees, imports). Use this to understand how code fits into the broader architecture.',
|
||||
description: 'Search for code semantically AND show directly connected code elements with relationship types (CALLS, IMPORTS, DEFINES, CONTAINS). Shows what each match is connected to and how.',
|
||||
schema: z.object({
|
||||
query: z.string().describe('Natural language description of what you are looking for'),
|
||||
limit: z.number().optional().describe('Number of initial matches to find (default: 5)'),
|
||||
hops: z.number().optional().describe('Number of graph hops to expand (default: 2, max: 3)'),
|
||||
limit: z.number().optional().describe('Number of semantic matches to find (default: 5)'),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
|
@ -258,6 +378,7 @@ export const createGraphRAGTools = (
|
|||
|
||||
return [
|
||||
executeCypherTool,
|
||||
executeVectorCypherTool,
|
||||
semanticSearchTool,
|
||||
semanticSearchWithContextTool,
|
||||
getSchemaTool,
|
||||
|
|
|
|||
|
|
@ -59,9 +59,13 @@ export type ProviderConfig = AzureOpenAIConfig | GeminiConfig | OllamaConfig;
|
|||
*/
|
||||
export interface LLMSettings {
|
||||
activeProvider: LLMProvider;
|
||||
azureOpenAI?: Omit<AzureOpenAIConfig, 'provider'>;
|
||||
gemini?: Omit<GeminiConfig, 'provider'>;
|
||||
ollama?: Omit<OllamaConfig, 'provider'>;
|
||||
/**
|
||||
* Provider settings are persisted to localStorage and may be partially configured.
|
||||
* We validate required fields at runtime before creating a ProviderConfig.
|
||||
*/
|
||||
azureOpenAI?: Partial<Omit<AzureOpenAIConfig, 'provider'>>;
|
||||
gemini?: Partial<Omit<GeminiConfig, 'provider'>>;
|
||||
ollama?: Partial<Omit<OllamaConfig, 'provider'>>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -158,6 +162,7 @@ IMPORTANT QUERY PATTERNS:
|
|||
3. SEMANTIC SEARCH (embeddings in separate table - MUST JOIN):
|
||||
CALL QUERY_VECTOR_INDEX('CodeEmbedding', 'code_embedding_idx', $queryVector, 10)
|
||||
YIELD node AS emb, distance
|
||||
WITH emb, distance -- KuzuDB requires WITH after YIELD before WHERE
|
||||
WHERE distance < 0.4
|
||||
MATCH (n:CodeNode {id: emb.nodeId}) -- JOIN required!
|
||||
RETURN n.name, n.label, n.filePath, distance
|
||||
|
|
@ -166,6 +171,7 @@ IMPORTANT QUERY PATTERNS:
|
|||
4. Semantic search + graph expansion:
|
||||
CALL QUERY_VECTOR_INDEX('CodeEmbedding', 'code_embedding_idx', $queryVector, 5)
|
||||
YIELD node AS emb, distance
|
||||
WITH emb, distance
|
||||
WHERE distance < 0.5
|
||||
MATCH (match:CodeNode {id: emb.nodeId})
|
||||
MATCH (match)-[r:CodeRelation*1..2]-(connected:CodeNode)
|
||||
|
|
@ -179,6 +185,21 @@ IMPORTANT QUERY PATTERNS:
|
|||
MATCH (f:CodeNode {name: $fileName})-[r:CodeRelation {type: 'IMPORTS'}]->(imported:CodeNode)
|
||||
RETURN imported.name AS imports
|
||||
|
||||
7. Unified vector + graph traversal in ONE query (recommended pattern):
|
||||
CALL QUERY_VECTOR_INDEX('CodeEmbedding', 'code_embedding_idx', $queryVector, 10)
|
||||
YIELD node AS emb, distance
|
||||
WITH emb, distance
|
||||
WHERE distance < 0.5
|
||||
MATCH (match:CodeNode {id: emb.nodeId})
|
||||
MATCH (match)-[r:CodeRelation*1..2]-(ctx:CodeNode)
|
||||
RETURN match.name AS found, match.label AS label, match.filePath AS path,
|
||||
distance, collect(DISTINCT ctx.name) AS context
|
||||
ORDER BY distance
|
||||
|
||||
TOOLING NOTE (for execute_vector_cypher):
|
||||
- When using the execute_vector_cypher tool, write Cypher containing {{QUERY_VECTOR}} where the vector should go.
|
||||
- The tool will replace {{QUERY_VECTOR}} with a CAST([..] AS FLOAT[384]) literal.
|
||||
|
||||
NOTES:
|
||||
- Always use WHERE clauses to filter by label when possible for performance
|
||||
- Use LIMIT to avoid returning too many results
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue