Commit 1f071951dc0ac6987c18bfa9fa4058a41c6809c6

Authored by tangwang
1 parent edd38328

补充调试信息,记录包括各个阶段的 比如query分析结果 检索表达式 各阶段耗时 ES搜索的检索表达式

@@ -17,6 +17,7 @@ class SearchRequest(BaseModel): @@ -17,6 +17,7 @@ class SearchRequest(BaseModel):
17 aggregations: Optional[Dict[str, Any]] = Field(None, description="Aggregation specifications") 17 aggregations: Optional[Dict[str, Any]] = Field(None, description="Aggregation specifications")
18 sort_by: Optional[str] = Field(None, description="Sort field name") 18 sort_by: Optional[str] = Field(None, description="Sort field name")
19 sort_order: Optional[str] = Field("desc", description="Sort order: 'asc' or 'desc'") 19 sort_order: Optional[str] = Field("desc", description="Sort order: 'asc' or 'desc'")
  20 + debug: bool = Field(False, description="Enable debug information output")
20 21
21 22
22 class ImageSearchRequest(BaseModel): 23 class ImageSearchRequest(BaseModel):
@@ -35,6 +36,7 @@ class SearchResponse(BaseModel): @@ -35,6 +36,7 @@ class SearchResponse(BaseModel):
35 aggregations: Dict[str, Any] = Field(default_factory=dict, description="Aggregation results") 36 aggregations: Dict[str, Any] = Field(default_factory=dict, description="Aggregation results")
36 query_info: Dict[str, Any] = Field(default_factory=dict, description="Query processing information") 37 query_info: Dict[str, Any] = Field(default_factory=dict, description="Query processing information")
37 performance_info: Optional[Dict[str, Any]] = Field(None, description="Detailed performance timing information") 38 performance_info: Optional[Dict[str, Any]] = Field(None, description="Detailed performance timing information")
  39 + debug_info: Optional[Dict[str, Any]] = Field(None, description="Debug information (only when debug=True)")
38 40
39 41
40 class DocumentResponse(BaseModel): 42 class DocumentResponse(BaseModel):
api/routes/search.py
@@ -71,7 +71,8 @@ async def search(request: SearchRequest, http_request: Request): @@ -71,7 +71,8 @@ async def search(request: SearchRequest, http_request: Request):
71 context=context, 71 context=context,
72 aggregations=request.aggregations, 72 aggregations=request.aggregations,
73 sort_by=request.sort_by, 73 sort_by=request.sort_by,
74 - sort_order=request.sort_order 74 + sort_order=request.sort_order,
  75 + debug=request.debug
75 ) 76 )
76 77
77 # Include performance summary in response 78 # Include performance summary in response
@@ -85,7 +86,8 @@ async def search(request: SearchRequest, http_request: Request): @@ -85,7 +86,8 @@ async def search(request: SearchRequest, http_request: Request):
85 took_ms=result.took_ms, 86 took_ms=result.took_ms,
86 aggregations=result.aggregations, 87 aggregations=result.aggregations,
87 query_info=result.query_info, 88 query_info=result.query_info,
88 - performance_info=performance_summary 89 + performance_info=performance_summary,
  90 + debug_info=result.debug_info
89 ) 91 )
90 92
91 except Exception as e: 93 except Exception as e:
frontend/index.html
@@ -109,10 +109,10 @@ @@ -109,10 +109,10 @@
109 <!-- Pagination --> 109 <!-- Pagination -->
110 <div class="pagination" id="pagination" style="display: none;"></div> 110 <div class="pagination" id="pagination" style="display: none;"></div>
111 111
112 - <!-- Debug Info (Collapsible) --> 112 + <!-- Debug Info -->
113 <details class="debug-info"> 113 <details class="debug-info">
114 - <summary>Debug Info (Query Details)</summary>  
115 - <div id="queryInfo" class="query-info-content"></div> 114 + <summary>调试信息</summary>
  115 + <div id="debugInfo"></div>
116 </details> 116 </details>
117 </div> 117 </div>
118 118
frontend/static/js/app.js
@@ -13,12 +13,15 @@ let state = { @@ -13,12 +13,15 @@ let state = {
13 sortBy: '', 13 sortBy: '',
14 sortOrder: 'desc', 14 sortOrder: 'desc',
15 aggregations: null, 15 aggregations: null,
16 - lastSearchData: null 16 + lastSearchData: null,
  17 + debug: true // Always enable debug mode for test frontend
17 }; 18 };
18 19
19 // Initialize 20 // Initialize
20 document.addEventListener('DOMContentLoaded', function() { 21 document.addEventListener('DOMContentLoaded', function() {
21 console.log('SearchEngine loaded'); 22 console.log('SearchEngine loaded');
  23 + console.log('Debug mode: always enabled (test frontend)');
  24 +
22 document.getElementById('searchInput').focus(); 25 document.getElementById('searchInput').focus();
23 }); 26 });
24 27
@@ -100,7 +103,8 @@ async function performSearch(page = 1) { @@ -100,7 +103,8 @@ async function performSearch(page = 1) {
100 filters: Object.keys(state.filters).length > 0 ? state.filters : null, 103 filters: Object.keys(state.filters).length > 0 ? state.filters : null,
101 aggregations: aggregations, 104 aggregations: aggregations,
102 sort_by: state.sortBy || null, 105 sort_by: state.sortBy || null,
103 - sort_order: state.sortOrder 106 + sort_order: state.sortOrder,
  107 + debug: state.debug
104 }) 108 })
105 }); 109 });
106 110
@@ -116,7 +120,7 @@ async function performSearch(page = 1) { @@ -116,7 +120,7 @@ async function performSearch(page = 1) {
116 displayResults(data); 120 displayResults(data);
117 displayAggregations(data.aggregations); 121 displayAggregations(data.aggregations);
118 displayPagination(); 122 displayPagination();
119 - displayQueryInfo(data.query_info); 123 + displayDebugInfo(data);
120 updateProductCount(data.total); 124 updateProductCount(data.total);
121 updateClearFiltersButton(); 125 updateClearFiltersButton();
122 126
@@ -483,58 +487,92 @@ function goToPage(page) { @@ -483,58 +487,92 @@ function goToPage(page) {
483 window.scrollTo({ top: 0, behavior: 'smooth' }); 487 window.scrollTo({ top: 0, behavior: 'smooth' });
484 } 488 }
485 489
486 -// Display query info  
487 -function displayQueryInfo(queryInfo) {  
488 - if (!queryInfo) return; 490 +// Display debug info
  491 +function displayDebugInfo(data) {
  492 + const debugInfoDiv = document.getElementById('debugInfo');
  493 +
  494 + if (!state.debug || !data.debug_info) {
  495 + // If debug mode is off or no debug info, show basic query info
  496 + if (data.query_info) {
  497 + let html = '<div style="padding: 10px;">';
  498 + html += `<div><strong>查询:</strong> ${escapeHtml(data.query_info.original_query || 'N/A')}</div>`;
  499 + html += `<div><strong>语言:</strong> ${getLanguageName(data.query_info.detected_language)}</div>`;
  500 + html += '</div>';
  501 + debugInfoDiv.innerHTML = html;
  502 + } else {
  503 + debugInfoDiv.innerHTML = '';
  504 + }
  505 + return;
  506 + }
489 507
490 - const queryInfoDiv = document.getElementById('queryInfo'); 508 + // Display comprehensive debug info when debug mode is on
  509 + const debugInfo = data.debug_info;
  510 + let html = '<div style="padding: 10px; font-family: monospace; font-size: 12px;">';
  511 +
  512 + // Query Analysis
  513 + if (debugInfo.query_analysis) {
  514 + html += '<div style="margin-bottom: 15px;"><strong style="font-size: 14px;">查询分析:</strong>';
  515 + html += `<div>原始查询: ${escapeHtml(debugInfo.query_analysis.original_query || 'N/A')}</div>`;
  516 + html += `<div>标准化: ${escapeHtml(debugInfo.query_analysis.normalized_query || 'N/A')}</div>`;
  517 + html += `<div>重写后: ${escapeHtml(debugInfo.query_analysis.rewritten_query || 'N/A')}</div>`;
  518 + html += `<div>检测语言: ${getLanguageName(debugInfo.query_analysis.detected_language)}</div>`;
  519 + html += `<div>域: ${escapeHtml(debugInfo.query_analysis.domain || 'default')}</div>`;
  520 + html += `<div>简单查询: ${debugInfo.query_analysis.is_simple_query ? '是' : '否'}</div>`;
  521 +
  522 + if (debugInfo.query_analysis.translations && Object.keys(debugInfo.query_analysis.translations).length > 0) {
  523 + html += '<div>翻译: ';
  524 + for (const [lang, translation] of Object.entries(debugInfo.query_analysis.translations)) {
  525 + if (translation) {
  526 + html += `${getLanguageName(lang)}: ${escapeHtml(translation)}; `;
  527 + }
  528 + }
  529 + html += '</div>';
  530 + }
  531 +
  532 + if (debugInfo.query_analysis.boolean_ast) {
  533 + html += `<div>布尔AST: ${escapeHtml(debugInfo.query_analysis.boolean_ast)}</div>`;
  534 + }
  535 +
  536 + html += `<div>向量嵌入: ${debugInfo.query_analysis.has_vector ? '已启用' : '未启用'}</div>`;
  537 + html += '</div>';
  538 + }
491 539
492 - let html = '<div class="info-grid">'; 540 + // Feature Flags
  541 + if (debugInfo.feature_flags) {
  542 + html += '<div style="margin-bottom: 15px;"><strong style="font-size: 14px;">功能开关:</strong>';
  543 + html += `<div>翻译: ${debugInfo.feature_flags.translation_enabled ? '启用' : '禁用'}</div>`;
  544 + html += `<div>嵌入: ${debugInfo.feature_flags.embedding_enabled ? '启用' : '禁用'}</div>`;
  545 + html += `<div>重排序: ${debugInfo.feature_flags.rerank_enabled ? '启用' : '禁用'}</div>`;
  546 + html += '</div>';
  547 + }
493 548
494 - html += `  
495 - <div class="info-item">  
496 - <strong>Original Query</strong>  
497 - <span>${escapeHtml(queryInfo.original_query || 'N/A')}</span>  
498 - </div>  
499 - <div class="info-item">  
500 - <strong>Rewritten Query</strong>  
501 - <span>${escapeHtml(queryInfo.rewritten_query || 'N/A')}</span>  
502 - </div>  
503 - <div class="info-item">  
504 - <strong>Detected Language</strong>  
505 - <span>${getLanguageName(queryInfo.detected_language)}</span>  
506 - </div>  
507 - <div class="info-item">  
508 - <strong>Domain</strong>  
509 - <span>${escapeHtml(queryInfo.domain || 'default')}</span>  
510 - </div>  
511 - `; 549 + // ES Response
  550 + if (debugInfo.es_response) {
  551 + html += '<div style="margin-bottom: 15px;"><strong style="font-size: 14px;">ES响应:</strong>';
  552 + html += `<div>耗时: ${debugInfo.es_response.took_ms}ms</div>`;
  553 + html += `<div>总命中数: ${debugInfo.es_response.total_hits}</div>`;
  554 + html += `<div>最高分: ${debugInfo.es_response.max_score?.toFixed(3) || 0}</div>`;
  555 + html += '</div>';
  556 + }
512 557
513 - if (queryInfo.translations && Object.keys(queryInfo.translations).length > 0) {  
514 - for (const [lang, translation] of Object.entries(queryInfo.translations)) {  
515 - if (translation) {  
516 - html += `  
517 - <div class="info-item">  
518 - <strong>Translation (${getLanguageName(lang)})</strong>  
519 - <span>${escapeHtml(translation)}</span>  
520 - </div>  
521 - `;  
522 - } 558 + // Stage Timings
  559 + if (debugInfo.stage_timings) {
  560 + html += '<div style="margin-bottom: 15px;"><strong style="font-size: 14px;">各阶段耗时:</strong>';
  561 + for (const [stage, duration] of Object.entries(debugInfo.stage_timings)) {
  562 + html += `<div>${stage}: ${duration.toFixed(2)}ms</div>`;
523 } 563 }
  564 + html += '</div>';
524 } 565 }
525 566
526 - if (queryInfo.has_vector) {  
527 - html += `  
528 - <div class="info-item">  
529 - <strong>Semantic Search</strong>  
530 - <span style="color: #27ae60;">✓ Enabled (Vector)</span>  
531 - </div>  
532 - `; 567 + // ES Query
  568 + if (debugInfo.es_query) {
  569 + html += '<div style="margin-bottom: 15px;"><strong style="font-size: 14px;">ES查询DSL:</strong>';
  570 + html += `<pre style="background: #f5f5f5; padding: 10px; overflow: auto; max-height: 400px;">${escapeHtml(JSON.stringify(debugInfo.es_query, null, 2))}</pre>`;
  571 + html += '</div>';
533 } 572 }
534 573
535 html += '</div>'; 574 html += '</div>';
536 -  
537 - queryInfoDiv.innerHTML = html; 575 + debugInfoDiv.innerHTML = html;
538 } 576 }
539 577
540 // Helper functions 578 // Helper functions
search/searcher.py
@@ -28,7 +28,8 @@ class SearchResult: @@ -28,7 +28,8 @@ class SearchResult:
28 max_score: float, 28 max_score: float,
29 took_ms: int, 29 took_ms: int,
30 aggregations: Optional[Dict[str, Any]] = None, 30 aggregations: Optional[Dict[str, Any]] = None,
31 - query_info: Optional[Dict[str, Any]] = None 31 + query_info: Optional[Dict[str, Any]] = None,
  32 + debug_info: Optional[Dict[str, Any]] = None
32 ): 33 ):
33 self.hits = hits 34 self.hits = hits
34 self.total = total 35 self.total = total
@@ -36,10 +37,11 @@ class SearchResult: @@ -36,10 +37,11 @@ class SearchResult:
36 self.took_ms = took_ms 37 self.took_ms = took_ms
37 self.aggregations = aggregations or {} 38 self.aggregations = aggregations or {}
38 self.query_info = query_info or {} 39 self.query_info = query_info or {}
  40 + self.debug_info = debug_info
39 41
40 def to_dict(self) -> Dict[str, Any]: 42 def to_dict(self) -> Dict[str, Any]:
41 """Convert to dictionary representation.""" 43 """Convert to dictionary representation."""
42 - return { 44 + result = {
43 "hits": self.hits, 45 "hits": self.hits,
44 "total": self.total, 46 "total": self.total,
45 "max_score": self.max_score, 47 "max_score": self.max_score,
@@ -47,6 +49,9 @@ class SearchResult: @@ -47,6 +49,9 @@ class SearchResult:
47 "aggregations": self.aggregations, 49 "aggregations": self.aggregations,
48 "query_info": self.query_info 50 "query_info": self.query_info
49 } 51 }
  52 + if self.debug_info is not None:
  53 + result["debug_info"] = self.debug_info
  54 + return result
50 55
51 56
52 class Searcher: 57 class Searcher:
@@ -106,7 +111,8 @@ class Searcher: @@ -106,7 +111,8 @@ class Searcher:
106 context: Optional[RequestContext] = None, 111 context: Optional[RequestContext] = None,
107 aggregations: Optional[Dict[str, Any]] = None, 112 aggregations: Optional[Dict[str, Any]] = None,
108 sort_by: Optional[str] = None, 113 sort_by: Optional[str] = None,
109 - sort_order: Optional[str] = "desc" 114 + sort_order: Optional[str] = "desc",
  115 + debug: bool = False
110 ) -> SearchResult: 116 ) -> SearchResult:
111 """ 117 """
112 Execute search query. 118 Execute search query.
@@ -121,6 +127,7 @@ class Searcher: @@ -121,6 +127,7 @@ class Searcher:
121 aggregations: Aggregation specifications for faceted search 127 aggregations: Aggregation specifications for faceted search
122 sort_by: Field name for sorting 128 sort_by: Field name for sorting
123 sort_order: Sort order: 'asc' or 'desc' 129 sort_order: Sort order: 'asc' or 'desc'
  130 + debug: Enable debug information output
124 131
125 Returns: 132 Returns:
126 SearchResult object 133 SearchResult object
@@ -408,6 +415,35 @@ class Searcher: @@ -408,6 +415,35 @@ class Searcher:
408 total_duration = context.end_stage(RequestContextStage.TOTAL) 415 total_duration = context.end_stage(RequestContextStage.TOTAL)
409 context.performance_metrics.total_duration = total_duration 416 context.performance_metrics.total_duration = total_duration
410 417
  418 + # Collect debug information if requested
  419 + debug_info = None
  420 + if debug:
  421 + debug_info = {
  422 + "query_analysis": {
  423 + "original_query": context.query_analysis.original_query,
  424 + "normalized_query": context.query_analysis.normalized_query,
  425 + "rewritten_query": context.query_analysis.rewritten_query,
  426 + "detected_language": context.query_analysis.detected_language,
  427 + "translations": context.query_analysis.translations,
  428 + "has_vector": context.query_analysis.query_vector is not None,
  429 + "is_simple_query": context.query_analysis.is_simple_query,
  430 + "boolean_ast": context.get_intermediate_result('boolean_ast'),
  431 + "domain": context.query_analysis.domain
  432 + },
  433 + "es_query": context.get_intermediate_result('es_query', {}),
  434 + "es_response": {
  435 + "took_ms": es_response.get('took', 0),
  436 + "total_hits": total_value,
  437 + "max_score": max_score,
  438 + "shards": es_response.get('_shards', {})
  439 + },
  440 + "feature_flags": context.metadata.get('feature_flags', {}),
  441 + "stage_timings": {
  442 + k: round(v, 2) for k, v in context.performance_metrics.stage_timings.items()
  443 + },
  444 + "search_params": context.metadata.get('search_params', {})
  445 + }
  446 +
411 # Build result 447 # Build result
412 result = SearchResult( 448 result = SearchResult(
413 hits=hits, 449 hits=hits,
@@ -415,7 +451,8 @@ class Searcher: @@ -415,7 +451,8 @@ class Searcher:
415 max_score=max_score, 451 max_score=max_score,
416 took_ms=int(total_duration), 452 took_ms=int(total_duration),
417 aggregations=aggregations, 453 aggregations=aggregations,
418 - query_info=parsed_query.to_dict() 454 + query_info=parsed_query.to_dict(),
  455 + debug_info=debug_info
419 ) 456 )
420 457
421 # Log complete performance summary 458 # Log complete performance summary