diff --git a/frontend/static/css/style.css b/frontend/static/css/style.css
index ac81869..4e26c5e 100644
--- a/frontend/static/css/style.css
+++ b/frontend/static/css/style.css
@@ -357,16 +357,22 @@ body {
color: #555;
border-left: 1px dashed #eee;
padding-left: 12px;
- max-height: 260px;
+ max-height: 540px;
overflow: auto;
}
.product-debug-title {
font-weight: 600;
- margin-bottom: 6px;
+ margin-bottom: 8px;
color: #333;
}
+.product-debug-subtitle {
+ margin: 10px 0 6px;
+ font-weight: 600;
+ color: #666;
+}
+
.product-debug-line {
margin-bottom: 2px;
}
@@ -418,6 +424,191 @@ body {
word-break: break-word;
}
+.debug-panel {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ padding: 12px;
+ font-family: Menlo, Consolas, "Courier New", monospace;
+ font-size: 12px;
+}
+
+.debug-section-block {
+ background: #fff;
+ border: 1px solid #e8e8e8;
+ border-radius: 10px;
+ padding: 14px;
+}
+
+.debug-section-title {
+ font-size: 13px;
+ font-weight: 700;
+ color: #222;
+ margin-bottom: 10px;
+}
+
+.debug-stage-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
+ gap: 12px;
+}
+
+.debug-stage-card {
+ border: 1px solid #ececec;
+ border-radius: 8px;
+ padding: 12px;
+ background: linear-gradient(180deg, #fff 0%, #fafafa 100%);
+}
+
+.debug-stage-title {
+ font-size: 13px;
+ font-weight: 700;
+ color: #333;
+}
+
+.debug-stage-subtitle {
+ margin: 4px 0 8px;
+ color: #888;
+ font-size: 11px;
+}
+
+.debug-metrics {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
+ gap: 8px;
+}
+
+.debug-metric {
+ padding: 8px 9px;
+ background: #f7f7f7;
+ border-radius: 6px;
+ border: 1px solid #efefef;
+}
+
+.debug-metric-label {
+ font-size: 11px;
+ color: #777;
+ margin-bottom: 2px;
+}
+
+.debug-metric-value {
+ color: #222;
+ font-weight: 600;
+ word-break: break-word;
+}
+
+.debug-score-pills {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 6px;
+}
+
+.debug-score-pill {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 10px;
+ border-radius: 999px;
+ border: 1px solid #e3e3e3;
+ background: #f7f7f7;
+}
+
+.debug-score-pill-label {
+ color: #666;
+}
+
+.debug-score-pill-value {
+ color: #111;
+ font-weight: 700;
+}
+
+.tone-es {
+ background: #f8f1ff;
+ border-color: #e6d5ff;
+}
+
+.tone-coarse {
+ background: #eef8ff;
+ border-color: #cae8ff;
+}
+
+.tone-fine {
+ background: #f3fbef;
+ border-color: #d8f1c8;
+}
+
+.tone-rerank {
+ background: #fff4e8;
+ border-color: #ffd9b0;
+}
+
+.tone-final {
+ background: #fff1f0;
+ border-color: #ffc9c4;
+}
+
+.tone-neutral {
+ background: #f5f5f5;
+}
+
+.debug-details {
+ margin-top: 10px;
+}
+
+.debug-details summary {
+ cursor: pointer;
+ color: #555;
+ font-weight: 600;
+}
+
+.debug-json-pre {
+ margin-top: 8px;
+ padding: 10px;
+ background: #f5f5f5;
+ border-radius: 6px;
+ overflow: auto;
+ max-height: 240px;
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+
+.debug-timing-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.debug-timing-row {
+ display: grid;
+ grid-template-columns: 220px 1fr 90px;
+ gap: 10px;
+ align-items: center;
+}
+
+.debug-timing-label {
+ color: #444;
+}
+
+.debug-timing-bar-wrap {
+ height: 10px;
+ background: #f0f0f0;
+ border-radius: 999px;
+ overflow: hidden;
+}
+
+.debug-timing-bar {
+ height: 100%;
+ background: linear-gradient(90deg, #f39c12 0%, #e74c3c 100%);
+ border-radius: 999px;
+}
+
+.debug-timing-value {
+ text-align: right;
+ color: #666;
+ font-weight: 600;
+}
+
.product-debug-link {
display: inline-block;
margin-top: 0;
@@ -687,10 +878,41 @@ footer span {
}
.product-grid {
- grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
- gap: 15px;
padding: 15px;
}
+
+ .product-card {
+ flex-direction: column;
+ }
+
+ .product-main {
+ width: 100%;
+ }
+
+ .product-image-wrapper {
+ width: 100%;
+ max-width: 320px;
+ }
+
+ .product-debug {
+ width: 100%;
+ border-left: none;
+ border-top: 1px dashed #eee;
+ padding-left: 0;
+ padding-top: 12px;
+ }
+
+ .debug-stage-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .debug-timing-row {
+ grid-template-columns: 1fr;
+ }
+
+ .debug-timing-value {
+ text-align: left;
+ }
.pagination {
padding: 20px 15px;
@@ -699,10 +921,6 @@ footer span {
}
@media (max-width: 480px) {
- .product-grid {
- grid-template-columns: repeat(2, 1fr);
- }
-
.header-left {
gap: 15px;
}
diff --git a/frontend/static/js/app.js b/frontend/static/js/app.js
index c597d96..a18528f 100644
--- a/frontend/static/js/app.js
+++ b/frontend/static/js/app.js
@@ -407,89 +407,12 @@ function displayResults(data) {
let debugHtml = '';
if (debug) {
- const esScore = typeof debug.es_score === 'number' ? debug.es_score.toFixed(4) : String(debug.es_score ?? '');
- const es_score_normalized = typeof debug.es_score_normalized === 'number'
- ? debug.es_score_normalized.toFixed(4)
- : (debug.es_score_normalized == null ? '' : String(debug.es_score_normalized));
- const rerankScore = typeof debug.rerank_score === 'number'
- ? debug.rerank_score.toFixed(4)
- : (debug.rerank_score == null ? '' : String(debug.rerank_score));
-
- const fusedScore = typeof debug.fused_score === 'number'
- ? debug.fused_score.toFixed(4)
- : (debug.fused_score == null ? '' : String(debug.fused_score));
-
- // Build multilingual title info
- let titleLines = '';
- if (debug.title_multilingual && typeof debug.title_multilingual === 'object') {
- Object.entries(debug.title_multilingual).forEach(([lang, val]) => {
- if (val) {
- titleLines += `
title.${escapeHtml(String(lang))}: ${escapeHtml(String(val))}
`;
- }
- });
- }
-
- const resultJson = customStringify(result);
- const rawUrl = `${API_BASE_URL}/search/es-doc/${encodeURIComponent(spuId)}?tenant_id=${encodeURIComponent(tenantId)}`;
- const rerankInputHtml = debug.rerank_input
- ? `
-
- Rerank input
- ${escapeHtml(customStringify(debug.rerank_input))}
-
- `
- : '';
- const styleIntentHtml = debug.style_intent_sku
- ? `
-
- Selected SKU
- ${escapeHtml(customStringify(debug.style_intent_sku))}
-
- `
- : '';
- const matchedQueriesHtml = debug.matched_queries
- ? `
-
- matched_queries
- ${escapeHtml(customStringify(debug.matched_queries))}
-
- `
- : '';
-
- debugHtml = `
-
-
Ranking Debug
-
spu_id: ${escapeHtml(String(spuId || ''))}
-
Position before rerank: ${escapeHtml(String(debug.initial_rank ?? ''))}
-
Position after rerank: ${escapeHtml(String(debug.final_rank ?? ''))}
-
ES score: ${esScore}
-
ES normalized: ${es_score_normalized}
-
Rerank score: ${rerankScore}
-
rerank_factor: ${escapeHtml(String(debug.rerank_factor ?? ''))}
-
text_score: ${escapeHtml(String(debug.text_score ?? ''))}
-
text_factor: ${escapeHtml(String(debug.text_factor ?? ''))}
-
knn_score: ${escapeHtml(String(debug.knn_score ?? ''))}
-
knn_factor: ${escapeHtml(String(debug.knn_factor ?? ''))}
-
Fused score: ${fusedScore}
- ${titleLines}
- ${rerankInputHtml}
- ${styleIntentHtml}
- ${matchedQueriesHtml}
-
-
-
- `;
+ debugHtml = buildProductDebugHtml({
+ debug,
+ result,
+ spuId,
+ tenantId,
+ });
}
html += `
@@ -527,6 +450,126 @@ function displayResults(data) {
grid.innerHTML = html;
}
+function formatDebugNumber(value, digits = 4) {
+ if (typeof value === 'number' && Number.isFinite(value)) {
+ return value.toFixed(digits);
+ }
+ return value == null || value === '' ? 'N/A' : String(value);
+}
+
+function renderMetricList(items) {
+ const rows = items
+ .filter((item) => item && item.value !== undefined && item.value !== null && item.value !== '')
+ .map((item) => `
+
+
${escapeHtml(item.label)}
+
${escapeHtml(String(item.value))}
+
+ `)
+ .join('');
+ return rows ? `${rows}
` : '';
+}
+
+function renderScorePills(items) {
+ const pills = items
+ .filter((item) => item && item.value !== undefined && item.value !== null && item.value !== '')
+ .map((item) => `
+
+ ${escapeHtml(item.label)}
+ ${escapeHtml(String(item.value))}
+
+ `)
+ .join('');
+ return pills ? `${pills}
` : '';
+}
+
+function renderJsonDetails(title, payload, open = false) {
+ if (!payload || (typeof payload === 'object' && Object.keys(payload).length === 0)) {
+ return '';
+ }
+ return `
+
+ ${escapeHtml(title)}
+ ${escapeHtml(customStringify(payload))}
+
+ `;
+}
+
+function buildProductDebugHtml({ debug, result, spuId, tenantId }) {
+ const resultJson = customStringify(result);
+ const rawUrl = `${API_BASE_URL}/search/es-doc/${encodeURIComponent(spuId)}?tenant_id=${encodeURIComponent(tenantId)}`;
+
+ const rankSummary = renderMetricList([
+ { label: 'Initial Rank', value: debug.initial_rank ?? 'N/A' },
+ { label: 'Final Rank', value: debug.final_rank ?? 'N/A' },
+ { label: 'Rank Delta', value: (debug.initial_rank && debug.final_rank) ? String(debug.initial_rank - debug.final_rank) : 'N/A' },
+ { label: 'SPU', value: spuId || 'N/A' },
+ ]);
+
+ const stageScores = renderScorePills([
+ { label: 'ES', value: formatDebugNumber(debug.es_score), tone: 'tone-es' },
+ { label: 'ES Norm', value: formatDebugNumber(debug.es_score_normalized), tone: 'tone-neutral' },
+ { label: 'Coarse', value: formatDebugNumber(debug.coarse_score), tone: 'tone-coarse' },
+ { label: 'Fine', value: formatDebugNumber(debug.fine_score), tone: 'tone-fine' },
+ { label: 'Rerank', value: formatDebugNumber(debug.rerank_score), tone: 'tone-rerank' },
+ { label: 'Fused', value: formatDebugNumber(debug.fused_score), tone: 'tone-final' },
+ ]);
+
+ const factorMetrics = renderMetricList([
+ { label: 'coarse_text_factor', value: formatDebugNumber(debug.coarse_text_factor) },
+ { label: 'coarse_knn_factor', value: formatDebugNumber(debug.coarse_knn_factor) },
+ { label: 'text_factor', value: formatDebugNumber(debug.text_factor) },
+ { label: 'knn_factor', value: formatDebugNumber(debug.knn_factor) },
+ { label: 'fine_factor', value: formatDebugNumber(debug.fine_factor) },
+ { label: 'rerank_factor', value: formatDebugNumber(debug.rerank_factor) },
+ ]);
+
+ const signalMetrics = renderMetricList([
+ { label: 'text_score', value: formatDebugNumber(debug.text_score) },
+ { label: 'text_source', value: formatDebugNumber(debug.text_source_score) },
+ { label: 'text_translation', value: formatDebugNumber(debug.text_translation_score) },
+ { label: 'text_primary', value: formatDebugNumber(debug.text_primary_score) },
+ { label: 'text_support', value: formatDebugNumber(debug.text_support_score) },
+ { label: 'knn_score', value: formatDebugNumber(debug.knn_score) },
+ { label: 'text_knn', value: formatDebugNumber(debug.text_knn_score) },
+ { label: 'image_knn', value: formatDebugNumber(debug.image_knn_score) },
+ ]);
+
+ const titlePayload = {};
+ if (debug.title_multilingual) titlePayload.title = debug.title_multilingual;
+ if (debug.brief_multilingual) titlePayload.brief = debug.brief_multilingual;
+ if (debug.vendor_multilingual) titlePayload.vendor = debug.vendor_multilingual;
+
+ return `
+
+
Ranking Funnel
+ ${rankSummary}
+ ${stageScores}
+
Fusion Factors
+ ${factorMetrics}
+
Signal Breakdown
+ ${signalMetrics}
+ ${renderJsonDetails('Rerank Input', debug.rerank_input, true)}
+ ${renderJsonDetails('Selected SKU', debug.style_intent_sku, true)}
+ ${renderJsonDetails('Matched Queries', debug.matched_queries, false)}
+ ${renderJsonDetails('Multilingual Fields', titlePayload, false)}
+
+
+
+ `;
+}
+
// Display facets as filter tags (一级分类 + 三个属性分面)
function displayFacets(facets) {
if (!facets || !Array.isArray(facets)) {
@@ -919,127 +962,174 @@ function formatIntentDetectionHtml(intent) {
return block;
}
+function buildStageCard(title, subtitle, metrics, extraHtml = '') {
+ return `
+
+
${escapeHtml(title)}
+ ${subtitle ? `
${escapeHtml(subtitle)}
` : ''}
+ ${renderMetricList(metrics)}
+ ${extraHtml}
+
+ `;
+}
+
+function renderTimingBars(stageTimings) {
+ if (!stageTimings || typeof stageTimings !== 'object') {
+ return '';
+ }
+ const orderedStages = [
+ 'query_parsing',
+ 'query_building',
+ 'elasticsearch_search_primary',
+ 'coarse_ranking',
+ 'style_sku_prepare_hits',
+ 'fine_ranking',
+ 'reranking',
+ 'elasticsearch_page_fill',
+ 'result_processing',
+ 'total_search',
+ ];
+ const entries = Object.entries(stageTimings)
+ .sort((a, b) => {
+ const ai = orderedStages.indexOf(a[0]);
+ const bi = orderedStages.indexOf(b[0]);
+ return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
+ });
+ const total = Number(stageTimings.total_search || 0);
+ return `
+
+ ${entries.map(([stage, duration]) => {
+ const numeric = Number(duration) || 0;
+ const width = total > 0 ? Math.max(2, Math.round((numeric / total) * 100)) : 2;
+ return `
+
+
${escapeHtml(stage)}
+
+
${numeric.toFixed(2)}ms
+
+ `;
+ }).join('')}
+
+ `;
+}
+
+function buildGlobalFunnelHtml(data, debugInfo) {
+ const queryAnalysis = debugInfo.query_analysis || {};
+ const searchParams = debugInfo.search_params || {};
+ const featureFlags = debugInfo.feature_flags || {};
+ const esResponse = debugInfo.es_response || {};
+ const esQueryContext = debugInfo.es_query_context || {};
+ const coarseInfo = debugInfo.coarse_rank || {};
+ const fineInfo = debugInfo.fine_rank || {};
+ const rerankInfo = debugInfo.rerank || {};
+ const translations = queryAnalysis.translations || {};
+
+ const summaryHtml = `
+
+
Query Context
+ ${renderMetricList([
+ { label: 'original_query', value: queryAnalysis.original_query || 'N/A' },
+ { label: 'rewritten_query', value: queryAnalysis.rewritten_query || 'N/A' },
+ { label: 'detected_language', value: queryAnalysis.detected_language || 'N/A' },
+ { label: 'index_languages', value: (queryAnalysis.index_languages || []).join(', ') || 'N/A' },
+ { label: 'query_tokens', value: (queryAnalysis.query_tokens || []).join(', ') || 'N/A' },
+ { label: 'translation_enabled', value: featureFlags.translation_enabled ? 'enabled' : 'disabled' },
+ { label: 'embedding_enabled', value: featureFlags.embedding_enabled ? 'enabled' : 'disabled' },
+ { label: 'style_intent_active', value: featureFlags.style_intent_active ? 'yes' : 'no' },
+ ])}
+ ${Object.keys(translations).length ? renderJsonDetails('Translations', translations, true) : ''}
+ ${formatIntentDetectionHtml(queryAnalysis.intent_detection ?? queryAnalysis.style_intent_profile)}
+
+ `;
+
+ const funnelHtml = `
+
+
Ranking Funnel
+
+ ${buildStageCard('ES Recall', 'First-pass retrieval', [
+ { label: 'fetch_from', value: searchParams.es_fetch_from ?? 0 },
+ { label: 'fetch_size', value: searchParams.es_fetch_size ?? 'N/A' },
+ { label: 'total_hits', value: esResponse.total_hits ?? 'N/A' },
+ { label: 'es_took_ms', value: esResponse.took_ms ?? 'N/A' },
+ { label: 'include_named_queries_score', value: esQueryContext.include_named_queries_score ? 'yes' : 'no' },
+ ])}
+ ${buildStageCard('Coarse Rank', 'Lexical + vector fusion only', [
+ { label: 'docs_in', value: coarseInfo.docs_in ?? searchParams.es_fetch_size ?? 'N/A' },
+ { label: 'docs_out', value: coarseInfo.docs_out ?? 'N/A' },
+ { label: 'formula', value: 'text x knn' },
+ ], coarseInfo.fusion ? renderJsonDetails('Coarse Fusion', coarseInfo.fusion, false) : '')}
+ ${buildStageCard('Fine Rank', 'Lightweight reranker', [
+ { label: 'service_url', value: fineInfo.service_url || 'N/A' },
+ { label: 'docs', value: fineInfo.docs ?? fineInfo.top_n ?? 'N/A' },
+ { label: 'top_n', value: fineInfo.top_n ?? 'N/A' },
+ { label: 'query_template', value: fineInfo.query_template || 'N/A' },
+ ], fineInfo.meta ? renderJsonDetails('Fine Meta', fineInfo.meta, false) : '')}
+ ${buildStageCard('Final Rerank', 'Heavy reranker + final fusion', [
+ { label: 'service_url', value: rerankInfo.service_url || 'N/A' },
+ { label: 'docs', value: rerankInfo.docs ?? 'N/A' },
+ { label: 'top_n', value: rerankInfo.top_n ?? 'N/A' },
+ { label: 'query_template', value: rerankInfo.query_template || 'N/A' },
+ ], `${rerankInfo.fusion ? renderJsonDetails('Final Fusion', rerankInfo.fusion, false) : ''}${rerankInfo.meta ? renderJsonDetails('Rerank Meta', rerankInfo.meta, false) : ''}`)}
+ ${buildStageCard('Page Return', 'Final slice returned to UI', [
+ { label: 'from', value: searchParams.from_ ?? 0 },
+ { label: 'size', value: searchParams.size ?? 'N/A' },
+ { label: 'returned', value: (data.results || []).length },
+ { label: 'max_score', value: formatDebugNumber(esResponse.max_score, 3) },
+ ])}
+
+
+ `;
+
+ const timingHtml = `
+
+
Timing Breakdown
+ ${renderTimingBars(debugInfo.stage_timings)}
+
+ `;
+
+ const rawPayloadHtml = `
+
+
Raw Payloads
+ ${renderJsonDetails('ES Query DSL', debugInfo.es_query, false)}
+ ${renderJsonDetails('ES Query Context', debugInfo.es_query_context, false)}
+ ${renderJsonDetails('Search Params', debugInfo.search_params, false)}
+
+ `;
+
+ return `
+
+ ${summaryHtml}
+ ${funnelHtml}
+ ${timingHtml}
+ ${rawPayloadHtml}
+
+ `;
+}
+
// 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 += `
original_query: ${escapeHtml(data.query_info.original_query || 'N/A')}
`;
- html += `
detected_language: ${escapeHtml(data.query_info.detected_language || 'N/A')}
`;
- html += '
';
- debugInfoDiv.innerHTML = html;
+ debugInfoDiv.innerHTML = `
+
+
+
Query Context
+ ${renderMetricList([
+ { label: 'original_query', value: data.query_info.original_query || 'N/A' },
+ { label: 'detected_language', value: data.query_info.detected_language || 'N/A' },
+ ])}
+
+
+ `;
} else {
debugInfoDiv.innerHTML = '';
}
return;
}
-
- // Display comprehensive debug info when debug mode is on
- const debugInfo = data.debug_info;
- let html = '';
-
- // Query Analysis
- if (debugInfo.query_analysis) {
- html += '
Query Analysis:';
- html += `
original_query: ${escapeHtml(debugInfo.query_analysis.original_query || 'N/A')}
`;
- html += `
query_normalized: ${escapeHtml(debugInfo.query_analysis.query_normalized || 'N/A')}
`;
- html += `
rewritten_query: ${escapeHtml(debugInfo.query_analysis.rewritten_query || 'N/A')}
`;
- html += `
detected_language: ${escapeHtml(debugInfo.query_analysis.detected_language || 'N/A')}
`;
- html += `
index_languages: ${escapeHtml((debugInfo.query_analysis.index_languages || []).join(', ') || 'N/A')}
`;
- html += `
query_tokens: ${escapeHtml((debugInfo.query_analysis.query_tokens || []).join(', ') || 'N/A')}
`;
-
- if (debugInfo.query_analysis.translations && Object.keys(debugInfo.query_analysis.translations).length > 0) {
- html += '
translations: ';
- for (const [lang, translation] of Object.entries(debugInfo.query_analysis.translations)) {
- if (translation) {
- html += `${lang}: ${escapeHtml(translation)}; `;
- }
- }
- html += '
';
- }
-
- if (debugInfo.query_analysis.boolean_ast) {
- html += `
boolean_ast: ${escapeHtml(debugInfo.query_analysis.boolean_ast)}
`;
- }
- const intentPayload = debugInfo.query_analysis.intent_detection ?? debugInfo.query_analysis.style_intent_profile;
- html += formatIntentDetectionHtml(intentPayload);
-
- html += '
';
- }
-
- // Feature Flags
- if (debugInfo.feature_flags) {
- html += '
Feature Flags:';
- html += `
translation_enabled: ${debugInfo.feature_flags.translation_enabled ? 'enabled' : 'disabled'}
`;
- html += `
embedding_enabled: ${debugInfo.feature_flags.embedding_enabled ? 'enabled' : 'disabled'}
`;
- html += `
rerank_enabled: ${debugInfo.feature_flags.rerank_enabled ? 'enabled' : 'disabled'}
`;
- if (debugInfo.feature_flags.style_intent_enabled !== undefined) {
- html += `
style_intent_enabled: ${debugInfo.feature_flags.style_intent_enabled ? 'enabled' : 'disabled'}
`;
- }
- if (debugInfo.feature_flags.style_intent_active !== undefined) {
- html += `
style_intent_active: ${debugInfo.feature_flags.style_intent_active ? 'yes' : 'no'}
`;
- }
- html += '
';
- }
-
- // ES Response
- if (debugInfo.es_response) {
- html += '
ES Response:';
- html += `
took_ms: ${debugInfo.es_response.took_ms}ms
`;
- html += `
total_hits: ${debugInfo.es_response.total_hits}
`;
- html += `
max_score: ${debugInfo.es_response.max_score?.toFixed(3) || 0}
`;
- html += `
es_score_normalization_factor: ${escapeHtml(String(debugInfo.es_response.es_score_normalization_factor ?? ''))}
`;
- html += '
';
- }
-
- if (debugInfo.rerank) {
- html += '
Rerank:';
- html += `
query_template: ${escapeHtml(debugInfo.rerank.query_template || 'N/A')}
`;
- html += `
doc_template: ${escapeHtml(debugInfo.rerank.doc_template || 'N/A')}
`;
- html += `
query_text: ${escapeHtml(debugInfo.rerank.query_text || 'N/A')}
`;
- html += `
docs: ${escapeHtml(String(debugInfo.rerank.docs ?? ''))}
`;
- html += `
top_n: ${escapeHtml(String(debugInfo.rerank.top_n ?? ''))}
`;
- if (debugInfo.rerank.fusion) {
- html += '
fusion:
';
- html += `
${escapeHtml(customStringify(debugInfo.rerank.fusion))}`;
- }
- html += '
';
- }
-
- // Stage Timings
- if (debugInfo.stage_timings) {
- html += '
Stage Timings:';
- const bounds = debugInfo.stage_time_bounds_ms || {};
- for (const [stage, duration] of Object.entries(debugInfo.stage_timings)) {
- const b = bounds[stage];
- if (b && b.start_unix_ms != null && b.end_unix_ms != null) {
- html += `
${stage}: ${Number(duration).toFixed(2)}ms (start ${b.start_unix_ms} → end ${b.end_unix_ms} unix ms)
`;
- } else {
- html += `
${stage}: ${Number(duration).toFixed(2)}ms
`;
- }
- }
- html += '
';
- }
-
- // ES Query
- if (debugInfo.es_query) {
- html += '
ES Query DSL:';
- html += `
${escapeHtml(customStringify(debugInfo.es_query))}`;
- html += '
';
- }
-
- if (debugInfo.es_query_context) {
- html += '
ES Query Context:';
- html += `
${escapeHtml(customStringify(debugInfo.es_query_context))}`;
- html += '
';
- }
-
- html += '
';
- debugInfoDiv.innerHTML = html;
+ debugInfoDiv.innerHTML = buildGlobalFunnelHtml(data, data.debug_info);
}
// Custom JSON stringify that compresses numeric arrays (like embeddings) to single line
@@ -1070,4 +1160,3 @@ function formatDate(dateStr) {
return dateStr;
}
}
-
diff --git a/search/searcher.py b/search/searcher.py
index e032504..20f08ec 100644
--- a/search/searcher.py
+++ b/search/searcher.py
@@ -897,6 +897,16 @@ class Searcher:
if doc_id is None:
continue
rerank_debug_by_doc[str(doc_id)] = item
+ coarse_debug_raw = context.get_intermediate_result('coarse_rank_scores', None)
+ coarse_debug_by_doc: Dict[str, Dict[str, Any]] = {}
+ if isinstance(coarse_debug_raw, list):
+ for item in coarse_debug_raw:
+ if not isinstance(item, dict):
+ continue
+ doc_id = item.get("doc_id")
+ if doc_id is None:
+ continue
+ coarse_debug_by_doc[str(doc_id)] = item
fine_debug_raw = context.get_intermediate_result('fine_rank_scores', None)
fine_debug_by_doc: Dict[str, Dict[str, Any]] = {}
if isinstance(fine_debug_raw, list):
@@ -937,6 +947,9 @@ class Searcher:
rerank_debug = None
if doc_id is not None:
rerank_debug = rerank_debug_by_doc.get(str(doc_id))
+ coarse_debug = None
+ if doc_id is not None:
+ coarse_debug = coarse_debug_by_doc.get(str(doc_id))
fine_debug = None
if doc_id is not None:
fine_debug = fine_debug_by_doc.get(str(doc_id))
@@ -974,6 +987,11 @@ class Searcher:
"vendor_multilingual": vendor_multilingual,
}
+ if coarse_debug:
+ debug_entry["coarse_score"] = coarse_debug.get("coarse_score")
+ debug_entry["coarse_text_factor"] = coarse_debug.get("coarse_text_factor")
+ debug_entry["coarse_knn_factor"] = coarse_debug.get("coarse_knn_factor")
+
# 若存在重排调试信息,则补充 doc 级别的融合分数信息
if rerank_debug:
debug_entry["doc_id"] = rerank_debug.get("doc_id")
--
libgit2 0.21.2