# rag-knowledge-base **Repository Path**: weh_coder/rag-knowledge-base ## Basic Information - **Project Name**: rag-knowledge-base - **Description**: Python版本:知识库问答平台是一个基于 **检索增强生成(Retrieval-Augmented Generation)** 技术的企业级智能问答平台。系统支持多租户、多部门的文档管理,通过混合检索(Dense + BM25)和重排序(Rerank)技术,结合大语言模型生成准确、有依据的回答。 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-04 - **Last Updated**: 2026-08-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: Python, RAG, 知识库问答 ## README # 企业知识库 RAG 问答系统 · 开发文档 > 版本:2.0.0 > 技术栈:FastAPI + Milvus + Redis(后端) / Vue 3 + Pinia + Element Plus + Vite(前端) > 适用对象:参与本项目的开发人员、运维人员 --- ## 1. 项目概述 本项目是一个**企业级知识库问答系统**,核心能力是基于 RAG(检索增强生成)的私有知识库问答,同时附带一个「AI 直接对话」模式用于开放式聊天。 系统特点: - **双模式问答**:`knowledge`(知识库 RAG 问答)与 `ai`(AI 直接对话)两套会话完全隔离。 - **多租户 + 部门权限**:基于租户(tenant)与部门(department)的数据隔离,员工只能看到本部门 / 全公司公开的文档。 - **版本化管理文档**:上传的 Markdown 文档按版本入库,新版本发布时旧版本自动失效,支持回滚。 - **可观测检索链路**:前端展示召回 Chunk 数、重排精选数、权限过滤条件、每个候选 Chunk 的检索分 / 重排分。 - **流式输出**:RAG 问答与 AI 对话均支持 SSE 流式响应,前端打字机效果。 - **演示身份体系**:通过 `Bearer Token` 模拟多租户、多部门的用户身份,无需真实登录系统。 --- ## 2. 技术架构 ### 2.1 总体架构 ``` ┌──────────────────────────────────────────────────────────┐ │ 浏览器 / 前端 │ │ Vue 3 + Pinia + Element Plus + Vite │ │ - ChatView(聊天主界面) │ │ - Sidebar(会话列表 / 身份切换 / 文档管理 / 数据管理) │ │ - stores: chat / conversation / user / knowledge │ └───────────────┬──────────────────────────┬───────────────┘ │ Axios (REST) │ fetch (SSE) │ /api/* │ /api/*/stream ▼ ▼ ┌──────────────────────────────────────────────────────────┐ │ FastAPI 后端 │ │ routers: ai / knowledge / session / history / │ │ documents / departments │ │ service: AiService / KnowledgeService / │ │ DocumentService / AuthService │ │ dao: AiDao / KnowledgeDao / DocumentDao / AuthDao │ └───┬───────────────────┬───────────────────┬──────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────┐ ┌─────────────┐ ┌─────────────┐ │ Milvus │ │ Redis │ │ LLM API │ │ 向量库 │ │ 会话历史存储 │ │ (兼容OpenAI)│ │ (Zilliz) │ │ 部门缓存 │ │ Embed/Rerank│ └─────────┘ └─────────────┘ └─────────────┘ ``` ### 2.2 分层职责 | 层 | 目录 | 职责 | |----|------|------| | API 层 | `app/api/` | 接收 HTTP 请求、参数校验、依赖注入 service、返回响应 | | Service 层 | `app/service/` | 业务编排:调用 dao、组装历史记录、保存会话 | | DAO 层 | `app/dao/` | 数据访问:调用 Milvus / Redis / LLM,执行具体查询 | | Store 层 | `app/store/` | 外部存储封装:Milvus、Redis、用户/部门缓存 | | Model 层 | `app/models/` | 数据结构定义(Pydantic / dataclass) | | Utils 层 | `app/utils/` | 分块、过滤、Embedding 模型等工具 | | Config 层 | `app/config/` | 配置加载(.env)、Milvus 配置 | --- ## 3. 后端开发指南 ### 3.1 目录结构 ``` app/ ├── main.py # 应用入口、CORS、路由挂载、静态文件、异常处理 ├── api/ # 路由层 │ ├── ai_api.py # AI 对话接口 │ ├── knowledge_api.py # 知识库问答接口 │ ├── auth_api.py # 会话/用户身份接口 │ ├── history_api.py # 会话历史接口 │ ├── document_api.py # 文档管理接口 │ └── department_api.py # 部门接口 ├── service/ # 业务层 │ ├── ai_service.py │ ├── knowledge_service.py │ ├── document_service.py │ └── auth_service.py ├── dao/ # 数据访问层 │ ├── ai_dao.py # LLM / Embedding / Rerank 封装 │ ├── knowledge_dao.py # RAG 主流程 │ ├── document_dao.py # 文档入库/版本管理 │ └── auth_dao.py # Token 校验 ├── store/ # 存储封装 │ ├── milvus_store.py # 向量库 CRUD / 混合检索 │ ├── redis_store.py # 会话历史 / 部门缓存 │ ├── user_store.py # 演示用户数据 │ └── department_store.py # 部门缓存 ├── models/ # 数据模型 │ ├── user.py │ ├── document.py │ ├── history.py │ └── grounded_answer.py ├── utils/ │ ├── markdown_chunker.py # Markdown 分块 │ ├── filtering.py # 权限过滤表达式构造 │ ├── embedding_model.py # 本地 Embedding(预留) │ └── camel_to_snake.py ├── config/ │ ├── settings.py # 全局配置(Pydantic Settings) │ └── milvus_config.py └── exception/ └── exceptions.py # 业务异常定义 ``` ### 3.2 核心配置(`.env`) 所有配置通过 `app/config/settings.py` 的 Pydantic `BaseSettings` 从 `.env` 自动加载: | 配置项 | 说明 | 默认值 | |--------|------|--------| | `APP_PORT` | 后端服务端口 | 3000 | | `AI_API_URL` | 大模型对话 API(兼容 OpenAI) | 空 | | `AI_API_KEY` | 大模型 API Key | 空 | | `AI_MODEL_NAME` | 对话模型名 | 空 | | `EMBEDDING_API_URL` | Embedding 向量 API | 空 | | `EMBEDDING_MODEL_NAME` | Embedding 模型名 | 空 | | `EMBEDDING_DIMENSIONS` | 向量维度(256/512/1024/2048) | 512 | | `RERANK_API_URL` | 重排模型 API | 空 | | `RERANK_MODEL_NAME` | 重排模型名 | 空 | | `MILVUS_ADDRESS` | Milvus / Zilliz 地址 | http://127.0.0.1:19530 | | `MILVUS_TOKEN` | Milvus Token | 空 | | `MILVUS_COLLECTION` | Collection 名称 | rag_knowledge_chunks | | `STORAGE_ROOT` | 原始 Markdown 存储目录 | ./storage/documents | | `REDIS_HOST` / `REDIS_PORT` / `REDIS_DB` / `REDIS_PASSWORD` | Redis 连接 | localhost / 6379 / 0 | > ⚠️ **生产环境必须配置**:`AI_API_URL` + `AI_API_KEY`、`EMBEDDING_*`、`RERANK_*`、`MILVUS_*`、`REDIS_*`。缺任何一项会导致对应功能优雅降级(返回错误提示而非崩溃)。 ### 3.3 身份认证与权限模型 #### Token 体系 本项目使用**演示身份**(无需密码),前端在 `localStorage` 保存 `rag_token`,每次请求通过 `Authorization: Bearer ` 头传递。 后端 `AuthDao.get_user_from_authorization` 从 `app/store/user_store.py` 的 `DEMO_USER_BY_TOKEN` 字典解析用户。内置 4 个演示用户: | Token | 姓名 | 租户 | 部门 | 角色 | |-------|------|------|------|------| | `demo-qiteng-admin` | 张三 | qiteng(奇腾科技) | platform | admin | | `demo-qiteng-customer-service` | 李四 | qiteng | customer-service | employee | | `demo-qiteng-finance` | 王五 | qiteng | finance | employee | | `demo-youqu-admin` | 魏六 | youqu(有趣名食) | platform | admin | #### 权限过滤(`app/utils/filtering.py`) 检索时,`build_permission_filter(user)` 生成 Milvus 过滤表达式: - **管理员**:`tenant_id == "<租户>" and is_active == true`(可看本租户全部文档) - **员工**:`tenant_id == "<租户>" and is_active == true and (visibility == "company" or department_id == "<部门>")` > 权限在**召回前**生效(检索时即带 filter),而不是召回后应用层过滤,避免越权数据泄露。 ### 3.4 RAG 知识库问答流程(`KnowledgeDao.query_knowledge_stream`) ``` 用户问题 │ ├─ 1. create_embeddings(question) → 生成 Dense 向量 ├─ 2. milvus.hybrid_search() → Dense(ANN) + BM25(Sparse) 双路召回,RRF 融合 │ 过滤条件:build_permission_filter(user) │ 返回:chunks(候选集,top_k=12) ├─ 3. ai.rerank(question, chunks, 4) → 重排精选 Top 4 ├─ 4. _build_prompt() → 拼接 system + 用户问题 + Chunk 内容 ├─ 5. ai.generate_answer_stream() → LLM 流式生成 JSON │ 要求返回:{"status": "answered|insufficient_evidence", │ "answer": "...", "sourceChunkIds": ["..."]} ├─ 6. _validate_answer() → 校验结构 + 来源 ID 合法性 └─ 7. 绑定 sources + 保存历史 ``` **关键数据模型**(`app/models/grounded_answer.py`): ```python Status = Literal["answered", "insufficient_evidence"] @dataclass(frozen=True) class GroundedAnswer: status: Status answer: str source_chunk_ids: list[str] ``` > **设计要点**:LLM 只返回 Chunk ID,最终来源信息(标题、内容、版本等)由系统从候选集重新绑定,避免 LLM 编造来源。`_validate_answer` 在校验 `insufficient_evidence` 时**保留** LLM 返回的 `sourceChunkIds`(如有),让前端能展示「查了哪些但无法回答」,避免「有来源却说无法回答」的矛盾。 ### 3.5 文档入库流程(`DocumentDao._save_version`) 1. `normalize_markdown` 统一换行符 2. `chunk_markdown` 按标题/段落切分(单块上限 700 字符,重叠 80 字符) 3. 查询该文档已有 Chunk,计算 checksum 判断内容是否变化(不变则跳过,避免重复生成向量) 4. 生成新版本号,调用 `ai.create_embeddings` 生成向量 5. 组装 Milvus 写入行(Chunk ID 规则:`{tenant}:{docId}:v{version}:{index}:{hash[:12]}`) 6. 保存原始 Markdown 到 `STORAGE_ROOT` 7. 写入新 Chunk,旧 Chunk 软失效(`set_active(False)`),新 Chunk 激活(`set_active(True)`) 8. 激活失败自动回滚到旧版本 ### 3.6 SSE 事件协议 #### 知识库问答(`/api/knowledge/stream/{conversation_id}`) | event type | 字段 | 说明 | |-----------|------|------| | `retrieving` | — | 检索开始 | | `sources` | `sources`, `pipeline`, `answerStatus` | 来源信息 + 检索链路 | | `chunk` | `content` | 答案文本片段 | | `done` | `answer`, `status`, `sources` | 流结束 | | `error` | `message` | 错误 | | `warning` | `message` | 警告(如历史保存失败) | #### AI 对话(`/api/ai/stream/{conversation_id}`) | event type | 字段 | 说明 | |-----------|------|------| | `chunk` | `content` | 回复文本片段 | | `done` | `answer` | 流结束 | | `error` | `message` | 错误 | | `warning` | `message` | 警告 | ### 3.7 API 端点汇总 | 方法 | 路径 | 说明 | 权限 | |------|------|------|------| | GET | `/api/health` | 健康检查 | 公开 | | GET | `/api/session/users` | 演示用户列表 | 公开 | | POST | `/api/session` | 创建会话(返回 conversation_id) | 登录 | | POST | `/api/knowledge/query/{id}` | 知识库问答(同步) | 登录 | | POST | `/api/knowledge/stream/{id}` | 知识库问答(SSE) | 登录 | | POST | `/api/ai/chat/{id}` | AI 对话(同步) | 登录 | | POST | `/api/ai/stream/{id}` | AI 对话(SSE) | 登录 | | GET | `/api/history/user` | 获取用户全部会话 | 登录 | | GET | `/api/history/session/{id}` | 获取指定会话 | 登录 | | DELETE | `/api/history/session/{id}` | 删除会话 | 登录 | | DELETE | `/api/history/user` | 清除用户全部会话 | 登录 | | PUT | `/api/history/session/{id}/title` | 重命名会话 | 登录 | | GET | `/api/history/stats` | 使用统计(可按 mode 过滤) | 登录 | | GET | `/api/documents` | 文档列表(按权限过滤) | 登录 | | POST | `/api/documents` | 上传文档(multipart) | **管理员** | | PUT | `/api/documents/{id}` | 发布新版本 | **管理员** | | DELETE | `/api/documents/{id}` | 软删除文档 | **管理员** | | GET | `/api/documents/{id}/versions` | 版本历史 | **管理员** | | GET | `/api/departments` | 部门列表 | 登录 | ### 3.8 异常处理 所有业务异常继承自 `KnowledgeBaseError`(`app/exception/exceptions.py`),`main.py` 统一捕获并返回 `{ "message": ... }`: | 异常 | HTTP 状态码 | |------|------------| | `BadRequestError` | 400 | | `UnauthorizedError` | 401 | | `ForbiddenError` | 403 | | `NotFoundError` | 404 | | `ServiceUnavailableError` | 503 | | `KnowledgeBaseError`(基类) | 500 | --- ## 4. 前端开发指南 ### 4.1 目录结构 ``` web/rag-knowledge-vue/ ├── src/ │ ├── api/ # 接口封装 │ │ ├── index.js # Axios 实例 + 拦截器(自动附加 Token、401 处理) │ │ ├── auth.js # 用户/会话/健康 │ │ ├── chat.js # 问答接口 + SSE 解析 │ │ ├── history.js # 历史记录 │ │ ├── document.js # 文档管理 │ │ └── index.js │ ├── stores/ # Pinia 状态管理 │ │ ├── chat.js # 消息/对话核心逻辑 │ │ ├── conversation.js # 会话列表/历史 │ │ ├── user.js # 用户身份 │ │ └── knowledge.js # 文档缓存 │ ├── components/ │ │ ├── Chat/ # MessageBubble / SourceCards / PipelineInfo / ThinkingIndicator / WelcomePanel │ │ ├── Sidebar/ # 会话列表 / 文档管理 / 用户切换 / 数据管理 │ │ └── ... │ ├── views/ │ │ └── ChatView.vue # 主聊天界面 │ ├── composables/ # useSidebar / useTheme │ ├── utils/ │ │ └── msgId.js # 全局消息 ID 生成器 │ ├── router/ # 路由(单页:/ → ChatView) │ └── styles/ # 全局样式 + 主题变量 ``` ### 4.2 状态管理(Pinia Stores) #### `stores/chat.js`(核心) 职责:管理当前会话的消息列表、发送/重新生成/停止逻辑、流式解析。 关键导出: - `MessageType`:`{ USER: 'user', AI: 'ai', SYSTEM: 'system' }` - `ChatMode`:`{ KNOWLEDGE: 'knowledge', AI: 'ai' }` 核心 actions: | Action | 说明 | |--------|------| | `ask(text)` | 按当前模式发送消息(自动选同步/流式) | | `askKnowledge` / `askAI` | 具体模式发送 | | `regenerate(targetId)` | **按消息 ID 原位覆盖重新生成**(精确重生成被点选的那条 AI 消息) | | `stopGeneration()` | 停止当前流式请求(自增请求令牌,丢弃过期结果) | | `addUSERMessage` / `addAIMessage` / `updateAIMessage` / `appendToAIMessage` | 消息增改 | | `loadFromConversation(conv)` | 切换会话时加载消息(重分配消息 ID) | | `clearMessages()` | 清空当前消息 | **请求令牌机制**(`_requestToken`):每次新请求/停止/切换会话都会自增令牌。所有回调中比对 `token !== this._requestToken` 即丢弃结果,避免: 1. 停止后旧结果覆盖「已停止」提示 2. 切换会话后旧请求写入新会话 3. 并发竞态 **消息 ID 生成器**(`utils/msgId.js`):全局单调递增 `nextMsgId()`,前端新建与后端加载的消息共用同一序列,杜绝同毫秒 / 同 timestamp 导致的 ID 撞号(曾导致 AI 回答误写入用户气泡的严重 bug)。 #### `stores/conversation.js` 职责:多会话管理(知识库 / AI 两套会话隔离)、与后端 `/api/history/*` 同步、本地 localStorage 缓存降级。 关键机制: - `activeKnowledgeId` / `activeAiId`:两种模式各自维护活跃会话(持久化) - `applyMode(mode)`:切换模式时恢复该模式活跃会话 - `loadFromBackend()`:从后端拉取全部会话(后端不可用时降级到本地缓存) - `convertBackendMessage()`:后端消息 → 前端消息(**ID 由 `nextMsgId()` 分配,绝不用 timestamp**) #### `stores/user.js` 职责:演示用户列表、当前身份、Token 持久化。 #### `stores/knowledge.js` 职责:文档列表缓存(localStorage 降级)、上传/更新/删除文档。 ### 4.3 SSE 解析(`api/chat.js` 的 `parseSSEStream`) 前端通过 `fetch` + `ReadableStream` 接收 SSE,按 `data:` 行解析 JSON,根据 `type` 分发回调: ```js parseSSEStream(stream, { onRetrieving, // 检索开始 onSources, // 来源信息 onChunk, // 文本片段 onDone, // 结束 onError, // 错误 onWarning, // 警告 }) ``` ### 4.4 关键组件 | 组件 | 职责 | |------|------| | `ChatView.vue` | 主界面:顶栏(模式切换/健康状态/新建)、消息列表、输入框(Ctrl+K 聚焦、Enter 发送、停止按钮) | | `MessageBubble.vue` | 单条消息气泡:用户/AI/系统三种样式、复制/重新生成/重试操作、证据不足提示 | | `SourceCards.vue` | 参考来源卡片 + 检索链路展示(召回数/重排数/权限过滤/候选分) | | `Sidebar/index.vue` | 侧边栏容器:Logo、会话列表入口、底部用户信息、管理面板弹窗 | | `Sidebar/ConversationList.vue` | 会话列表:模式 tabs、新建、分组(今天/昨天/7天/更早)、重命名/删除 | | `Sidebar/UserSelector.vue` | 身份切换 | | `Sidebar/DocumentList.vue` | 文档列表 + 上传/更新/删除 | | `Sidebar/DataManager.vue` | 数据管理(使用统计、消息搜索) | | `Sidebar/GeneralSettings.vue` | 通用设置(流式/同步切换) | --- ## 5. 本地开发环境搭建 ### 5.1 后端 ```bash # 1. 进入项目根目录(含 main.py 与 .env) cd rag-knowledge-base # 2. 创建虚拟环境(推荐) python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate # 3. 安装依赖 pip install -r requirements.txt # 或使用 uv:uv sync # 4. 配置 .env(参考 3.2 节,至少填 LLM / Milvus / Redis) # 5. 启动服务(默认端口 3000) python main.py # 或:uvicorn main:app --reload --port 3000 ``` **依赖服务**: - **Redis**:会话历史与部门缓存。`redis-server` 启动即可。 - **Milvus / Zilliz Cloud**:向量存储。本地可用 `milvus-lite` 或自建;也可直接用 Zilliz Cloud Serverless(配置 `MILVUS_ADDRESS` + `MILVUS_TOKEN`)。 > 若 Milvus 未启动:服务仍可启动,但知识库问答会返回「知识库服务当前不可用」的优雅降级提示。 ### 5.2 前端 ```bash cd web/rag-knowledge-vue npm install # 或 pnpm install npm run dev # 默认端口 5173,代理 /api → http://127.0.0.1:3000 ``` 开发依赖(devDependencies): - `vite` ^8 - `@vitejs/plugin-vue` ^5 - `unplugin-auto-import` / `unplugin-vue-components`(Element Plus 自动按需引入) ### 5.3 生产构建 ```bash # 前端构建(产物默认输出到 dist/) cd web/rag-knowledge-vue npm run build # 将 dist/ 内容复制到后端 static/ 目录后,由 main.py 的 StaticFiles 托管 # 启动后端:python main.py(访问 http://127.0.0.1:3000) ``` > Vite 配置见 `vite.config.js`:`@` 别名指向 `src`,dev server 代理 `/api` 到后端 3000 端口。 --- ## 6. 数据存储说明 ### 6.1 Redis Key 设计(`redis_store.py`) | Key | 结构 | 说明 | |-----|------|------| | `conversation:{user_id}:knowledge:history` | Hash: conv_id → JSON | 知识库会话历史 | | `conversation:{user_id}:ai:history` | Hash: conv_id → JSON | AI 对话历史 | | `conversation:{user_id}:history` | Hash(旧版共享,只读兼容) | 旧数据兼容 | | `departments:cache` | Hash: id → name | 部门缓存(首次访问 seed) | > 会话历史 30 天过期(`expire`)。知识库与 AI 两套会话**分离存储**,互不影响。 ### 6.2 Milvus Collection Schema(`milvus_store.py`) 核心字段: | 字段 | 类型 | 说明 | |------|------|------| | `chunk_id` | VARCHAR(256) PK | Chunk 唯一 ID | | `tenant_id` | VARCHAR(64) | 租户 ID(分区键) | | `document_id` | VARCHAR(64) | 文档 ID | | `version` | INT32 | 版本号 | | `chunk_index` | INT32 | Chunk 序号 | | `is_active` | BOOL | 是否生效版本 | | `department_id` | VARCHAR(64) | 部门 ID | | `visibility` | VARCHAR(32) | `company` / `department` | | `title` | VARCHAR(256) | 文档标题 | | `source_path` | VARCHAR(512) | 原始 Markdown 路径 | | `content` | VARCHAR(8192) | Chunk 正文(启用 jieba 分词 + BM25) | | `dense_vector` | FLOAT_VECTOR | Dense 向量(维度可配) | | `sparse_vector` | SPARSE_FLOAT_VECTOR | BM25 稀疏向量(由 content 自动生成) | | `updated_at` | INT64 | 更新时间戳(ms) | 索引:`dense_vector` → AUTOINDEX(COSINE);`sparse_vector` → SPARSE_INVERTED_INDEX(BM25)。 --- ## 7. 数据流示例 ### 7.1 知识库问答(流式) ``` 前端 ChatView.sendMessage() → chatStore.ask(text) [加载中锁定,防重入] → ensureConversation() [无活跃会话则 POST /api/session] → addUSERMessage() + addAIMessage() [占位 thinking] → _askKnowledgeStream() └─ queryKnowledgeStream() [fetch POST /api/knowledge/stream/{id}] → parseSSEStream(): onRetrieving → retrieving = true(显示"正在检索") onSources → 写入 sources + pipeline onChunk → appendToAIMessage()(打字机) onDone → 最终答案 + status=done → _endRequest() [loading 复位] ``` ### 7.2 重新生成(精确原位覆盖) ``` 点击某条 AI 消息的「重新生成」 → MessageBubble.handleRegenerate(props.message.id) → chatStore.regenerate(targetId) ├─ 定位目标 AI 消息(按 id + type==='ai') ├─ 找到其前最近的用户问题 ├─ 用该会话自身 mode(非当前 chat.mode)重新请求 └─ 原地覆盖目标消息(保留 id 与位置,清空内容后重填) ``` > 设计要点:重新生成**不会**删除再新建消息,而是原地覆盖,保持消息顺序,且其后消息不受影响。 --- ## 8. 常见问题与排查 | 现象 | 可能原因 | 排查方向 | |------|---------|---------| | 知识库问答返回「知识库服务当前不可用」 | Milvus 未连接 | 检查 `MILVUS_ADDRESS` / `MILVUS_TOKEN`,确认服务存活 | | AI 对话一直报错 | `AI_API_URL` / `AI_API_KEY` 未配置或错误 | 检查 `.env`,测试 API 连通性 | | 上传文档后检索不到 | 向量维度不匹配 / Embedding 配置错误 | 确认 `EMBEDDING_DIMENSIONS` 与 Milvus collection 维度一致 | | 会话列表为空但之前有对话 | Redis 数据过期 / 切换了用户 | 确认 `REDIS_DB` 与 Token 对应的用户 | | 前端报 401 | Token 失效或丢失 | 重新选择身份(localStorage 的 `rag_token`) | | 重新生成/发送后回答错位 | 旧构建产物 / HMR 残留 | 硬刷新或重启 `npm run dev`,生产重新 `npm run build` | | 有来源却显示「无法回答」 | LLM 过度拒答 / 来源 ID 被丢弃 | 已修复:优化 prompt + `_validate_answer` 保留来源 ID | ### 8.1 后端联调(无 Redis / Milvus 环境) 可使用内存 Redis mock + LLM 桩 + `fastapi.testclient.TestClient` 验证端点契约,无需真实依赖。详见历史开发记录(`_itest.py` 思路):注入 `redis.Redis` 内存实现与 LLM `urlopen` 桩,验证 SSE 事件顺序与 JSON 结构。 --- ## 9. 代码规范与约定 - **API 层工厂依赖**:每个 router 使用 `get_xxx_service()` 工厂函数显式构造 service,避免 FastAPI 反射解析问题。 - **ID 生成**:前端消息 ID 统一走 `utils/msgId.js` 的 `nextMsgId()`,禁止用 `Date.now()` 或 `timestamp` 作 ID。 - **权限前置**:Milvus 检索 filter 在召回前生效,不在应用层后过滤。 - **优雅降级**:Milvus / Redis / LLM 任一不可用时,相关功能返回清晰错误提示,不导致整个服务崩溃。 - **同步 vs 流式**:发送时由 `chatStore.useStream`(持久化到 localStorage)决定走哪条路径;流式可中断,同步为一次性阻塞不可中断。 --- ## 10. 待办 / 可扩展点 - [ ] 真实身份认证(替换演示 Token 体系) - [ ] 多实例部署时 `DocumentDao` 的租户-文档锁需替换为分布式锁 / 任务队列 - [ ] 文档分块可升级为 `markdown-it-py` 等 AST 解析器,支持更复杂 Markdown - [ ] 知识库问答增加「无相关文档」的主动提示与推荐 - [ ] 历史记录增加分页 / 游标,避免长会话全量加载 - [ ] 前端增加对话导出、消息搜索高亮等增强能力