Spaces:
Running
Running
| # 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 向量数据库的表现与可维护性。 | |