mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-29 02:52:27 +08:00
well at least query decomposition is working..for now
This commit is contained in:
parent
5e50a2918d
commit
7062e51f2d
@ -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) {
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)}`);
|
||||
|
Loading…
x
Reference in New Issue
Block a user