diff --git a/api/models.py b/api/models.py index be1dffa..858538d 100644 --- a/api/models.py +++ b/api/models.py @@ -17,6 +17,7 @@ class SearchRequest(BaseModel): aggregations: Optional[Dict[str, Any]] = Field(None, description="Aggregation specifications") sort_by: Optional[str] = Field(None, description="Sort field name") sort_order: Optional[str] = Field("desc", description="Sort order: 'asc' or 'desc'") + debug: bool = Field(False, description="Enable debug information output") class ImageSearchRequest(BaseModel): @@ -35,6 +36,7 @@ class SearchResponse(BaseModel): aggregations: Dict[str, Any] = Field(default_factory=dict, description="Aggregation results") query_info: Dict[str, Any] = Field(default_factory=dict, description="Query processing information") performance_info: Optional[Dict[str, Any]] = Field(None, description="Detailed performance timing information") + debug_info: Optional[Dict[str, Any]] = Field(None, description="Debug information (only when debug=True)") class DocumentResponse(BaseModel): diff --git a/api/routes/search.py b/api/routes/search.py index 6ded0aa..21b44d1 100644 --- a/api/routes/search.py +++ b/api/routes/search.py @@ -71,7 +71,8 @@ async def search(request: SearchRequest, http_request: Request): context=context, aggregations=request.aggregations, sort_by=request.sort_by, - sort_order=request.sort_order + sort_order=request.sort_order, + debug=request.debug ) # Include performance summary in response @@ -85,7 +86,8 @@ async def search(request: SearchRequest, http_request: Request): took_ms=result.took_ms, aggregations=result.aggregations, query_info=result.query_info, - performance_info=performance_summary + performance_info=performance_summary, + debug_info=result.debug_info ) except Exception as e: diff --git a/frontend/index.html b/frontend/index.html index ade6bac..ba1afbc 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -109,10 +109,10 @@ - +
- Debug Info (Query Details) -
+ 调试信息 +
diff --git a/frontend/static/js/app.js b/frontend/static/js/app.js index 78ab599..dc29e68 100644 --- a/frontend/static/js/app.js +++ b/frontend/static/js/app.js @@ -13,12 +13,15 @@ let state = { sortBy: '', sortOrder: 'desc', aggregations: null, - lastSearchData: null + lastSearchData: null, + debug: true // Always enable debug mode for test frontend }; // Initialize document.addEventListener('DOMContentLoaded', function() { console.log('SearchEngine loaded'); + console.log('Debug mode: always enabled (test frontend)'); + document.getElementById('searchInput').focus(); }); @@ -100,7 +103,8 @@ async function performSearch(page = 1) { filters: Object.keys(state.filters).length > 0 ? state.filters : null, aggregations: aggregations, sort_by: state.sortBy || null, - sort_order: state.sortOrder + sort_order: state.sortOrder, + debug: state.debug }) }); @@ -116,7 +120,7 @@ async function performSearch(page = 1) { displayResults(data); displayAggregations(data.aggregations); displayPagination(); - displayQueryInfo(data.query_info); + displayDebugInfo(data); updateProductCount(data.total); updateClearFiltersButton(); @@ -483,58 +487,92 @@ function goToPage(page) { window.scrollTo({ top: 0, behavior: 'smooth' }); } -// Display query info -function displayQueryInfo(queryInfo) { - if (!queryInfo) return; +// Display debug info +function displayDebugInfo(data) { + const debugInfoDiv = document.getElementById('debugInfo'); + + if (!state.debug || !data.debug_info) { + // If debug mode is off or no debug info, show basic query info + if (data.query_info) { + let html = '
'; + html += `
查询: ${escapeHtml(data.query_info.original_query || 'N/A')}
`; + html += `
语言: ${getLanguageName(data.query_info.detected_language)}
`; + html += '
'; + debugInfoDiv.innerHTML = html; + } else { + debugInfoDiv.innerHTML = ''; + } + return; + } - const queryInfoDiv = document.getElementById('queryInfo'); + // Display comprehensive debug info when debug mode is on + const debugInfo = data.debug_info; + let html = '
'; + + // Query Analysis + if (debugInfo.query_analysis) { + html += '
查询分析:'; + html += `
原始查询: ${escapeHtml(debugInfo.query_analysis.original_query || 'N/A')}
`; + html += `
标准化: ${escapeHtml(debugInfo.query_analysis.normalized_query || 'N/A')}
`; + html += `
重写后: ${escapeHtml(debugInfo.query_analysis.rewritten_query || 'N/A')}
`; + html += `
检测语言: ${getLanguageName(debugInfo.query_analysis.detected_language)}
`; + html += `
域: ${escapeHtml(debugInfo.query_analysis.domain || 'default')}
`; + html += `
简单查询: ${debugInfo.query_analysis.is_simple_query ? '是' : '否'}
`; + + if (debugInfo.query_analysis.translations && Object.keys(debugInfo.query_analysis.translations).length > 0) { + html += '
翻译: '; + for (const [lang, translation] of Object.entries(debugInfo.query_analysis.translations)) { + if (translation) { + html += `${getLanguageName(lang)}: ${escapeHtml(translation)}; `; + } + } + html += '
'; + } + + if (debugInfo.query_analysis.boolean_ast) { + html += `
布尔AST: ${escapeHtml(debugInfo.query_analysis.boolean_ast)}
`; + } + + html += `
向量嵌入: ${debugInfo.query_analysis.has_vector ? '已启用' : '未启用'}
`; + html += '
'; + } - let html = '
'; + // Feature Flags + if (debugInfo.feature_flags) { + html += '
功能开关:'; + html += `
翻译: ${debugInfo.feature_flags.translation_enabled ? '启用' : '禁用'}
`; + html += `
嵌入: ${debugInfo.feature_flags.embedding_enabled ? '启用' : '禁用'}
`; + html += `
重排序: ${debugInfo.feature_flags.rerank_enabled ? '启用' : '禁用'}
`; + html += '
'; + } - html += ` -
- Original Query - ${escapeHtml(queryInfo.original_query || 'N/A')} -
-
- Rewritten Query - ${escapeHtml(queryInfo.rewritten_query || 'N/A')} -
-
- Detected Language - ${getLanguageName(queryInfo.detected_language)} -
-
- Domain - ${escapeHtml(queryInfo.domain || 'default')} -
- `; + // ES Response + if (debugInfo.es_response) { + html += '
ES响应:'; + html += `
耗时: ${debugInfo.es_response.took_ms}ms
`; + html += `
总命中数: ${debugInfo.es_response.total_hits}
`; + html += `
最高分: ${debugInfo.es_response.max_score?.toFixed(3) || 0}
`; + html += '
'; + } - if (queryInfo.translations && Object.keys(queryInfo.translations).length > 0) { - for (const [lang, translation] of Object.entries(queryInfo.translations)) { - if (translation) { - html += ` -
- Translation (${getLanguageName(lang)}) - ${escapeHtml(translation)} -
- `; - } + // Stage Timings + if (debugInfo.stage_timings) { + html += '
各阶段耗时:'; + for (const [stage, duration] of Object.entries(debugInfo.stage_timings)) { + html += `
${stage}: ${duration.toFixed(2)}ms
`; } + html += '
'; } - if (queryInfo.has_vector) { - html += ` -
- Semantic Search - ✓ Enabled (Vector) -
- `; + // ES Query + if (debugInfo.es_query) { + html += '
ES查询DSL:'; + html += `
${escapeHtml(JSON.stringify(debugInfo.es_query, null, 2))}
`; + html += '
'; } html += '
'; - - queryInfoDiv.innerHTML = html; + debugInfoDiv.innerHTML = html; } // Helper functions diff --git a/search/searcher.py b/search/searcher.py index ed26657..eb5e101 100644 --- a/search/searcher.py +++ b/search/searcher.py @@ -28,7 +28,8 @@ class SearchResult: max_score: float, took_ms: int, aggregations: Optional[Dict[str, Any]] = None, - query_info: Optional[Dict[str, Any]] = None + query_info: Optional[Dict[str, Any]] = None, + debug_info: Optional[Dict[str, Any]] = None ): self.hits = hits self.total = total @@ -36,10 +37,11 @@ class SearchResult: self.took_ms = took_ms self.aggregations = aggregations or {} self.query_info = query_info or {} + self.debug_info = debug_info def to_dict(self) -> Dict[str, Any]: """Convert to dictionary representation.""" - return { + result = { "hits": self.hits, "total": self.total, "max_score": self.max_score, @@ -47,6 +49,9 @@ class SearchResult: "aggregations": self.aggregations, "query_info": self.query_info } + if self.debug_info is not None: + result["debug_info"] = self.debug_info + return result class Searcher: @@ -106,7 +111,8 @@ class Searcher: context: Optional[RequestContext] = None, aggregations: Optional[Dict[str, Any]] = None, sort_by: Optional[str] = None, - sort_order: Optional[str] = "desc" + sort_order: Optional[str] = "desc", + debug: bool = False ) -> SearchResult: """ Execute search query. @@ -121,6 +127,7 @@ class Searcher: aggregations: Aggregation specifications for faceted search sort_by: Field name for sorting sort_order: Sort order: 'asc' or 'desc' + debug: Enable debug information output Returns: SearchResult object @@ -408,6 +415,35 @@ class Searcher: total_duration = context.end_stage(RequestContextStage.TOTAL) context.performance_metrics.total_duration = total_duration + # Collect debug information if requested + debug_info = None + if debug: + debug_info = { + "query_analysis": { + "original_query": context.query_analysis.original_query, + "normalized_query": context.query_analysis.normalized_query, + "rewritten_query": context.query_analysis.rewritten_query, + "detected_language": context.query_analysis.detected_language, + "translations": context.query_analysis.translations, + "has_vector": context.query_analysis.query_vector is not None, + "is_simple_query": context.query_analysis.is_simple_query, + "boolean_ast": context.get_intermediate_result('boolean_ast'), + "domain": context.query_analysis.domain + }, + "es_query": context.get_intermediate_result('es_query', {}), + "es_response": { + "took_ms": es_response.get('took', 0), + "total_hits": total_value, + "max_score": max_score, + "shards": es_response.get('_shards', {}) + }, + "feature_flags": context.metadata.get('feature_flags', {}), + "stage_timings": { + k: round(v, 2) for k, v in context.performance_metrics.stage_timings.items() + }, + "search_params": context.metadata.get('search_params', {}) + } + # Build result result = SearchResult( hits=hits, @@ -415,7 +451,8 @@ class Searcher: max_score=max_score, took_ms=int(total_duration), aggregations=aggregations, - query_info=parsed_query.to_dict() + query_info=parsed_query.to_dict(), + debug_info=debug_info ) # Log complete performance summary -- libgit2 0.21.2