Преглед на файлове

v0.0.4-debug-修复父文档id错误绑定bug

WangXuMing преди 3 месеца
родител
ревизия
da1281061e

+ 1 - 3
core/construction_review/component/ai_review_engine.py

@@ -205,9 +205,6 @@ class AIReviewEngine(BaseReviewer):
             async with self.semaphore:
                 return await check_func(**kwargs)
 
-        # 外层超时配置(单个任务的整体超时时间)
-        # 计算公式: 模型单次超时(15秒) × (1次初始 + 最大重试次数2次) + 缓冲时间(10秒)
-        # = 15 × 3 + 10 = 55秒
         TASK_TIMEOUT = 55
 
         basic_tasks = []
@@ -1070,6 +1067,7 @@ class AIReviewEngine(BaseReviewer):
                     "error_message": error_msg
                 }
             }
+
     async def reference_basis_reviewer(self, review_data: Dict[str, Any], trace_id: str,
                                 state: dict = None, stage_name: str = None) -> Dict[str, Any]:
         """

+ 3 - 5
foundation/ai/rag/retrieval/entities_enhance.py

@@ -36,12 +36,10 @@ class EntitiesEnhance():
                 max_results=5       # 最终最多返回20个实体文本
             ))
 
-            # top_k,二次重排最多返回数量
-            bfp_result = run_async(retrieval_manager.async_bfp_recall(entity_list,background,top_k=3))
-            server_logger.info(f"bfp_result:{bfp_result}")
+            # BFP背景增强召回
+            bfp_result = run_async(retrieval_manager.async_bfp_recall(entity_list, background, top_k=3))
             self.bfp_result_lists.append(bfp_result)
-            server_logger.info("实体增强召回结束")
-        #self.test_file(self.bfp_result_lists,seve=True)
+        self.test_file(self.bfp_result_lists,seve=True)
         return self.bfp_result_lists
             
 

+ 44 - 19
foundation/ai/rag/retrieval/retrieval.py

@@ -265,8 +265,14 @@ class RetrievalManager:
             self.logger.warning("background为空,跳过二次重排序,直接返回高分文档")
             return high_score_results
 
-        # 提取高分文档的文本内容用于二次重排
-        high_score_text_content = list(set([item['text_content'] for item in high_score_results]))
+        # 提取高分文档的文本内容用于二次重排(保持顺序去重)
+        seen_texts = set()
+        high_score_text_content = []
+        for item in high_score_results:
+            text = item['text_content']
+            if text not in seen_texts:
+                seen_texts.add(text)
+                high_score_text_content.append(text)
         self.logger.info(f"提取高分文档文本内容,共 {len(high_score_text_content)} 个,准备二次重排")
 
         # 二次重排 - 使用配置的重排序模型
@@ -279,19 +285,32 @@ class RetrievalManager:
         # 根据重排结果重新组织数据
         reorganize_start = time.time()
         final_results = []
-        text_to_metadata = {item['text_content']: item for item in high_score_results}
+        
+        # 构建 text_content -> 原始文档列表 的映射(保留所有匹配项)
+        text_to_items = {}
+        for item in high_score_results:
+            text = item['text_content']
+            if text not in text_to_items:
+                text_to_items[text] = []
+            text_to_items[text].append(item)
 
         # 处理二次重排序的高分文档
+        added_texts = set()  # 用于跟踪已添加的文本,避免重复
         for rerank_item in bfp_rerank_result:
             text = rerank_item.get('text', '')
             parent_id = rerank_item.get('parent_id', '')
             score = rerank_item.get('score', 0.0)
 
-            if text in text_to_metadata:
-                original_item = text_to_metadata[text].copy()
-                original_item['bfp_rerank_score'] = score
-                original_item['bfp_rerank_parent_id'] = parent_id
-                final_results.append(original_item)
+            if text in text_to_items and text not in added_texts:
+                # 获取该文本的所有候选文档,选择 rerank_score 最高的
+                candidates = text_to_items[text]
+                best_candidate = max(candidates, key=lambda x: x.get('rerank_score', 0.0))
+
+                result_item = best_candidate.copy()
+                result_item['bfp_rerank_score'] = score
+                result_item['bfp_rerank_parent_id'] = parent_id
+                final_results.append(result_item)
+                added_texts.add(text)  # 标记该文本已添加
 
         reorganize_end = time.time()
         total_time = reorganize_end - start_time
@@ -337,14 +356,6 @@ class RetrievalManager:
 
             # 详细记录混合搜索结果
             self.logger.info(f"混合搜索召回返回 {len(results)} 个结果")
-            # for i, result in enumerate(results):
-            #     text_content = result.get('text_content', '')
-            #     metadata = result.get('metadata', {})
-            #     title = metadata.get('title', 'N/A')
-            #     file = metadata.get('file', 'N/A')
-            #     self.logger.info(f"混合搜索结果 {i+1}: 标题='{title}', 文件='{file}', 内容长度={len(text_content)}")
-            #     # self.logger.info(f"  完整元数据: {metadata}")
-            #     # self.logger.info(f"  文本内容: '{text_content}'")
 
             return results
 
@@ -435,9 +446,23 @@ class RetrievalManager:
                 rerank_text = api_result.get('text', '')
                 rerank_score = float(api_result.get('score', '0.0'))
 
-                # 使用去重时的索引映射
-                original_index = original_indices_map[i][0]  # 取第一个原始索引
-                original_candidate = unique_candidates[i]  # 获取原始候选文档(包含元数据)
+                # 根据 rerank_text 在 unique_candidates 中查找匹配项
+                # (rerank 会改变顺序,不能直接用索引 i)
+                found_index = None
+                original_candidate = None
+
+                for idx, candidate in enumerate(unique_candidates):
+                    if candidate.get('text_content', '') == rerank_text:
+                        found_index = idx
+                        original_candidate = candidate
+                        break
+
+                if original_candidate is None:
+                    self.logger.warning(f"[rerank_recall] 未找到匹配的候选文档,跳过: {rerank_text[:50]}...")
+                    continue
+
+                # 使用找到的索引获取原始索引映射
+                original_index = original_indices_map[found_index][0] if found_index < len(original_indices_map) else i
 
                 # 获取原始混合搜索的评分信息
                 hybrid_distance = original_candidate.get('distance', 0.0)

+ 519 - 0
utils_test/RAG_Test/rag_pipeline_web/app.js

@@ -0,0 +1,519 @@
+/**
+ * RAG管道测试可视化工具
+ * 用于展示RAG链路各环节输入输出数据流转
+ */
+
+// API配置
+const API_BASE = 'http://localhost:8765';
+
+// 全局数据存储
+let pipelineData = null;
+let currentSelectedStep = null;
+let serverConnected = false;
+
+// DOM元素引用
+const uploadArea = document.getElementById('uploadArea');
+const fileInput = document.getElementById('fileInput');
+const pipelineOverview = document.getElementById('pipelineOverview');
+const pipelineFlow = document.getElementById('pipelineFlow');
+const detailPanel = document.getElementById('detailPanel');
+const stagesDetail = document.getElementById('stagesDetail');
+const flowContainer = document.getElementById('flowContainer');
+const stagesAccordion = document.getElementById('stagesAccordion');
+
+// 阶段配置映射
+const stageConfig = {
+    '1_query_extract': {
+        icon: '🔍',
+        title: '查询提取',
+        description: '从输入内容中提取查询对'
+    },
+    '2_entity_enhance_retrieval': {
+        icon: '🎯',
+        title: '实体增强检索',
+        description: '实体召回 + BFP召回'
+    },
+    '3_parent_doc_enhancement': {
+        icon: '📚',
+        title: '父文档增强',
+        description: '使用父文档增强检索结果'
+    },
+    '4_extract_first_result': {
+        icon: '✂️',
+        title: '结果提取',
+        description: '提取最终检索结果'
+    },
+    '1_rag_retrieval': {
+        icon: '🔄',
+        title: 'RAG检索',
+        description: '完整RAG检索流程'
+    },
+    '2_parameter_compliance_check': {
+        icon: '✅',
+        title: '参数合规检查',
+        description: 'LLM审查参数合规性'
+    }
+};
+
+// 初始化事件监听
+document.addEventListener('DOMContentLoaded', () => {
+    initUploadEvents();
+    initTabEvents();
+    checkServerStatus();
+    // 定时检查服务状态
+    setInterval(checkServerStatus, 10000);
+});
+
+/**
+ * 检查服务器状态
+ */
+function checkServerStatus() {
+    fetch(`${API_BASE}/api/health`)
+        .then(response => response.json())
+        .then(data => {
+            serverConnected = true;
+            updateServerStatus(true, data.milvus_ready);
+        })
+        .catch(() => {
+            serverConnected = false;
+            updateServerStatus(false, false);
+        });
+}
+
+/**
+ * 更新服务器状态显示
+ */
+function updateServerStatus(connected, milvusReady) {
+    const statusEl = document.getElementById('serverStatus');
+    const dot = statusEl.querySelector('.status-dot');
+    const text = statusEl.querySelector('.status-text');
+    
+    dot.className = 'status-dot ' + (connected ? (milvusReady ? 'online' : 'warning') : 'offline');
+    
+    if (connected) {
+        text.textContent = milvusReady ? '服务已连接 (Milvus就绪)' : '服务已连接 (Milvus未就绪)';
+    } else {
+        text.textContent = '服务未连接 - 请启动 rag_pipeline_server.py';
+    }
+    
+    document.getElementById('runRagBtn').disabled = !connected;
+}
+
+/**
+ * 执行RAG检索
+ */
+function runRAG() {
+    const content = document.getElementById('ragInput').value.trim();
+    
+    if (!content) {
+        alert('请输入测试文本');
+        return;
+    }
+    
+    if (!serverConnected) {
+        alert('服务未连接,请先启动 rag_pipeline_server.py');
+        return;
+    }
+    
+    // 显示加载状态
+    document.getElementById('loadingOverlay').style.display = 'flex';
+    document.getElementById('runRagBtn').disabled = true;
+    
+    fetch(`${API_BASE}/api/rag`, {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({ content: content })
+    })
+    .then(response => {
+        if (!response.ok) throw new Error(`请求失败: ${response.status}`);
+        return response.json();
+    })
+    .then(data => {
+        pipelineData = data;
+        renderPipeline(data);
+        document.getElementById('loadingOverlay').style.display = 'none';
+        document.getElementById('runRagBtn').disabled = false;
+    })
+    .catch(err => {
+        alert(`RAG执行失败: ${err.message}`);
+        document.getElementById('loadingOverlay').style.display = 'none';
+        document.getElementById('runRagBtn').disabled = false;
+    });
+}
+
+/**
+ * 初始化上传事件
+ */
+function initUploadEvents() {
+    uploadArea.addEventListener('click', () => fileInput.click());
+    
+    uploadArea.addEventListener('dragover', (e) => {
+        e.preventDefault();
+        uploadArea.classList.add('dragover');
+    });
+    
+    uploadArea.addEventListener('dragleave', () => {
+        uploadArea.classList.remove('dragover');
+    });
+    
+    uploadArea.addEventListener('drop', (e) => {
+        e.preventDefault();
+        uploadArea.classList.remove('dragover');
+        const file = e.dataTransfer.files[0];
+        if (file) handleFile(file);
+    });
+    
+    fileInput.addEventListener('change', (e) => {
+        const file = e.target.files[0];
+        if (file) handleFile(file);
+    });
+}
+
+/**
+ * 初始化标签页事件
+ */
+function initTabEvents() {
+    document.querySelectorAll('.tab-btn').forEach(btn => {
+        btn.addEventListener('click', () => {
+            const tabId = btn.dataset.tab;
+            switchTab(tabId);
+        });
+    });
+}
+
+/**
+ * 切换标签页
+ */
+function switchTab(tabId) {
+    document.querySelectorAll('.tab-btn').forEach(btn => {
+        btn.classList.toggle('active', btn.dataset.tab === tabId);
+    });
+    document.querySelectorAll('.tab-pane').forEach(pane => {
+        pane.classList.toggle('active', pane.id === tabId + 'Pane');
+    });
+}
+
+/**
+ * 处理上传的文件
+ */
+function handleFile(file) {
+    if (!file.name.endsWith('.json')) {
+        alert('请上传JSON文件');
+        return;
+    }
+    
+    const reader = new FileReader();
+    reader.onload = (e) => {
+        try {
+            const data = JSON.parse(e.target.result);
+            pipelineData = data;
+            renderPipeline(data);
+        } catch (err) {
+            alert('JSON解析失败: ' + err.message);
+        }
+    };
+    reader.readAsText(file);
+}
+
+/**
+ * 默认数据文件路径
+ */
+const DEFAULT_DATA_PATH = '../../../temp/entity_bfp_recall/rag_pipeline_data.json';
+
+/**
+ * 加载默认数据文件
+ */
+function loadSampleData() {
+    // 优先从服务器API加载
+    if (serverConnected) {
+        fetch(`${API_BASE}/api/data`)
+            .then(response => {
+                if (!response.ok) throw new Error('数据不存在');
+                return response.json();
+            })
+            .then(data => {
+                pipelineData = data;
+                renderPipeline(data);
+            })
+            .catch(() => {
+                // 服务器没有数据,尝试本地文件
+                loadFromPath(DEFAULT_DATA_PATH);
+            });
+    } else {
+        loadFromPath(DEFAULT_DATA_PATH);
+    }
+}
+
+/**
+ * 从指定路径加载JSON数据
+ */
+function loadFromPath(path) {
+    fetch(path)
+        .then(response => {
+            if (!response.ok) {
+                throw new Error(`文件加载失败: ${response.status} ${response.statusText}`);
+            }
+            return response.json();
+        })
+        .then(data => {
+            pipelineData = data;
+            renderPipeline(data);
+        })
+        .catch(err => {
+            alert(`加载数据失败: ${err.message}\n\n请确保已运行 test_rag_pipeline.py 生成数据文件,或手动上传JSON文件。`);
+            console.error('加载数据失败:', err);
+        });
+}
+
+
+/**
+ * 清空数据
+ */
+function clearData() {
+    pipelineData = null;
+    currentSelectedStep = null;
+    document.getElementById('ragInput').value = '';
+    pipelineOverview.style.display = 'none';
+    pipelineFlow.style.display = 'none';
+    detailPanel.style.display = 'none';
+    stagesDetail.style.display = 'none';
+    flowContainer.innerHTML = '';
+    stagesAccordion.innerHTML = '';
+}
+
+/**
+ * 渲染管道数据
+ */
+function renderPipeline(data) {
+    renderOverview(data);
+    renderFlowChart(data);
+    renderStagesDetail(data);
+    
+    pipelineOverview.style.display = 'block';
+    pipelineFlow.style.display = 'block';
+    stagesDetail.style.display = 'block';
+}
+
+/**
+ * 渲染概览信息
+ */
+function renderOverview(data) {
+    const steps = data.steps || {};
+    const stepCount = Object.keys(steps).length;
+    
+    document.getElementById('stageCount').textContent = stepCount + ' 个';
+    
+    // 显示总执行时间
+    if (data.total_execution_time) {
+        document.getElementById('totalTime').textContent = data.total_execution_time + ' 秒';
+    } else {
+        document.getElementById('totalTime').textContent = formatTimestamp(data.timestamp);
+    }
+    
+    const hasError = data.error || (data.final_result && data.final_result.retrieval_status === 'no_results');
+    document.getElementById('execStatus').textContent = hasError ? '异常' : '成功';
+    document.getElementById('execStatus').style.color = hasError ? '#ff5555' : '#00ff88';
+}
+
+/**
+ * 渲染流程图
+ */
+function renderFlowChart(data) {
+    flowContainer.innerHTML = '';
+    const steps = data.steps || {};
+    
+    Object.entries(steps).forEach(([stepKey, stepData], index) => {
+        const config = stageConfig[stepKey] || {
+            icon: '📦',
+            title: stepData.name || stepKey,
+            description: ''
+        };
+        
+        const node = document.createElement('div');
+        node.className = 'flow-node';
+        node.dataset.step = stepKey;
+        
+        const inputCount = stepData.input ? Object.keys(stepData.input).length : 0;
+        const outputCount = stepData.output ? Object.keys(stepData.output).length : 0;
+        const execTime = stepData.execution_time ? `${stepData.execution_time}s` : '-';
+        
+        node.innerHTML = `
+            <div class="node-header">
+                <span class="node-icon">${config.icon}</span>
+                <span class="node-title">${stepData.name || config.title}</span>
+                <span class="node-step">Step ${index + 1}</span>
+            </div>
+            <div class="node-stats">
+                <div class="node-stat">
+                    <span>输入字段</span>
+                    <span class="node-stat-value">${inputCount}</span>
+                </div>
+                <div class="node-stat">
+                    <span>输出字段</span>
+                    <span class="node-stat-value">${outputCount}</span>
+                </div>
+            </div>
+            <div class="node-time">⏱️ ${execTime}</div>
+        `;
+        
+        node.addEventListener('click', () => selectStep(stepKey, stepData));
+        flowContainer.appendChild(node);
+    });
+}
+
+/**
+ * 选择步骤显示详情
+ */
+function selectStep(stepKey, stepData) {
+    currentSelectedStep = stepKey;
+    
+    // 更新节点选中状态
+    document.querySelectorAll('.flow-node').forEach(node => {
+        node.classList.toggle('active', node.dataset.step === stepKey);
+    });
+    
+    // 显示详情面板
+    detailPanel.style.display = 'block';
+    
+    // 渲染输入输出数据
+    document.getElementById('inputViewer').innerHTML = formatJson(stepData.input || {});
+    document.getElementById('outputViewer').innerHTML = formatJson(stepData.output || {});
+    document.getElementById('rawViewer').innerHTML = formatJson(stepData);
+    
+    // 滚动到详情面板
+    detailPanel.scrollIntoView({ behavior: 'smooth', block: 'start' });
+}
+
+/**
+ * 渲染各阶段详情
+ */
+function renderStagesDetail(data) {
+    stagesAccordion.innerHTML = '';
+    const steps = data.steps || {};
+    
+    Object.entries(steps).forEach(([stepKey, stepData], index) => {
+        const config = stageConfig[stepKey] || {
+            icon: '📦',
+            title: stepKey,
+            description: ''
+        };
+        
+        const item = document.createElement('div');
+        item.className = 'accordion-item';
+        
+        item.innerHTML = `
+            <div class="accordion-header" onclick="toggleAccordion(this)">
+                <div class="accordion-title">
+                    <span>${config.icon}</span>
+                    <span>Step ${index + 1}: ${config.title}</span>
+                </div>
+                <span class="accordion-icon">▼</span>
+            </div>
+            <div class="accordion-content">
+                ${renderStepContent(stepKey, stepData)}
+            </div>
+        `;
+        
+        stagesAccordion.appendChild(item);
+    });
+}
+
+/**
+ * 渲染步骤内容
+ */
+function renderStepContent(stepKey, stepData) {
+    let html = `
+        <div class="data-flow">
+            <div class="data-section input">
+                <h4>输入数据</h4>
+                <pre class="json-viewer">${formatJson(stepData.input || {})}</pre>
+            </div>
+            <div class="data-section output">
+                <h4>输出数据</h4>
+                <pre class="json-viewer">${formatJson(stepData.output || {})}</pre>
+            </div>
+        </div>
+    `;
+    
+    // 如果有子步骤(如实体增强检索的process_details)
+    if (stepData.output && stepData.output.process_details) {
+        html += `<div class="sub-steps"><h4>🔬 子步骤详情</h4>`;
+        stepData.output.process_details.forEach((detail, idx) => {
+            html += `
+                <div class="sub-step">
+                    <div class="sub-step-header">查询对 ${detail.index || idx + 1}</div>
+                    <div class="data-flow">
+                        <div class="data-section input">
+                            <h4>输入</h4>
+                            <pre class="json-viewer">${formatJson(detail.input || {})}</pre>
+                        </div>
+                        <div class="data-section output">
+                            <h4>子步骤</h4>
+                            <pre class="json-viewer">${formatJson(detail.steps || {})}</pre>
+                        </div>
+                    </div>
+                </div>
+            `;
+        });
+        html += `</div>`;
+    }
+    
+    return html;
+}
+
+/**
+ * 切换手风琴
+ */
+function toggleAccordion(header) {
+    const content = header.nextElementSibling;
+    const isActive = header.classList.contains('active');
+    
+    // 关闭所有
+    document.querySelectorAll('.accordion-header').forEach(h => h.classList.remove('active'));
+    document.querySelectorAll('.accordion-content').forEach(c => c.classList.remove('active'));
+    
+    // 如果之前不是激活状态,则打开当前
+    if (!isActive) {
+        header.classList.add('active');
+        content.classList.add('active');
+    }
+}
+
+/**
+ * 格式化JSON为带语法高亮的HTML
+ */
+function formatJson(obj) {
+    const json = JSON.stringify(obj, null, 2);
+    return syntaxHighlight(json);
+}
+
+/**
+ * JSON语法高亮
+ */
+function syntaxHighlight(json) {
+    json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+    return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
+        let cls = 'json-number';
+        if (/^"/.test(match)) {
+            if (/:$/.test(match)) {
+                cls = 'json-key';
+            } else {
+                cls = 'json-string';
+            }
+        } else if (/true|false/.test(match)) {
+            cls = 'json-boolean';
+        } else if (/null/.test(match)) {
+            cls = 'json-null';
+        }
+        return '<span class="' + cls + '">' + match + '</span>';
+    });
+}
+
+/**
+ * 格式化时间戳
+ */
+function formatTimestamp(timestamp) {
+    if (!timestamp) return '-';
+    const date = new Date(timestamp * 1000);
+    return date.toLocaleString('zh-CN');
+}

+ 116 - 0
utils_test/RAG_Test/rag_pipeline_web/index.html

@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>RAG管道测试可视化工具</title>
+    <link rel="stylesheet" href="styles.css">
+</head>
+<body>
+    <div class="container">
+        <header class="header">
+            <h1>🔍 RAG管道测试可视化工具</h1>
+            <p class="subtitle">RAG链路各环节输入输出数据流转调试工具</p>
+        </header>
+
+        <!-- RAG测试输入区域 -->
+        <section class="rag-input-section">
+            <h2>🚀 RAG链路测试</h2>
+            <div class="server-status" id="serverStatus">
+                <span class="status-dot offline"></span>
+                <span class="status-text">服务未连接</span>
+            </div>
+            <div class="input-area">
+                <textarea id="ragInput" placeholder="输入测试文本,点击执行RAG检索...&#10;&#10;示例:主要部件说明&#10;1、主梁总成&#10;主梁总成由主梁和导梁构成。主梁单节长12m,共7节,每节重10.87t..."></textarea>
+            </div>
+            <div class="action-buttons">
+                <button class="btn btn-primary" id="runRagBtn" onclick="runRAG()">
+                    <span class="btn-icon">▶</span> 执行RAG检索
+                </button>
+                <button class="btn btn-secondary" onclick="loadSampleData()">加载历史数据</button>
+                <button class="btn btn-secondary" onclick="clearData()">清空</button>
+            </div>
+            <div class="loading-overlay" id="loadingOverlay" style="display: none;">
+                <div class="loading-spinner"></div>
+                <p>正在执行RAG检索...</p>
+            </div>
+        </section>
+
+        <!-- 文件上传区域 -->
+        <section class="upload-section">
+            <div class="upload-area" id="uploadArea">
+                <div class="upload-icon">📁</div>
+                <p>或拖拽JSON文件到此处查看历史数据</p>
+                <input type="file" id="fileInput" accept=".json" hidden>
+            </div>
+        </section>
+
+        <!-- 管道概览 -->
+        <section class="pipeline-overview" id="pipelineOverview" style="display: none;">
+            <h2>📊 管道概览</h2>
+            <div class="overview-cards">
+                <div class="overview-card">
+                    <div class="card-icon">⏱️</div>
+                    <div class="card-content">
+                        <span class="card-label">执行时间</span>
+                        <span class="card-value" id="totalTime">-</span>
+                    </div>
+                </div>
+                <div class="overview-card">
+                    <div class="card-icon">📝</div>
+                    <div class="card-content">
+                        <span class="card-label">处理阶段</span>
+                        <span class="card-value" id="stageCount">-</span>
+                    </div>
+                </div>
+                <div class="overview-card">
+                    <div class="card-icon">✅</div>
+                    <div class="card-content">
+                        <span class="card-label">执行状态</span>
+                        <span class="card-value" id="execStatus">-</span>
+                    </div>
+                </div>
+            </div>
+        </section>
+
+        <!-- 管道流程图 -->
+        <section class="pipeline-flow" id="pipelineFlow" style="display: none;">
+            <h2>🔄 管道流程</h2>
+            <div class="flow-container" id="flowContainer">
+                <!-- 动态生成流程节点 -->
+            </div>
+        </section>
+
+        <!-- 详细数据面板 -->
+        <section class="detail-panel" id="detailPanel" style="display: none;">
+            <h2>📋 详细数据</h2>
+            <div class="tabs">
+                <button class="tab-btn active" data-tab="input">输入数据</button>
+                <button class="tab-btn" data-tab="output">输出数据</button>
+                <button class="tab-btn" data-tab="raw">原始JSON</button>
+            </div>
+            <div class="tab-content">
+                <div class="tab-pane active" id="inputPane">
+                    <pre class="json-viewer" id="inputViewer"></pre>
+                </div>
+                <div class="tab-pane" id="outputPane">
+                    <pre class="json-viewer" id="outputViewer"></pre>
+                </div>
+                <div class="tab-pane" id="rawPane">
+                    <pre class="json-viewer" id="rawViewer"></pre>
+                </div>
+            </div>
+        </section>
+
+        <!-- 各阶段详情 -->
+        <section class="stages-detail" id="stagesDetail" style="display: none;">
+            <h2>🔬 各阶段详情</h2>
+            <div class="accordion" id="stagesAccordion">
+                <!-- 动态生成各阶段详情 -->
+            </div>
+        </section>
+    </div>
+
+    <script src="app.js"></script>
+</body>
+</html>

+ 323 - 0
utils_test/RAG_Test/rag_pipeline_web/rag_pipeline_server.py

@@ -0,0 +1,323 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+RAG管道测试服务器
+提供HTTP API供前端调用RAG链路
+"""
+
+import sys
+import os
+import json
+import time
+import asyncio
+from http.server import HTTPServer, SimpleHTTPRequestHandler
+from urllib.parse import parse_qs, urlparse
+import threading
+
+# 添加项目根目录到路径
+project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
+sys.path.insert(0, project_root)
+
+from core.construction_review.component.infrastructure.milvus import MilvusConfig, MilvusManager
+from core.construction_review.component.infrastructure.parent_tool import (
+    enhance_with_parent_docs,
+    extract_first_result
+)
+from foundation.ai.rag.retrieval.entities_enhance import entity_enhance
+from foundation.ai.rag.retrieval.query_rewrite import query_rewrite_manager
+from foundation.ai.rag.retrieval.retrieval import retrieval_manager
+from foundation.observability.logger.loggering import server_logger as logger
+
+# 全局Milvus Manager
+milvus_manager = None
+
+
+def init_milvus():
+    """初始化Milvus Manager"""
+    global milvus_manager
+    if milvus_manager is None:
+        print("📌 初始化Milvus Manager...")
+        milvus_manager = MilvusManager(MilvusConfig())
+        print("✅ Milvus Manager 初始化成功")
+    return milvus_manager
+
+
+def run_async(coro):
+    """在合适的环境中运行异步函数"""
+    try:
+        loop = asyncio.get_running_loop()
+        import concurrent.futures
+        with concurrent.futures.ThreadPoolExecutor() as executor:
+            future = executor.submit(asyncio.run, coro)
+            return future.result()
+    except RuntimeError:
+        return asyncio.run(coro)
+
+
+def rag_enhanced_check(query_content: str) -> dict:
+    """
+    RAG增强检查 - 完整链路
+    复用 ai_review_engine.py rag_enhanced_check 方法的逻辑
+    """
+    global milvus_manager
+    if milvus_manager is None:
+        init_milvus()
+
+    pipeline_data = {
+        "stage": "rag_enhanced_check",
+        "timestamp": time.time(),
+        "input_content": query_content,
+        "steps": {}
+    }
+
+    logger.info(f"[RAG增强] 开始处理, 内容长度: {len(query_content)}")
+
+    # Step 1: 查询提取
+    step1_start = time.time()
+    query_pairs = query_rewrite_manager.query_extract(query_content)
+    step1_time = time.time() - step1_start
+
+    pipeline_data["steps"]["1_query_extract"] = {
+        "name": "查询提取",
+        "execution_time": round(step1_time, 3),
+        "input": {
+            "content_length": len(query_content),
+            "content_full": query_content,
+            "content_preview": query_content[:200]
+        },
+        "output": {
+            "query_pairs_count": len(query_pairs),
+            "query_pairs": query_pairs
+        }
+    }
+    logger.info(f"[RAG增强] 提取到 {len(query_pairs)} 个查询对")
+
+    # Step 2: 实体增强检索 - 直接复用 entity_enhance.entities_enhance_retrieval()
+    step2_start = time.time()
+    bfp_result_lists = entity_enhance.entities_enhance_retrieval(query_pairs)
+    step2_time = time.time() - step2_start
+
+    pipeline_data["steps"]["2_entity_enhance_retrieval"] = {
+        "name": "实体增强检索",
+        "execution_time": round(step2_time, 3),
+        "input": {
+            "query_pairs_count": len(query_pairs),
+            "query_pairs": query_pairs
+        },
+        "output": {
+            "results_count": len(bfp_result_lists) if bfp_result_lists else 0,
+            "results": bfp_result_lists if bfp_result_lists else []
+        }
+    }
+
+    # 检查检索结果
+    if not bfp_result_lists:
+        logger.warning("[RAG增强] 实体检索未返回结果")
+        # 返回空结果
+        pipeline_data["final_result"] = {
+            'vector_search': [],
+            'retrieval_status': 'no_results',
+            'file_name': '',
+            'text_content': '',
+            'metadata': {}
+        }
+        pipeline_data["total_execution_time"] = round(time.time() - pipeline_data["timestamp"], 3)
+
+        # 保存到文件
+        os.makedirs(os.path.join(project_root, "temp", "entity_bfp_recall"), exist_ok=True)
+        with open(os.path.join(project_root, "temp", "entity_bfp_recall", "rag_pipeline_data.json"), "w", encoding='utf-8') as f:
+            json.dump(pipeline_data, f, ensure_ascii=False, indent=2, default=str)
+
+        return pipeline_data
+
+    # Step 3: 父文档增强 (使用独立工具函数 - 显式返回)
+    step3_start = time.time()
+    try:
+        enhancement_result = enhance_with_parent_docs(milvus_manager, bfp_result_lists)
+        enhanced_results = enhancement_result['enhanced_results']
+        enhanced_count = enhancement_result['enhanced_count']
+        parent_docs = enhancement_result['parent_docs']
+
+        # 保存增强后的结果
+        with open(os.path.join(project_root, "temp", "entity_bfp_recall", "enhance_with_parent_docs.json"), "w", encoding='utf-8') as f:
+            json.dump(enhanced_results, f, ensure_ascii=False, indent=4)
+
+        logger.info(f"[RAG增强] 成功增强 {enhanced_count} 个结果")
+        logger.info(f"[RAG增强] 使用了 {len(parent_docs)} 个父文档")
+
+        pipeline_data["steps"]["3_parent_doc_enhancement"] = {
+            "name": "父文档增强",
+            "execution_time": round(time.time() - step3_start, 3),
+            "input": {"bfp_results_count": len(bfp_result_lists)},
+            "output": {
+                "enhanced_count": enhanced_count,
+                "parent_docs_count": len(parent_docs),
+                "parent_docs": parent_docs,
+                "enhanced_results": enhanced_results
+            }
+        }
+    except Exception as e:
+        logger.error(f"[RAG增强] 父文档增强失败: {e}")
+        enhanced_results = bfp_result_lists
+        pipeline_data["steps"]["3_parent_doc_enhancement"] = {
+            "name": "父文档增强",
+            "execution_time": round(time.time() - step3_start, 3),
+            "input": {"bfp_results_count": len(bfp_result_lists)},
+            "output": {"error": str(e), "enhanced_results": enhanced_results}
+        }
+
+    # Step 4: 提取结果
+    step4_start = time.time()
+    final_result = extract_first_result(enhanced_results) if enhanced_results else {
+        'vector_search': [], 'retrieval_status': 'no_results',
+        'file_name': '', 'text_content': '', 'metadata': {}
+    }
+
+    pipeline_data["steps"]["4_extract_first_result"] = {
+        "name": "结果提取",
+        "execution_time": round(time.time() - step4_start, 3),
+        "input": {"enhanced_results_count": len(enhanced_results) if enhanced_results else 0},
+        "output": {"final_result": final_result}
+    }
+
+    pipeline_data["final_result"] = final_result
+    pipeline_data["total_execution_time"] = round(time.time() - pipeline_data["timestamp"], 3)
+
+    # 保存到文件
+    os.makedirs(os.path.join(project_root, "temp", "entity_bfp_recall"), exist_ok=True)
+    with open(os.path.join(project_root, "temp", "entity_bfp_recall", "rag_pipeline_data.json"), "w", encoding='utf-8') as f:
+        json.dump(pipeline_data, f, ensure_ascii=False, indent=2, default=str)
+
+    return pipeline_data
+
+
+class RAGPipelineHandler(SimpleHTTPRequestHandler):
+    """HTTP请求处理器"""
+
+    def do_GET(self):
+        """处理GET请求"""
+        parsed = urlparse(self.path)
+
+        if parsed.path == '/api/health':
+            self.send_json_response({'status': 'ok', 'milvus_ready': milvus_manager is not None})
+        elif parsed.path == '/api/data':
+            # 返回最新的pipeline数据
+            data_path = os.path.join(project_root, "temp", "entity_bfp_recall", "rag_pipeline_data.json")
+            if os.path.exists(data_path):
+                with open(data_path, 'r', encoding='utf-8') as f:
+                    data = json.load(f)
+                self.send_json_response(data)
+            else:
+                self.send_json_response({'error': '数据文件不存在'}, 404)
+        else:
+            # 静态文件服务
+            super().do_GET()
+
+    def do_POST(self):
+        """处理POST请求"""
+        parsed = urlparse(self.path)
+
+        if parsed.path == '/api/rag':
+            content_length = int(self.headers['Content-Length'])
+            post_data = self.rfile.read(content_length)
+
+            try:
+                body = json.loads(post_data.decode('utf-8'))
+                query_content = body.get('content', '')
+
+                if not query_content:
+                    self.send_json_response({'error': '请提供content参数'}, 400)
+                    return
+
+                print(f"\n📝 收到RAG请求, 内容长度: {len(query_content)}")
+                result = rag_enhanced_check(query_content)
+                print(f"✅ RAG处理完成, 耗时: {result.get('total_execution_time', 0)}秒")
+
+                self.send_json_response(result)
+
+            except json.JSONDecodeError:
+                self.send_json_response({'error': 'JSON解析失败'}, 400)
+            except Exception as e:
+                logger.error(f"RAG处理失败: {e}", exc_info=True)
+                self.send_json_response({'error': str(e)}, 500)
+
+        elif parsed.path == '/api/init':
+            try:
+                init_milvus()
+                self.send_json_response({'status': 'ok', 'message': 'Milvus初始化成功'})
+            except Exception as e:
+                self.send_json_response({'error': str(e)}, 500)
+        else:
+            self.send_json_response({'error': 'Not Found'}, 404)
+
+    def do_OPTIONS(self):
+        """处理CORS预检请求"""
+        self.send_response(200)
+        self.send_header('Access-Control-Allow-Origin', '*')
+        self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
+        self.send_header('Access-Control-Allow-Headers', 'Content-Type')
+        self.end_headers()
+
+    def send_json_response(self, data, status=200):
+        """发送JSON响应"""
+        self.send_response(status)
+        self.send_header('Content-Type', 'application/json; charset=utf-8')
+        self.send_header('Access-Control-Allow-Origin', '*')
+        self.end_headers()
+        self.wfile.write(json.dumps(data, ensure_ascii=False, default=str).encode('utf-8'))
+
+    def end_headers(self):
+        self.send_header('Access-Control-Allow-Origin', '*')
+        super().end_headers()
+
+
+def run_server(port=8765):
+    """启动服务器"""
+    web_dir = os.path.dirname(os.path.abspath(__file__))
+    
+    # 切换到项目根目录,确保内部模块的相对路径正确
+    os.chdir(project_root)
+    # 确保temp目录存在
+    os.makedirs(os.path.join(project_root, "temp", "entity_bfp_recall"), exist_ok=True)
+
+    # 自定义Handler,指定静态文件目录
+    class CustomHandler(RAGPipelineHandler):
+        def translate_path(self, path):
+            # 静态文件从web目录提供
+            if path.startswith('/api'):
+                return path
+            # 去掉开头的/
+            path = path.lstrip('/')
+            return os.path.join(web_dir, path)
+
+    server = HTTPServer(('0.0.0.0', port), CustomHandler)
+    print(f"\n{'='*60}")
+    print(f"🚀 RAG管道测试服务器已启动")
+    print(f"{'='*60}")
+    print(f"📍 访问地址: http://localhost:{port}")
+    print(f"📍 工作目录: {project_root}")
+    print(f"📍 API端点:")
+    print(f"   POST /api/rag     - 执行RAG检索")
+    print(f"   POST /api/init    - 初始化Milvus")
+    print(f"   GET  /api/data    - 获取最新数据")
+    print(f"   GET  /api/health  - 健康检查")
+    print(f"{'='*60}\n")
+
+    # 预初始化Milvus
+    try:
+        init_milvus()
+    except Exception as e:
+        print(f"⚠️  Milvus初始化失败: {e}")
+        print("   可通过 POST /api/init 重新初始化\n")
+
+    server.serve_forever()
+
+
+if __name__ == "__main__":
+    import argparse
+    parser = argparse.ArgumentParser(description='RAG管道测试服务器')
+    parser.add_argument('--port', type=int, default=8765, help='服务端口 (默认: 8765)')
+    args = parser.parse_args()
+
+    run_server(args.port)

+ 673 - 0
utils_test/RAG_Test/rag_pipeline_web/styles.css

@@ -0,0 +1,673 @@
+/* 基础样式 */
+* {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+}
+
+body {
+    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+    background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
+    min-height: 100vh;
+    color: #e0e0e0;
+    line-height: 1.6;
+}
+
+.container {
+    max-width: 1400px;
+    margin: 0 auto;
+    padding: 20px;
+}
+
+/* 头部样式 */
+.header {
+    text-align: center;
+    padding: 30px 0;
+    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+    margin-bottom: 30px;
+}
+
+.header h1 {
+    font-size: 2.2rem;
+    color: #00d4ff;
+    margin-bottom: 10px;
+    text-shadow: 0 0 20px rgba(0, 212, 255, 0.3);
+}
+
+.subtitle {
+    color: #888;
+    font-size: 1rem;
+}
+
+/* 上传区域 */
+.upload-section {
+    margin-bottom: 30px;
+}
+
+.upload-area {
+    border: 2px dashed #4a4a6a;
+    border-radius: 12px;
+    padding: 40px;
+    text-align: center;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    background: rgba(255, 255, 255, 0.02);
+}
+
+.upload-area:hover {
+    border-color: #00d4ff;
+    background: rgba(0, 212, 255, 0.05);
+}
+
+.upload-area.dragover {
+    border-color: #00ff88;
+    background: rgba(0, 255, 136, 0.1);
+}
+
+.upload-icon {
+    font-size: 3rem;
+    margin-bottom: 15px;
+}
+
+.upload-area p {
+    margin: 5px 0;
+}
+
+.upload-area .hint {
+    font-size: 0.85rem;
+    color: #666;
+}
+
+.sample-buttons {
+    display: flex;
+    gap: 10px;
+    justify-content: center;
+    margin-top: 15px;
+}
+
+/* 按钮样式 */
+.btn {
+    padding: 10px 20px;
+    border: none;
+    border-radius: 6px;
+    cursor: pointer;
+    font-size: 0.9rem;
+    transition: all 0.3s ease;
+}
+
+.btn-primary {
+    background: linear-gradient(135deg, #00d4ff, #0099cc);
+    color: white;
+}
+
+.btn-secondary {
+    background: rgba(255, 255, 255, 0.1);
+    color: #e0e0e0;
+    border: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+.btn:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 4px 15px rgba(0, 212, 255, 0.3);
+}
+
+/* 概览卡片 */
+.pipeline-overview {
+    margin-bottom: 30px;
+}
+
+.pipeline-overview h2,
+.pipeline-flow h2,
+.detail-panel h2,
+.stages-detail h2 {
+    color: #00d4ff;
+    margin-bottom: 20px;
+    font-size: 1.3rem;
+}
+
+.overview-cards {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+    gap: 20px;
+}
+
+.overview-card {
+    background: rgba(255, 255, 255, 0.05);
+    border-radius: 12px;
+    padding: 20px;
+    display: flex;
+    align-items: center;
+    gap: 15px;
+    border: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.card-icon {
+    font-size: 2rem;
+}
+
+.card-content {
+    display: flex;
+    flex-direction: column;
+}
+
+.card-label {
+    font-size: 0.85rem;
+    color: #888;
+}
+
+.card-value {
+    font-size: 1.2rem;
+    font-weight: 600;
+    color: #00ff88;
+}
+
+
+/* 流程图样式 */
+.flow-container {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 20px;
+    padding: 20px;
+    background: rgba(0, 0, 0, 0.2);
+    border-radius: 12px;
+    overflow-x: auto;
+}
+
+.flow-node {
+    background: rgba(255, 255, 255, 0.05);
+    border: 2px solid #4a4a6a;
+    border-radius: 12px;
+    padding: 20px;
+    min-width: 200px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    position: relative;
+}
+
+.flow-node:hover {
+    border-color: #00d4ff;
+    transform: translateY(-3px);
+    box-shadow: 0 8px 25px rgba(0, 212, 255, 0.2);
+}
+
+.flow-node.active {
+    border-color: #00ff88;
+    background: rgba(0, 255, 136, 0.1);
+}
+
+.flow-node::after {
+    content: '→';
+    position: absolute;
+    right: -25px;
+    top: 50%;
+    transform: translateY(-50%);
+    font-size: 1.5rem;
+    color: #4a4a6a;
+}
+
+.flow-node:last-child::after {
+    display: none;
+}
+
+.node-header {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    margin-bottom: 10px;
+}
+
+.node-icon {
+    font-size: 1.5rem;
+}
+
+.node-title {
+    font-weight: 600;
+    color: #00d4ff;
+}
+
+.node-step {
+    font-size: 0.75rem;
+    background: rgba(0, 212, 255, 0.2);
+    padding: 2px 8px;
+    border-radius: 10px;
+    color: #00d4ff;
+}
+
+.node-stats {
+    display: flex;
+    flex-direction: column;
+    gap: 5px;
+    font-size: 0.85rem;
+    color: #888;
+}
+
+.node-stat {
+    display: flex;
+    justify-content: space-between;
+}
+
+.node-stat-value {
+    color: #00ff88;
+}
+
+/* 标签页样式 */
+.tabs {
+    display: flex;
+    gap: 5px;
+    margin-bottom: 15px;
+    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+    padding-bottom: 10px;
+}
+
+.tab-btn {
+    padding: 10px 20px;
+    background: transparent;
+    border: none;
+    color: #888;
+    cursor: pointer;
+    border-radius: 6px 6px 0 0;
+    transition: all 0.3s ease;
+}
+
+.tab-btn:hover {
+    color: #e0e0e0;
+    background: rgba(255, 255, 255, 0.05);
+}
+
+.tab-btn.active {
+    color: #00d4ff;
+    background: rgba(0, 212, 255, 0.1);
+    border-bottom: 2px solid #00d4ff;
+}
+
+.tab-content {
+    background: rgba(0, 0, 0, 0.3);
+    border-radius: 8px;
+    padding: 20px;
+}
+
+.tab-pane {
+    display: none;
+}
+
+.tab-pane.active {
+    display: block;
+}
+
+/* JSON查看器样式 */
+.json-viewer {
+    background: rgba(0, 0, 0, 0.4);
+    border-radius: 8px;
+    padding: 15px;
+    overflow-x: auto;
+    font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+    font-size: 0.85rem;
+    line-height: 1.5;
+    max-height: 500px;
+    overflow-y: auto;
+    white-space: pre-wrap;
+    word-break: break-all;
+}
+
+.json-key {
+    color: #ff79c6;
+}
+
+.json-string {
+    color: #50fa7b;
+}
+
+.json-number {
+    color: #bd93f9;
+}
+
+.json-boolean {
+    color: #ffb86c;
+}
+
+.json-null {
+    color: #6272a4;
+}
+
+/* 手风琴样式 */
+.accordion {
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+}
+
+.accordion-item {
+    background: rgba(255, 255, 255, 0.03);
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    border-radius: 8px;
+    overflow: hidden;
+}
+
+.accordion-header {
+    padding: 15px 20px;
+    cursor: pointer;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    transition: background 0.3s ease;
+}
+
+.accordion-header:hover {
+    background: rgba(255, 255, 255, 0.05);
+}
+
+.accordion-header.active {
+    background: rgba(0, 212, 255, 0.1);
+    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.accordion-title {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+}
+
+.accordion-icon {
+    transition: transform 0.3s ease;
+}
+
+.accordion-header.active .accordion-icon {
+    transform: rotate(180deg);
+}
+
+.accordion-content {
+    display: none;
+    padding: 20px;
+}
+
+.accordion-content.active {
+    display: block;
+}
+
+/* 数据流展示 */
+.data-flow {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 20px;
+}
+
+.data-section {
+    background: rgba(0, 0, 0, 0.2);
+    border-radius: 8px;
+    padding: 15px;
+}
+
+.data-section h4 {
+    color: #00d4ff;
+    margin-bottom: 10px;
+    font-size: 0.95rem;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+}
+
+.data-section h4::before {
+    content: '';
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+}
+
+.data-section.input h4::before {
+    background: #ffb86c;
+}
+
+.data-section.output h4::before {
+    background: #50fa7b;
+}
+
+/* 子步骤样式 */
+.sub-steps {
+    margin-top: 15px;
+    padding-top: 15px;
+    border-top: 1px dashed rgba(255, 255, 255, 0.1);
+}
+
+.sub-step {
+    background: rgba(0, 0, 0, 0.2);
+    border-radius: 8px;
+    padding: 15px;
+    margin-bottom: 10px;
+    border-left: 3px solid #00d4ff;
+}
+
+.sub-step-header {
+    font-weight: 600;
+    color: #00d4ff;
+    margin-bottom: 10px;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+    .data-flow {
+        grid-template-columns: 1fr;
+    }
+    
+    .flow-container {
+        flex-direction: column;
+    }
+    
+    .flow-node::after {
+        content: '↓';
+        right: 50%;
+        top: auto;
+        bottom: -25px;
+        transform: translateX(50%);
+    }
+    
+    .overview-cards {
+        grid-template-columns: 1fr;
+    }
+}
+
+/* 滚动条样式 */
+::-webkit-scrollbar {
+    width: 8px;
+    height: 8px;
+}
+
+::-webkit-scrollbar-track {
+    background: rgba(0, 0, 0, 0.2);
+    border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb {
+    background: #4a4a6a;
+    border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+    background: #5a5a7a;
+}
+
+/* 加载动画 */
+.loading {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    padding: 40px;
+}
+
+.loading-spinner {
+    width: 40px;
+    height: 40px;
+    border: 3px solid rgba(0, 212, 255, 0.2);
+    border-top-color: #00d4ff;
+    border-radius: 50%;
+    animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+    to {
+        transform: rotate(360deg);
+    }
+}
+
+/* 空状态 */
+.empty-state {
+    text-align: center;
+    padding: 60px 20px;
+    color: #666;
+}
+
+.empty-state-icon {
+    font-size: 4rem;
+    margin-bottom: 20px;
+}
+
+
+/* RAG输入区域样式 */
+.rag-input-section {
+    background: rgba(255, 255, 255, 0.03);
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    border-radius: 12px;
+    padding: 25px;
+    margin-bottom: 30px;
+    position: relative;
+}
+
+.rag-input-section h2 {
+    color: #00d4ff;
+    margin-bottom: 15px;
+    font-size: 1.3rem;
+}
+
+.server-status {
+    position: absolute;
+    top: 25px;
+    right: 25px;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    font-size: 0.85rem;
+}
+
+.status-dot {
+    width: 10px;
+    height: 10px;
+    border-radius: 50%;
+    animation: pulse 2s infinite;
+}
+
+.status-dot.online {
+    background: #00ff88;
+    box-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
+}
+
+.status-dot.warning {
+    background: #ffb86c;
+    box-shadow: 0 0 10px rgba(255, 184, 108, 0.5);
+}
+
+.status-dot.offline {
+    background: #ff5555;
+    box-shadow: 0 0 10px rgba(255, 85, 85, 0.5);
+    animation: none;
+}
+
+@keyframes pulse {
+    0%, 100% { opacity: 1; }
+    50% { opacity: 0.5; }
+}
+
+.input-area {
+    margin-bottom: 15px;
+}
+
+.input-area textarea {
+    width: 100%;
+    min-height: 150px;
+    background: rgba(0, 0, 0, 0.3);
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    border-radius: 8px;
+    padding: 15px;
+    color: #e0e0e0;
+    font-family: inherit;
+    font-size: 0.95rem;
+    line-height: 1.6;
+    resize: vertical;
+    transition: border-color 0.3s ease;
+}
+
+.input-area textarea:focus {
+    outline: none;
+    border-color: #00d4ff;
+}
+
+.input-area textarea::placeholder {
+    color: #666;
+}
+
+.action-buttons {
+    display: flex;
+    gap: 10px;
+    flex-wrap: wrap;
+}
+
+.btn-icon {
+    margin-right: 5px;
+}
+
+.btn:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+    transform: none !important;
+}
+
+.loading-overlay {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(26, 26, 46, 0.95);
+    border-radius: 12px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    gap: 15px;
+    z-index: 10;
+}
+
+.loading-overlay p {
+    color: #00d4ff;
+    font-size: 1rem;
+}
+
+/* 调整上传区域样式 */
+.upload-section {
+    margin-bottom: 30px;
+}
+
+.upload-area {
+    border: 2px dashed #4a4a6a;
+    border-radius: 12px;
+    padding: 20px;
+    text-align: center;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    background: rgba(255, 255, 255, 0.02);
+}
+
+.upload-area .upload-icon {
+    font-size: 2rem;
+    margin-bottom: 10px;
+}
+
+.upload-area p {
+    font-size: 0.9rem;
+    color: #888;
+}
+
+/* 流程节点执行时间显示 */
+.node-time {
+    font-size: 0.75rem;
+    color: #ffb86c;
+    margin-top: 5px;
+}

+ 648 - 0
utils_test/RAG_Test/test_rag_pipeline.py

@@ -0,0 +1,648 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+RAG链路独立测试工具
+用于快速调通和验证参数合规性检查的RAG检索+LLM审查功能
+
+核心功能:
+1. rag_enhanced_check() - 完整的RAG检索逻辑
+2. check_parameter_compliance() - 参数合规性检查(与原链路完全一致)
+
+使用方法:
+    python test_rag_pipeline.py
+"""
+
+import sys
+import os
+import json
+import time
+import asyncio
+from typing import Dict, Any
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from core.construction_review.component.infrastructure.milvus import MilvusConfig, MilvusManager
+from core.construction_review.component.infrastructure.parent_tool import (
+    enhance_with_parent_docs,
+    extract_first_result
+)
+from core.construction_review.component.reviewers.base_reviewer import BaseReviewer, ReviewResult
+from foundation.ai.rag.retrieval.entities_enhance import entity_enhance
+from foundation.ai.rag.retrieval.query_rewrite import query_rewrite_manager
+from foundation.ai.agent.generate.model_generate import generate_model_client
+from core.construction_review.component.reviewers.utils.prompt_loader import prompt_loader
+from foundation.observability.logger.loggering import server_logger as logger
+
+
+# ============================================================================
+# 简化的BaseReviewer类 - 用于调用LLM审查
+# ============================================================================
+
+class SimpleReviewer(BaseReviewer):
+    """
+    简化的审查器 - 继承BaseReviewer,用于调用LLM审查
+    """
+
+    def __init__(self):
+        """初始化简化的审查器"""
+        super().__init__()
+        self.model_client = generate_model_client
+        self.prompt_loader = prompt_loader
+
+
+# 全局审查器实例
+simple_reviewer = SimpleReviewer()
+
+
+# ============================================================================
+# 核心RAG链路函数
+# ============================================================================
+
+def rag_enhanced_check(milvus_manager, unit_content: dict) -> dict:
+    """
+    RAG增强检查 - 完整链路
+
+    流程:
+    1. 查询提取 (query_rewrite_manager.query_extract)
+    2. 实体增强检索 (entity_enhance.entities_enhance_retrieval)
+    3. 父文档增强 (enhance_with_parent_docs)
+    4. 提取第一个结果 (extract_first_result)
+
+    Args:
+        milvus_manager: MilvusManager实例
+        unit_content: 包含content字段的字典,格式: {"content": "待检索的文本内容"}
+
+    Returns:
+        dict: RAG检索结果,包含:
+            - vector_search: 向量检索结果列表
+            - retrieval_status: 检索状态
+            - file_name: 参考文件名
+            - text_content: 参考文本内容
+            - metadata: 元数据信息
+    """
+    # 创建数据流跟踪字典
+    pipeline_data = {
+        "stage": "rag_enhanced_check",
+        "timestamp": time.time(),
+        "steps": {}
+    }
+
+    query_content = unit_content['content']
+    logger.info(f"[RAG增强] 开始处理, 内容长度: {len(query_content)}")
+
+    # Step 1: 查询提取
+    logger.info("=" * 80)
+    logger.info("Step 1: 查询提取")
+    logger.info("=" * 80)
+
+    logger.info(f"开始查询提取, 输入内容长度: {len(query_content)}")
+    logger.info(f"输入内容预览: {query_content[:200]}...")
+
+    # 执行查询提取
+    query_pairs = query_rewrite_manager.query_extract(query_content)
+
+    logger.info(f"[RAG增强] 提取到 {len(query_pairs)} 个查询对")
+
+    # 打印查询对详情
+    for idx, query_pair in enumerate(query_pairs):
+        logger.info(f"  查询对 {idx+1}: {query_pair}")
+
+    # 保存Step 1的输入输出
+    pipeline_data["steps"]["1_query_extract"] = {
+        "input": {
+            "content_length": len(query_content),
+            "content_full": query_content,
+            "content_preview": query_content[:200]
+        },
+        "output": {
+            "query_pairs_count": len(query_pairs),
+            "query_pairs": [str(qp) for qp in query_pairs],  # 转为字符串列表便于查看
+            "extraction_timestamp": time.time()
+        }
+    }
+
+    # Step 2: 实体增强检索
+    logger.info("=" * 80)
+    logger.info("Step 2: 实体增强检索")
+    logger.info("=" * 80)
+
+    logger.info(f"开始实体增强检索, 输入查询对数量: {len(query_pairs)}")
+
+    # 保存输入
+    entity_enhance_input = {
+        "query_pairs": [str(qp) for qp in query_pairs],
+        "query_pairs_count": len(query_pairs)
+    }
+
+    # 详细记录每个查询对的处理过程
+    entity_enhance_process_details = []
+
+    # 手动展开实体增强检索的每个步骤,便于记录数据流
+    import asyncio
+
+    def run_async(coro):
+        """在合适的环境中运行异步函数"""
+        try:
+            loop = asyncio.get_running_loop()
+            import concurrent.futures
+            with concurrent.futures.ThreadPoolExecutor() as executor:
+                future = executor.submit(asyncio.run, coro)
+                return future.result()
+        except RuntimeError:
+            return asyncio.run(coro)
+
+    # 导入retrieval_manager
+    from foundation.ai.rag.retrieval.retrieval import retrieval_manager
+
+    bfp_result_lists = []
+
+    # 遍历每个查询对进行处理
+    for idx, query_pair in enumerate(query_pairs):
+        logger.info(f"\n{'='*60}")
+        logger.info(f"处理查询对 {idx+1}/{len(query_pairs)}")
+        logger.info(f"{'='*60}")
+
+        # 提取查询对的各个字段
+        entity = query_pair['entity']
+        search_keywords = query_pair['search_keywords']
+        background = query_pair['background']
+
+        logger.info(f"  实体(entity): {entity}")
+        logger.info(f"  搜索关键词(search_keywords): {search_keywords}")
+        logger.info(f"  背景(background): {background}")
+
+        # 记录当前查询对的输入
+        current_query_detail = {
+            "index": idx + 1,
+            "input": {
+                "entity": entity,
+                "search_keywords": search_keywords,
+                "background": background
+            },
+            "steps": {}
+        }
+
+        # Step 2.1: 实体召回 (entity_recall)
+        logger.info(f"  Step 2.1: 实体召回 (recall_top_k=5, max_results=5)")
+
+        entity_list = run_async(retrieval_manager.entity_recall(
+            entity,
+            search_keywords,
+            recall_top_k=5,
+            max_results=5
+        ))
+
+        logger.info(f"  ✅ 实体召回完成, 召回实体数量: {len(entity_list) if entity_list else 0}")
+
+        # 记录实体召回结果
+        current_query_detail["steps"]["2_1_entity_recall"] = {
+            "input": {
+                "entity": entity,
+                "search_keywords": search_keywords,
+                "recall_top_k": 5,
+                "max_results": 5
+            },
+            "output": {
+                "entity_list": entity_list,
+                "entity_count": len(entity_list) if entity_list else 0
+            }
+        }
+
+        # Step 2.2: BFP召回 (async_bfp_recall)
+        logger.info(f"  Step 2.2: BFP召回 (top_k=3)")
+
+        bfp_result = run_async(retrieval_manager.async_bfp_recall(
+            entity_list,
+            background,
+            top_k=3
+        ))
+
+        logger.info(f"  ✅ BFP召回完成, BFP结果数量: {len(bfp_result) if bfp_result else 0}")
+        logger.info(f"  bfp_result: {bfp_result}")
+
+        # 记录BFP召回结果
+        current_query_detail["steps"]["2_2_bfp_recall"] = {
+            "input": {
+                "entity_list": entity_list,
+                "background": background,
+                "top_k": 3
+            },
+            "output": {
+                "bfp_result": bfp_result,
+                "bfp_result_count": len(bfp_result) if bfp_result else 0
+            }
+        }
+
+        bfp_result_lists.append(bfp_result)
+
+        entity_enhance_process_details.append(current_query_detail)
+
+        logger.info(f"✅ 查询对 {idx+1} 处理完成")
+
+    logger.info(f"\n{'='*80}")
+    logger.info(f"实体增强检索全部完成")
+    logger.info(f"总查询对数: {len(query_pairs)}")
+    logger.info(f"总BFP结果数: {len(bfp_result_lists)}")
+    logger.info(f"{'='*80}")
+
+    # 保存Step 2的详细输出
+    pipeline_data["steps"]["2_entity_enhance_retrieval"] = {
+        "input": entity_enhance_input,
+        "output": {
+            "results_count": len(bfp_result_lists),
+            "results": bfp_result_lists,
+            "process_details": entity_enhance_process_details  # 每个查询对的详细处理过程
+        },
+        "timestamp": time.time()
+    }
+
+    # Step 3: 检查检索结果
+    if not bfp_result_lists:
+        logger.warning("[RAG增强] 实体检索未返回结果")
+
+        # 保存最终数据流
+        os.makedirs(r"temp\entity_bfp_recall", exist_ok=True)
+        with open(rf"temp\entity_bfp_recall\rag_pipeline_data.json", "w", encoding='utf-8') as f:
+            json.dump(pipeline_data, f, ensure_ascii=False, indent=4)
+
+        return {
+            'vector_search': [],
+            'retrieval_status': 'no_results',
+            'file_name': '',
+            'text_content': '',
+            'metadata': {}
+        }
+
+    logger.info(f"[RAG增强] 实体检索返回 {len(bfp_result_lists)} 个结果")
+
+    # Step 4: 父文档增强 (使用独立工具函数)
+    logger.info("=" * 80)
+    logger.info("Step 3: 父文档增强")
+    logger.info("=" * 80)
+
+    try:
+        enhancement_result = enhance_with_parent_docs(milvus_manager, bfp_result_lists)
+        enhanced_results = enhancement_result['enhanced_results']
+        enhanced_count = enhancement_result['enhanced_count']
+        parent_docs = enhancement_result['parent_docs']
+
+        # 保存Step 3输出
+        pipeline_data["steps"]["3_parent_doc_enhancement"] = {
+            "input": {
+                "bfp_results_count": len(bfp_result_lists)
+            },
+            "output": {
+                "enhanced_count": enhanced_count,
+                "parent_docs_count": len(parent_docs),
+                "parent_docs": parent_docs,
+                "enhanced_results": enhanced_results
+            }
+        }
+
+        # 保存增强后的结果
+        os.makedirs(r"temp\entity_bfp_recall", exist_ok=True)
+        with open(rf"temp\entity_bfp_recall\enhance_with_parent_docs.json", "w", encoding='utf-8') as f:
+            json.dump(enhanced_results, f, ensure_ascii=False, indent=4)
+
+        logger.info(f"[RAG增强] 成功增强 {enhanced_count} 个结果")
+        logger.info(f"[RAG增强] 使用了 {len(parent_docs)} 个父文档")
+
+        # 打印父文档信息
+        for idx, parent_doc in enumerate(parent_docs):
+            logger.info(f"  父文档 {idx+1}: {parent_doc.get('file_name', 'unknown')}")
+
+    except Exception as e:
+        logger.error(f"[RAG增强] 父文档增强失败: {e}", exc_info=True)
+
+        # 保存错误信息
+        pipeline_data["steps"]["3_parent_doc_enhancement"] = {
+            "input": {
+                "bfp_results_count": len(bfp_result_lists)
+            },
+            "output": {
+                "error": str(e),
+                "error_type": type(e).__name__
+            }
+        }
+
+        # 失败时使用原始结果
+        enhanced_results = bfp_result_lists
+        parent_docs = []
+
+    # Step 5: 提取第一个结果返回 (使用增强后的结果)
+    logger.info("=" * 80)
+    logger.info("Step 4: 提取第一个结果")
+    logger.info("=" * 80)
+
+    final_result = extract_first_result(enhanced_results)
+
+    # 保存Step 4输出
+    pipeline_data["steps"]["4_extract_first_result"] = {
+        "input": {
+            "enhanced_results_count": len(enhanced_results)
+        },
+        "output": {
+            "final_result": final_result
+        }
+    }
+
+    # 保存最终结果用于调试
+    with open(rf"temp\entity_bfp_recall\extract_first_result.json", "w", encoding='utf-8') as f:
+        json.dump(final_result, f, ensure_ascii=False, indent=4)
+
+    # 保存完整数据流
+    pipeline_data["final_result"] = final_result
+    os.makedirs(r"temp\entity_bfp_recall", exist_ok=True)
+    with open(rf"temp\entity_bfp_recall\rag_pipeline_data.json", "w", encoding='utf-8') as f:
+        json.dump(pipeline_data, f, ensure_ascii=False, indent=4)
+
+    logger.info(f"[RAG增强] 最终提取结果文件名: {final_result.get('file_name', '无')}")
+    logger.info(f"[RAG增强] 最终提取结果内容长度: {len(final_result.get('text_content', ''))}")
+    logger.info(f"[RAG增强] 完整数据流已保存到: temp/entity_bfp_recall/rag_pipeline_data.json")
+
+    return final_result
+
+
+# ============================================================================
+# 参数合规性检查函数 (与原链路完全一致)
+# ============================================================================
+
+async def check_parameter_compliance(trace_id_idx: str, review_content: str, review_references: str,
+                                    reference_source: str, review_location_label: str, state: str, stage_name: str) -> Dict[str, Any]:
+    """
+    参数合规性检查 - 实体概念/工程术语知识库
+    (与原链路完全一致的方法签名和实现)
+
+    Args:
+        trace_id_idx: 追踪ID索引
+        review_content: 审查内容
+        review_references: 审查参考信息
+        reference_source: 参考来源
+        review_location_label: 审查位置标签
+        state: 状态字典
+        stage_name: 阶段名称
+
+    Returns:
+        Dict[str, Any]: 参数合规性检查结果
+    """
+    # 从原链路导入Stage枚举
+    from core.construction_review.component.ai_review_engine import Stage
+
+    reviewer_type = Stage.TECHNICAL.value['reviewer_type']
+    prompt_name = Stage.TECHNICAL.value['parameter']
+    trace_id = prompt_name + trace_id_idx
+
+    # 直接调用原链路的review方法
+    return await simple_reviewer.review("parameter_compliance_check", trace_id, reviewer_type, prompt_name, review_content, review_references,
+                                       reference_source, review_location_label, state, stage_name, timeout=45)
+
+
+# ============================================================================
+# 主测试函数
+# ============================================================================
+
+async def main():
+    """
+    主测试函数 - 测试参数合规性检查的完整流程
+
+    流程:
+    1. 初始化Milvus Manager
+    2. 准备测试内容
+    3. 调用RAG获取参考信息
+    4. 调用参数合规性检查(与原链路一致)
+    5. 保存完整数据流
+    """
+    print("\n" + "=" * 80)
+    print("RAG链路独立测试工具 - 参数合规性检查".center(80))
+    print("=" * 80 + "\n")
+
+    # 初始化Milvus Manager
+    print("📌 初始化Milvus Manager...")
+    logger.info("初始化Milvus Manager...")
+    try:
+        milvus_manager = MilvusManager(MilvusConfig())
+        print("✅ Milvus Manager 初始化成功\n")
+    except Exception as e:
+        print(f"❌ Milvus Manager 初始化失败: {e}")
+        logger.error(f"Milvus Manager 初始化失败: {e}", exc_info=True)
+        return
+
+    # 测试内容
+    test_content = """主要部件说明
+1、主梁总成
+主梁总成由主梁和导梁构成。主梁单节长12m,共7节,每节重10.87t,主梁为主要承载受力构件,其上弦杆上方设有轨道供纵移桁车走行,实现预制梁的纵向移动;下弦设有反滚轮行走轨道,作为导梁纵移、前中支腿移动纵行轨道。导梁长18m,主要是为降低过孔挠度和承受中支腿移动荷载,起安全引导、辅助过孔作用。主梁、导梁为三角桁架构件单元,采用销轴连接,前、后端各设置横联构架。
+
+图4-1 主梁总成图
+注意事项:
+(1)更换上、下弦销轴时,应优先向设备供应方购买符合要求的备件。自行更换时,材料性能必须优于设计零件性能,并按规定进行热处理,否则可能造成人员、设备事故。
+(2)销轴不得弯曲受力,不得用销轴作为锤砸工具,不得任意放置及焊接"""
+
+    unit_content = {"content": test_content}
+
+    print(f"📝 测试内容长度: {len(test_content)} 字符")
+    print(f"📝 测试内容预览:\n{test_content[:200]}...\n")
+
+    # 创建数据流跟踪字典
+    pipeline_data = {
+        "stage": "parameter_compliance_check_full_pipeline",
+        "timestamp": time.time(),
+        "steps": {}
+    }
+
+    # Step 1: RAG增强检索
+    print("=" * 80)
+    print("【Step 1】RAG增强检索".center(80))
+    print("=" * 80)
+
+    logger.info("=" * 80)
+    logger.info("Step 1: RAG增强检索")
+    logger.info("=" * 80)
+
+    start_time = time.time()
+
+    rag_result = rag_enhanced_check(milvus_manager, unit_content)
+    review_references = rag_result.get('text_content', '')
+    reference_source = rag_result.get('file_name', '')
+
+    # 保存Step 1数据
+    pipeline_data["steps"]["1_rag_retrieval"] = {
+        "input": {
+            "unit_content": unit_content
+        },
+        "output": {
+            "rag_result": rag_result,
+            "review_references_length": len(review_references),
+            "reference_source": reference_source
+        },
+        "execution_time": time.time() - start_time
+    }
+
+    if not review_references:
+        logger.warning("RAG检索未返回参考信息,将继续使用空参考进行审查")
+        print("⚠️  RAG检索未返回参考信息\n")
+    else:
+        print(f"✅ RAG检索成功")
+        print(f"   参考来源: {reference_source}")
+        print(f"   参考内容长度: {len(review_references)} 字符\n")
+
+    # Step 2: 调用参数合规性检查 (使用原链路的方法)
+    print("=" * 80)
+    print("【Step 2】参数合规性检查 (LLM审查)".center(80))
+    print("=" * 80)
+
+    logger.info("=" * 80)
+    logger.info("Step 2: 参数合规性检查")
+    logger.info("=" * 80)
+
+    trace_id_idx = "_test_001"
+    review_location_label = "测试文档-第1章"
+    state = None
+    stage_name = "test_stage"
+
+    logger.info(f"开始调用参数合规性检查")
+    logger.info(f"  - trace_id_idx: {trace_id_idx}")
+    logger.info(f"  - review_content长度: {len(test_content)}")
+    logger.info(f"  - review_references长度: {len(review_references)}")
+    logger.info(f"  - reference_source: {reference_source}")
+
+    # 保存Step 2输入
+    pipeline_data["steps"]["2_parameter_compliance_check"] = {
+        "input": {
+            "trace_id_idx": trace_id_idx,
+            "review_content_length": len(test_content),
+            "review_content_preview": test_content[:200],
+            "review_references_length": len(review_references),
+            "review_references_preview": review_references[:200] if review_references else "",
+            "reference_source": reference_source,
+            "review_location_label": review_location_label,
+            "stage_name": stage_name
+        },
+        "output": {}
+    }
+
+    start_time = time.time()
+
+    try:
+        # 调用与原链路完全一致的方法
+        result = await check_parameter_compliance(
+            trace_id_idx=trace_id_idx,
+            review_content=test_content,
+            review_references=review_references,
+            reference_source=reference_source,
+            review_location_label=review_location_label,
+            state=state,
+            stage_name=stage_name
+        )
+
+        elapsed_time = time.time() - start_time
+
+        # 保存Step 2输出
+        pipeline_data["steps"]["2_parameter_compliance_check"]["output"] = {
+            "success": result.success,
+            "execution_time": result.execution_time,
+            "error_message": result.error_message,
+            "details": result.details
+        }
+
+        # 保存完整数据流
+        pipeline_data["final_result"] = {
+            "success": result.success,
+            "execution_time": result.execution_time,
+            "error_message": result.error_message,
+            "details": result.details
+        }
+
+        os.makedirs(r"temp\entity_bfp_recall", exist_ok=True)
+        with open(rf"temp\entity_bfp_recall\parameter_compliance_full_pipeline.json", "w", encoding='utf-8') as f:
+            json.dump(pipeline_data, f, ensure_ascii=False, indent=4)
+
+        logger.info(f"✅ 参数合规性检查完成, 总耗时: {elapsed_time:.2f}秒")
+        logger.info(f"📁 完整数据流已保存到: temp/entity_bfp_recall/parameter_compliance_full_pipeline.json")
+
+    except Exception as e:
+        error_msg = f"参数合规性检查失败: {str(e)}"
+        logger.error(error_msg, exc_info=True)
+
+        # 保存错误信息
+        pipeline_data["steps"]["2_parameter_compliance_check"]["output"] = {
+            "error": error_msg,
+            "error_type": type(e).__name__,
+            "traceback": str(e)
+        }
+        pipeline_data["error"] = {
+            "error_message": error_msg,
+            "error_type": type(e).__name__
+        }
+
+        os.makedirs(r"temp\entity_bfp_recall", exist_ok=True)
+        with open(rf"temp\entity_bfp_recall\parameter_compliance_full_pipeline.json", "w", encoding='utf-8') as f:
+            json.dump(pipeline_data, f, ensure_ascii=False, indent=4)
+
+        print(f"❌ 参数合规性检查失败: {error_msg}\n")
+        return
+
+    # 输出测试结果
+    print("\n" + "=" * 80)
+    print("测试结果".center(80))
+    print("=" * 80)
+
+    status_icon = "✅" if result.success else "❌"
+    print(f"\n{status_icon} 参数合规性检查")
+    print(f"   执行时间: {result.execution_time:.2f}秒")
+
+    if result.success:
+        print(f"   审查成功!")
+        print(f"   详细信息: {result.details.get('name', 'N/A')}")
+
+        # 如果有RAG参考信息,打印出来
+        if 'rag_reference_source' in result.details:
+            print(f"\n   📚 RAG参考信息:")
+            print(f"      参考来源: {result.details['rag_reference_source']}")
+            print(f"      参考内容长度: {len(result.details.get('rag_review_references', ''))} 字符")
+
+        # 打印审查响应(截取前500字符)
+        response = result.details.get('response', '')
+        if response:
+            print(f"\n   📋 审查响应 (前500字符):")
+            print(f"      {response[:500]}...")
+    else:
+        print(f"   错误信息: {result.error_message}")
+
+    # 输出文件位置
+    print("\n" + "=" * 80)
+    print("详细结果已保存到:".center(80))
+    print("  📁 temp/entity_bfp_recall/rag_pipeline_data.json - RAG检索完整数据流")
+    print("  📁 temp/entity_bfp_recall/enhance_with_parent_docs.json - 父文档增强结果")
+    print("  📁 temp/entity_bfp_recall/extract_first_result.json - 最终提取结果")
+    print("  📁 temp/entity_bfp_recall/parameter_compliance_full_pipeline.json - 参数检查完整数据流")
+    print("=" * 80 + "\n")
+
+    print("✅ 测试完成!")
+
+    # 保存测试结果摘要
+    os.makedirs(r"temp\entity_bfp_recall", exist_ok=True)
+    test_summary = {
+        "test_type": "parameter_compliance",
+        "check_display_name": "参数合规性检查",
+        "timestamp": time.time(),
+        "result": {
+            'success': result.success,
+            'execution_time': result.execution_time,
+            'error_message': result.error_message,
+            'details_summary': {
+                'name': result.details.get('name'),
+                'has_rag_reference': 'rag_reference_source' in result.details,
+                'response_length': len(result.details.get('response', '')),
+                'response_preview': result.details.get('response', '')[:200]
+            }
+        }
+    }
+    with open(rf"temp\entity_bfp_recall\test_summary.json", "w", encoding='utf-8') as f:
+        json.dump(test_summary, f, ensure_ascii=False, indent=4)
+
+    return result
+
+
+if __name__ == "__main__":
+    # 运行异步主函数
+    asyncio.run(main())