Skip to content
RAG(检索增强生成)

RAG(检索增强生成)

如果说 LLM 是 AI 的大脑,那么 RAG 就是它的书架。没有书架的脑子再聪明,也记不住你昨晚写的 Bug 在哪一行。

什么是 RAG?

传统的 LLM(如 GPT-4、Gemini)在训练完成那一刻,它的知识就"锁死"了(即知识截止日期)。而 RAG 的核心思想是:在模型回答问题之前,先去外部资料库里"查一查",把查到的相关信息喂给模型,让它参考着给答案。

RAG 的三个核心步骤

  1. 检索 (Retrieval):当用户提问时,系统先去文档库(PDF、数据库、代码仓库等)里寻找最相关的片段
  2. 增强 (Augmentation):把找出的相关片段和原始问题拼在一起,组成信息量更大的 Prompt
  3. 生成 (Generation):模型阅读参考资料,写出准确、有据可查的回答

RAG 流程

RAG 架构

RAG 解决三大痛点

痛点 RAG 方案
幻觉 (Hallucination) 要求模型"根据已知事实回答",不确定时明确说"参考资料中未提及"
知识实时性 只需更新外部文档库,不需要重新训练模型
数据安全与隐私 公司机密数据存本地 RAG 库,模型只在推理时临时读取,不需要微调

在 AI Agent 中,RAG 的工作流程

1. 数据准备 (The Brain’s Library)

  • 切片 (Chunking):把长文档切成小段
  • 向量化 (Embedding):把文字转成数字向量
  • 向量数据库 (Vector DB):存进 Pinecone、Milvus、Weaviate 等

2. 实时检索 (The Search)

当用户提问 “我们公司去年的前端架构规范是什么?” → Agent 把问题转成向量 → 去数据库里匹配语义最接近的文档片段。

3. 最终输出

Prompt 示例:“你是一个专业的架构师。以下是从公司内部文档检索到的参考资料:[资料A:Vue 3 规范…] [资料B:Go 语言 DDD 实践…]。请根据这些资料回答用户问题:[用户问题]。”

向量:文本的"数字身份证"

向量是把一段文字的语义信息转换成一串固定长度的数字列表,让计算机能"看懂"文字的含义并做相似度计算。

向量维度

向量的维度决定了语义匹配的精准度。例如三维向量 [0.3, 0.32, 0.24],可以规定每一维代表不同主题(情绪、动作、倾向等)的得分强度。

  • 维度越多 → 语义特征记录越精细 → 匹配越精准 → 但计算量越大
  • 一般 1536 维度即可满足大部分场景

余弦相似度

向量的数字序列共同决定了向量在高维空间的方向和长度。余弦相似度抛开长度只考虑方向夹角夹角越小越相似

余弦相似度

公式

$$\cos(\vec{\alpha}, \vec{\beta}) = \frac{\vec{\alpha} \cdot \vec{\beta}}{||\vec{\alpha}|| \times ||\vec{\beta}||}$$

公式图

点积

对应分量相乘再相加。例如 a[1,3], b[2,4] → 点积 = 1×2 + 3×4 = 14

模长

所有分量的平方之和的平方根。

模长公式

例如 a[3,4,2,1] → 模长 = √(9+16+4+1) = √30

Go 实现

package utils

import (
    "errors"
    "math"
)

// CosSim 计算两个向量的余弦相似度
func CosSim(a, b []float64) (float64, error) {
    if len(a) != len(b) {
        return 0.0, errors.New("vector lengths do not match")
    }
    if len(a) == 0 {
        return 0.0, errors.New("vectors are empty")
    }

    normA := Norm(a)
    if normA == 0 {
        return 0.0, errors.New("vector 'a' is a zero vector")
    }

    normB := Norm(b)
    if normB == 0 {
        return 0.0, errors.New("vector 'b' is a zero vector")
    }

    return Product(a, b) / (normA * normB), nil
}

// Product 计算两个向量的点积(内积)
func Product(a, b []float64) float64 {
    result := 0.0
    for i := range a {
        result += a[i] * b[i]
    }
    return result
}

// Norm 计算向量的 L2 范数(模长)
func Norm(a []float64) float64 {
    result := 0.0
    for _, v := range a {
        result += v * v
    }
    return math.Sqrt(result)
}

为什么不直接用"长文本模型"?

现在 Gemini 或 GPT 的上下文窗口越来越大(百万 token 级)。那还需要 RAG 吗?

答案:依然需要。

维度 长上下文 RAG
成本 塞几十万字非常贵 只塞最相关的几千字,省钱
效率 读几十万字需要时间 毫秒级检索
精准度 “中间遗忘"现象 更精准定位信息