在AI浪潮中,大模型不再是孤岛,RAG正在重塑企业知识的天际线。
引言:巨人的阿喀琉斯之踵
RAG(检索增强生成)是近年来解决大模型知识闭环问题的重要技术之一。要真正理解RAG的精髓,我们必须从基座模型的局限性出发。基座模型如GPT、ERNIE、DeepSeek、LLaMA等在通用知识上极为强大,它们能吟诗作对、编写代码、解答常识问题,但在专业动态或非公开领域常常力不从心。
一、基座模型的三大“先天不足”
在正式拆解RAG之前,我们必须先直面一个现实:即便是最强大的基座模型,在面对特定场景时也会暴露出其固有的局限性。这些局限性不是模型不够“聪明”,而是其技术范式本身决定的。
1. AI 基座模型的知识库局限性与幻觉问题
1.1 知识的时间与范围局限
基座模型的知识来自于训练数据,而这些数据通常截止于某个时间点。例如:
- GPT-4 的训练数据主要覆盖至 2023 年;
- DeepSeek-V2 的训练数据覆盖至 2024 年上半年;
- 企业私域知识、实时资讯、公司文档等并未包含在其语料中。
因此,当用户询问诸如“某公司最新政策”、“某设备参数更新”或“某法律修订”时,大模型往往无从得知。
例子: 你问 GPT-4:“2025 年新能源汽车补贴政策是什么?” 它可能回答 2022 年的旧政策,甚至凭空生成一条“听起来合理”的解释——这就是知识滞后导致的幻觉。
1.2 知识的真实性与可溯源性不足
基座模型训练语料来源广泛,包括网页、百科、论文、论坛等,难以保证每条信息的可靠性和可追溯性。 因此:
- 同一问题,不同模型可能给出完全不同答案;
- 模型无法提供引用来源或权威出处;
- 用户难以验证答案真伪。
这类问题在医疗、金融、法律等高风险场景中尤其严重。
💡 案例: 某医院尝试用 GPT 回答“阿莫西林与头孢是否可以同服”这一医学问题,模型可能给出自信但错误的答案,带来潜在安全风险。
1.3 知识表达的“幻觉(Hallucination)”问题
幻觉是指模型生成了看似合理但实际上错误或虚构的内容。 幻觉通常源于以下几类原因:
- 缺乏真实知识 → 模型“编造”;
- 上下文提示不完整;
- 模型试图维持语言连贯性;
- 概率生成机制使得模型“填补空白”。
🔍 例子: 让模型“列出2025年诺贝尔物理学奖获奖名单”,它会认真地“造出”几位科学家名字和获奖理由,因为它并不知道真实答案。
1.4 知识孤岛与更新成本高
基座模型一旦训练完毕,其参数就被冻结。要更新知识,必须重新训练或微调,这意味着:
- 成本高昂(GPU 消耗与数据整理成本);
- 更新周期长;
- 无法满足“实时知识同步”需求。
这正是企业开始引入 RAG 架构 的根本原因——让模型在推理阶段实时检索外部知识,而非依赖旧的内部语料。
2. AI 大模型如何回答非公开数据的问题
2.1 基座模型的“盲区”:非公开与私有知识
基座模型无法访问:
- 企业内部文件(报告、邮件、知识库);
- 政府或行业非公开数据;
- 需要身份验证的 API、数据库;
- 用户私密信息或定制文档。
在实际业务中,这些数据往往最有价值:
💬 例如: “请分析我公司近三年的客户流失率原因” 这种问题只有公司内部 CRM 系统的数据才能回答,通用模型完全无从下手。
2.2 RAG 的介入:让模型“接入外脑”
RAG 的核心思想是:
在生成前检索知识,而不是在训练中硬编码知识。
其工作流程如下:
- 用户提出问题(如“请概述我司2024年季度销售情况”);
- 检索模块从外部知识库(PDF、数据库、网站、文档等)中找到相关内容;
- 语义匹配挑选最相关的段落;
- 生成模块将检索结果与原始问题一并输入模型,生成答案;
- 输出带有溯源信息的结果,可附带出处。
这样,大模型的“思维”被实时补充了新的知识来源,就能回答原本它“知识盲区”里的问题。
🧠 类比: 如果说基座模型是“记忆强大的学生”, 那么 RAG 就是“让他在考试时能查阅笔记和资料”。
2.3 私有知识的安全集成方法
为了让模型安全访问非公开数据,通常采用以下三种方式:
| 方法 | 核心特点 | 场景示例 |
|---|---|---|
| 向量检索库 (Vector DB) | 将企业文档分块编码为向量,通过语义相似度检索相关内容 | 文档问答系统、企业知识助手 |
| API检索接入 | 模型调用内部系统接口,动态查询实时数据 | 财务报表生成、库存查询 |
| 混合检索 (Hybrid Retrieval) | 结合关键词检索与向量检索 | 复杂搜索任务,如产品对比或政策解读 |
2.4 价值体现
通过 RAG,大模型具备以下能力:
- ✅ 结合最新信息(如实时政策、市场数据);
- ✅ 回答非公开、私域问题;
- ✅ 减少幻觉、增强可信度;
- ✅ 提供可追溯的知识来源。
🌟 小结:从“背书的书生”到“带智库的专家”
通过以上分析,可以清晰地看到:基座模型像是一位学识渊博但书籍已绝版的学者——他的知识停留在毕业那一刻,无法获取新出版的文献,也无法翻阅图书馆的禁书区。而RAG,正是为这位学者配备了一个实时更新的数字图书馆和精准的检索员。
但问题来了:这个“数字图书馆”应该如何搭建?检索员如何判断哪些信息是真正相关的?当检索结果冲突时,模型又该如何取舍?
二、搭建RAG应用:Streamlit + LangChain实战(附完整代码解析)
在开始写代码之前,先看看我们项目的整体结构:
rag_demo/
├── .env # 环境变量配置文件
├── app.py # 主应用程序
├── knowledge_base/ # 知识库文件夹
│ └── documents.txt # 原始文档
└── requirements.txt # 依赖包列表
2.1 完整代码及逐行解析
2.1.1 导入必要的库
python
# 导入 Streamlit 库用于构建Web应用界面
import streamlit as st
# 导入 os.path 用于处理文件路径
import os.path
# 导入文本分割器,用于将长文本分割成较小的块
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 导入文本加载器,用于读取文本文件
from langchain_community.document_loaders import TextLoader
# 导入 FAISS 向量存储库,用于存储和检索向量
from langchain_community.vectorstores import FAISS
# 导入OpenAI的embedding模型(如果使用OpenAI的话)
from langchain_openai import OpenAIEmbeddings
# 导入ChatPrompt模板,用于构建提示词模板
from langchain_core.prompts import ChatPromptTemplate
# 导入OpenAI聊天模型
from langchain_openai import ChatOpenAI
# 导入可运行的并行处理和透传组件
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
# 导入字符串输出解析器
from langchain_core.output_parsers import StrOutputParser
# 导入 os 模块用于获取环境变量
import os
# 导入 load_dotenv 用于加载 .env 文件中的环境变量
from dotenv import load_dotenv
# 导入达摩院(阿里云)的embedding模型
from langchain_community.embeddings import DashScopeEmbeddings
| 导入模块 | 作用 | 在RAG中的角色 |
|---|---|---|
streamlit | 构建Web界面 | 提供用户交互界面 |
TextLoader | 加载文档 | 读取知识库文件 |
RecursiveCharacterTextSplitter | 分割文本 | 将长文档切成小块 |
FAISS | 向量存储 | 存储和检索相似文档 |
OpenAIEmbeddings | 文本转向量 | 将问题/文档转为向量 |
ChatPromptTemplate | 构建提示词 | 设计给LLM的指令 |
ChatOpenAI | 调用LLM | 生成最终答案 |
RunnableParallel | 并行处理 | 同时处理多个任务 |
StrOutputParser | 解析输出 | 提取LLM返回的文本 |
2.1.2 模型配置方案
# 初始化阿里云的embedding模型,用于将文本转换为向量
embeddings = DashScopeEmbeddings(
model="text-embedding-v2",
dashscope_api_key="你自己的,如何找看文末")
# 初始化阿里云通义千问的大语言模型,temperature控制生成的随机性(0.7为中等随机性)
llm = ChatOpenAI(base_url='https://dashscope.aliyuncs.com/compatible-mode/v1',
api_key="你自己的",
model="qwen2.5-72b-instruct", temperature=0.7)
temperature=0.7:温度越高,生成的回答越有创意。在0-1之间。
2.1.3 文档分割(chunking)
在RAG系统中,文档分割(Chunking) 是一个至关重要的预处理步骤。如何将长文档合理地切分成小块,直接影响检索的准确性和生成的连贯性
# 定义文本分割函数
def text_chunk(file_path):
"""
将指定路径的文本文件加载并分割成较小的文本块
Args:
file_path (str): 文本文件的路径
Returns:
list: 分割后的文档块列表
"""
# 1. 加载文档
loader = TextLoader(file_path, encoding='utf-8')
docs = loader.load()
# 打印文档元数据(调试用)
print(docs[0].metadata)
# 2. 创建文本分割器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个文本块的最大字符数
chunk_overlap=50, # 块之间的重叠字符数
)
# 3. 执行分割
chunks = text_splitter.split_documents(docs)
# 4. 返回结果
return chunks
这个看似简单的 text_chunk 函数,实际上是RAG系统的第一道关口:
- chunk_size 决定了检索的颗粒度
- chunk_overlap 保证了语义的连贯性
- 元数据打印 帮助调试和溯源
- 函数封装 让代码易于复用和维护
最佳实践口诀:
- 问答用小块(200-500)
- 摘要用中块(500-1000)
- 翻译用大块(1000-2000)
- 重叠10% 左右最稳妥
2.1.4 向量存储模块
在RAG系统中,将文本块转换为向量并存入向量数据库,是连接文本世界和数学世界的关键桥梁
# 定义文本块向量化函数
def chunk2vector(docs, embeddings):
"""
将文本块列表转换为向量并创建FAISS向量库
Args:
docs (list): 经过分割的文本块列表,每个元素是Document对象
embeddings (object): Embedding模型实例,用于将文本转换为向量
Returns:
FAISS: FAISS向量库对象,可用于后续的相似度检索
"""
# 使用FAISS将文档转换为向量并创建向量库
vector = FAISS.from_documents(
documents=docs, # 输入的文档列表(经过分割的文本块)
embedding=embeddings # 使用的embedding模型,将文本转换为向量
)
# 返回FAISS向量库对象
return vector
| 设计点 | 目的 | 好处 |
|---|---|---|
| 两个参数 | 接收文档和embedding模型 | 解耦,可以灵活替换 |
| 返回向量库 | 返回FAISS对象 | 便于后续检索使用 |
| 单一职责 | 只做向量化存储 | 符合软件工程原则 |
这个看似只有两行的 chunk2vector 函数,实际上是RAG系统的核心枢纽:
- 一行代码:
FAISS.from_documents() - 两个输入:文档块 + Embedding模型
- 一个输出:可检索的向量库
- 无数可能性:为后续的智能问答奠定基础
核心价值:
- 将文本转化为计算机能理解的语言(向量)
- 建立可快速检索的索引结构
- 为相似度匹配提供数学基础
- 让大模型能够 "查资料"而不只是"背课本"
2.1.5 构建核心问答链
经过前面的文档加载、文本分割、向量化存储,我们终于来到了RAG系统的核心环节——将检索到的文档与用户问题结合起来,交给大语言模型生成最终答案。
# 定义大语言模型处理链函数
def llm_chain(vector):
"""
构建RAG问答处理链
Args:
vector (FAISS): 已经构建好的向量库对象
Returns:
Chain: 可执行的RAG处理链
"""
# 1. 定义RAG的提示词模板
template = """You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know.
Question: {question}
Context: {context}
Answer:"""
# 2. 使用模板创建ChatPrompt对象
prompt = ChatPromptTemplate.from_template(template)
# 3. 将向量库转换为检索器
retriever = vector.as_retriever()
# 4. 构建处理链
chain = (
RunnableParallel({"context": retriever, "question": RunnablePassthrough()})
| prompt
| llm
| StrOutputParser()
)
# 5. 返回构建好的处理链
return chain
1. 函数设计思想
def llm_chain(vector):
为什么这样设计?
| 设计点 | 目的 | 好处 |
|---|---|---|
| 一个参数 | 接收向量库 | 链的构建完全基于向量库 |
| 返回chain | 返回可执行对象 | 调用时只需传入问题 |
| 内部封装 | 隐藏复杂逻辑 | 使用简单,维护方便 |
2. 提示词模板(最关键的部分)
模板各部分的作用:
| 部分 | 作用 | 为什么重要 |
|---|---|---|
| 角色设定 | 明确模型身份 | 让模型知道自己是问答助手 |
| 使用上下文 | 限定知识来源 | 防止模型用自己的知识"编造" |
| 不知道就说不知道 | 控制幻觉 | 避免生成错误信息 |
| Question占位符 | 接收用户输入 | 动态填充用户问题 |
| Context占位符 | 接收检索结果 | 动态填充相关文档 |
| Answer标记 | 指示开始回答 | 告诉模型从这里开始生成 |
3. 创建提示词对象
python
prompt = ChatPromptTemplate.from_template(template)
这行代码做了什么?
"模板字符串" → [ChatPromptTemplate] → "可填充的提示词对象"
↓
当传入{question}和{context}时
↓
自动填充生成完整的提示词
4. 创建检索器
python
retriever = vector.as_retriever()
检索器是什么?
# 原来我们是这样手动检索的
docs = vector.similarity_search("什么是RAG?", k=4)
# 有了retriever,可以这样用
docs = retriever.get_relevant_documents("什么是RAG?")
# 结果是一样的,但retriever可以无缝集成到链中
retriever的默认参数:
retriever = vector.as_retriever(
search_type="similarity", # 使用相似度搜索
search_kwargs={"k": 4} # 返回最相似的4个文档
)
5. 构建处理链(核心中的核心)
python
chain = (
RunnableParallel({"context": retriever, "question": RunnablePassthrough()})
| prompt
| llm
| StrOutputParser()
)
处理链如下图流程:
2.1.6 整合所有模块的完整问答函数
经过前面几个模块的逐一构建——文档加载分割、向量化存储、问答链组装,我们终于来到了最终整合阶段。今天,我们将所有零散的组件组合成一个完整的、可直接使用的RAG问答函数。
# 定义完整的RAG问答函数
def llm_an(file_path, question):
"""
完整的RAG问答函数:输入文件路径和问题,返回基于文件内容的答案
Args:
file_path (str): 知识库文件的路径
question (str): 用户提出的问题
Returns:
str: 基于文件内容生成的答案
"""
# 1. 输入验证:避免question输入为空导致报错
if not question:
question = "hello"
# 2. 文档分割:将文件分割成小块
docs = text_chunk(file_path)
# 3. 向量化存储:将文本块转换为向量并存入FAISS
vector = chunk2vector(docs, embeddings)
# 4. 构建处理链:创建RAG问答链
chain = llm_chain(vector)
# 5. 执行问答:传入问题获取答案
answer = chain.invoke(question)
# 6. 返回结果
return answer
1. 函数设计哲学
def llm_an(file_path, question):
为什么这样设计?
| 设计决策 | 目的 | 好处 |
|---|---|---|
| 两个参数 | 只需输入文件路径和问题 | 使用简单,无需关心内部复杂流程 |
| 返回字符串 | 直接返回答案文本 | 便于集成到各种应用场景 |
| 内部封装 | 隐藏所有实现细节 | 符合"高内聚低耦合"原则 |
这个函数就像是RAG系统的"一键启动按钮":
- 用户只需要知道:
给我文件路径和问题,我返回答案 - 内部发生了什么?完全不用关心!
2. 简述代码流程
这个看似简单的 llm_an 函数,实际上是整个RAG系统的总指挥官:
函数职责
| 步骤 | 函数调用 | 职责 |
|---|---|---|
| 1 | text_chunk() | 文档加载与分割 |
| 2 | chunk2vector() | 向量化与存储 |
| 3 | llm_chain() | 构建问答链 |
| 4 | chain.invoke() | 执行问答 |
设计亮点
- 简单易用:只需文件路径和问题
- 模块化:每个步骤独立,便于维护
- 可扩展:可以轻松添加缓存、日志、错误处理
- 通用性:适用于各种应用场景
一句话总结
这个函数就像RAG系统的总控室,把所有复杂的内部流程封装成一个简单的接口——给一个文件和一个问题,还你一个准确的答案。
2.1.7 构建完整的Streamlit交互界面
为用户创建一个友好、直观的交互界面。使用Streamlit构建一个完整的RAG辅助阅读应用。
# 定义Streamlit交互式界面函数
def interactive(file_path):
"""
创建Streamlit交互式界面,让用户可以通过网页与RAG系统交互
Args:
file_path (str): 知识库文件的路径,用于加载文档
"""
# 1. 设置页面标题
st.title("📚 RAG辅助阅读文献")
# 2. 创建可展开的知识库区域
with st.expander("🔍 RAG知识库"):
# 定义问题输入框的标签
question_title = "您的问题是?"
# 创建文本输入框,获取用户输入的问题
usr_question = st.text_input(question_title)
# 调用llm_an函数获取对用户问题的答案
usr_ans = llm_an(file_path, usr_question)
# 在Streamlit应用中显示生成的答案
st.write(f'💡 {usr_ans}')
1. 函数设计思想
def interactive(file_path):
为什么这样设计?
| 设计点 | 目的 | 好处 |
|---|---|---|
| 一个参数 | 只需传入文件路径 | 简化调用,专注于界面 |
| 无返回值 | 直接渲染界面 | Streamlit自动处理显示 |
| 内部调用 | 调用已封装的llm_an | 复用已有逻辑,保持一致性 |
2. 设置页面标题
python
st.title("📚 RAG辅助阅读文献")
效果:
text
┌─────────────────────────────────────────────┐
│ 📚 RAG辅助阅读文献 │
│ (大号粗体文字,页面最顶部) │
├─────────────────────────────────────────────┤
│ │
为什么加emoji?
- 📚 书籍emoji暗示这是阅读相关应用
- 视觉上更吸引人,增加界面友好度
- 符合现代Web应用的审美趋势
3. 创建可展开容器
python
with st.expander("🔍 RAG知识库"):
这个结构的作用:
text
┌─────────────────────────────────────────────┐
│ 📚 RAG辅助阅读文献 │
├─────────────────────────────────────────────┤
│ ▼ 🔍 RAG知识库 │ ← 点击可展开/折叠
│ ┌─────────────────────────────────────────┐ │
│ │ 您的问题是? │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ 请输入您的问题... │ │ │ ← 输入框
│ │ └─────────────────────────────────────┘ │ │
│ │ │ │
│ │ 💡 RAG是检索增强生成技术... │ │ ← 答案显示
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
为什么要用st.expander?
| 优点 | 说明 |
|---|---|
| 节省空间 | 默认折叠,界面整洁 |
| 按需展开 | 用户需要时才显示 |
| 层次分明 | 区分不同功能区域 |
4. 定义问题标签
question_title = "您的问题是?"
设计考虑:
- 使用问句形式,引导用户输入
- 语言亲切自然,降低使用门槛
- 单独定义变量,便于后续修改
5. 创建文本输入框
python
usr_question = st.text_input(question_title)
这行代码的背后:
text
用户看到: "您的问题是?" [ ]
↑
用户在这里输入
程序获取: usr_question = "什么是RAG技术?"
Streamlit的响应式特性:
- 用户输入任何文字,页面会自动重新运行
usr_question会实时更新为用户输入的内容- 无需手动提交按钮(当然也可以加按钮)
6. 调用问答函数
usr_ans = llm_an(file_path, usr_question)
这行代码触发的完整流程:
text
usr_question = "什么是RAG技术?"
↓
llm_an(file_path, "什么是RAG技术?")
↓
├─ text_chunk() → 加载并分割文档
├─ chunk2vector() → 向量化存储
├─ llm_chain() → 构建问答链
├─ chain.invoke() → 执行问答
↓
usr_ans = "RAG是检索增强生成技术..."
7. 显示答案
python
st.write(f'💡 {usr_ans}')
为什么用st.write而不是st.text?
| 方法 | 特点 | 适用场景 |
|---|---|---|
st.write | 智能渲染,支持多种格式 | 通用显示,自动处理 |
st.text | 纯文本,固定格式 | 需要严格格式时 |
st.markdown | 支持Markdown语法 | 需要富文本时 |
💡 emoji的作用:
- 灯泡图标暗示这是"答案"或"启发"
- 视觉区分问题和答案
- 增加界面的趣味性
三、实操流程
3.1 成功配置必备条件中所需要的环境:
1.安装 vscode和Anaconda
2.创建 Conda虚拟环境
在终端使用指令:
conda create –-name rag python=3.10
3.切换到新环境中,并根据 RAG/requirements.txt 文件安装所需依赖
pip install -r requirements.txt
requirements.txt:
streamlit==1.35.0
langchain==0.2.9
langchain-cli==0.0.25
langchain-community==0.2.7
langchain-core==0.2.21
langchain-openai==0.1.17
langchain-text-splitters==0.2.2
python-dotenv==1.0.1
faiss-cpu==1.8.0.post1
4.输入命令启动
下文为run.py
import streamlit as st
from rag import llm_an
def interactive(file_path):
st.title("RAG")
# st.sidebar.header("")
with st.expander("RAG知识库"):
# 创建一个问题
question_title = "您的问题是?"
# 创建问答框,并获取用户输入
usr_question = st.text_input(question_title)
# 获取答案
usr_ans = llm_an(file_path, usr_question)
# 显示用户输入的内容
st.write(f' {usr_ans}')
if __name__ == "__main__":
# 设置文件地址
file_path = 'D:/BaiduNetdiskDownload/AI/RAG/你的知识库.txt'
# 展示
interactive(file_path)
3.2 编写代码以连接阿里云大模型服务:
1.注册并登录阿里云,获取API密钥
具体api-key获取请参考官网手册:help.aliyun.com/zh/model-st…
2.在rag.py文件中编写代码以读取和处理 .env 文件中的 API 密钥和端点信息;
# 初始化阿里云的embedding模型,用于将文本转换为向量
embeddings = DashScopeEmbeddings(
model="text-embedding-v2",
dashscope_api_key="替换成你自己获得的")
# 初始化阿里云通义千问的大语言模型,temperature控制生成的随机性(0.7为中等随机性)
llm = ChatOpenAI(base_url='https://dashscope.aliyuncs.com/compatible-mode/v1',
api_key="替换成你自己获得的",
model="qwen2.5-72b-instruct", temperature=0.7)
注意:该代码流程为单文档的RAG问答系统
关于作者:一个正在求职的Java开发者/AI应用开发者,坚持通过项目实践和技术写作提升自己。GitHub: [@yangziyue](Yzy000000 | 掘金: @Wiittch