Skip to content

RAG 文档问答系统 — 项目设计 ​

设计目标 ​

构建一个可量化评估的企业级 RAG 系统,重点解决三个核心问题:

  1. 检索质量:如何从海量文档中精准找到相关内容(Recall + Precision)
  2. 生成忠诚度:如何确保 LLM 基于检索结果回答,而非编造(Faithfulness)
  3. 工程可靠性:如何在生产环境下稳定、低延迟运行

系统架构 ​

┌─────────────────────────── 离线索引管线 ─────────────────────────────┐
│                                                                        │
│  文档源 ─→ Loader ─→ Splitter ─→ Embedding API ─→ VectorStore         │
│  (PDF/MD)   (解析)    (分块)      (向量化)         (存储+索引)         │
│                          │                                             │
│                          └─→ BM25 Index (倒排索引)                     │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────── 在线查询管线 ─────────────────────────────┐
│                                                                        │
│  Query ─→ Query Rewrite ─→ ┌─ Vector Search (Top-20) ─┐              │
│            (Multi-Query/    │                            │  RRF        │
│             Decomposition)  └─ BM25 Search (Top-20)  ──┘ Fusion      │
│                                       │                                │
│                                       ▼                                │
│                               Reranker (Top-5)                         │
│                                       │                                │
│                                       ▼                                │
│                             Prompt Assembly + LLM                       │
│                                       │                                │
│                                       ▼                                │
│                             Citation + Hallucination Check              │
│                                       │                                │
│                                       ▼                                │
│                               Response (Stream)                        │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────── 评估管线 ─────────────────────────────────┐
│                                                                        │
│  TestSet(Q+A+Source) ─→ Pipeline ─→ Metrics(Recall, MRR, Faith.)      │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

核心接口定义 ​

Loader — 文档加载器 ​

go
type Document struct {
    ID       string
    Content  string
    Metadata Metadata
}

type Metadata struct {
    Source   string // 文件路径
    Title    string // 文档标题
    Page     int    // 页码(PDF)
    Section  string // 章节标题(Markdown)
}

type Loader interface {
    Load(ctx context.Context, path string) ([]Document, error)
    SupportedExtensions() []string
}

Splitter — 文档分块器 ​

go
type Chunk struct {
    ID       string
    Content  string
    Metadata Metadata
    Index    int // 在原文档中的顺序
}

type SplitterConfig struct {
    ChunkSize    int      // 目标 chunk 大小(字符数)
    ChunkOverlap int      // overlap 大小
    Separators   []string // 分隔符优先级
}

type Splitter interface {
    Split(doc Document) []Chunk
}

Embedding — 向量化 ​

go
type EmbeddingClient interface {
    Embed(ctx context.Context, text string) ([]float64, error)
    EmbedBatch(ctx context.Context, texts []string) ([][]float64, error)
    Dimension() int
}

VectorStore — 向量存储 ​

go
type SearchResult struct {
    Chunk      Chunk
    Score      float64 // 相似度分数
    SearchType string  // "vector" | "bm25" | "hybrid"
}

type VectorStore interface {
    Upsert(ctx context.Context, chunks []Chunk, embeddings [][]float64) error
    Search(ctx context.Context, query []float64, topK int, threshold float64) ([]SearchResult, error)
    Delete(ctx context.Context, filter map[string]string) error
}

type BM25Index interface {
    Index(ctx context.Context, chunks []Chunk) error
    Search(ctx context.Context, query string, topK int) ([]SearchResult, error)
}

Retriever — 检索器 ​

go
type RetrieverConfig struct {
    TopK           int
    ScoreThreshold float64
    UseRerank      bool
    UseMultiQuery  bool
    UseHybrid      bool
}

type Retriever interface {
    Retrieve(ctx context.Context, query string, cfg RetrieverConfig) ([]SearchResult, error)
}

Generator — 生成器 ​

go
type Answer struct {
    Content    string
    Citations  []Citation
    Confidence float64
    TokenUsage TokenUsage
}

type Citation struct {
    ChunkID string
    Source  string
    Section string
    Score   float64
}

type Generator interface {
    Generate(ctx context.Context, query string, contexts []SearchResult) (*Answer, error)
    GenerateStream(ctx context.Context, query string, contexts []SearchResult, onChunk func(string)) error
}

Evaluator — 评估器 ​

go
type EvalResult struct {
    RecallAtK    float64 // 检索召回率
    MRR          float64 // 平均倒数排名
    Faithfulness float64 // 生成忠诚度(答案是否基于检索结果)
    Relevance    float64 // 答案与问题的相关性
}

type TestCase struct {
    Question       string
    ExpectedAnswer string
    ExpectedSources []string // 期望召回的文档来源
}

type Evaluator interface {
    Evaluate(ctx context.Context, testCases []TestCase) (*EvalResult, error)
}

关键算法设计 ​

1. RRF(Reciprocal Rank Fusion)混合检索融合 ​

go
// RRF 融合算法
// 将多路检索结果按统一分数排序
// 公式:RRF(d) = Σ 1/(k + rank_i(d)),k=60
func RRFMerge(results ...[]SearchResult) []SearchResult {
    scores := make(map[string]float64) // chunkID -> 累计分数
    const k = 60
    for _, resultList := range results {
        for rank, r := range resultList {
            scores[r.Chunk.ID] += 1.0 / float64(k+rank+1)
        }
    }
    // 按累计分数降序排列
    // ...
}

2. Multi-Query 查询改写 ​

go
// 用 LLM 将原始查询改写为多个不同角度的查询
// 每个查询并行检索,结果 RRF 融合
func (r *MultiQueryRetriever) Retrieve(ctx context.Context, query string) ([]SearchResult, error) {
    // 1. LLM 生成 3-4 个改写查询
    queries := r.rewriteQuery(ctx, query)
    
    // 2. 并行检索(goroutine + errgroup)
    var allResults [][]SearchResult
    g, gctx := errgroup.WithContext(ctx)
    for _, q := range queries {
        q := q
        g.Go(func() error {
            res, err := r.baseRetriever.Retrieve(gctx, q)
            // ...
        })
    }
    
    // 3. RRF 融合
    return RRFMerge(allResults...), nil
}

3. 幻觉检测 — 引用溯源 ​

go
// 答案中的每个关键断言都必须有检索结果支撑
// 通过 NLI (Natural Language Inference) 思路判断
func (g *CitationGenerator) VerifyFaithfulness(answer string, contexts []SearchResult) float64 {
    // 1. 将答案拆分为独立断言(claim)
    claims := g.extractClaims(answer)
    
    // 2. 对每个断言,检查是否被某个 context 支持
    supported := 0
    for _, claim := range claims {
        for _, ctx := range contexts {
            if g.isSupported(claim, ctx.Chunk.Content) {
                supported++
                break
            }
        }
    }
    
    // 3. Faithfulness = supported / total
    return float64(supported) / float64(len(claims))
}

查询处理流水线 ​

用户输入 "RAG 系统怎么处理幻觉?"
    │
    ▼ [Query Understanding]
判断查询类型:事实问答 / 对比分析 / 操作指导
    │
    ▼ [Query Rewrite] (可选,复杂查询启用)
生成改写:
  - "RAG hallucination mitigation techniques"
  - "检索增强生成中的幻觉抑制方法"
  - "如何验证 RAG 输出的准确性"
    │
    ▼ [Hybrid Retrieval]
向量检索 Top-20 + BM25 Top-20 → RRF 融合 → Top-10
    │
    ▼ [Rerank]
Cross-Encoder 对 (query, chunk) 逐对打分 → Top-5
    │
    ▼ [Threshold Filter]
过滤 score < 0.6 的结果(不相关内容)
    │
    ▼ [Prompt Assembly]
System: "你是知识库助手,只基于参考资料回答..."
Context: [chunk1] [chunk2] [chunk3]
Question: "RAG 系统怎么处理幻觉?"
    │
    ▼ [LLM Generation] (streaming)
生成答案 + 标注引用 [1][2][3]
    │
    ▼ [Post-processing]
  - 引用溯源:[1] → source: "rag_notes.md", section: "幻觉抑制"
  - 置信度评分:基于检索分数加权
  - 拒答判断:若所有 context score < 阈值 → "信息不足,无法回答"
    │
    ▼ [Response]
返回答案 + 引用列表 + 置信度

性能与可靠性设计 ​

延迟优化 ​

环节目标延迟优化手段
Embedding< 200ms批量请求、连接池复用
向量检索< 50msHNSW 索引、预热缓存
BM25 检索< 30ms内存倒排索引
重排序< 300ms仅对 Top-10 精排
LLM 生成流式SSE 流式输出,首 token < 1s

可靠性 ​

  • 重试:Embedding/LLM API 调用失败 → 指数退避重试(最多 3 次)
  • 降级:重排序服务不可用 → 跳过精排,直接返回粗排结果
  • 拒答:检索结果全部低于阈值 → 明确返回"信息不足"
  • 超时:context.WithTimeout 控制全链路超时(默认 30s)

可观测 ​

  • Trace:OpenTelemetry 全链路追踪,每个环节耗时可视化
  • Metrics:Prometheus 暴露检索 QPS、延迟分位数、缓存命中率
  • Logging:结构化日志,记录每次检索的 query → results → answer

评估方案 ​

评测指标 ​

指标含义计算方式
Recall@K前 K 个结果中包含正确答案来源的比例命中数 / 总测试数
MRR正确结果首次出现的位置倒数平均1/rank 的平均值
NDCG@K考虑位置加权的排序质量DCG/IDCG
Faithfulness答案中有检索支撑的断言比例supported_claims / total_claims
Answer Relevance答案与问题的相关程度LLM-as-Judge 评分

测试集构建 ​

json
{
  "test_cases": [
    {
      "question": "RAG 系统中如何处理文档分块?",
      "expected_answer": "常见方法包括固定长度分块、递归分割、语义分块...",
      "expected_sources": ["rag_notes.md#chunking"],
      "difficulty": "easy"
    }
  ]
}

评估命令 ​

bash
# 运行全量评估
rag eval --testset=eval/testsets/rag_qa.json --output=eval/results/

# 对比两种配置
rag eval --config=configs/baseline.yaml --config=configs/hybrid.yaml --compare

开发分期 ​

Phase 1:核心管线(MVP) ​

目标:跑通"索引 → 检索 → 生成"最小链路

  • 文档加载器(Markdown)
  • 递归字符分块
  • Embedding 客户端(OpenAI 兼容)
  • 内存向量存储 + 暴力搜索
  • 基础 Prompt 模板 + LLM 生成
  • CLI: rag index + rag ask

Phase 2:检索质量提升 ​

目标:混合检索 + 重排序,Recall@5 达到 0.85+

  • BM25 倒排索引
  • RRF 混合检索融合
  • Multi-Query 查询改写
  • Cross-Encoder 重排序
  • 评估框架 + 基础测试集

Phase 3:生成质量 + 工程化 ​

目标:Faithfulness 达到 0.90+,生产可用

  • 引用溯源 + 幻觉检测
  • 流式输出(SSE)
  • 查询缓存 + Embedding 缓存
  • 置信度评分 + 拒答机制
  • Qdrant 集成
  • 全链路 Trace

Phase 4:Agent 集成 ​

目标:RAG 作为 Agent Tool,支持复杂推理

  • Function Calling Tool 接口
  • 对话记忆管理
  • Query Decomposition(问题分解)
  • HTTP 服务模式(rag serve)

持续学习,持续构建。