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 @@
-
+
';
+
+ // 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