# 法规对话模块优化设计文档 **日期**: 2026-05-20 **状态**: 已批准,实施中 --- ## 背景 当前法规对话模块存在以下问题: 1. `compliance.py` `/compliance/chat/{segment_id}` 返回硬编码 Mock 数据 2. `rag.py` `/rag/chat` 返回硬编码 Mock 数据(前端实际调用 `/agent/chat/stream`,此路由可统一) 3. 前端快速问题列表硬编码在 `rag.ts`,未调用后端 4. 仅有 Dense 向量检索(COSINE),无 BM25 混合检索与精排 5. LLM 输出的 `[1][2]` 引用编号未在前端内联高亮 6. 会话存储仅为内存(100条上限,30分钟过期) --- ## 方案:分层优先(4个阶段) ### Phase 1 — 接入真实服务(Week 1) **目标**:消灭所有 Mock,让系统真正可用。 #### 后端变更 **`backend/app/schemas/compliance.py`** - `ComplianceChatRequest` 新增 `segment_context: str | None = None` **`backend/app/api/routes/compliance.py`** - 移除 `get_mock_compliance_chat_response` 导入 - `/compliance/chat/{segment_id}` 接入 `get_agent_conversation_service().stream_chat()` - 将 `segment_context` 拼接到 query 前缀作为上下文 - 将 agent `content` 事件翻译为 `{"type":"chunk","text":"..."}` 格式,保持前端兼容 **`backend/app/api/routes/rag.py`** - 移除 `get_mock_retrieval`、`get_mock_rag_answer` 导入 - `/rag/chat` 接入 `get_agent_conversation_service().stream_chat()` - 翻译 agent 事件为 rag 格式(`retrieved`/`chunk`/`done`) **`backend/app/schemas/rag.py`** - `RagChatRequest` 新增 `session_id: str | None = None`,`filters: str | None = None` #### 前端变更 **`frontend/src/api/rag.ts`** - `getQuickQuestions()` 改为真实调用 `GET /api/v1/rag/quick-questions`,失败时降级为本地数组 **`frontend/src/api/compliance.ts`** - `complianceChat()` 新增第三个参数 `segmentContext: string | undefined`,传入 request body **`frontend/src/pages/Compliance/CompliancePage.tsx`** - `sendChatMessage()` 中构建 `segmentContext`(intent + content 摘要 + 法规名) - 传给 `complianceChat()` --- ### Phase 2 — 混合检索 + Reranking(Week 2-3) **目标**:提升召回质量(BM25 + dense RRF融合)+ 精排。 #### 2a — Cross-Encoder Reranking(优先,无需 schema 变更) - 新增端口 `backend/app/domain/retrieval/ports.py`: `Reranker` ABC - 新增适配器 `backend/app/infrastructure/vectorstore/cross_encoder_reranker.py` - 调用 OpenAI-compatible reranker API(BAAI/bge-reranker-v2-m3) - 修改 `KnowledgeRetrievalService`:先 retrieve top-20,再 rerank 到 top-5 - 新增 settings: `reranker_enabled: bool = False`,`reranker_model: str`,`reranker_top_k: int = 5` #### 2b — Milvus Sparse BM25(需 schema 迁移) - Milvus collection 新增 `sparse_embedding SPARSE_FLOAT_VECTOR` 字段 - 新增端口 `SparseEmbeddingProvider`(sparse embed 接口) - 适配器优先使用 BGE-M3 API(同时输出 dense + sparse),不可用时降级为 TF-IDF keyword weights - `MilvusVectorIndex.upsert()` 同时写入 sparse 向量 - `MilvusVectorIndex.search()` 改为 hybrid search(`WeightedRanker` 或 `RRFRanker`) - 提供一次性迁移脚本:dump 所有 chunks → recreate collection → re-embed → re-insert --- ### Phase 3 — 引用溯源 + 筛选 UI(Week 4) **目标**:答案文本中 `[1][2]` 可点击跳转原文片段;法规类型/版本可筛选。 #### 引用内联解析 - 新增 React 组件 `CitedAnswer`:接受 `text` + `sources[]` - 用正则 `/\[(\d+)\]/g` 拆分文本,将 `[N]` 渲染为可点击 `` 元素 - 点击高亮/滚动到来源面板对应条目 - `RagChatPage.tsx` 将 assistant 消息改用 `CitedAnswer` 组件渲染 - 来源面板各条目加 `id="source-N"` 属性 #### 法规筛选 UI - `RagChatPage.tsx` 在输入框上方加筛选栏:`regulation_type` 下拉 + `version` 输入 - 筛选值作为 `filters` 参数传到 `/agent/chat/stream` - `MilvusVectorIndex.search()` 解析 `filters` 字符串为 Milvus expr(如 `regulation_type == "GB"`) #### 快速问题动态化 - 后端 `/rag/quick-questions` 改为从 settings 或配置文件加载,不硬编码 - 前端已在 Phase 1 中改为调用后端 --- ### Phase 4 — 会话持久化 + 上下文压缩(Week 5) **目标**:长对话不丢失;上下文过长时智能压缩。 #### PostgreSQL 会话存储 - 新增 `backend/app/infrastructure/session/postgres_conversation_store.py` - 实现 `ConversationStore` port - 复用现有 PostgreSQL 连接 - DDL: `conversation_sessions` + `conversation_messages` 两张表 - 新增 settings: `conversation_store_backend: str = "memory"` - `bootstrap.py` 按 settings 切换实现 #### 上下文压缩 - 在 `AgentConversationService` 中,当对话轮数 > N 时,将早期消息用 LLM 摘要 - 摘要替换为一条 `system` 消息 `"对话摘要:..."` - 新增 `AnswerGenerator.summarize(messages) -> str` 方法 --- ## 关键设计决策 | 决策 | 选择 | 原因 | |------|------|------| | BM25 实现 | Milvus sparse vectors | 不引入新服务,Milvus 2.4+ 原生支持 | | Reranking | API-based cross-encoder | 复用现有 OpenAI-compatible 接口 | | 会话持久化 | PostgreSQL | 复用现有 PostgreSQL 基础设施 | | 引用解析 | 前端纯客户端 | LLM 已输出 [N] 编号,无需后端改动 | | segment_context | 前端构建传后端 | 合规结果存在前端状态,无需后端持久化 | --- ## 验证标准 - Phase 1: 合规对话面板返回真实法规检索结果,不再固定响应 - Phase 2: 相同 query 在 hybrid 模式下比 dense-only 召回更相关结果 - Phase 3: 点击答案中的 `[1]` 能跳转到来源面板第一条 - Phase 4: 刷新页面后历史对话仍可恢复