codevault
Version:
AI-powered semantic code search via Model Context Protocol
210 lines • 8.32 kB
JavaScript
import { searchCode, getChunk } from '../core/search.js';
import { createChatLLMProvider } from '../providers/chat-llm.js';
import { buildSystemPrompt, buildUserPrompt, buildMultiQueryPrompt, parseMultiQueryResponse } from './prompt-builder.js';
export async function synthesizeAnswer(query, options = {}) {
const { provider = 'auto', chatProvider = 'auto', workingPath = '.', scope = {}, maxChunks = 10, useReranking = true, useMultiQuery = false, temperature = 0.7 } = options;
try {
const chatLLM = createChatLLMProvider(chatProvider);
if (chatLLM.init) {
await chatLLM.init();
}
let allResults = [];
let queriesUsed = [query];
let usedMultiQuery = false;
// Multi-query support for complex questions
if (useMultiQuery && isComplexQuestion(query)) {
try {
const multiQueryPrompt = buildMultiQueryPrompt(query);
const multiQueryResponse = await chatLLM.generateCompletion([
{ role: 'user', content: multiQueryPrompt }
], { temperature: 0.3, maxTokens: 500 });
const subQueries = parseMultiQueryResponse(multiQueryResponse);
if (subQueries.length > 0) {
queriesUsed = subQueries;
usedMultiQuery = true;
if (!process.env.CODEVAULT_QUIET) {
console.log(`📝 Breaking down query into ${subQueries.length} sub-queries:`);
subQueries.forEach((q, i) => console.log(` ${i + 1}. "${q}"`));
console.log('');
}
}
}
catch (error) {
// Fall back to single query if multi-query fails
if (!process.env.CODEVAULT_QUIET) {
console.warn('Multi-query breakdown failed, using original query');
}
}
}
// Execute searches
const searchScope = {
...scope,
reranker: useReranking ? 'api' : 'off',
hybrid: true,
bm25: true,
symbol_boost: true
};
for (const searchQuery of queriesUsed) {
const searchResult = await searchCode(searchQuery, maxChunks, provider, workingPath, searchScope);
if (searchResult.success && searchResult.results.length > 0) {
allResults.push(...searchResult.results);
}
}
// Deduplicate results by SHA
const uniqueResults = new Map();
for (const result of allResults) {
if (!uniqueResults.has(result.sha)) {
uniqueResults.set(result.sha, result);
}
else {
// Keep the one with higher score
const existing = uniqueResults.get(result.sha);
if (result.meta.score > existing.meta.score) {
uniqueResults.set(result.sha, result);
}
}
}
const deduplicatedResults = Array.from(uniqueResults.values())
.sort((a, b) => b.meta.score - a.meta.score)
.slice(0, maxChunks);
if (deduplicatedResults.length === 0) {
return {
success: false,
error: 'no_results',
query,
queriesUsed,
chunksAnalyzed: 0,
chatProvider: chatLLM.getName(),
embeddingProvider: provider,
metadata: {
multiQuery: usedMultiQuery,
totalResults: 0
}
};
}
// Retrieve code chunks
const codeChunks = new Map();
if (!process.env.CODEVAULT_QUIET) {
console.log(`🔍 Retrieved ${deduplicatedResults.length} relevant code chunks`);
}
for (const result of deduplicatedResults) {
const chunkResult = await getChunk(result.sha, workingPath);
if (chunkResult.success && chunkResult.code) {
codeChunks.set(result.sha, chunkResult.code);
}
}
// Build context for LLM
const context = {
query,
results: deduplicatedResults,
codeChunks,
metadata: {
searchType: deduplicatedResults[0]?.meta?.searchType,
provider,
totalChunks: deduplicatedResults.length
}
};
// Generate synthesis
const systemPrompt = buildSystemPrompt();
const userPrompt = buildUserPrompt(context, { maxContextChunks: maxChunks });
const messages = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
];
if (!process.env.CODEVAULT_QUIET) {
console.log(`🤖 Synthesizing answer with ${chatLLM.getName()}...`);
}
const answer = await chatLLM.generateCompletion(messages, {
temperature,
maxTokens: parseInt(process.env.CODEVAULT_CHAT_MAX_TOKENS || '4096', 10)
});
return {
success: true,
answer,
query,
queriesUsed,
chunksAnalyzed: deduplicatedResults.length,
chatProvider: chatLLM.getName(),
embeddingProvider: provider,
metadata: {
searchType: deduplicatedResults[0]?.meta?.searchType,
totalResults: deduplicatedResults.length,
multiQuery: usedMultiQuery
}
};
}
catch (error) {
return {
success: false,
error: error.message,
query,
chunksAnalyzed: 0,
chatProvider: chatProvider,
embeddingProvider: provider
};
}
}
export async function* synthesizeAnswerStreaming(query, options = {}) {
const { provider = 'auto', chatProvider = 'auto', workingPath = '.', scope = {}, maxChunks = 10, useReranking = true, temperature = 0.7 } = options;
const chatLLM = createChatLLMProvider(chatProvider);
if (chatLLM.init) {
await chatLLM.init();
}
// Execute search
const searchScope = {
...scope,
reranker: useReranking ? 'api' : 'off',
hybrid: true,
bm25: true,
symbol_boost: true
};
const searchResult = await searchCode(query, maxChunks, provider, workingPath, searchScope);
if (!searchResult.success || searchResult.results.length === 0) {
yield `**No relevant code found for:** "${query}"\n\n`;
yield `Please ensure the project is indexed and try rephrasing your question.`;
return;
}
// Retrieve code chunks
const codeChunks = new Map();
for (const result of searchResult.results.slice(0, maxChunks)) {
const chunkResult = await getChunk(result.sha, workingPath);
if (chunkResult.success && chunkResult.code) {
codeChunks.set(result.sha, chunkResult.code);
}
}
// Build context
const context = {
query,
results: searchResult.results.slice(0, maxChunks),
codeChunks,
metadata: {
searchType: searchResult.searchType,
provider: searchResult.provider,
totalChunks: searchResult.results.length
}
};
// Generate streaming response
const systemPrompt = buildSystemPrompt();
const userPrompt = buildUserPrompt(context, { maxContextChunks: maxChunks });
const messages = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
];
for await (const chunk of chatLLM.generateStreamingCompletion(messages, { temperature })) {
yield chunk;
}
}
function isComplexQuestion(query) {
const complexIndicators = [
/\bhow\s+(does|do|can|should)\b/i,
/\bwhat\s+(is|are|does)\b/i,
/\bexplain\b/i,
/\bwalk\s+me\s+through\b/i,
/\bstep\s+by\s+step\b/i,
/\band\b.*\band\b/i, // Multiple "and"s suggest complex query
/\bor\b.*\bor\b/i, // Multiple "or"s
/\?.*\?/, // Multiple questions
];
return complexIndicators.some(pattern => pattern.test(query));
}
//# sourceMappingURL=synthesizer.js.map