146 lines
5.7 KiB
Markdown
146 lines
5.7 KiB
Markdown
|
|
# 法规对话模块优化设计文档
|
|||
|
|
|
|||
|
|
**日期**: 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]` 渲染为可点击 `<cite>` 元素
|
|||
|
|
- 点击高亮/滚动到来源面板对应条目
|
|||
|
|
- `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: 刷新页面后历史对话仍可恢复
|