LangChain RAG 基础

基于你已经学习完:

  • 文档加载
  • 文本分割
  • 向量存储(Chroma)
  • 语义检索

一、什么是 RAG?(超通俗解释)

RAG = Retrieval-Augmented Generation

检索增强生成

大白话:

  1. 大模型本身不知道你的文档内容
  2. 所以先去文档里检索相关内容
  3. 把检索到的内容喂给大模型
  4. 让大模型只根据文档内容回答

这就叫 RAG


二、RAG 标准 5 步流程(必须背下来)

  1. 加载文档(PDF / TXT / Word)
  2. 文本分割(切成小块,保留语义)
  3. 向量化(小块 → 向量)
  4. 存入向量库(Chroma / FAISS)
  5. 用户提问 → 检索 → 给模型回答

三、RAG 为什么比直接问 LLM 强?

  • LLM 会瞎编
  • RAG 只根据文档回答,不会编
  • LLM 不知道你的私有数据
  • RAG 可以读取私有文档
  • RAG 回答永远准确、可追溯

四、现在!我带你实现 最简单、最标准、可直接运行的 RAG 系统

只用你已经学过的知识:

  • Document Loader
  • Text Splitter
  • Chroma 向量库
  • Embedding
  • LLM + Prompt
  • 普通 Chain

五、完整可运行代码(极简纯净版 RAG)

1. 安装依赖

复制代码
pip install -U langchain langchain-openai langchain-community pypdf chromadb python-dotenv

2. 完整代码(直接跑)

复制代码
import os
from dotenv import load_dotenv
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.vectorstores import Chroma
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 加载环境变量
load_dotenv()

# ================= 配置 ====================
PDF_PATH = "Dubbo面试.pdf"
CHROMA_PATH = "./chroma_dir"

# 智普模型
# llm = ChatOpenAI(
#     api_key=os.getenv("QWEN_API_KEY"),
#     base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
#     model='qwen3-max',  # 必须用 pro模型,支持工具调用
#     temperature=0 # 必须0 保证不乱造
# )

llm = ChatOpenAI(
    api_key=os.getenv("ZHIPU_API_KEY"),
    base_url="https://open.bigmodel.cn/api/paas/v4",
    model='glm-4.6',  # 必须用 pro模型,支持工具调用
    temperature=0 # 必须0 保证不乱造
)

# 嵌入模型 model="embedding-3",  # 智谱嵌入模型
embedding = OpenAIEmbeddings(
    openai_api_key=os.getenv("ZHIPU_API_KEY"),  # 智谱API Key
    openai_api_base="https://open.bigmodel.cn/api/paas/v4",
    model="embedding-3"
)


# 加载PDF
loader = PyPDFLoader(PDF_PATH)
documents = loader.load()

# 文本分割
splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n\n", "\n", "。", "!", "?", " "]
)
chunks = splitter.split_documents(documents)
cleaned = []
# 清空文档中的空 保留文章的完整性 文档里面有图片的话就会出现空
for chunk in chunks:
    # 1. 必须有 page_content
    if not hasattr(chunk, "page_content"):
        continue

    # 2. 内容不能为空
    content = chunk.page_content.strip()
    if len(content) == 0:
        continue

    # 3. 内容不能是奇怪的非字符串(防止通义报错)
    if not isinstance(content, str):
        continue

    chunk.page_content = str(content)
    cleaned.append(chunk)

# 构建向量库
vector_db = Chroma.from_documents(
    documents = cleaned,
    embedding = embedding,
    persist_directory = CHROMA_PATH,
)
# 重复持久化  过时了 会有警告提示
# vector_db.persist()

# 检索器 核心
retriever = vector_db.as_retriever(search_kwargs={"k":3})

# rag 提示词
prompt = ChatPromptTemplate.from_template("""
你是一个严格的文档问答助手。
只根据下面的【文档内容】回答问题,绝对不能编造信息。

文档内容:
{context}

用户问题:
{question}

如果文档中没有答案,请直接回复:
"文档中没有相关内容"
""")

# 构建RAG 链
rag_chain = (
    {"context":retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

# 运行 RAG
print("✅️ RAG 系统启动成功! 开始提问吧")
while True:
    question = input("你:")
    if question == "q": break
    ans = rag_chain.invoke(question)
    print(f"🤖: {ans} \n")

六、这个 RAG 系统能做什么?

  • 上传任意 PDF
  • 问文档里的内容
  • 只根据文档回答,不会编
  • 不知道就说:文档中没有相关内容
  • 回答准确、可靠

七、我用最简单的话讲清楚 RAG 链的结构

复制代码
rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

逐行解释:

  1. context = retriever → 去向量库检索相关内容
  2. question = 用户问题 → 直接传递问题
  3. 把两者拼起来给 Prompt
  4. LLM 根据文档回答
  5. 输出解析成字符串

这就是 最简单、最标准的 RAG 结构

八、总结一句最核心的话

RAG = 检索 + 提示 + 生成

检索 = 从文档找答案

提示 = 告诉模型只看文档

生成 = 模型回答

==================================分隔符==================================

一、给 RAG 文档问答做 可视化网页界面( 可视化界面 RAG**)**

Streamlit 做,零前端代码、一行命令就能跑,自带漂亮网页 UI:

  • 侧边栏上传 PDF
  • 自动加载、分割、建 Chroma 向量库
  • 聊天式对话框
  • 基于文档 RAG 回答,不瞎编
  • 展示检索到的参考文档片段

1. 安装依赖

复制代码
pip install -U langchain langchain-openai langchain-community pypdf chromadb python-dotenv streamlit

2. 项目结构

复制代码
你的项目文件夹/
├── .env
├── app_rag_ui.py   # 可视化界面主程序
└── test.pdf        # 可上传任意PDF

3. .env 文件

复制代码
DOUBAO_API_KEY=你的豆包API_KEY

二、完整可运行代码 app_rag_ui.py

复制代码
import os
import shutil
from dotenv import load_dotenv
import streamlit as st

# LangChain 相关
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# 加载环境变量
load_dotenv()

# ===================== 全局配置 =====================
CHROMA_DB_DIR = "./chroma_rag_ui_db"
CHUNK_SIZE = 1000
CHUNK_OVERLAP = 200

# 初始化 LLM
llm = ChatOpenAI(
    api_key=os.getenv("DOUBAO_API_KEY"),
    base_url="https://ark.cn-beijing.volces.com/api/v3",
    model="doubao-pro-32k",
    temperature=0.0
)

# 初始化嵌入模型
embedding = OpenAIEmbeddings(
    api_key=os.getenv("DOUBAO_API_KEY"),
    base_url="https://ark.cn-beijing.volces.com/api/v3"
)

# ===================== 工具函数 =====================
def clear_chroma_db():
    """清空旧向量库"""
    if os.path.exists(CHROMA_DB_DIR):
        shutil.rmtree(CHROMA_DB_DIR)

def load_and_split_pdf(pdf_path):
    """加载PDF + 语义分割"""
    loader = PyPDFLoader(pdf_path)
    docs = loader.load()
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=CHUNK_SIZE,
        chunk_overlap=CHUNK_OVERLAP,
        separators=["\n\n", "\n", "。", "!", "?", " "]
    )
    chunks = splitter.split_documents(docs)
    return chunks

def build_vector_db(chunks):
    """构建Chroma向量库"""
    vector_db = Chroma.from_documents(
        documents=chunks,
        embedding=embedding,
        persist_directory=CHROMA_DB_DIR
    )
    vector_db.persist()
    return vector_db

def build_rag_chain(vector_db):
    """构建RAG问答链"""
    retriever = vector_db.as_retriever(k=3)

    prompt = ChatPromptTemplate.from_template("""
你是专业文档问答助手,**严格仅根据提供的文档上下文回答**。
规则:
1. 只使用上下文信息,禁止编造、禁止使用外部知识
2. 没有相关内容就直接回复:文档中没有相关内容
3. 回答简洁条理清晰

文档上下文:
{context}

用户问题:
{question}
""")

    rag_chain = (
        {"context": retriever, "question": RunnablePassthrough()}
        | prompt
        | llm
        | StrOutputParser()
    )
    return rag_chain, retriever

# ===================== Streamlit 页面 =====================
st.set_page_config(page_title="RAG 文档问答机器人", layout="wide")
st.title("📚 RAG 文档问答可视化系统")

# 侧边栏
with st.sidebar:
    st.header("📄 文档上传")
    uploaded_file = st.file_uploader("上传 PDF 文件", type="pdf")

    if uploaded_file:
        # 保存临时PDF
        temp_pdf_path = "temp_upload.pdf"
        with open(temp_pdf_path, "wb") as f:
            f.write(uploaded_file.read())

        # 清空旧库
        clear_chroma_db()

        with st.spinner("正在加载文档、分割、构建向量库..."):
            chunks = load_and_split_pdf(temp_pdf_path)
            vector_db = build_vector_db(chunks)
            rag_chain, retriever = build_rag_chain(vector_db)
            # 存入session全局复用
            st.session_state["rag_chain"] = rag_chain
            st.session_state["retriever"] = retriever

        st.success("✅ 文档加载完成,可以开始提问!")

# 主页面聊天
if "chat_history" not in st.session_state:
    st.session_state["chat_history"] = []

# 渲染历史对话
for msg in st.session_state["chat_history"]:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])

# 输入框
user_query = st.chat_input("请输入你的问题...")

if user_query:
    # 展示用户问题
    st.chat_message("user").markdown(user_query)
    st.session_state["chat_history"].append({"role":"user","content":user_query})

    if "rag_chain" not in st.session_state:
        ans = "⚠️ 请先在侧边栏上传 PDF 文档"
    else:
        # RAG 生成回答
        with st.spinner("AI 正在思考并检索文档..."):
            ans = st.session_state["rag_chain"].invoke(user_query)
            # 检索参考片段
            refs = st.session_state["retriever"].get_relevant_documents(user_query)

    # 展示AI回答
    with st.chat_message("assistant"):
        st.markdown(ans)
        # 折叠展示参考文档片段
        with st.expander("📖 查看检索参考文档片段"):
            for idx, doc in enumerate(refs, 1):
                st.write(f"**参考片段 {idx}(第{doc.metadata.get('page','未知')}页)**")
                st.write(doc.page_content)
                st.divider()

    st.session_state["chat_history"].append({"role":"assistant","content":ans})

三、运行方式

终端执行:

复制代码
streamlit run app_rag_ui.py

自动弹出浏览器网页界面,功能:

  1. 左侧上传任意 PDF
  2. 自动:加载 → 分割 → 向量化 → 存入 Chroma
  3. 下方对话框直接提问
  4. 底部可展开查看检索到的原文参考片段
  5. 严格基于文档回答,不瞎编

四、界面功能亮点

  • 🎨 网页可视化,不用控制台
  • 📤 支持随时上传新 PDF,自动清空旧向量库
  • 💬 聊天式对话历史
  • 🔍 可查看每一轮用到的检索原文片段 + 页码
  • 🧠 纯 RAG 标准流程,语义检索,比关键词匹配精准很多
相关推荐
墨北小七3 小时前
使用InspireFace进行智慧楼宇门禁人脸识别的训练微调
人工智能·深度学习·神经网络
HackTorjan3 小时前
深度神经网络的反向传播与梯度优化原理
人工智能·spring boot·神经网络·机器学习·dnn
老前端的功夫3 小时前
【Java从入门到入土】28:Stream API:告别for循环的新时代
java·开发语言·python
PersistJiao3 小时前
Codex、Claude Code、gstack三者的关系
人工智能
yaoxin5211233 小时前
397. Java 文件操作基础 - 创建常规文件与临时文件
java·开发语言·python
dFObBIMmai3 小时前
MySQL主从同步中大事务导致的延迟_如何拆分大事务优化同步
jvm·数据库·python
szccyw03 小时前
mysql如何限制特定存储过程执行权限_MySQL存储过程安全访问
jvm·数据库·python
一切皆是因缘际会3 小时前
AI数字分身的底层原理:破解意识、自我与人格复刻的核心难题
大数据·人工智能·ai·架构
翔云1234563 小时前
vLLM全解析:定义、用途与竞品对比
人工智能·ai·大模型