well at least query decomposition is working..for now

This commit is contained in:
perf3ct 2025-04-17 17:19:52 +00:00
parent 5e50a2918d
commit 7062e51f2d
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
5 changed files with 1002 additions and 795 deletions

View File

@ -134,7 +134,25 @@ export class ContextService {
// Step 1: Decompose query if requested
if (useQueryDecomposition) {
log.info(`Decomposing query for better understanding`);
decomposedQuery = queryProcessor.decomposeQuery(userQuestion);
try {
// Use the async version with the LLM service
decomposedQuery = await queryProcessor.decomposeQuery(userQuestion, undefined, llmService);
log.info(`Successfully decomposed query complexity: ${decomposedQuery.complexity}/10 with ${decomposedQuery.subQueries.length} sub-queries`);
} catch (error) {
log.error(`Error in query decomposition, using fallback: ${error}`);
// Fallback to simpler decomposition
decomposedQuery = {
originalQuery: userQuestion,
subQueries: [{
id: `sq_fallback_${Date.now()}`,
text: userQuestion,
reason: "Fallback to original query due to decomposition error",
isAnswered: false
}],
status: 'pending',
complexity: 1
};
}
// Extract sub-queries to use for search
if (decomposedQuery.subQueries.length > 0) {

View File

@ -16,6 +16,7 @@ import { QUERY_DECOMPOSITION_STRINGS } from '../../constants/query_decomposition
import JsonExtractor from '../../utils/json_extractor.js';
import type { LLMServiceInterface } from '../../interfaces/agent_tool_interfaces.js';
import { SEARCH_CONSTANTS } from '../../constants/search_constants.js';
import aiServiceManager from '../../ai_service_manager.js';
// Interfaces
export interface SubQuery {
@ -39,16 +40,31 @@ export class QueryProcessor {
// Prompt templates
private enhancerPrompt = CONTEXT_PROMPTS.QUERY_ENHANCER;
/**
* Get a valid LLM service or null if none available
*
* @returns Available LLM service or null
*/
private async getLLMService(): Promise<LLMServiceInterface | null> {
try {
// Get the service from the AI service manager
return aiServiceManager.getService();
} catch (error: any) {
log.error(`Error getting LLM service: ${error.message || String(error)}`);
return null;
}
}
/**
* Generate enhanced search queries for better semantic matching
*
* @param userQuestion - The user's question
* @param llmService - The LLM service to use for generating queries
* @param llmService - The LLM service to use for generating queries, or null to auto-detect
* @returns Array of search queries
*/
async generateSearchQueries(
userQuestion: string,
llmService: LLMServiceInterface
llmService?: LLMServiceInterface
): Promise<string[]> {
if (!userQuestion || userQuestion.trim() === '') {
return []; // Return empty array for empty input
@ -62,6 +78,13 @@ export class QueryProcessor {
return cached;
}
// Get LLM service if not provided
const service = llmService || await this.getLLMService();
if (!service) {
log.info(`No LLM service available for query enhancement, using original query`);
return [userQuestion];
}
// Prepare the prompt with JSON formatting instructions
const enhancedPrompt = `${this.enhancerPrompt}
IMPORTANT: You must respond with valid JSON arrays. Always include commas between array elements.
@ -81,7 +104,7 @@ Format your answer as a valid JSON array without markdown code blocks, like this
};
// Get the response from the LLM
const response = await llmService.generateChatCompletion(messages, options);
const response = await service.generateChatCompletion(messages, options);
const responseText = response.text;
// Use the JsonExtractor to parse the response
@ -115,9 +138,14 @@ Format your answer as a valid JSON array without markdown code blocks, like this
*
* @param query The original user query
* @param context Optional context about the current note being viewed
* @param llmService Optional LLM service to use for advanced decomposition
* @returns A decomposed query object with sub-queries
*/
decomposeQuery(query: string, context?: string): DecomposedQuery {
async decomposeQuery(
query: string,
context?: string,
llmService?: LLMServiceInterface
): Promise<DecomposedQuery> {
try {
// Log the decomposition attempt
log.info(`Decomposing query: "${query}"`);
@ -136,9 +164,16 @@ Format your answer as a valid JSON array without markdown code blocks, like this
const complexity = this.assessQueryComplexity(query);
log.info(`Query complexity assessment: ${complexity}/10`);
// For simple queries, just return the original as a single sub-query
if (complexity < 3) {
log.info(`Simple query detected (complexity: ${complexity}), using direct approach`);
// Try to get LLM service if not provided
const service = llmService || await this.getLLMService();
// For when no LLM service is available, use the basic approach
if (!service) {
if (!service) {
log.info(`No LLM service available for query decomposition, using original query`);
}
log.info(`Using basic decomposition approach (complexity: ${complexity})`);
const mainSubQuery = {
id: this.generateSubQueryId(),
@ -163,7 +198,29 @@ Format your answer as a valid JSON array without markdown code blocks, like this
};
}
// For complex queries, break it down into sub-queries
// For when the LLM available, we can use more advanced decomposition
if (service) {
try {
// Try to use LLM for advanced decomposition
log.info(`Using advanced LLM-based decomposition for complex query (complexity: ${complexity})`);
const enhancedSubQueries = await this.createLLMSubQueries(query, context, service);
if (enhancedSubQueries && enhancedSubQueries.length > 0) {
log.info(`LLM decomposed query into ${enhancedSubQueries.length} sub-queries: ${JSON.stringify(enhancedSubQueries)}`);
return {
originalQuery: query,
subQueries: enhancedSubQueries,
status: 'pending',
complexity
};
}
} catch (error: any) {
log.error(`Error during LLM-based decomposition: ${error.message}, falling back to basic decomposition`);
// Continue to fallback with basic decomposition
}
}
// Fallback to basic decomposition
const subQueries = this.createSubQueries(query, context);
log.info(`Decomposed query into ${subQueries.length} sub-queries`);
@ -191,6 +248,87 @@ Format your answer as a valid JSON array without markdown code blocks, like this
}
}
/**
* Use LLM to create advanced sub-queries from a complex query
*
* @param query The original complex query
* @param context Optional context to help with decomposition
* @param llmService LLM service to use for advanced decomposition
* @returns Array of sub-queries
*/
private async createLLMSubQueries(
query: string,
context?: string,
llmService?: LLMServiceInterface
): Promise<SubQuery[]> {
// If no LLM service, use basic decomposition
if (!llmService) {
return this.createSubQueries(query, context);
}
try {
// Build a prompt from existing templates in the constants
const contextPart = context ? `\nContext: ${context}` : '';
// Use existing templates from QUERY_DECOMPOSITION_STRINGS to build the prompt
const prompt = `I need to break down a complex query into sub-queries.
Query: ${query}${contextPart}
Please analyze this query and identify the key aspects that need to be addressed.`;
const messages = [
{ role: "system" as const, content: prompt }
];
const options = {
temperature: SEARCH_CONSTANTS.TEMPERATURE.QUERY_PROCESSOR,
maxTokens: SEARCH_CONSTANTS.LIMITS.QUERY_PROCESSOR_MAX_TOKENS,
bypassFormatter: true,
expectsJsonResponse: true,
_bypassContextProcessing: true // Prevent recursive calls
};
// Get the response from the LLM
const response = await llmService.generateChatCompletion(messages, options);
const responseText = response.text;
// Try to extract structured sub-queries from the response
try {
// Expected format is an array of objects with "text" and "reason" keys
interface RawSubQuery {
text: string;
reason?: string;
}
// Extract JSON from the response
const extractedData = JsonExtractor.extract<RawSubQuery[]>(responseText, {
extractArrays: true,
applyFixes: true,
useFallbacks: true
});
if (Array.isArray(extractedData) && extractedData.length > 0) {
// Convert the raw data to SubQuery objects
return extractedData.map(item => ({
id: this.generateSubQueryId(),
text: item.text,
reason: item.reason || "Sub-aspect of the main question",
isAnswered: false
}));
}
} catch (error: any) {
log.error(`Error extracting sub-queries from LLM response: ${error.message}`);
// Fall through to traditional decomposition
}
// Fallback to traditional decomposition
return this.createSubQueries(query, context);
} catch (error: any) {
log.error(`Error in createLLMSubQueries: ${error.message}`);
return this.createSubQueries(query, context);
}
}
/**
* Create sub-queries from a complex query
*

View File

@ -21,7 +21,53 @@ export class QueryDecompositionTool {
*/
decomposeQuery(query: string, context?: string): DecomposedQuery {
log.info('Using compatibility layer for QueryDecompositionTool.decomposeQuery');
return queryProcessor.decomposeQuery(query, context);
// Since the main implementation is now async but we need to maintain a sync interface,
// we'll use a simpler approach that doesn't require LLM
// Get the complexity to determine approach
const complexity = queryProcessor.assessQueryComplexity(query);
if (!query || query.trim().length === 0) {
return {
originalQuery: query,
subQueries: [],
status: 'pending',
complexity: 0
};
}
// Create a baseline decomposed query
let subQueries = [];
// For compatibility, we'll use the basic SubQuery generation
// This avoids the async LLM call which would break the sync interface
const mainSubQuery = {
id: `sq_${Date.now()}_sync_0`,
text: query,
reason: "Main question (for direct matching)",
isAnswered: false
};
subQueries.push(mainSubQuery);
// Add a generic exploration query for context
const genericQuery = {
id: `sq_${Date.now()}_sync_1`,
text: `What information is related to ${query}?`,
reason: "General exploration to find related content",
isAnswered: false
};
subQueries.push(genericQuery);
// Simplified implementation that doesn't require async/LLM calls
return {
originalQuery: query,
subQueries: subQueries,
status: 'pending',
complexity
};
}
/**

View File

@ -19,13 +19,6 @@ export interface LLMServiceInterface {
stream?: boolean;
systemPrompt?: string;
}): Promise<ChatResponse>;
/**
* Generate search queries by decomposing a complex query into simpler ones
* @param query The original user query to decompose
* @returns An array of decomposed search queries
*/
generateSearchQueries?(query: string): Promise<string[]>;
}
/**

View File

@ -168,16 +168,28 @@ export class ChatPipeline {
log.info(`========== STAGE 2: QUERY DECOMPOSITION ==========`);
log.info('Performing query decomposition to generate effective search queries');
const llmService = await this.getLLMService();
let searchQueries = [userQuery]; // Default to original query
let searchQueries = [userQuery];
if (llmService && llmService.generateSearchQueries) {
if (llmService) {
try {
const decompositionResult = await llmService.generateSearchQueries(userQuery);
if (decompositionResult && decompositionResult.length > 0) {
searchQueries = decompositionResult;
log.info(`Generated ${searchQueries.length} search queries: ${JSON.stringify(searchQueries)}`);
// Import the query processor and use its decomposeQuery method
const queryProcessor = (await import('../context/services/query_processor.js')).default;
// Use the enhanced query processor with the LLM service
const decomposedQuery = await queryProcessor.decomposeQuery(userQuery, undefined, llmService);
if (decomposedQuery && decomposedQuery.subQueries && decomposedQuery.subQueries.length > 0) {
// Extract search queries from the decomposed query
searchQueries = decomposedQuery.subQueries.map(sq => sq.text);
// Always include the original query if it's not already included
if (!searchQueries.includes(userQuery)) {
searchQueries.unshift(userQuery);
}
log.info(`Query decomposed with complexity ${decomposedQuery.complexity}/10 into ${searchQueries.length} search queries`);
} else {
log.info('Query decomposition returned no results, using original query');
log.info('Query decomposition returned no sub-queries, using original query');
}
} catch (error: any) {
log.error(`Error in query decomposition: ${error.message || String(error)}`);