Commit 1f071951dc0ac6987c18bfa9fa4058a41c6809c6

Authored by tangwang
1 parent edd38328

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

api/models.py
... ... @@ -17,6 +17,7 @@ class SearchRequest(BaseModel):
17 17 aggregations: Optional[Dict[str, Any]] = Field(None, description="Aggregation specifications")
18 18 sort_by: Optional[str] = Field(None, description="Sort field name")
19 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 23 class ImageSearchRequest(BaseModel):
... ... @@ -35,6 +36,7 @@ class SearchResponse(BaseModel):
35 36 aggregations: Dict[str, Any] = Field(default_factory=dict, description="Aggregation results")
36 37 query_info: Dict[str, Any] = Field(default_factory=dict, description="Query processing information")
37 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 42 class DocumentResponse(BaseModel):
... ...
api/routes/search.py
... ... @@ -71,7 +71,8 @@ async def search(request: SearchRequest, http_request: Request):
71 71 context=context,
72 72 aggregations=request.aggregations,
73 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 78 # Include performance summary in response
... ... @@ -85,7 +86,8 @@ async def search(request: SearchRequest, http_request: Request):
85 86 took_ms=result.took_ms,
86 87 aggregations=result.aggregations,
87 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 93 except Exception as e:
... ...
frontend/index.html
... ... @@ -109,10 +109,10 @@
109 109 <!-- Pagination -->
110 110 <div class="pagination" id="pagination" style="display: none;"></div>
111 111  
112   - <!-- Debug Info (Collapsible) -->
  112 + <!-- Debug Info -->
113 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 116 </details>
117 117 </div>
118 118  
... ...
frontend/static/js/app.js
... ... @@ -13,12 +13,15 @@ let state = {
13 13 sortBy: '',
14 14 sortOrder: 'desc',
15 15 aggregations: null,
16   - lastSearchData: null
  16 + lastSearchData: null,
  17 + debug: true // Always enable debug mode for test frontend
17 18 };
18 19  
19 20 // Initialize
20 21 document.addEventListener('DOMContentLoaded', function() {
21 22 console.log('SearchEngine loaded');
  23 + console.log('Debug mode: always enabled (test frontend)');
  24 +
22 25 document.getElementById('searchInput').focus();
23 26 });
24 27  
... ... @@ -100,7 +103,8 @@ async function performSearch(page = 1) {
100 103 filters: Object.keys(state.filters).length > 0 ? state.filters : null,
101 104 aggregations: aggregations,
102 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 120 displayResults(data);
117 121 displayAggregations(data.aggregations);
118 122 displayPagination();
119   - displayQueryInfo(data.query_info);
  123 + displayDebugInfo(data);
120 124 updateProductCount(data.total);
121 125 updateClearFiltersButton();
122 126  
... ... @@ -483,58 +487,92 @@ function goToPage(page) {
483 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 574 html += '</div>';
536   -
537   - queryInfoDiv.innerHTML = html;
  575 + debugInfoDiv.innerHTML = html;
538 576 }
539 577  
540 578 // Helper functions
... ...
search/searcher.py
... ... @@ -28,7 +28,8 @@ class SearchResult:
28 28 max_score: float,
29 29 took_ms: int,
30 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 34 self.hits = hits
34 35 self.total = total
... ... @@ -36,10 +37,11 @@ class SearchResult:
36 37 self.took_ms = took_ms
37 38 self.aggregations = aggregations or {}
38 39 self.query_info = query_info or {}
  40 + self.debug_info = debug_info
39 41  
40 42 def to_dict(self) -> Dict[str, Any]:
41 43 """Convert to dictionary representation."""
42   - return {
  44 + result = {
43 45 "hits": self.hits,
44 46 "total": self.total,
45 47 "max_score": self.max_score,
... ... @@ -47,6 +49,9 @@ class SearchResult:
47 49 "aggregations": self.aggregations,
48 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 57 class Searcher:
... ... @@ -106,7 +111,8 @@ class Searcher:
106 111 context: Optional[RequestContext] = None,
107 112 aggregations: Optional[Dict[str, Any]] = None,
108 113 sort_by: Optional[str] = None,
109   - sort_order: Optional[str] = "desc"
  114 + sort_order: Optional[str] = "desc",
  115 + debug: bool = False
110 116 ) -> SearchResult:
111 117 """
112 118 Execute search query.
... ... @@ -121,6 +127,7 @@ class Searcher:
121 127 aggregations: Aggregation specifications for faceted search
122 128 sort_by: Field name for sorting
123 129 sort_order: Sort order: 'asc' or 'desc'
  130 + debug: Enable debug information output
124 131  
125 132 Returns:
126 133 SearchResult object
... ... @@ -408,6 +415,35 @@ class Searcher:
408 415 total_duration = context.end_stage(RequestContextStage.TOTAL)
409 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 447 # Build result
412 448 result = SearchResult(
413 449 hits=hits,
... ... @@ -415,7 +451,8 @@ class Searcher:
415 451 max_score=max_score,
416 452 took_ms=int(total_duration),
417 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 458 # Log complete performance summary
... ...