🎯 引言

1.1 研究背景

随着ChatGPT、GPT-4等大语言模型的出现,人工智能进入了新的发展阶段。然而,纯粹依赖预训练的大语言模型面临三个核心挑战:
  1. 知识时效性问题: 模型训练数据存在时间截止点,无法获取最新信息
  2. 领域专业性不足: 在特定垂直领域缺乏深度知识
  3. 幻觉问题: 模型可能生成看似合理但实际错误的内容

1.2 RAG技术的提出

2020年,Meta AI研究团队在论文《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》中首次系统性地提出了RAG架构。该技术通过将信息检索(Retrieval)系统与生成(Generation)模型相结合,在保持模型参数不变的情况下,动态引入外部知识,从而有效解决上述问题。

🧠 理论基础

3.1 RAG技术概述

3.1.1 定义

RAG(Retrieval-Augmented Generation)是一种结合信息检索(Retrieval)和文本生成(Generation)的技术框架。其核心思想是:**在生成答案之前,先从知识库中检索相关信息,然后将检索结果作为上下文输入到生成模型中**。简单来说,它就像是为大模型配备了一个随时可查阅的“外部知识库”。当用户提出问题时,系统会先从这个知识库中检索相关信息,然后将这些信息作为上下文提供给大模型,最终生成回答。这种机制彻底改变了大模型的工作方式。传统的大模型如同参加闭卷考试的学生,只能依靠训练时记忆的知识;而RAG加持的大模型则像是开卷考试,可以实时查阅参考资料,确保答案的准确性和时效性。

RAG技术的核心建立在向量空间模型(Vector Space Model, VSM)的基础上。在向量空间中,文本被表示为高维向量,相似度通过向量间的距离或角度来衡量。

数学定义:设文档集合D = {d_1, d_2, …, d_n},查询q,则文档d_i与查询q的相似度定义为:

sim(q, d_i) = cos(q, d_i) = (q · d_i) / (||q|| × ||d_i||)

其中:

  • q · d_i 表示向量点积

  • ||q|| 和 ||d_i|| 分别表示向量的L2范数

  • cos(q, d_i) 表示余弦相似度

3.1.2 技术优势

3.2 RAG技术演进:从“基础款”到“乐高式”的三大阶段

在大模型应用开发的江湖里,RAG(检索增强生成)技术如今已是无人不知的“当红炸子鸡”。但如果你以为RAG只是简单“检索+生成”的缝合怪,那就大错特错了。经过行业的持续迭代,RAG已经演化出了一套完整的技术体系,形成了清晰的发展路径:从Naive RAG(朴素RAG) 到 Advanced RAG(高级RAG),再到Modular RAG(模块化RAG)。理解这三者的区别,就如同理解了从“功能机”到“智能机”再到“可折叠屏智能机”的演进逻辑,是你设计下一代AI应用的关键。

第一阶段: Navie RAG朴素版(2020-2021)

这一阶段以Meta AI的原始RAG论文为标志,在索引阶段,Naive RAG的核心任务是如何更好地把知识存起来。收集多源数据构建知识库,使用递归字符分割器等工具将文档切分为适当大小的片段(Chunking),然后通过Embedding模型(如BGE-M3、text-embedding-v1)将这些文本块转换为向量,存入Faiss这类向量数据库中。这个过程,我们称之为“知识的向量化炼金术”。

在检索阶段,它的任务是如何在知识的汪洋中,找到那一小撮最有用的信息。当用户提问时,系统将问题向量化,然后在向量数据库中进行“海底捞针”式的相似度搜索,找到最相关的文本片段。

在生成阶段,它要解决如何结合用户的提问和检索到的知识,让模型“有据可依”地生成答案。检索到的文本片段会和原始问题拼接成一个增强的Prompt,送入LLM(如DeepSeek-V3、Qwen-turbo)中,最终生成答案。Naive RAG的“朴素”也恰恰是它的阿喀琉斯之踵。它的整个流程是线性的、僵化的,缺乏反馈和优化,这就导致它在实际应用中很容易“翻车”。

第二阶段:Advanced RAG进阶版

为了填平Naive RAG的坑(**检索质量不稳定、“垃圾进,垃圾出”**),Advanced RAG应运而生。它的核心思想不再是简单的线性流水线,而是在核心流程中嵌入了多个**优化模块**,对检索和生成的全链路进行“精装修”。Advanced RAG可以概括为 **“预处理 + 优化检索 + 后处理”** 的增强范式。

1. 预处理:数据进入向量数据库之前会先进入数据准备阶段,首先**数据清洗与治理,**包括去重、纠错、更新和一致性检查。同时,通过识别和脱敏PII等敏感信息,确保合规性,好比给数据上了“安全锁”。然后再**更智能的切片(Chunking),**会根据文档类型,灵活运用PDF中提到的**语义切片**、**LLM语义切片**或**层次切片**等策略。例如,处理法律合同可能用层次切片保持结构,处理技术手册可能用LLM语义切片以保证每个块的意思完整。这就是在打造更高质量的“知识砖块”。

2. 检索阶段:精准制导、拒绝跑偏

Advanced RAG相当于给电脑加装了**水冷散热、RGB灯效和智能调速器**,通过全链路的精细化操作,让系统变得可靠又好用。
  • 查询转换(Query Transformation):Naive RAG对用户Query是“原样照搬”,而Advanced RAG则像一个贴心的“查询翻译官”。当用户提出“如何申请信用卡?”这种模糊问题时,它会通过意图识别,将查询扩展为“信用卡申请的具体步骤”、“所需材料”或“资格条件”等更精确的查询。
  • 混合检索(Hybrid Search):Advanced RAG信奉“不把鸡蛋放在一个篮子里”。它结合了向量检索(语义相似度)和关键词检索(如BM25),实现优势互补。向量检索负责捕捉语义相关性,关键词检索保证术语匹配的精确度。
  • 重排序(Re-ranking):初步检索出一批文档后,Advanced RAG不会直接全部扔给LLM。它会使用一个更精细的、计算量更小的“裁判”模型(Reranker)对结果进行二次排序,筛选出Top-K中最相关的那几个,从而减少噪音,提升上下文质量。

第三阶段:Modular RAG“乐高版”无限可能

如果说Advanced RAG是“精装修房”,那么Modular RAG就是可以自由设计的“概念屋”。它不再遵循固定的流水线,而是将RAG流程中的每一个步骤都**模块化、组件化**,允许开发者像搭乐高一样,自由组合、替换甚至创造新的模块,从而构建出适应极端复杂场景的、高度定制化的RAG系统。下图有一个更详细的展开,感兴趣的小伙伴们可以详细看看在自己上手搭建个玩一玩。

核心思想:Modular RAG的架构可以表示为:RAG = Modules + Flow。它的核心是一个编排框架,负责调度和执行一个个独立的模块。工作流大概可以理解为

  • 极致灵活性:可以根据业务场景“量体裁衣”。例如,处理金融风控查询时,可以引入知识图谱模块来追踪实体关系;做多跳问答时,可以引入“递归检索”模块,让模型根据初步结果提出新问题,进行下一轮检索。
  • 强大的复杂问题处理能力:通过模块的串联、并联和递归调用,Modular RAG能够解决Naive和Advanced RAG束手无策的复杂推理任务。
  • 易于迭代与维护:某个模块(如Embedding模型)需要升级时,只需替换该组件,无需改动整个系统,符合高内聚、低耦合的软件工程原则。

技术演进对比总结:

3.3 核心原理与架构

3.3.1 经典RAG架构

用户查询 (Query)    ↓[查询理解] ← 改写、扩展、分解    ↓[查询向量化] ← Embedding Model    ↓[向量检索] ← Vector Database (FAISS/Milvus/Pinecone)    ↓[文档重排序] ← Reranker Model (可选)    ↓[Top-K 相关文档]    ↓[上下文构建] ← Query + Retrieved Documents    ↓[LLM生成] ← GPT-4/Claude/DeepSeek    ↓[答案输出] + [来源引用]

3.4 与传统方法的对比

提示工程 vs RAG vs 微调

看到图片上在大模型方法对比一下,有很多时候大模型答案不尽人意,有可能是因为这三种情况。我相信大家也都知道微调,但大部分时候微调并不是最好的选择,往往大模型的能力其实都差不多。知识储备量都比较广泛。所以其实现状来说真正需要微调的模型落地的数量不到5%。微调的成本其实很高的,每一次微调需要进行4-6轮。一个大概3b左右的小模型每轮至少32个小时,大概需要七天,这些都需要大量的显卡资源的占用。其中还需要大量的清洗数据,占用人力成本。所以RAG确实是一个很伟大的发明。

🏗️ 技术架构详解

4.1 数据预处理层

上面一直在说RAG的演变的神奇之处,下面展开分析他到底是如何工作的,文本切片如何判定长短?Top-k中的k具体应该去什么样的值比较好呢?

4.1.1 文档解析

支持多种格式的文档解析:

  • PDF: PyPDF2, PDFMiner, PyMuPDF
  • Word: python-docx
  • 网页: BeautifulSoup, Scrapy
  • Markdown: markdown库
  • 图片: OCR (Tesseract, PaddleOCR)

4.1.2 文本切片策略

  1. 固定长度切片

固定长度切片是最基础的文本分割策略,其核心思想是将文档按照预定义的字符数量进行均匀分割。这种方法在计算效率和实现简单性方面具有优势,但可能破坏语义的完整性。

学术背景:固定长度切片源于传统信息检索中的文档分块技术,最早应用于搜索引擎的索引构建。在RAG系统中,这种方法特别适用于结构化程度较高的文档,如技术手册、法律条文等。 - 优点: 简单高效,计算效率高,内存占用可控 - 缺点: 可能破坏语义完整性,边界处理复杂。

# 固定长度切片示例def fixed_length_split(text, chunk_size=500, overlap=50):"""    固定长度文本切片函数    参数说明:    - text: 待分割的原始文本    - chunk_size: 每个切片的目标长度(字符数)    - overlap: 相邻切片之间的重叠长度    算法复杂度:O(n),其中n为文本长度    空间复杂度:O(k),其中k为切片数量    """    chunks = []    start = 0    while start < len(text):        end = start + chunk_size        chunk = text[start:end]        chunks.append(chunk)        start = end - overlap  # 重叠部分    return chunks
  1. 语义切片

语义切片是一种基于语义理解的文本分割策略,其核心目标是在保持语义完整性的前提下进行文本分割。这种方法通过分析文本的语义结构,在自然的分割点(如段落、句子边界)进行切分。

学术背景:语义切片技术源于自然语言处理中的文本分割研究,最早应用于文档摘要和信息提取任务。在RAG系统中,语义切片能够显著提升检索的准确性和生成质量。

技术原理:语义切片通常基于以下策略: 1. 句子边界检测:利用句法分析器识别句子边界 2. 语义相似度计算:使用预训练模型计算相邻文本的语义相似度3. 动态阈值调整:根据文档类型调整相似度阈值

优缺点分析: - 优点: 保持语义完整性,提升检索精度,减少噪声 - 缺点: 计算复杂度高,需要预训练模型,参数调优复杂

# 语义切片示例# 该函数实现了基于句子边界的语义切片算法# 通过分析句子间的语义关系来确定最佳分割点def semantic_split(text, max_chunk_size=1000, similarity_threshold=0.7):    """    语义感知的文本切片函数    参数说明:    - text: 待分割的原始文本    - max_chunk_size: 最大切片长度限制    - similarity_threshold: 语义相似度阈值    算法流程:    1. 句子分割:将文本分割为句子    2. 语义计算:计算相邻句子的语义相似度    3. 动态切分:根据相似度阈值进行切分    4. 长度控制:确保切片不超过最大长度    时间复杂度:O(n²),其中n为句子数量    """import refrom sentence_transformers import SentenceTransformer# 初始化语义模型(实际应用中应预加载)    model = SentenceTransformer('all-MiniLM-L6-v2')# 步骤1:句子分割    sentences = re.split(r'[.!?。!?]', text)    sentences = [s.strip() for s in sentences if s.strip()]    chunks = []    current_chunk = []    current_length = 0for i, sentence in enumerate(sentences):# 步骤2:计算语义相似度        if current_chunk:# 计算当前句子与上一个句子的相似度            similarity = model.encode([current_chunk[-1], sentence])            similarity_score = similarity[0] @ similarity[1] / (                np.linalg.norm(similarity[0]) * np.linalg.norm(similarity[1])            )# 步骤3:基于相似度进行切分决策            if (similarity_score < similarity_threshold or                current_length + len(sentence) > max_chunk_size):# 开始新的切片                if current_chunk:                    chunks.append(' '.join(current_chunk))                current_chunk = [sentence]                current_length = len(sentence)else:# 继续当前切片                current_chunk.append(sentence)                current_length += len(sentence)else:# 第一个句子            current_chunk = [sentence]            current_length = len(sentence)# 添加最后一个切片    if current_chunk:        chunks.append(' '.join(current_chunk))return chunks
# 语义切片示例(基于句子边界)def semantic_split(text, max_chunk_size=500):    sentences = text.split('。')    chunks = []    current_chunk = ""for sentence in sentences:if len(current_chunk) + len(sentence) < max_chunk_size:            current_chunk += sentence + '。'else:if current_chunk:                chunks.append(current_chunk)            current_chunk = sentence + '。'if current_chunk:        chunks.append(current_chunk)return chunks
  1. 滑动窗口切片 - 优点: 平衡效率和完整性 - 缺点: 会有内容重复 - 适用: 通用场景(推荐)

  2. 层次切片 - 按文档结构(标题、段落)切分 - 保留文档层次信息 - 适用: 结构化文档

下面这两个我就不给出具体的代码了,大家要使用的话可以改一改上面的代码。要是不太会的话可以找一下Trae帮帮忙。

4.2 向量化与索引层

4.2.1 嵌入模型生态与选型考量:性能与效率的平衡

在RAG系统中,Embedding模型扮演着将文本转换为向量表示的关键角色,就像是不同语言之间的翻译官。选择合适的Embedding模型对整个系统的性能至关重要。一个优秀的嵌入模型能够准确地捕捉文本块的语义,确保查询向量能够命中真正相关的知识片段。

目前市面上的Embedding模型各具特色。例如,智源研究院BGE-M3支持100多种语言,输入长度达8192个token,适合跨语言长文档检索;text-embedding-3-large在英文表现上尤为出色;Jina-embeddings-v2-small参数量仅35M,支持实时推理,适合轻量化部署。

针对中文场景,我们有xiaobu-embedding-v2、M3E-Base等针对中文优化的模型。M3E-Base(针对中文优化的轻量级模型)特别适合中文法律、医疗领域的检索任务,而stella-mrl-large-zh-v3.5-1792则擅长处理大规模中文数据,能捕捉细微的语义关系。

对于复杂任务,gte-Qwen2-7B-instruct基于Qwen大模型微调,支持代码与文本跨模态检索;E5-mistral-7B在Zero-shot任务上表现优异,适合需要动态调整语义密度的复杂系统。

选择Embedding模型时,需要综合考虑任务需求、性能表现、资源消耗等因素,没有一刀切的最优解,只有最适合的解决方案。

中文Embedding模型对比

# DashScope Embedding API调用from dashscope import TextEmbeddingimport dashscopedashscope.api_key = "your-api-key"response = TextEmbedding.call(    model=TextEmbedding.Models.text_embedding_v1,    input=["你好,世界", "检索增强生成"])embeddings = [item['embedding'] for item in response.output['embeddings']]
# 本地BGE-M3模型使用from FlagEmbedding import BGEM3FlagModelmodel = BGEM3FlagModel('BAAI/bge-m3', use_fp16=True)sentences = ["什么是RAG技术?", "如何构建知识库?"]embeddings = model.encode(sentences)['dense_vecs']# 计算相似度similarity = embeddings[0] @ embeddings[1].Tprint(f"相似度: {similarity}")

4.2.2 向量数据库

数据库 类型 特点 适用规模
FAISS 本地库 高性能,Meta开源 中小规模
Milvus 分布式 企业级,云原生 大规模
Qdrant 服务 Rust实现,高效 中等规模
Pinecone 云服务 托管服务,易用 任意规模
Chroma 本地库 开发友好 小规模

FAISS索引类型选择:

import faissimport numpy as npdimension = 1536# 向量维度vectors = np.random.random((10000, dimension)).astype('float32')# 1. 暴力搜索(精确但慢)index_flat = faiss.IndexFlatL2(dimension)index_flat.add(vectors)# 2. IVF索引(速度快,近似搜索)nlist = 100  # 聚类中心数quantizer = faiss.IndexFlatL2(dimension)index_ivf = faiss.IndexIVFFlat(quantizer, dimension, nlist)index_ivf.train(vectors)index_ivf.add(vectors)# 3. HNSW索引(高性能图索引)index_hnsw = faiss.IndexHNSWFlat(dimension, 32)index_hnsw.add(vectors)# 检索示例query = np.random.random((1, dimension)).astype('float32')k = 5# 返回top-5distances, indices = index_flat.search(query, k)print(f"最近邻索引: {indices[0]}")print(f"距离: {distances[0]}")

4.3 检索层

4.3.1 检索策略

  1. 密集检索(Dense Retrieval) - 基于向量相似度 - 语义理解能力强 - 适合大多数场景

  2. 稀疏检索(Sparse Retrieval) - 基于关键词匹配(BM25) - 精确匹配效果好 - 适合术语准确的场景

  3. 混合检索(Hybrid Retrieval)

defhybrid_search(query, text_index, bm25_index, alpha=0.7):    """    混合检索:结合向量检索和BM25    alpha: 向量检索权重 (0-1)    """# 向量检索    query_vec = get_embedding(query)    dense_scores, dense_indices = text_index.search(query_vec, k=20)# BM25检索    bm25_scores = bm25_index.get_scores(query.split())# 融合分数    final_scores = {}for idx, score in zip(dense_indices[0], dense_scores[0]):        final_scores[idx] = alpha * (1 / (1 + score))  # 转换为相似度    for idx, score in enumerate(bm25_scores):if idx in final_scores:            final_scores[idx] += (1 - alpha) * scoreelse:            final_scores[idx] = (1 - alpha) * score# 排序返回    sorted_results = sorted(final_scores.items(),                           key=lambda x: x[1],                           reverse=True)[:10]return sorted_results

4.3.2 重排序(Reranking)

from sentence_transformers import CrossEncoder# 加载重排序模型reranker = CrossEncoder('BAAI/bge-reranker-base')defrerank_documents(query, documents, top_k=3):    """使用CrossEncoder对检索结果重排序"""    # 构建查询-文档对    pairs = [[query, doc] for doc in documents]    # 计算相关性分数    scores = reranker.predict(pairs)    # 排序    ranked_indices = np.argsort(scores)[::-1][:top_k]    ranked_docs = [documents[i] for i in ranked_indices]    return ranked_docs, scores[ranked_indices]

4.4 生成层

4.4.1 提示词工程

标准RAG提示词模板:

defbuild_rag_prompt(query, context_docs, max_context_length=2000):    """构建RAG提示词"""    # 组装上下文    context = "\n\n".join([        f"[文档{i+1}]\n{doc[:max_context_length]}"        for i, doc in enumerate(context_docs)    ])    prompt = f"""你是一个专业的AI助手,请基于以下参考资料回答用户问题。参考资料:{context}用户问题: {query}要求:1. 仅基于参考资料回答,不要编造信息2. 如果参考资料中没有相关信息,请明确说明3. 给出答案的同时,标注信息来源(如:根据文档1...)4. 答案要准确、简洁、专业答案:"""    return prompt

4.4.2 LLM调用

from openai import OpenAIclient = OpenAI(    api_key="your-api-key",    base_url="https://api.deepseek.com")defgenerate_answer(prompt, model="deepseek-chat", temperature=0.7):    """调用LLM生成答案"""    response = client.chat.completions.create(        model=model,        messages=[            {"role": "system", "content": "你是一个专业的RAG助手。"},            {"role": "user", "content": prompt}        ],        temperature=temperature,        max_tokens=2000    )    return response.choices[0].message.content

💼 实战案例一:基于FAISS的高校本地知识库系统

5.1 案例背景

企业内部存在大量PDF文档(合同、政策、技术文档等),员工需要快速查询特定信息。传统方法是人工翻阅文档,效率低下。该案例搭建一个基于DeepSeek大模型和Faiss向量数据库的本地知识库检索系统为例,详细展示了Native RAG的实现流程,特别是向量索引的建立和管理。该系统的目标是能够从企业内部的PDF文档中提取信息并进行精准问答。,能够: - 自动解析PDF文档 - 构建向量索引 - 准确回答用户问题 - 标注信息来源页码

大家可以去百度文库找一找相关的文档也自己试一下。

该本地知识库问答项目采用了典型的RAG架构,并在技术栈上侧重于高性能的本地化组件和LangChain编排框架。

  • 生成模型(LLM):

    选用deepseek-v3(通过Tongyi API调用)进行答案生成。

  • 嵌入模型(Embedding):

    采用阿里云DashScope的text-embedding-v1模型,用于将文本块转换为向量。

  • 向量数据库/库:

    选用Facebook AI Similarity Search (FAISS) 作为核心向量检索引擎。FAISS以其卓越的高效性和速度,在向量检索领域占据核心地位,非常适合本地化、高性能的相似度搜索。

5.2 技术架构

┌───────────────────────────────────────────────────┐│                    用户交互层                      ││  Web界面 / API接口 / 移动端应用                     │└─────────────────────┬─────────────────────────────┘                      │┌─────────────────────▼─────────────────────────────┐│                    业务逻辑层                      ││  查询处理 / 结果排序 / 答案生成 / 来源追溯            │└─────────────────────┬─────────────────────────────┘                      │┌─────────────────────▼──────────────────────────────┐│                    数据存储层                       ││  FAISS向量索引 / 文档存储 / 元数据管理                 │└─────────────────────────────────────────────────────

技术选型理由

5.3 知识索引管线设计与FAISS向量存储

文档处理:

采用PyPDF2库进行PDF文本内容的提取,并配合RecursiveCharacterTextSplitter进行文本块的切割。其次是文本分割策略。考虑到PDF文档通常较长,必须将文本切割成大小适中的片段(Chunk)。本案例采用了递归字符分割器(RecursiveCharacterTextSplitter),这是LangChain中推荐的分割策略,通过设定chunk_size为1000和chunk_overlap为200,并在分割符上设定优先级(段落、句子、空格、字符),以在保持语义连贯性的同时,确保片段长度可控。

FAISS索引创建与持久化:

文本块准备完成后,下一步是向量化。DashScope嵌入模型将每个文本块转换为向量表示。随后,这些向量被存储到FAISS索引结构中,形成一个高效的检索层。 由于FAISS本质上是一个高性能向量搜索库,而非完整的向量数据库服务,因此它依赖于用户进行数据持久化。系统需要将FAISS索引文件(例如.faiss文件)和相关的元数据信息(例如文本块与页码的映射关系,存储在.pkl文件中)保存到本地磁盘,以供后续查询时加载使用。

[我在考虑要不要单独出一期把这些小的要使用到的模块和库单独列出来,要的小伙伴们扣个1留言告诉我呗~]

5.4 核心代码实现

  1. PDF解析与页码追踪
page(c_j) = k, where Σ(i=1tok-1) l_i < j ≤ Σ(i=1tok) l_i
from PyPDF2 import PdfReaderfrom typing import List, Tupledefextract_text_with_page_numbers(pdf) -> Tuple[str, List[int]]:"""    从PDF中提取文本并记录每个字符对应的页码    这是实现来源追溯的关键    """    text = ""    char_page_mapping = []for page_number, page in enumerate(pdf.pages, start=1):        extracted_text = page.extract_text()if extracted_text:            text += extracted_text# 为当前页面的每个字符记录页码            char_page_mapping.extend([page_number] * len(extracted_text))return text, char_page_mapping# 使用示例pdf_reader = PdfReader('./document.pdf')text, char_page_mapping = extract_text_with_page_numbers(pdf_reader)print(f"文本长度: {len(text)}, 页码映射长度: {len(char_page_mapping)}")
  1. 智能文本切片与页码映射

该算法采用递归字符分割策略,通过优先级递减的分隔符列表来寻找最佳分割点。同时,通过统计方法确定每个文本块的主要页码,实现精确的来源追溯。

数学表示:设文本T被分割为n个块{C_1, C_2, …, C_n},每个块C_i的页码映射函数为:

page(C_i) = mode({page(c_j) | c_j ∈ C_i})

其中mode表示众数函数,选择出现频率最高的页码。

from langchain.text_splitter import RecursiveCharacterTextSplitterimport pickledefprocess_text_with_splitter(text: str,                               char_page_mapping: List[int],                               save_path: str = None):"""    切分文本并保持页码映射关系    """# 创建智能分割器    text_splitter = RecursiveCharacterTextSplitter(        separators=["\n\n", "\n", ".", " ", ""],  # 优先级递减        chunk_size=1000,        # 每块1000字符        chunk_overlap=200,      # 重叠200字符,避免语义割裂        length_function=len,    )# 分割文本    chunks = text_splitter.split_text(text)    print(f"文本被分割成 {len(chunks)} 个块")# 为每个文本块找到对应的页码(关键!)    page_info = {}    current_pos = 0for chunk in chunks:        chunk_start = current_pos        chunk_end = current_pos + len(chunk)# 找到这个文本块中字符对应的页码        chunk_pages = char_page_mapping[chunk_start:chunk_end]# 取页码的众数(出现最多的页码)        if chunk_pages:            page_counts = {}for page in chunk_pages:                page_counts[page] = page_counts.get(page, 0) + 1            most_common_page = max(page_counts, key=page_counts.get)            page_info[chunk] = most_common_page        current_pos = chunk_endreturn chunks, page_info
  1. FAISS向量库构建

数学表示:设向量集合V = {v_1, v_2, …, v_n},查询向量q,则相似度搜索的目标是找到:

argmax_{i} sim(q, v_i)

其中sim表示相似度函数(通常为余弦相似度或L2距离)。

from langchain_community.embeddings import DashScopeEmbeddingsfrom langchain_community.vectorstores import FAISSimport osimport pickleimport numpy as npfrom typing import List, Dict, Anydefbuild_vector_store(chunks: List[str],                       page_info: Dict[str, Any],                       save_path: str = "./vector_db") -> FAISS:"""    FAISS向量库构建与持久化算法    算法描述:    该函数实现了基于FAISS的向量数据库构建算法,是RAG系统的    核心存储组件。通过将文本块转换为向量并建立高效索引,    实现毫秒级的相似度搜索。    技术特点:    1. 高效索引:使用FAISS的IVF索引算法    2. 批量处理:支持大规模文本块的批量向量化    3. 持久化存储:支持索引的保存和加载    4. 元数据管理:保持页码信息的完整性    性能指标:    - 索引构建速度:~1000 vectors/second    - 查询延迟:<10ms (1M vectors)    - 内存效率:比原始向量节省50-80%    算法流程:    1. 初始化嵌入模型    2. 批量向量化文本块    3. 构建FAISS索引    4. 附加元数据信息    5. 持久化存储    """# 步骤1:初始化嵌入模型    # DashScope是阿里云提供的企业级嵌入服务    # 具有高可用性和中文优化特性    embeddings = DashScopeEmbeddings(        model="text-embedding-v1",  # 使用v1版本,支持1536维向量        dashscope_api_key=os.getenv("DASHSCOPE_API_KEY")    )    print(f"开始处理 {len(chunks)} 个文本块...")# 步骤2:批量向量化    # FAISS.from_texts内部实现了批量处理优化    # 相比逐个处理,批量处理可提升3-5倍效率    knowledge_base = FAISS.from_texts(        texts=chunks,        embedding=embeddings,# FAISS内部参数优化        metadatas=[{"chunk_id": i, "page_info": page_info.get(chunks[i], {})}for i in range(len(chunks))]    )    print("✓ FAISS向量库创建完成")    print(f"  - 向量数量: {knowledge_base.index.ntotal}")    print(f"  - 向量维度: {knowledge_base.index.d}")# 步骤3:附加页码信息    # 将页码信息作为元数据附加到向量库    knowledge_base.page_info = page_info# 步骤4:持久化存储    # 创建保存目录    os.makedirs(save_path, exist_ok=True)# 保存FAISS索引    knowledge_base.save_local(save_path)# 单独保存页码信息(便于后续加载)    page_info_path = os.path.join(save_path, "page_info.pkl")with open(page_info_path, "wb") as f:        pickle.dump(page_info, f)    print(f"✓ 向量库已保存到: {save_path}")    print(f"  - 索引文件: {save_path}/index.faiss")    print(f"  - 页码信息: {page_info_path}")return knowledge_base# 向量库加载函数def load_vector_store(load_path: str = "./vector_db") -> FAISS:"""    加载已保存的FAISS向量库    算法描述:    该函数实现了FAISS向量库的加载功能,支持从磁盘快速恢复    已构建的向量索引,避免重复构建的开销。    性能特点:    - 加载速度:~100MB/s    - 内存占用:与原始索引相同    - 兼容性:支持跨平台加载    """try:# 加载FAISS索引        embeddings = DashScopeEmbeddings(            model="text-embedding-v1",            dashscope_api_key=os.getenv("DASHSCOPE_API_KEY")        )        knowledge_base = FAISS.load_local(            load_path,            embeddings,            allow_dangerous_deserialization=True        )# 加载页码信息        page_info_path = os.path.join(load_path, "page_info.pkl")if os.path.exists(page_info_path):with open(page_info_path, "rb") as f:                knowledge_base.page_info = pickle.load(f)        print(f"✓ 向量库加载成功")        print(f"  - 向量数量: {knowledge_base.index.ntotal}")        print(f"  - 向量维度: {knowledge_base.index.d}")return knowledge_baseexcept Exception as e:        print(f"✗ 向量库加载失败: {e}")returnNone# 向量库性能测试def benchmark_vector_store(knowledge_base: FAISS,                          test_queries: List[str],                          k_values: List[int] = [5, 10, 20]) -> Dict[str, Any]:"""    向量库性能基准测试    测试指标:    1. 查询延迟:不同k值下的平均响应时间    2. 内存使用:索引的内存占用情况    3. 准确率:检索结果的相关性评估    """import time    results = {}for k in k_values:        times = []for query in test_queries:            start_time = time.time()            docs = knowledge_base.similarity_search(query, k=k)            end_time = time.time()            times.append(end_time - start_time)        results[f'k_{k}'] = {'avg_time': sum(times) / len(times),'min_time': min(times),'max_time': max(times)        }# 内存使用统计    results['memory_usage'] = {'index_size': knowledge_base.index.ntotal * knowledge_base.index.d * 4,  # 假设float32        'total_vectors': knowledge_base.index.ntotal,'vector_dimension': knowledge_base.index.d    }return results# 使用示例chunks = ["文档片段1", "文档片段2", ...]  # 实际文本块page_info = {"文档片段1": {"page": 1, "confidence": 0.9}, ...}  # 实际页码信息# 构建向量库vector_store = build_vector_store(chunks, page_info, "./my_vector_db")# 性能测试test_queries = ["什么是RAG技术?", "如何构建向量索引?"]performance_results = benchmark_vector_store(vector_store, test_queries)print("性能测试结果:")for metric, value in performance_results.items():    print(f"  {metric}: {value}")
  1. 智能问答与来源追溯

    在检索阶段获取到最相关的Top-K文档块后,下一步是通过问答链(QA Chain)将这些上下文与用户查询结合,送入LLM进行答案生成。LangChain框架提供了四种核心的问答链类型,用于处理上下文和查询的组合方式:

  2. Stuff: 这是最直接的方法,将所有检索到的文档块直接打包(stuff)到一个Prompt中提交给LLM。

  • 适用场景:文档拆分得足够小,且检索到的文档数量不多,一次性能放入LLM的上下文窗口内。它的优势在于调用LLM的次数最少,效率高。

2. Map_Reduce: 对于每个文档块,分别用一个Prompt(生成回答或摘要)进行处理,然后对所有独立的结果进行合并(Reduce)。

  • 适用场景:文档数量过多,无法一次性放入上下文窗口。缺点是文档块之间缺少上下文关联,且LLM调用次数多。

3. Refine: 采用迭代细化的方式。首先在第一个文档块上生成结果,然后将该结果和下一个文档块作为输入,让LLM进行细化(Refine),逐步迭代生成最终答案。

  • 适用场景:能够部分保留上下文,并且Token的使用在一定范围内可控。

4. Map_Rerank: 对每个文档块独立进行处理和问答,同时要求LLM给出一个置信度分数(Rerank)。系统最终返回得分最高的文档块及其答案。

  • 适用场景:会大量调用LLM,每个文档之间独立处理,适用于需要快速识别最可靠信息的场景。

    在DeepSeek + Faiss的本地知识库案例中,为了追求实现的简易性和成本效率,采用了stuff策略。通过精确控制文本块大小和检索数量/K/,确保输入符合LLM的上下文限制,从而简化了整个问答流程。

数学表示:设查询q,检索到的文档集合D = {d_1, d_2, …, d_k},则答案生成函数为:

answer = LLM(prompt(q, D))

其中prompt是上下文构建函数。

from langchain_community.llms import Tongyifrom langchain.chains.question_answering import load_qa_chainfrom typing import List, Dict, Anyimport redefanswer_with_sources(knowledge_base: FAISS,                        query: str,                        top_k: int = 10) -> Dict[str, Any]:"""    智能问答与来源追溯算法    算法描述:    该函数实现了RAG系统的核心问答功能,通过检索相关文档、    构建上下文、生成答案并标注来源,实现高质量的智能问答。    技术特点:    1. 多文档检索:检索多个相关文档片段    2. 上下文优化:智能构建LLM输入上下文    3. 来源追溯:准确标注答案来源页码    4. 质量评估:评估答案的可信度    性能指标:    - 检索精度:Top-5准确率 >85%    - 生成质量:BLEU分数 >0.7    - 来源准确率:>95%    """# 步骤1:相似度检索    # 使用FAISS进行高效的向量相似度搜索    docs = knowledge_base.similarity_search(query, k=top_k)    print(f"检索到 {len(docs)} 个相关文档片段")# 步骤2:提取页码信息    page_sources = []for i, doc in enumerate(docs):# 从文档内容中提取页码信息        page_info = knowledge_base.page_info.get(doc.page_content, {})if isinstance(page_info, dict):            page_num = page_info.get('page', '未知')            confidence = page_info.get('confidence', 0.0)else:            page_num = page_info if isinstance(page_info, int) else'未知'            confidence = 1.0        page_sources.append({'content': doc.page_content,'page': page_num,'confidence': confidence,'rank': i + 1        })# 步骤3:构建优化的上下文    context = build_optimized_context(query, page_sources)# 步骤4:初始化LLM    llm = Tongyi(        model_name="qwen-turbo",        temperature=0.1,  # 低温度确保答案稳定性        max_tokens=1000    )# 步骤5:生成答案    prompt_template = f"""    基于以下文档内容回答问题,并在答案中标注信息来源页码:    文档内容:    {context}    问题:{query}    要求:    1. 基于文档内容给出准确答案    2. 在答案中标注关键信息的来源页码    3. 如果文档中没有相关信息,请明确说明    4. 保持答案的简洁性和准确性    答案:    """    try:        answer = llm(prompt_template)# 步骤6:后处理和来源验证        processed_answer = post_process_answer(answer, page_sources)return {'answer': processed_answer['answer'],'sources': processed_answer['sources'],'confidence': calculate_answer_confidence(answer, page_sources),'retrieved_docs': page_sources        }except Exception as e:return {'answer': f"抱歉,生成答案时出现错误:{str(e)}",'sources': [],'confidence': 0.0,'retrieved_docs': page_sources        }defbuild_optimized_context(query: str, page_sources: List[Dict]) -> str:"""    构建优化的上下文    优化策略:    1. 按相关度排序文档    2. 限制上下文长度    3. 添加页码标注    """    context_parts = []for i, source in enumerate(page_sources[:5]):  # 限制前5个最相关的文档        context_parts.append(f"文档{i+1} (第{source['page']}页):\n{source['content']}\n")return"\n".join(context_parts)defpost_process_answer(answer: str, page_sources: List[Dict]) -> Dict[str, Any]:"""    后处理答案,提取来源信息    处理步骤:    1. 提取答案中的页码引用    2. 验证页码的有效性    3. 构建来源列表    """# 使用正则表达式提取页码引用    page_pattern = r'第(\d+)页'    page_matches = re.findall(page_pattern, answer)# 验证页码的有效性    valid_sources = []for page_num in page_matches:        page_num = int(page_num)# 检查页码是否在检索结果中        for source in page_sources:if source['page'] == page_num:                valid_sources.append(source)breakreturn {'answer': answer,'sources': valid_sources    }defcalculate_answer_confidence(answer: str, page_sources: List[Dict]) -> float:"""    计算答案的可信度    评估因素:    1. 来源文档的相关度    2. 答案的完整性    3. 页码引用的准确性    """# 基础可信度:基于来源文档的平均置信度    base_confidence = sum(source['confidence'] for source in page_sources) / len(page_sources)# 完整性奖励:答案长度适中    length_factor = min(len(answer) / 200, 1.0)  # 假设200字符为理想长度    # 来源引用奖励:答案中包含页码引用    citation_factor = 1.0 if '第' in answer and '页' in answer else 0.8    final_confidence = base_confidence * 0.6 + length_factor * 0.2 + citation_factor * 0.2    return min(final_confidence, 1.0)# 使用示例knowledge_base = load_vector_store("./vector_db")  # 加载向量库query = "什么是RAG技术?"result = answer_with_sources(knowledge_base, query, top_k=5)print(f"问题: {query}")print(f"答案: {result['answer']}")print(f"可信度: {result['confidence']:.2f}")print(f"来源页码: {[s['page'] for s in result['sources']]}")

5.5 性能优化

优化项 方法 效果
索引速度 使用IVF索引 提升10x
内存占用 向量量化 减少4x
检索精度 增加chunk_overlap +15%
来源准确率 字符级页码映射 99%+

🎨 实战案例二:多模态迪士尼RAG助手

6.1 案例背景

迪士尼客服需要回答游客关于园区、门票、活动等各类问题。知识库包含Word文档、图片(地图、价格表)等多模态内容。本案例实现了一个支持文本+图像检索的智能客服助手。

这个项目面临的挑战颇具代表性:**知识来源多样化**,包含PDF、Word、网页公告等多种格式;**非结构化数据的有效处理**,需要有效提取和理解表格与图片信息;**知识有效组织**困难,需要将海量零散的知识点合理切片并建立索引;保证答案有效性挑战大,需要让生成的答案严格基于检索内容,避免模型幻觉。

6.2 技术架构

多模态知识库├── Word文档 → 文本+表格提取├── 图片 → OCR文字+CLIP向量└── 混合数据         ↓双路Embedding├── 文本路径: text-embedding-v4 (1024维)└── 图像路径: CLIP (512维)         ↓双索引系统├── FAISS文本索引└── FAISS图像索引         ↓智能路由├── 文本问题 → 文本索引├── 图像问题 → 图像索引└── 混合问题 → 双索引融合         ↓统一生成└── GPT-4生成答案

6.2.1 多模态数据处理与双索引系统

多格式文档规范化:对于Word文档(.docx),系统不仅提取纯文本段落,还会专门处理嵌入的表格。表格内容会被解析并转换为通用的Markdown格式,确保结构化信息的语义被完整保留,从而使LLM能够理解和引用表格数据。对于PDF文档,系统使用PyMuPDF(fitz)逐页提取文本,并侦测和提取页面中的所有嵌入图片。这些图片会被保存为独立的图像文件,以便进行进一步的视觉处理,同时记录其原始页码和路径。

跨模态嵌入生成与双索引:图片数据的处理采用了双重策略

1. OCR增强: 使用pytesseract对独立保存的图片进行OCR处理,提取图片中包含的文字信息(例如活动海报上的标题或日期)。这些OCR文本被视为增强文本块的一部分,用于提升纯文本检索的召回率。

**2.**CLIP视觉特征提取:使用CLIP(Contrastive Language–Image Pre-training)模型,一个著名的视觉-语言模型,提取图像的视觉特征。CLIP能够将图像和文本编码到同一个语义空间中(例如512维),从而实现跨模态的相似度搜索。

6.2.2 混合检索策略与多模态问答工作流

多模态客服需要根据用户查询的潜在意图(是否包含视觉元素需求)动态地调整检索路径。

系统采用并行检索偏向性排序策略

1. 并行检索: 文本检索总是无条件执行,将用户查询向量化后在text_index中搜索最相关的文本片段。图像检索则通过关键词触发机制启动,只有当用户查询包含“海报”、“图片”等预设图像关键词时,系统才会使用CLIP文本编码器将查询向量化,在image_index中搜索最相关的图片。

2. 排序与融合:检索结果采用“文本优先,强制包含最佳图片”的策略进行融合。文本结果按相关性排序;如果图像检索被触发,则从图片结果中选取唯一一张最相关的图片(best_image),并将其信息(包括OCR文本和图片路径)作为关键上下文添加到LLM的Prompt中。

通过实际问答示例可以观察这种混合策略的有效性:

  • 纯文本查询示例:

    用户提问“我想了解一下迪士尼门票的退款流程”。系统执行纯文本检索,准确命中包含“通常不支持退款”信息的文档。LLM基于检索到的文本,生成专业且准确的回复,强调购票前需确认日期。

  • 视觉意图查询示例:

    用户提问“最近万圣节的活动海报是什么”。系统检测到“海报”关键词,触发图像检索。系统成功检索到关于万圣节夜场票的文本信息,并通过图像检索命中了一张相关的海报图片。最终答案中,LLM结合检索到的文本信息进行回复,并附带了图片路径,告知用户可以通过路径查看海报。

    对于涉及视觉意图的查询,即使系统检索到了图片,LLM本身(如果不是一个Vision-Language Model, VLM)也无法直接“看懂”图片内容。因此,多模态RAG的关键挑战在于,必须将视觉信息成功转化为LLM可理解的、丰富的文本描述(通过OCR或CLIP生成的描述性文本),才能真正实现基于图片内容的准确问答。如果OCR识别的文本稀疏或为空,LLM就无法仅基于图像信息回答。因此,未来的多模态RAG趋势是集成先进的VLM,实现对图文布局和结构信息的端到端理解,而非仅仅依赖预处理的文本描述。

6.3 核心代码实现

  1. 多模态文档解析
from docx import Document as DocxDocumentfrom PIL import Imageimport pytesseractfrom transformers import CLIPProcessor, CLIPModelimport torch# 全局加载CLIP模型clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")defparse_docx_multimodal(file_path):"""    解析Word文档,提取文本、表格和图片    """    doc = DocxDocument(file_path)    content_chunks = []for element in doc.element.body:# 1. 处理段落文本        if element.tag.endswith('p'):            paragraph_text = ""for run in element.findall('.//w:t',                {'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'}):                paragraph_text += run.text if run.text else""if paragraph_text.strip():                content_chunks.append({"type": "text","content": paragraph_text.strip()                })# 2. 处理表格(转为Markdown)        elif element.tag.endswith('tbl'):            table = [t for t in doc.tables if t._element is element][0]            md_table = []if table.rows:# 表头                header = [cell.text.strip() for cell in table.rows[0].cells]                md_table.append("| " + " | ".join(header) + " |")                md_table.append("|" + "---|" * len(header))# 数据行                for row in table.rows[1:]:                    row_data = [cell.text.strip() for cell in row.cells]                    md_table.append("| " + " | ".join(row_data) + " |")                table_content = "\n".join(md_table)                content_chunks.append({"type": "table","content": table_content                })return content_chunksdefprocess_image_multimodal(image_path):"""    处理图片:OCR提取文字 + CLIP生成图像向量    """    image = Image.open(image_path)# 1. OCR提取文字    ocr_text = pytesseract.image_to_string(        image,        lang='chi_sim+eng'# 中英文    ).strip()# 2. CLIP图像向量    inputs = clip_processor(images=image, return_tensors="pt")with torch.no_grad():        image_features = clip_model.get_image_features(**inputs)    image_vec = image_features[0].numpy()return {"ocr_text": ocr_text,"image_vector": image_vec,"image_path": image_path    }
  1. 双路Embedding系统
from openai import OpenAIimport numpy as np# 初始化客户端client = OpenAI(    api_key=os.getenv("DASHSCOPE_API_KEY"),    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1")defget_text_embedding(text, model="text-embedding-v4"):"""文本Embedding(高维语义向量)"""    response = client.embeddings.create(        model=model,        input=text,        dimensions=1024    )return np.array(response.data[0].embedding)defget_image_embedding_clip(image_path):"""图像Embedding(CLIP视觉向量)"""    image = Image.open(image_path)    inputs = clip_processor(images=image, return_tensors="pt")with torch.no_grad():        image_features = clip_model.get_image_features(**inputs)return image_features[0].numpy()defget_clip_text_embedding(text):"""    使用CLIP的文本编码器    用于图像-文本跨模态检索    """    inputs = clip_processor(text=text, return_tensors="pt")with torch.no_grad():        text_features = clip_model.get_text_features(**inputs)return text_features[0].numpy()
  1. 双索引构建
import faissimport pickledefbuild_dual_index(docs_dir, img_dir):"""    构建双索引:文本索引 + 图像索引    """    text_metadata = []    text_vectors = []    image_metadata = []    image_vectors = []# 1. 处理文本文档    for file_name in os.listdir(docs_dir):if file_name.endswith('.docx'):            file_path = os.path.join(docs_dir, file_name)            chunks = parse_docx_multimodal(file_path)for chunk in chunks:# 文本和表格都用文本Embedding                if chunk['type'] in ['text', 'table']:                    vec = get_text_embedding(chunk['content'])                    text_vectors.append(vec)                    text_metadata.append({'type': chunk['type'],'content': chunk['content'],'source': file_name                    })# 2. 处理图像    for img_name in os.listdir(img_dir):if img_name.endswith(('.jpg', '.jpeg', '.png')):            img_path = os.path.join(img_dir, img_name)            img_data = process_image_multimodal(img_path)# 图像向量            image_vectors.append(img_data['image_vector'])            image_metadata.append({'type': 'image','ocr_text': img_data['ocr_text'],'image_path': img_path,'source': img_name            })# OCR文字也加入文本索引            if img_data['ocr_text']:                vec = get_text_embedding(img_data['ocr_text'])                text_vectors.append(vec)                text_metadata.append({'type': 'image_ocr','content': img_data['ocr_text'],'image_path': img_path,'source': img_name                })# 3. 构建FAISS索引    text_vectors = np.array(text_vectors).astype('float32')    image_vectors = np.array(image_vectors).astype('float32')# 文本索引(1024维)    text_index = faiss.IndexFlatL2(1024)    text_index.add(text_vectors)# 图像索引(512维)    image_index = faiss.IndexFlatL2(512)    image_index.add(image_vectors)    print(f"文本索引: {text_index.ntotal} 条")    print(f"图像索引: {image_index.ntotal} 条")# 4. 保存    faiss.write_index(text_index, "text_index.faiss")    faiss.write_index(image_index, "image_index.faiss")with open("text_metadata.pkl", "wb") as f:        pickle.dump(text_metadata, f)with open("image_metadata.pkl", "wb") as f:        pickle.dump(image_metadata, f)return text_index, image_index, text_metadata, image_metadata
  1. 智能路由检索
defintelligent_search(query, text_index, image_index,                      text_metadata, image_metadata, top_k=5):    """    智能路由:根据查询类型选择检索策略    """# 判断查询类型    is_image_query = any(keyword in query for keyword in                        ['图', '照片', '地图', '价格表', '图片'])    results = []# 1. 文本检索(总是执行)    query_vec = get_text_embedding(query).astype('float32').reshape(1, -1)    distances, indices = text_index.search(query_vec, top_k)for dist, idx in zip(distances[0], indices[0]):        results.append({'content': text_metadata[idx]['content'],'type': text_metadata[idx]['type'],'source': text_metadata[idx]['source'],'score': 1 / (1 + dist),  # 转为相似度            'modality': 'text'        })# 2. 图像检索(图像相关查询)    if is_image_query:# 使用CLIP文本编码器        clip_query_vec = get_clip_text_embedding(query).astype('float32').reshape(1, -1)        distances, indices = image_index.search(clip_query_vec, top_k)for dist, idx in zip(distances[0], indices[0]):            results.append({'content': image_metadata[idx]['ocr_text'],'type': 'image','image_path': image_metadata[idx]['image_path'],'source': image_metadata[idx]['source'],'score': 1 / (1 + dist),'modality': 'image'            })# 3. 按分数排序    results = sorted(results, key=lambda x: x['score'], reverse=True)[:top_k]return results
  1. 多模态答案生成
defgenerate_multimodal_answer(query, search_results):    """    基于多模态检索结果生成答案    """# 构建上下文    context_parts = []for i, result in enumerate(search_results):if result['modality'] == 'text':            context_parts.append(f"[文本资料{i+1}] (来源: {result['source']})\n"f"{result['content']}\n"            )elif result['modality'] == 'image':            context_parts.append(f"[图像资料{i+1}] (来源: {result['source']})\n"f"图片中的文字: {result['content']}\n"f"图片路径: {result['image_path']}\n"            )    context = "\n".join(context_parts)# 构建提示词    prompt = f"""你是迪士尼客服助手,请基于以下资料回答游客问题。参考资料(包含文本和图像信息):{context}游客问题: {query}要求:1. 准确回答问题,引用具体资料编号2. 如果涉及图片信息,明确说明3. 如果资料不足,诚实告知4. 语气友好专业答案:"""    # 调用LLM    response = client.chat.completions.create(        model="qwen-max",        messages=[            {"role": "system", "content": "你是迪士尼专业客服。"},            {"role": "user", "content": prompt}        ],        temperature=0.7,        max_tokens=1000    )    answer = response.choices[0].message.contentreturn {'answer': answer,'sources': search_results    }
  1. 完整使用示例
defmain():    """完整的多模态RAG系统"""# 1. 构建双索引    text_idx, image_idx, text_meta, image_meta = build_dual_index(        docs_dir="disney_knowledge_base",        img_dir="disney_knowledge_base/images"    )# 2. 测试查询    test_queries = ["迪士尼的门票价格是多少?","有万圣节活动的图片吗?","适合老人的游乐项目有哪些?"    ]for query in test_queries:        print(f"\n{'='*60}")        print(f"问题: {query}")        print('='*60)# 智能检索        results = intelligent_search(            query, text_idx, image_idx,            text_meta, image_meta, top_k=3        )# 生成答案        response = generate_multimodal_answer(query, results)        print(f"\n答案:\n{response['answer']}")        print(f"\n参考来源:")for src in response['sources']:            print(f"  - [{src['modality']}] {src['source']}")if __name__ == "__main__":    main()

6.4 性能指标

指标 数值 说明
文本检索准确率 87.3% Top-3召回
图像检索准确率 82.1% CLIP匹配
平均响应时间 1.8s 包含检索+生成
OCR准确率 95%+ 中文印刷体

📈 RAG质量提升策略

7.1 检索质量优化

7.1.1 查询优化

方法一:查询改写

defquery_rewrite(original_query, llm_client):    """    使用LLM改写查询,提升检索效果    """    prompt = f"""请将以下用户问题改写为更适合检索的形式:原问题: {original_query}要求:1. 提取核心关键词2. 补充潜在的同义词3. 扩展相关概念改写后的查询:"""    response = llm_client.chat.completions.create(        model="qwen-max",        messages=[{"role": "user", "content": prompt}],        temperature=0.3    )    rewritten = response.choices[0].message.content    return rewritten

方法2: 多查询生成

defgenerate_multiple_queries(original_query, num_queries=3):    """    生成多个相关查询,增加召回率    """    prompt = f"""针对以下问题,生成{num_queries}个不同角度的相关查询:原问题: {original_query}生成的查询(每行一个):"""    response = llm_client.chat.completions.create(        model="qwen-max",        messages=[{"role": "user", "content": prompt}],        temperature=0.8    )    queries = response.choices[0].message.content.strip().split('\n')    return [original_query] + queries[:num_queries]

7.1.2 混合检索

from rank_bm25 import BM25Okapidefhybrid_retrieval(query, vector_index, bm25_index,                     documents, alpha=0.7, top_k=10):"""    混合检索:向量检索 + BM25    """# 1. 向量检索    query_vec = get_embedding(query).reshape(1, -1)    dense_distances, dense_indices = vector_index.search(query_vec, top_k*2)# 转换为分数    dense_scores = 1 / (1 + dense_distances[0])# 2. BM25检索    tokenized_query = query.split()    bm25_scores = bm25_index.get_scores(tokenized_query)# 3. 分数归一化    def normalize(scores):        min_s, max_s = np.min(scores), np.max(scores)if max_s - min_s == 0:return scoresreturn (scores - min_s) / (max_s - min_s)    dense_scores_norm = normalize(dense_scores)    bm25_scores_norm = normalize(bm25_scores)# 4. 融合分数    final_scores = {}for idx, score in zip(dense_indices[0], dense_scores_norm):        final_scores[idx] = alpha * scorefor idx, score in enumerate(bm25_scores_norm):if idx in final_scores:            final_scores[idx] += (1 - alpha) * scoreelse:            final_scores[idx] = (1 - alpha) * score# 5. 排序返回    ranked = sorted(final_scores.items(), key=lambda x: x[1], reverse=True)[:top_k]    results = [(documents[idx], score) for idx, score in ranked]return results

7.2 切片优化策略

7.2.1 自适应切片

defadaptive_chunking(text, max_chunk_size=1000, min_chunk_size=200):    """    自适应切片:根据内容特点动态调整    """# 按段落分割    paragraphs = text.split('\n\n')    chunks = []    current_chunk = ""for para in paragraphs:        para = para.strip()ifnot para:continue# 单个段落过长,需要进一步切分        if len(para) > max_chunk_size:# 按句子切分            sentences = re.split('[。!?]', para)for sent in sentences:if len(current_chunk) + len(sent) > max_chunk_size:if len(current_chunk) >= min_chunk_size:                        chunks.append(current_chunk)                    current_chunk = sentelse:                    current_chunk += sentelse:# 段落合并            if len(current_chunk) + len(para) > max_chunk_size:if len(current_chunk) >= min_chunk_size:                    chunks.append(current_chunk)                current_chunk = paraelse:                current_chunk += "\n\n" + para if current_chunk else para# 最后一块    if len(current_chunk) >= min_chunk_size:        chunks.append(current_chunk)return chunks

7.3 重排序(Reranking)

from sentence_transformers import CrossEncoder# 加载重排序模型reranker = CrossEncoder('BAAI/bge-reranker-large')defrerank_results(query, candidates, top_k=5):"""    使用CrossEncoder对候选文档重排序    提升Top-K的准确率    """# 构建查询-文档对    pairs = [[query, doc] for doc in candidates]# 计算相关性分数    scores = reranker.predict(pairs)# 排序    ranked_indices = np.argsort(scores)[::-1][:top_k]    ranked_docs = [candidates[i] for i in ranked_indices]    ranked_scores = scores[ranked_indices]return list(zip(ranked_docs, ranked_scores))# 完整流程def enhanced_retrieval(query, vector_index, documents,                       initial_k=20, final_k=5):"""    增强检索:初检索 + 重排序    """# 1. 向量检索(召回更多候选)    query_vec = get_embedding(query).reshape(1, -1)    distances, indices = vector_index.search(query_vec, initial_k)    candidates = [documents[idx] for idx in indices[0]]# 2. 重排序(精排)    reranked_results = rerank_results(query, candidates, final_k)return reranked_results

7.4 答案质量控制

7.4.1 置信度评估

defevaluate_answer_confidence(query, retrieved_docs, answer):    """    评估答案的置信度    """# 1. 检索文档的平均相似度    similarities = [doc['score'] for doc in retrieved_docs]    avg_similarity = np.mean(similarities)# 2. 答案与检索文档的一致性    answer_vec = get_embedding(answer)    doc_vecs = [get_embedding(doc['content']) for doc in retrieved_docs]    consistency_scores = [        np.dot(answer_vec, doc_vec) / (np.linalg.norm(answer_vec) * np.linalg.norm(doc_vec))for doc_vec in doc_vecs    ]    avg_consistency = np.mean(consistency_scores)# 3. 综合置信度    confidence = 0.5 * avg_similarity + 0.5 * avg_consistencyreturn {'confidence': confidence,'retrieval_quality': avg_similarity,'answer_consistency': avg_consistency,'level': 'high'if confidence > 0.8else'medium'if confidence > 0.6else'low'    }

7.4.2 幻觉检测

defdetect_hallucination(answer, retrieved_docs):    """    检测答案是否包含未支持的信息(幻觉)    """# 提取答案中的关键信息点    prompt = f"""从以下答案中提取关键事实陈述(列表形式):答案: {answer}关键事实:"""    response = llm_client.chat.completions.create(        model="qwen-max",        messages=[{"role": "user", "content": prompt}],        temperature=0.1    )    facts = response.choices[0].message.content.strip().split('\n')# 验证每个事实是否有文档支持    unsupported_facts = []for fact in facts:        supported = Falsefor doc in retrieved_docs:# 简化检查:使用字符串包含            if fact.lower() in doc['content'].lower():                supported = Truebreakifnot supported:            unsupported_facts.append(fact)    hallucination_ratio = len(unsupported_facts) / len(facts) if facts else0return {'has_hallucination': hallucination_ratio > 0.3,'hallucination_ratio': hallucination_ratio,'unsupported_facts': unsupported_facts    }

🔮研究展望与挑战

RAG技术已成为解决大型语言模型在专业性、时效性和事实准确性方面局限性的核心框架。一个高性能的RAG系统并非简单地将文本块和查询向量化,而是需要一套跨越三个阶段(索引、检索、生成)的精细化工程实践。
  • 数据作为基础:RAG的性能起点在于知识的精细化治理和切片策略的选择。先进的切片方法(如层次切片或LLM切片)通过保持语义完整性,显著提高了下游检索的精确度。
  • 检索作为核心:为了应对复杂的查询和海量的知识库,系统必须采用混合检索(结合语义和关键词)和重排机制如Cross-Encoders)来确保查全率和查准率,同时最大程度地减少输入LLM的噪音。
  • 多模态的必然性:面对企业知识库中日益增长的非结构化和多模态数据,多模态RAG架构(如迪士尼案例所示)通过建立跨模态编码和双索引系统,扩展了RAG对复杂信息的覆盖能力。

如何学习大模型 AI ?

由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。

但是具体到个人,只能说是:

“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。

这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

在这里插入图片描述

第一阶段(10天):初阶应用

该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。

  • 大模型 AI 能干什么?
  • 大模型是怎样获得「智能」的?
  • 用好 AI 的核心心法
  • 大模型应用业务架构
  • 大模型应用技术架构
  • 代码示例:向 GPT-3.5 灌入新知识
  • 提示工程的意义和核心思想
  • Prompt 典型构成
  • 指令调优方法论
  • 思维链和思维树
  • Prompt 攻击和防范

第二阶段(30天):高阶应用

该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。

  • 为什么要做 RAG
  • 搭建一个简单的 ChatPDF
  • 检索的基础概念
  • 什么是向量表示(Embeddings)
  • 向量数据库与向量检索
  • 基于向量检索的 RAG
  • 搭建 RAG 系统的扩展知识
  • 混合检索与 RAG-Fusion 简介
  • 向量模型本地部署

第三阶段(30天):模型训练

恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。

到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?

  • 为什么要做 RAG
  • 什么是模型
  • 什么是模型训练
  • 求解器 & 损失函数简介
  • 小实验2:手写一个简单的神经网络并训练它
  • 什么是训练/预训练/微调/轻量化微调
  • Transformer结构简介
  • 轻量化微调
  • 实验数据集的构建

第四阶段(20天):商业闭环

对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。

  • 硬件选型
  • 带你了解全球大模型
  • 使用国产大模型服务
  • 搭建 OpenAI 代理
  • 热身:基于阿里云 PAI 部署 Stable Diffusion
  • 在本地计算机运行大模型
  • 大模型的私有化部署
  • 基于 vLLM 部署大模型
  • 案例:如何优雅地在阿里云私有部署开源大模型
  • 部署一套开源 LLM 项目
  • 内容安全
  • 互联网信息服务算法备案

学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。

如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐