03 — RAG 检索增强生成系统

企业AI应用的核心技术:让大模型拥有实时、准确的知识 目标:掌握RAG系统的设计、实现与优化方法


一、RAG 核心概念

1.1 什么是 RAG?

检索增强生成(Retrieval-Augmented Generation,RAG)是一种将外部知识检索大模型生成相结合的技术架构。

flowchart LR
    subgraph 传统LLM["传统大模型"]
        A1["用户查询"] --> A2["LLM直接回答"]
        A2 --> A3["可能过时/幻觉"]
    end
    
    subgraph RAG系统["RAG系统"]
        B1["用户查询"] --> B2["检索知识库"]
        B2 --> B3["获取相关文档"]
        B3 --> B4["注入上下文"]
        B4 --> B5["LLM生成回答"]
        B5 --> B6["准确/可追溯"]
    end
    
    style A3 fill:#ffebee
    style B6 fill:#e8f5e9

1.2 RAG vs Fine-tuning 对比

维度RAGFine-tuning
知识更新实时,添加新文档即可需重新训练模型
计算成本低,主要是向量检索高,需要大量算力
幻觉风险低,基于检索内容生成高,依赖模型记忆
专业适配容易,添加专业文档困难,需高质量训练数据
可解释性强,可追溯引用来源弱,黑盒生成
适用场景知识问答、文档检索风格迁移、特定任务

1.3 RAG 应用场景

mindmap
    root((RAG应用场景))
        企业知识管理
            内部文档问答
            政策制度查询
            技术文档检索
        客户服务
            智能客服
            售后支持
            产品FAQ
        内容创作
            研究报告生成
            新闻综述
            论文摘要
        数据分析
            SQL自动生成
            报告解读
            趋势分析

二、RAG 系统架构

2.1 核心流程

flowchart TB
    subgraph 索引阶段["📚 索引阶段(离线)"]
        A1["文档收集"] --> A2["文档分割"]
        A2 --> A3["文本向量化"]
        A3 --> A4["向量存储"]
    end
    
    subgraph 检索阶段["🔍 检索阶段(在线)"]
        B1["用户查询"] --> B2["查询向量化"]
        B2 --> B3["相似度检索"]
        B3 --> B4["返回Top-K文档"]
    end
    
    subgraph 生成阶段["🤖 生成阶段"]
        B4 --> C1["构建上下文"]
        C1 --> C2["LLM生成"]
        C2 --> C3["返回回答"]
    end
    
    A4 --> B3
    C3 --> C4["可选:反馈学习"]
    
    style A1 fill:#e3f2fd
    style B1 fill:#fff3e0
    style C2 fill:#e8f5e9

2.2 关键技术组件

graph TB
    subgraph 输入层["输入层"]
        I1["原始文档<br/>PDF/Word/HTML/数据库"]
        I2["用户查询<br/>自然语言问题"]
    end
    
    subgraph 处理层["处理层"]
        P1["文档解析器<br/>PdfParser/HtmlParser"]
        P2["文本分割器<br/>Character/Token/Semantic Splitter"]
        P3["Embedding模型<br/>text-embedding-ada-002/BGE"]
        P4["向量数据库<br/>Chroma/Milvus/Pinecone"]
    end
    
    subgraph 生成层["生成层"]
        G1["检索器<br/>向量相似度检索"]
        G2["重排序器<br/>Cross-Encoder ReRank"]
        G3["Prompt模板<br/>上下文拼接"]
        G4["LLM<br/>GPT-4/Claude/DeepSeek"]
    end
    
    I1 --> P1 --> P2 --> P3 --> P4
    I2 --> P3
    P4 --> G1 --> G2 --> G3 --> G4
    
    style P4 fill:#FFD700
    style G4 fill:#FF6B6B,color:#fff

三、文档处理

3.1 文档分割策略

mindmap
    root((文档分割策略))
        固定长度分割
            优点:实现简单
            缺点:可能破坏语义
        语义分割
            基于段落边界
            基于句子边界
            基于语义完整性
        递归分割
            先粗分后细分
            适合长文档
        重叠分割
            相邻chunk有重叠
            避免关键信息丢失
        分层分割
            先分章节再分子节
            层次化检索

3.2 分割参数建议

参数推荐值说明
Chunk Size500-1000 tokens每个文档片段的长度
Chunk Overlap100-200 tokens相邻片段的重叠部分
Separator\n\n (段落)优先在段落边界分割
Min Chunk Size100 tokens小于此值的片段丢弃

四、向量检索优化

4.1 检索策略

flowchart LR
    A["用户查询"] --> B{"检索策略"}
    
    B --> C["向量检索<br/>语义匹配"]
    B --> D["关键词检索<br/>精确匹配"]
    B --> E["混合检索<br/>两者结合"]
    
    C --> F["向量数据库<br/>余弦相似度"]
    D --> G["全文搜索引擎<br/>BM25"]
    E --> H["加权融合<br/>分数归一化"]
    
    F --> I["Top-K结果"]
    G --> I
    H --> I
    I --> J["可选:重排序"]
    
    style H fill:#FFD700
    style J fill:#e8f5e9

4.2 重排序(Re-ranking)

原始检索结果 → 相关性打分 → 重新排序 → 更准确的Top-K

示例:
检索返回:文档A(0.85), 文档B(0.82), 文档C(0.78)
重排序后:文档B(0.92), 文档A(0.88), 文档C(0.75)

4.3 嵌入模型选择

模型维度特点推荐场景
text-embedding-ada-0021536OpenAI官方,通用性强通用RAG系统
all-MiniLM-L6-v2384轻量级,本地部署友好资源受限场景
BGE-large-zh1024中文表现优秀中文RAG系统
E5-large1024专为检索优化高精度检索

五、RAG 系统完整搭建指南(PHP+Vue+UniApp)

针对PHP+Vue+UniApp技术栈团队的完整搭建方案

5.1 项目结构

rag-system/
├── backend/              # PHP后端(Laravel)
│   ├── app/
│   │   ├── Services/
│   │   │   ├── DocumentService.php     # 文档处理服务
│   │   │   ├── RetrievalService.php    # 检索服务
│   │   │   └── RAGService.php          # RAG主服务
│   │   ├── Models/
│   │   │   └── Document.php            # 文档模型
│   │   └── Http/
│   │       ├── Controllers/
│   │       │   ├── DocumentController.php
│   │       │   └── RagController.php
│   │       └── Routes/
│   │           └── api.php
│   ├── config/
│   │   └── rag.php                 # RAG配置
│   └── routes/
│       └── api.php
├── frontend/             # Vue前端管理后台
│   ├── src/
│   │   ├── views/
│   │   │   ├── DocumentManage.vue  # 文档管理
│   │   │   └── RagQuery.vue        # RAG查询
│   │   ├── components/
│   │   │   └── ChatBox.vue         # 聊天组件
│   │   └── api/
│   │       └── rag.js              # API调用
│   └── package.json
├── uniapp/               # UniApp移动端
│   └── pages/
│       └── index.vue           # 查询页面
├── vector-service/       # Python向量服务
│   ├── app.py                  # FastAPI服务
│   ├── embeddings.py           # 嵌入模型
│   └── requirements.txt
└── docker-compose.yml

5.2 核心代码实现

PHP后端 - 文档处理

<?php
 
namespace App\Services;
 
use App\Models\Document;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
 
class DocumentService
{
    protected $embeddingsApiKey;
    protected $vectorHost;
    
    public function __construct()
    {
        $this->embeddingsApiKey = config('rag.embeddings_api_key');
        $this->vectorHost = config('rag.vector_host');
    }
    
    /**
     * 上传并处理文档
     */
    public function uploadDocument($file): array
    {
        // 1. 保存文件
        $path = $file->store('documents');
        
        // 2. 解析文档内容
        $content = $this->parseDocument($path);
        
        // 3. 分割文档
        $chunks = $this->chunkText($content, 800, 150);
        
        // 4. 向量化并存储
        $document = Document::create([
            'original_path' => $path,
            'content' => $content,
            'chunk_count' => count($chunks),
            'status' => 'processing',
        ]);
        
        // 5. 异步处理向量化
        dispatch(function () use ($document, $chunks) {
            $this->vectorizeChunks($document, $chunks);
        });
        
        return [
            'id' => $document->id,
            'chunk_count' => count($chunks),
            'status' => 'processing',
        ];
    }
    
    /**
     * 文本分割
     */
    protected function chunkText(string $text, int $chunkSize = 800, int $overlap = 150): array
    {
        $chunks = [];
        $length = mb_strlen($text);
        
        for ($i = 0; $i < $length; $i += ($chunkSize - $overlap)) {
            $chunk = mb_substr($text, $i, $chunkSize);
            if (mb_strlen($chunk) > 50) { // 过滤过短的片段
                $chunks[] = $chunk;
            }
            if ($i + $chunkSize >= $length) {
                break;
            }
        }
        
        return $chunks;
    }
    
    /**
     * 向量化处理
     */
    protected function vectorizeChunks(Document $document, array $chunks): void
    {
        foreach ($chunks as $index => $chunk) {
            // 调用向量服务
            $response = Http::withHeaders([
                'Authorization' => 'Bearer ' . $this->embeddingsApiKey,
            ])->post($this->vectorHost . '/embed', [
                'model' => 'text-embedding-ada-002',
                'input' => $chunk,
            ]);
            
            if ($response->successful()) {
                $embedding = $response->json()['data'][0]['embedding'];
                
                // 存储到向量数据库
                Http::post($this->vectorHost . '/collections/rag_documents/upsert', [
                    'ids' => ["doc_{$document->id}_chunk_$index"],
                    'embeddings' => [$embedding],
                    'metadatas' => [[
                        'document_id' => $document->id,
                        'chunk_index' => $index,
                        'content' => $chunk,
                    ]],
                ]);
            }
        }
        
        $document->update(['status' => 'processed']);
    }
}

PHP后端 - RAG查询

<?php
 
namespace App\Services;
 
use Illuminate\Support\Facades\Http;
 
class RagService
{
    protected $vectorHost;
    protected $llmApiKey;
    protected $llmModel;
    
    public function query(string $question, int $topK = 3): array
    {
        // 1. 查询向量化
        $embeddingResponse = Http::withHeaders([
            'Authorization' => 'Bearer ' . $this->llmApiKey,
        ])->post($this->vectorHost . '/embed', [
            'model' => 'text-embedding-ada-002',
            'input' => $question,
        ]);
        
        $queryEmbedding = $embeddingResponse->json()['data'][0]['embedding'];
        
        // 2. 向量检索
        $searchResponse = Http::post($this->vectorHost . '/collections/rag_documents/query', [
            'query_embedding' => $queryEmbedding,
            'top_k' => $topK,
            'include_metadata' => true,
        ]);
        
        $results = $searchResponse->json()['results'] ?? [];
        
        // 3. 构建上下文
        $context = $this->buildContext($results);
        
        // 4. LLM生成回答
        $answer = $this->generateAnswer($question, $context);
        
        return [
            'question' => $question,
            'answer' => $answer,
            'sources' => array_map(fn($r) => [
                'content' => $r['metadata']['content'] ?? '',
                'score' => $r['distance'] ?? 0,
            ], $results),
        ];
    }
    
    protected function buildContext(array $results): string
    {
        $context = "";
        foreach ($results as $i => $result) {
            $context .= "[参考文档" . ($i + 1) . "]\n";
            $context .= ($result['metadata']['content'] ?? '') . "\n\n";
        }
        return $context;
    }
    
    protected function generateAnswer(string $question, string $context): string
    {
        $prompt = "请根据以下参考资料回答问题。如果资料中没有相关信息,请说明无法回答。\n\n"
                . "参考资料:\n{$context}\n\n"
                . "问题:{$question}\n\n"
                . "回答:";
        
        $response = Http::withHeaders([
            'Authorization' => 'Bearer ' . $this->llmApiKey,
            'Content-Type' => 'application/json',
        ])->post('https://api.openai.com/v1/chat/completions', [
            'model' => $this->llmModel,
            'messages' => [['role' => 'user', 'content' => $prompt]],
            'temperature' => 0.1,
            'max_tokens' => 1000,
        ]);
        
        return $response->json()['choices'][0]['message']['content'] ?? '暂无回答';
    }
}

5.3 Python向量服务

# vector-service/app.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from sentence_transformers import SentenceTransformer
import chromadb
from chromadb.config import Settings
 
app = FastAPI(title="RAG Vector Service")
 
# 加载嵌入模型
model = SentenceTransformer('all-MiniLM-L6-v2')
 
# 初始化向量数据库
client = chromadb.Client(Settings(
    chroma_db_impl="duckdb+parquet",
    persist_directory="./chroma_db"
))
collection = client.get_or_create_collection("rag_documents")
 
class EmbedRequest(BaseModel):
    model: str
    input: str
 
class QueryRequest(BaseModel):
    query_embedding: list
    top_k: int = 3
    include_metadata: bool = True
 
@app.post("/embed")
async def embed(request: EmbedRequest):
    embedding = model.encode([request.input]).tolist()
    return {"data": [{"embedding": embedding[0]}]}
 
@app.post("/collections/rag_documents/query")
async def query(request: QueryRequest):
    results = collection.query(
        query_embeddings=[request.query_embedding],
        n_results=request.top_k,
        include=["metadatas", "distances"]
    )
    return {"results": results}
 
@app.post("/collections/rag_documents/upsert")
async def upsert(request: dict):
    collection.upsert(
        ids=request["ids"],
        embeddings=request["embeddings"],
        metadatas=request["metadatas"]
    )
    return {"status": "success"}

5.4 Docker Compose 部署

# docker-compose.yml
version: '3.8'
 
services:
  php-app:
    build: ./backend
    ports:
      - "8080:80"
    depends_on:
      - vector-service
      - redis
    environment:
      - VECTOR_HOST=http://vector-service:8000
      - EMBEDDINGS_API_KEY=${EMBEDDINGS_API_KEY}
      - OPENAI_API_KEY=${OPENAI_API_KEY}
  
  vector-service:
    build: ./vector-service
    ports:
      - "8000:8000"
    volumes:
      - vector_data:/app/chroma_db
  
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
  
  frontend:
    build: ./frontend
    ports:
      - "3000:80"
    depends_on:
      - php-app
 
volumes:
  vector_data:

六、常见问题与优化

6.1 检索效果不佳

flowchart TD
    A["检索效果差"] --> B{"问题定位"}
    B --> C["文档分割不合理"]
    B --> D["嵌入模型不匹配"]
    B --> E["检索参数不当"]
    B --> F["上下文窗口不足"]
    
    C --> G["调整chunk大小和重叠"]
    D --> H["更换中文优化模型"]
    E --> I["调整top-k和相似度阈值"]
    F --> J["优化prompt模板"]

6.2 性能优化技巧

优化点方法预期效果
向量缓存Redis缓存高频查询的向量减少90%嵌入计算
批量处理批量上传文档,减少API调用提升5倍处理速度
异步检索检索与生成并行处理降低首屏延迟
分片检索大库分片,缩小检索范围提升检索速度

6.3 降低幻觉

方法实现
增加检索数量从Top-3增加到Top-5
优化提示词明确要求”仅基于检索内容回答”
引用溯源返回时附带来源文档
置信度评分低置信度时提示”无法确定”

七、本章总结

flowchart LR
    A["RAG核心原理<br/>检索+生成"] --> B["文档处理<br/>分割+向量化"]
    B --> C["向量检索<br/>相似度搜索"]
    C --> D["LLM生成<br/>注入上下文"]
    D --> E["优化迭代<br/>效果持续提升"]
    
    style A fill:#e3f2fd
    style B fill:#fff3e0
    style C fill:#e8f5e9
    style D fill:#f3e5f5
    style E fill:#fce4ec

核心结论: RAG = 把”死知识”变成”活答案”。通过检索增强,让LLM获得最新、最准确的领域知识,同时保持生成的灵活性和自然性。


延伸阅读