ClareCourseWare / docs /VECTOR_DB_LOGIC_AND_MULTI_COURSE.md
claudqunwang's picture
Add teacher agent, docs, and project updates; ClareVoiceV1 remains untracked (nested repo)
fbe1c8a

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=openaitext-embedding-3-small
  2. 运行时检索hf_space/ClareVoice/app.py

    • 配置:WEAVIATE_URLWEAVIATE_API_KEYWEAVIATE_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,例如:

{
  "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_filesallowed_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,如 GenAICoursesCourse_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_idcourse_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_chunksVectorStore() + build_index(chunks),重复建索引。
    • 改为:对同一份 chunks(按会话或按 course_id 的列表)建一次索引并缓存,只有 chunks 变化(如新上传)时再重建。这样检索延迟和 CPU 都会明显下降。
  • Chunk 与 embedding 分离缓存

    • 对「未改动的文件」缓存其 chunk 列表(或甚至只缓存 embedding),避免重复解析、重复调 OpenAI Embeddings;上传同一文件时可直接复用。
  • 检索参数可配置

    • top_kvector_similarity_thresholdRAG_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_iduploaded_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 向量数据库的表现与可维护性。