Spaces:
Sleeping
Sleeping
File size: 13,539 Bytes
fbe1c8a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | # ClareVoice 向量数据库逻辑 · 多课程支持 · 优化计划
本文档**只描述 ClareVoice(HF Space)**的向量库设计与扩展思路。
---
## 一、当前向量数据库是怎么做的
**ClareVoice 使用 Weaviate Cloud 作为课程知识库**,同时会话级 RAG 用内存 + FAISS。双路检索结果在对话时拼接。
### 1.1 Weaviate + 内存双路检索
| 数据源 | 存储位置 | 用途 | 检索入口 |
|--------|----------|------|----------|
| **Weaviate Cloud** | 持久化向量库(GCP) | GENAI 课程文档(151+ 文档,由 `build_weaviate_index.py` 一次性写入) | `_retrieve_from_weaviate(question)` |
| **内存 + FAISS** | 进程内 `SESSIONS[user_id]["rag_chunks"]` | Module 10 预读 + 用户上传文件 | `retrieve_relevant_chunks()` |
**Weaviate 流程简述:**
1. **建索引(一次性)**
- 脚本:`hf_space/GenAICoursesDB_space/build_weaviate_index.py`
- 读取 `GENAI COURSES` 目录(.md / .pdf / .txt / .py / .ipynb / .docx),用 LlamaIndex `SimpleDirectoryReader` 加载 → `VectorStoreIndex.from_documents()` + `WeaviateVectorStore`,写入 Weaviate Cloud 的 **collection `GenAICourses`**。
- Embedding:默认 `sentence-transformers/all-MiniLM-L6-v2`(与 ClareVoice 运行时一致);可选 `EMBEDDING_PROVIDER=openai` 用 `text-embedding-3-small`。
2. **运行时检索**(`hf_space/ClareVoice/app.py`)
- 配置:`WEAVIATE_URL`、`WEAVIATE_API_KEY`、`WEAVIATE_COLLECTION`(默认 `GenAICourses`),`USE_WEAVIATE_DIRECT=True` 时启用。
- `_get_weaviate_embed_model()`:懒加载并缓存 **HuggingFaceEmbedding(all-MiniLM-L6-v2)**(与建索引时一致)。
- `_retrieve_from_weaviate(question, top_k=5)`:连接 Weaviate Cloud → `WeaviateVectorStore` + `VectorStoreIndex.from_vector_store(vs)` → `as_retriever(similarity_top_k=top_k).retrieve(question)` → 将 node 内容用 `---` 拼接成字符串返回。
- 启动时 `_warmup_weaviate_embed()` 在后台线程预热 embedding 模型,减少首次检索超时。
- 未配置 Weaviate 时回退到调用 **GenAICoursesDB Space** 的 `/retrieve` 接口。
3. **对话时的组合**
- 先按会话用 `retrieve_relevant_chunks(sess["rag_chunks"])` 得到「Module 10 + 上传」的 context。
- 若配置了 Weaviate,再调 `_retrieve_from_weaviate(message)`,把返回的课程检索文本**拼在**上面 context 后面,一起交给 LLM。
---
### 1.2 数据从哪来、存在哪
| 来源 | 何时写入 | 存在哪 |
|------|----------|--------|
| Module 10 预读 | 进程启动时 | 全局 cache → 每会话 `rag_chunks` |
| 用户上传文件 | Gradio 上传回调 | 追加到 `sess["rag_chunks"]` |
| GENAI 课程文档 | 本地运行 `build_weaviate_index.py` | **Weaviate Cloud** collection `GenAICourses` |
### 1.3 Chunk 长什么样(内存/FAISS 侧)
会话级 `rag_chunks` 里每个元素是一个 dict,例如:
```python
{
"text": "一段正文...",
"source_file": "module10_responsible_ai.pdf", # 或上传的文件名
"section": "pdf_unstructured#1", # 段落/页码标识
"doc_type": "Literature Review / Paper", # Syllabus / Lecture Slides / ...
"embedding": [0.1, -0.02, ...] # 维度由 rag_engine 所用 embedding 模型决定
}
```
- **Embedding**:ClareVoice 的 `rag_engine` 里用 `clare_core.get_embedding()`(或同配置的 embedding)为每个 chunk 生成向量。
- **解析**:PDF 优先 `unstructured.io`,失败则 pypdf;DOCX/PPTX 用 python-docx/pptx;按空行分段落再按约 1400 字符打包成 chunk。
**Weaviate 侧**:由 LlamaIndex 写入的 document/node,带向量与元数据;检索时取 `node.get_content()` 拼成字符串,无单独 chunk dict 结构要求。
### 1.4 检索流程(retrieve_relevant_chunks,仅内存/FAISS)
1. **入参**
- `query`:用户问题
- `chunks`:当前会话的 `sess["rag_chunks"]`
- 可选:`allowed_source_files`、`allowed_doc_types`(例如只查「刚上传的那份文件」)
2. **先做范围过滤**
- 若有 `allowed_source_files` / `allowed_doc_types`,只保留符合的 chunks,再参与后续步骤。
3. **向量检索(默认开启)**
- 用当前配置的 embedding 模型对 query 生成向量;
- 用当前过滤后的 chunks 临时建一个 **VectorStore**(FAISS IndexFlatL2 或列表余弦);
- 取 top `k*2` 个候选(默认 k=4),再按 `vector_similarity_threshold`(默认 0.7)过滤。
4. **Rerank**
- 对候选做 **token 重叠**(query 与 chunk 的 token 交集);
- 综合分 = `0.7 * 向量相似度 + 0.3 * 归一化 token 重叠`,再取 top k。
5. **无结果或分数过低**
- 回退到**纯 token 重叠**检索(按词匹配,无向量)。
6. **拼上下文**
- 对最终 top chunks 做 token 截断(单 chunk 500、总 context 2000),拼成一段 `context_text` 返回给 LLM,同时返回 `used_chunks` 供前端展示引用。
### 1.5 可选:GenAICoursesDB 回退
- 若**未配置 Weaviate**,ClareVoice 用 `GENAI_COURSES_SPACE` 调用 GenAICoursesDB Space 的 `/retrieve` 接口,作为课程知识库的「远程 RAG」回退。
### 1.6 小结(当前逻辑)
- **Weaviate Cloud**:存 GENAI 课程知识库(`build_weaviate_index.py` 建索引,`_retrieve_from_weaviate` 检索)。
- **内存 + FAISS**:会话级 `rag_chunks`(Module 10 预读 + 用户上传),`retrieve_relevant_chunks()` 检索。
- **对话时**:先取 FAISS 的 context,再调 `_retrieve_from_weaviate` 把课程检索结果拼在后面,一起交给 LLM。
- **多课程**:当前 Weaviate 单 collection(GenAICourses);支持多门课时可在 Weaviate 内按 `course_id` 或分 collection 扩展(见下文)。
---
## 二、要支持多门课程,需要怎么做
目标:**多门课程并存、按课程(或 workspace)隔离检索**,且不破坏现有单会话、单课程使用方式。
**ClareVoice 已有 Weaviate**,多课程可在此基础上扩展(多 collection 或单 collection + course_id 过滤)。
### 2.1 数据模型上区分「课程」
- 为 chunk / 文档增加**课程维度**,例如:
- `course_id`:对应 `course_directory` 里的一门课;
- 或 `workspace_id`:对应某个 workspace(若一个 workspace 绑定一门课)。
- 会话侧增加「当前使用的课程/workspace」:
- 例如 `sess["course_id"]` 或 `sess["workspace_id"]`,由前端「选课/选 workspace」或默认规则设定。
这样:
- **建索引时**:每个 chunk 带上 `course_id`(或 `workspace_id`);
- **检索时**:只在该会话当前 `course_id`(或当前 workspace 绑定的课程)对应的数据里搜。
### 2.2 两种实现路径
**路径 A:内存 + 按课程分桶(仅会话级)**
- 会话结构扩展为例如:`sess["rag_chunks_by_course"] = { "course_ai_001": [...], "course_ml_002": [...] }`。
- 或保持 `sess["rag_chunks"]` 为「当前课程」的列表,在**切换课程**时从「课程级缓存」里加载对应列表。
- **课程级 chunk 从哪来**:
- **方案 A1**:仍以「上传」为主——用户选一门课再上传,上传时带 `course_id`,写入 `rag_chunks_by_course[course_id]`。
- **方案 A2**:预置「课程资料」——每门课有预解析好的 chunk 列表,登录/选课后加载到会话或课程缓存。
- **检索**:只对当前课程的 chunk 列表做现有 `retrieve_relevant_chunks()`,逻辑不变。
**路径 B:Weaviate 扩展(推荐)**
- ClareVoice 已用 **Weaviate Cloud** 存 GENAI 课程(单 collection `GenAICourses`)。多课程可:
- **方案 B1**:多 collection——每门课一个 collection,如 `GenAICourses`、`Course_ML_101`;`_retrieve_from_weaviate` 根据 `sess["course_id"]` 或配置选择 `index_name`。
- **方案 B2**:单 collection + 元数据过滤——在 Weaviate 的 class 上增加属性 `course_id`(或 `courseId`),建索引时写入;检索时用 Weaviate 的 **filter**(如 `where course_id == "course_ai_001"`)再做向量检索。
- **写入**:
- 预置课程:沿用或扩展 `build_weaviate_index.py`,按课程目录/配置写入,带 `course_id`;
- 用户上传:解析 → embedding → 写入 Weaviate,并带上当前 `course_id`(需在 ClareVoice 中接 Weaviate 写接口)。
- **检索**:
- query embedding + **filter (course_id = 当前课程)**;
- 再在应用层做 token rerank / 截断,拼 context。
- **会话**:存 `user_id`、`course_id`;多门课共享同一 Weaviate 集群,靠 collection 或 filter 隔离。
### 2.3 与 Gradio 前端配合
- **选课/选 workspace**:
- 若 UI 有选课或 workspace,在会话中维护 `sess["course_id"]` / `sess["workspace_id"]`,上传与检索时只作用当前课程。
- **上传**:
- 上传回调里从当前会话读 `course_id`(若有),写入 chunk 池或 Weaviate 时带课程维度。
- **对话**:
- 多课程下从「当前课程对应的 chunk 源」取数(内存分桶或 Weaviate filter),再调 `retrieve_relevant_chunks()` 或 `_retrieve_from_weaviate(..., course_id=...)`。
### 2.4 小结:多课程支持要做的事
| 项目 | 说明 |
|------|------|
| Chunk 增加 course_id(或 workspace_id) | 建索引/写入时必带;检索时按此过滤。 |
| 会话增加当前课程 | `sess["course_id"]` 或 `sess["workspace_id"]`,由选课/选 workspace 设定。 |
| 上传与选课绑定 | 上传时带 course_id,写入对应课程 chunk 池或向量库。 |
| 检索只查当前课程 | 内存方案:只对当前课程的 chunk 列表检索;向量库方案:filter by course_id。 |
| 可选:课程预置资料 | 每门课预解析+embedding,放入课程缓存或持久化向量库。 |
---
## 三、向量数据库的优化计划与想法
在「写清当前逻辑」和「多课程支持」之上,可以按阶段做以下优化。
### 3.1 短期(不引入新服务)
- **按会话/按课程复用 FAISS 索引**
- 当前是**每次** `retrieve_relevant_chunks` 都 `VectorStore()` + `build_index(chunks)`,重复建索引。
- 改为:对同一份 `chunks`(按会话或按 course_id 的列表)**建一次索引并缓存**,只有 chunks 变化(如新上传)时再重建。这样检索延迟和 CPU 都会明显下降。
- **Chunk 与 embedding 分离缓存**
- 对「未改动的文件」缓存其 chunk 列表(或甚至只缓存 embedding),避免重复解析、重复调 OpenAI Embeddings;上传同一文件时可直接复用。
- **检索参数可配置**
- `top_k`、`vector_similarity_threshold`、`RAG_CONTEXT_TOKEN_LIMIT` 等放进 config 或 API,便于按课程/场景调优(如考试模式用更大 top_k)。
- **多课程内存分桶**
- 实现 2.1 / 2.2 路径 A:`rag_chunks_by_course` + 选课接口,先支持多门课隔离,为后续迁到向量库打基础。
### 3.2 中期(扩展 Weaviate / 持久化向量库)
- **ClareVoice 已接 Weaviate Cloud**;可在此基础上:
- Schema 增加 `course_id`(及可选 `workspace_id`、`uploaded_at`);
- 多 collection 或单 collection + filter,实现多课程隔离;
- 上传时写入 Weaviate(需在服务端接 Weaviate 写接口),预置课程继续用 `build_weaviate_index.py` 或同类脚本。
- **统一检索接口**
- 封装一层「RAG 检索」:内部根据配置选择「内存 FAISS」或「Weaviate」(及可选 GenAICoursesDB 回退);对上层仍返回 `(context_text, used_chunks)`,便于后续扩展与多课程切换。
- **课程级预建索引**
- 每门课预解析 + 批量 embedding → 写入 Weaviate(按 course_id 或分 collection);用户选课后直接查。
### 3.3 中长期(效果与规模)
- **混合检索(Hybrid)**
- 向量检索 + 关键词(BM25/lexical)再融合(如 RRF),减少纯向量「语义漂移」或专有名词漏检。
- **Rerank 模型**
- 向量初筛后用小型的 cross-encoder 或 rerank API 精排,再截断进 context,提升引用准确度。
- **分片与规模**
- 按 `course_id` 分 collection 或分索引,便于按课程做备份、迁移、删除;单集合过大时再考虑按时间或按文档分片。
- **可观测性**
- 记录检索延迟、命中 chunk 数、分数分布;可选 A/B 不同 top_k 或阈值,便于调参。
---
## 四、总结表
| 维度 | ClareVoice 当前 | 多课程(建议) | 优化方向 |
|------|-----------------|----------------|----------|
| 课程知识库 | **Weaviate Cloud**(GenAICourses) | 多 collection 或 course_id 过滤 | 多课程 schema、统一检索接口 |
| 会话 RAG | 内存 rag_chunks + FAISS | 按 course_id 分桶或统一走 Weaviate | 索引复用、课程级预建 |
| 检索 | Weaviate + FAISS 双路,结果拼接 | 先按 course_id 过滤再检索 | Hybrid、rerank、可调参 |
| 数据来源 | Module10 预读 + 上传 + **Weaviate 课程** | + 课程预置资料、多课程上传 | 批量预置、可观测 |
按上述步骤:先把「当前向量库逻辑」和「多课程支持」在设计与实现上对齐,再分阶段做「缓存/索引复用 → 扩展 Weaviate 多课程 → 混合检索与规模化」,即可在支持多门课的同时逐步优化 ClareVoice 向量数据库的表现与可维护性。
|