跳转至

SAGE Chat 架构详解

Date: 2024-10-15\ Author: SAGE Team\ Summary: SAGE Chat 架构设计文档,包括对话管理、上下文处理和多轮对话支持

🎯 核心问题解答

Q1: 为什么 Mock LLM 也能支持 sage chat?

答案:因为 SAGE Chat 采用 RAG(检索增强生成)架构,检索和生成是解耦的两个阶段。


📊 SAGE Chat 完整架构

用户问题
   ↓
┌─────────────────────────────────────────────────────────────┐
│ 阶段 1: 文档检索(Retrieval)                                │
│                                                              │
│  问题 → Embedding → 向量检索 → Top-K 文档片段                │
│         (Hash/HF)    (SageDB)                                │
└─────────────────────────────────────────────────────────────┘
   ↓
   文档片段 (contexts) + 引用信息 (references)
   ↓
┌─────────────────────────────────────────────────────────────┐
│ 阶段 2: 答案生成(Generation)                               │
│                                                              │
│  [Mock 模式]                                                 │
│  └→ 简单拼接检索结果(无需真实 LLM)                          │
│                                                              │
│  [OpenAI 模式]                                               │
│  └→ 调用真实 LLM 理解并生成答案                              │
└─────────────────────────────────────────────────────────────┘
   ↓
   最终答案

🔍 阶段 1: 文档检索的工作原理

代码实现(第 1170-1200 行)

def retrieve_context(
    db: Any,
    embedder: EmbeddingModel,
    question: str,
    top_k: int,
) -> Dict[str, object]:
    # 1. 将问题转换为向量
    query_vector = embedder.embed(question)  # ← Embedding 在这里使用

    # 2. 在向量数据库中检索最相似的文档
    results = db.search(query_vector, top_k, True)  # ← 向量相似度搜索

    # 3. 提取文档内容和元数据
    contexts: List[str] = []
    references: List[Dict[str, str]] = []
    for item in results:
        metadata = dict(item.metadata)
        contexts.append(metadata.get("text", ""))  # 检索到的文档片段
        references.append({...})  # 引用信息

    return {"contexts": contexts, "references": references}

关键点:

  • Embedding 模型(Hash/HF/OpenAI)只在检索阶段使用
  • 用于将问题和文档都转换为向量
  • 通过向量相似度找到相关文档

🤖 阶段 2: 答案生成的两种模式

Mock 模式(第 590-603 行)

@staticmethod
def _mock_answer(
    question: str,
    contexts: Sequence[str],  # ← 检索阶段已经拿到的文档
    references: Sequence[Dict[str, str]],
) -> str:
    if not contexts:
        return "暂时没有从知识库检索到答案..."

    # 简单提取第一个文档的前 280 字符
    snippet = contexts[0].strip().replace("\n", " ")
    citation = top_ref.get("label", "Docs")

    # 直接拼接返回,无需 LLM!
    return (
        f"根据 {citation} 的说明:{snippet[:280]}...\n\n"
        "如需更多细节,可以输入 `more` 再次检索,或使用 `--backend openai` 启用真实模型。"
    )

特点:

  • ✅ 不需要真实 LLM
  • ✅ 不需要高质量 Embedding(只要能检索到文档即可)
  • ❌ 无语义理解:只是简单截取文档片段
  • ❌ 无法总结、推理、组合多个来源

OpenAI 模式(第 564-589 行)

def answer(
    self,
    question: str,
    contexts: Sequence[str],  # ← 检索阶段已经拿到的文档
    references: Sequence[Dict[str, str]],
    stream: bool = False,
) -> str:
    # 构建包含上下文的提示词
    messages = build_prompt(question, contexts)

    # 调用真实 LLM 进行理解和生成
    response = self.client.generate(
        messages,
        max_tokens=768,
        temperature=self.temperature,
        stream=stream,
    )
    return response

特点:

  • ✅ 深度语义理解:LLM 能理解文档内容
  • ✅ 智能总结:可以综合多个来源
  • ✅ 推理能力:可以回答隐含信息
  • ❌ 需要 API Key 和费用

💡 Q2: 为什么真实 LLM 就不需要更好的 Embedding?

误解澄清

这是一个误解! 实际上:

更好的 Embedding → 更准确的检索 → 更好的上下文 → 更好的答案

无论使用 Mock 还是 OpenAI 后端,更好的 Embedding 总是有帮助的。


🔬 三种配置的对比

配置 Embedding LLM Backend 检索质量 答案质量 适用场景
当前配置 Hash (Mock) Mock ⭐⭐ (只能精确匹配) ⭐ (截取片段) 快速测试、演示
建议配置 1 Hash (Mock) OpenAI ⭐⭐ (只能精确匹配) ⭐⭐⭐⭐ (LLM 理解) 节省成本、关键词明确
建议配置 2 HF (BGE) Mock ⭐⭐⭐⭐⭐ (语义理解) ⭐ (截取片段) 测试检索效果
最佳配置 HF (BGE) OpenAI ⭐⭐⭐⭐⭐ (语义理解) ⭐⭐⭐⭐⭐ (LLM 理解) 生产环境

🎯 实际测试对比

测试问题:"请列出所有可用的检索器算子"

配置 1: Hash Embedding + Mock LLM(当前)

检索结果:
1. SAGE 子模块管理策略 (得分: 1.2308) ← 不相关!
2. SAGE Applications 应用组件 - Tools (得分: 1.2355) ← 不相关!
3. SAGE Kernel 总览 (得分: 1.2424) ← 不相关!

回答:
根据 [ci_cd/submodule_management.md] 的说明:...

问题:Hash Embedding 无法理解"检索器算子"的语义,只能做简单的字符串匹配。


配置 2: Hash Embedding + OpenAI LLM(假设)

检索结果:
1. SAGE 子模块管理策略 (得分: 1.2308) ← 不相关!
2. SAGE Applications 应用组件 - Tools (得分: 1.2355) ← 不相关!
...

回答(LLM 生成):
根据检索到的文档,虽然没有直接找到"检索器算子"的完整列表,
但从 SAGE Applications 文档中可以看到工具集相关内容。
建议您查看 librarys/operators_reference.md 文档...

改进:LLM 能理解检索结果不相关,给出更合理的回复。但仍然受限于差的检索结果。


配置 3: BGE Embedding + Mock LLM(假设)

检索结果:
1. SAGE-Libs 算子参考手册 - RAG 检索器 (得分: 0.85) ← 高度相关!
2. SAGE-Libs 算子参考手册 - ChromaRetriever (得分: 0.87)
3. SAGE-Libs 算子参考手册 - MilvusDenseRetriever (得分: 0.88)

回答:
根据 [librarys/operators_reference.md] 的说明:
### ChromaRetriever
**路径**: sage.libs.rag.retriever.ChromaRetriever
**功能**: 基于 ChromaDB 的向量检索器...

改进:检索准确!但回答只是简单截取,不够智能。


配置 4: BGE Embedding + OpenAI LLM(最佳)

检索结果:
1. SAGE-Libs 算子参考手册 - RAG 检索器 (得分: 0.85) ← 高度相关!
2. SAGE-Libs 算子参考手册 - ChromaRetriever (得分: 0.87)
3. SAGE-Libs 算子参考手册 - MilvusDenseRetriever (得分: 0.88)

回答(LLM 智能总结):
SAGE-Libs 目前提供 4 种检索器算子:

1. **ChromaRetriever** - 轻量级向量检索,适合中小规模知识库
2. **MilvusDenseRetriever** - 生产级密集向量检索,支持大规模数据
3. **MilvusSparseRetriever** - 稀疏向量检索(BM25-like),适合关键词匹配
4. **Wiki18FAISSRetriever** - 基于 FAISS 的演示用检索器

选择建议:
- 本地开发 → ChromaRetriever
- 生产环境 → MilvusDenseRetriever
- 混合检索 → Dense + Sparse 结合

完美:准确检索 + 智能总结!


🚀 为什么当前测试能"工作"?

测试问题:"MilvusDenseRetriever"

query = "MilvusDenseRetriever"  # ← 精确的算子名称

# Hash Embedding 的工作原理
hash("MilvusDenseRetriever") → [固定向量]

# 在索引时,文档中也包含 "MilvusDenseRetriever" 这个词
hash("...MilvusDenseRetriever...") → [相似向量]

# 结果:能匹配上!

检索结果显示成功找到了 operators_reference.md:

知识引用:
1. SAGE-Libs 算子参考手册 - 高精度 RAG (得分: 1.0649)  ← 包含 "MilvusDenseRetriever"
2. SAGE-Libs 算子参考手册 - 混合检索 (得分: 1.1371)
...

但是,如果问"语义问题":

  • ❌ "如何选择合适的检索器?" → Hash 无法理解
  • ❌ "我需要高性能的向量搜索" → Hash 无法匹配到 MilvusDenseRetriever
  • ✅ "MilvusDenseRetriever" → Hash 能精确匹配

📝 总结

核心要点

  1. 检索和生成是解耦的:

  2. Embedding 只影响检索质量

  3. LLM Backend 只影响生成质量

  4. Mock LLM 能工作是因为:

  5. 它只是简单地返回检索到的文档片段

  6. 不需要理解语义,只需要"有内容可返回"

  7. 真实 LLM 的价值:

  8. 能从差的检索结果中提取有用信息

  9. 能综合多个来源生成答案
  10. 能推理和总结,而不只是复制粘贴

  11. 但真实 LLM ≠ 不需要好 Embedding:

  12. Garbage in, garbage out

  13. 更好的检索 → 更好的上下文 → 更好的答案
  14. 即使 LLM 再强大,也无法从不相关的文档中生成正确答案

🎓 类比理解

想象你要写一篇报告:

Mock 模式

老师:请根据资料写一篇关于检索器的报告
你:(随机翻开图书馆的书)复制前 280 字 ← Mock LLM
问题:如果拿错书了,复制的内容也是错的

OpenAI 模式(差 Embedding)

老师:请根据资料写一篇关于检索器的报告
你:(随机翻开图书馆的书)发现内容不对,但尽力理解并补充 ← 真实 LLM
改进:虽然资料不对,但能给出合理的回复

OpenAI 模式(好 Embedding)

老师:请根据资料写一篇关于检索器的报告
你:(精准找到相关书籍)深度理解并总结 ← 真实 LLM + 好检索
完美:找对资料 + 深度理解 = 高质量答案

🔧 实践建议

当前测试阶段

# 使用 Mock 模式快速验证功能
sage chat
# 输入具体的类名/函数名(精确匹配)
> ChromaRetriever

开发阶段

# 使用真实 LLM,但保持 Hash Embedding(节省资源)
sage chat --backend openai
# 可以问更自然的问题
> 如何选择合适的检索器?

生产环境

# 重建索引使用语义 Embedding
sage chat ingest --embedding-method hf --embedding-model BAAI/bge-base-zh-v1.5

# 使用真实 LLM
sage chat --backend openai
# 最佳体验
> 我想构建一个大规模知识库问答系统,应该用什么检索器?

希望这个解释清楚了!简单说:

  • Mock LLM = 复读机(复制检索结果)
  • 真实 LLM = 智能助手(理解并生成答案)
  • 好 Embedding = 精准的图书管理员(找对资料)
  • 差 Embedding = 随机找书(可能找错)

最佳效果 = 精准找书(好 Embedding)+ 智能理解(真实 LLM)!