详细拆解 RAG 聊天应用的实现逻辑

一、准备工作:环境与依赖配置

目标:搭建开发环境,配置敏感信息与核心依赖,为后续开发奠定基础。

1.1 安装核心依赖

RAG 应用需要三类核心工具:前端交互框架(Chainlit)、RAG 引擎(llama-index)、环境管理工具(python-dotenv)。在终端执行安装命令:

pip install chainlit

 检测是否安装成功

chainlit hello

如果报错如下

这个问题最主要的是pydantic的版本问题,解决方案如下

①卸载pydantic

pip uninstall pydantic

②安装pydantic为2.9.2版本

pip install pydantic==2.9.2

③检测

chainlit hello

1.2 配置环境变量(.env文件)

敏感信息(如 API 密钥、认证密码)需通过环境变量管理,避免硬编码。创建.env文件,内容如下:

# .env
CHAINLIT_AUTH_SECRET=your_chainlit_secret  # Chainlit认证密钥(可自定义)
KIMI_API_KEY=your_kimi_api_key            # 月之暗面模型API密钥
DEEPSEEK_API_KEY=your_deepseek_api_key    # DeepSeek模型API密钥(可选)

CHAINLIT_AUTH_SECRET用于 Chainlit 应用的访问认证,第三方模型 API 密钥则是调用 LLM 的凭证

二、模型配置:嵌入模型与 LLM 初始化

目标:配置文本嵌入模型(用于向量检索)和大语言模型(用于生成回答),是 RAG 的 “大脑”。

2.1 嵌入模型配置(embeddings.py

嵌入模型将文本转为向量,是实现 “检索” 的核心。选择中文支持好的BAAI/bge-small-zh-v1.5模型:

# embeddings.py
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

def get_embed_model():
    """加载本地中文嵌入模型"""
    # 模型会自动下载到本地(~/.cache/huggingface/hub)
    embed_model = HuggingFaceEmbedding(
        model_name="BAAI/bge-small-zh-v1.5",
        model_kwargs={"device": "cpu"},  # 本地运行用CPU,有GPU可改为"cuda"
        embed_batch_size=10  # 批量处理文本的大小
    )
    return embed_model

# 全局实例化,避免重复加载
embed_model = get_embed_model()

①嵌入模型将文档和用户问题转为高维向量,向量相似度越高,文本语义越接近(支撑后续 “找相关文档” 的逻辑)。

②选择bge-small-zh是因为它轻量(适合本地部署)且中文效果好,比英文模型更适配中文知识库

2.2 LLM 配置(llms.py

LLM 负责基于检索到的文档生成回答,这里适配第三方模型(以月之暗面kimi为例,兼容 OpenAI 接口):

# llms.py
from llama_index.llms.openai import OpenAI
from dotenv import load_dotenv
import os

load_dotenv()  # 加载.env文件

def get_llm(model_name: str = "kimi"):
    """获取配置好的LLM实例"""
    if model_name == "kimi":
        return OpenAI(
            model="kimi-k2-0711-preview",  # 月之暗面模型名称
            api_key=os.getenv("KIMI_API_KEY"),
            api_base="https://api.moonshot.cn/v1"  # 月之暗面API地址
        )
    elif model_name == "deepseek":
        return OpenAI(
            model="deepseek-chat",
            api_key=os.getenv("DEEPSEEK_API_KEY"),
            api_base="https://api.deepseek.com/v1"
        )
    else:
        raise ValueError(f"不支持的模型:{model_name}")

# 默认使用kimi模型
llm = get_llm("kimi")

①利用llama_indexOpenAI类适配第三方模型:只要模型提供兼容 OpenAI 的 API(如/v1/chat/completions接口),就能直接复用该类,无需重复开发调用逻辑

②通过model_name参数可快速切换模型,提升灵活性

三、RAG 核心逻辑:索引与聊天引擎(base_rag.py

目标:实现 “文档→索引→检索→生成” 的核心链路,是 RAG 的 “心脏”。

3.1 文档索引创建(将文档转为可检索的向量)

索引是文档的 “向量化存储库”,创建后可反复复用:

# base_rag.py
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core.storage.storage_context import StorageContext
from llama_index.core.persistence import PersistentDirectoryStore
from embeddings import embed_model
import os

INDEX_DIR = "./index"  # 索引持久化目录

def create_index(documents=None):
    """
    创建或加载文档索引
    - 若documents不为空:基于新文档创建索引并保存
    - 若documents为空:加载已保存的索引
    """
    # 若指定了新文档,先读取文档
    if documents:
        reader = SimpleDirectoryReader(documents=documents)  # 读取传入的文档
        docs = reader.load_data()
    else:
        docs = None

    # 配置存储上下文(指定索引保存路径)
    storage_context = StorageContext.from_defaults(
        persist_dir=INDEX_DIR,
        docstore=PersistentDirectoryStore.from_persist_dir(persist_dir=INDEX_DIR)
    )

    # 若索引目录不存在或有新文档,创建新索引
    if not os.path.exists(INDEX_DIR) or documents:
        index = VectorStoreIndex.from_documents(
            docs,
            storage_context=storage_context,
            embed_model=embed_model  # 使用前面配置的嵌入模型
        )
        index.storage_context.persist(persist_dir=INDEX_DIR)  # 保存索引
    else:
        # 加载已存在的索引
        index = VectorStoreIndex.from_storage(storage_context=storage_context)
    
    return index

①核心逻辑:VectorStoreIndex.from_documents会调用嵌入模型,将文档拆分为片段并转为向量,存储到INDEX_DIR

②增量更新:若用户上传新文件(documents不为空),会基于新文档更新索引,避免重复处理旧文档

3.2 聊天引擎初始化(结合索引、LLM 与对话记忆)

聊天引擎是 “检索 + 生成” 的执行者,需同时处理对话历史和文档检索:

# base_rag.py(续)
from llama_index.core.memory import ChatMemoryBuffer
from llama_index.core.chat_engine import ContextChatEngine
from llms import llm

def create_chat_engine(index):
    """基于索引创建带对话记忆的聊天引擎"""
    # 配置对话记忆(保存最近1024 token的对话历史)
    memory = ChatMemoryBuffer.from_defaults(token_limit=1024)
    
    # 创建聊天引擎:CONTEXT模式会先检索相关文档,再结合记忆生成回答
    chat_engine = ContextChatEngine.from_defaults(
        index=index,
        memory=memory,
        llm=llm,  # 使用前面配置的LLM
        system_prompt="你是一个基于文档的问答助手,回答必须结合提供的文档内容,不编造信息。"
    )
    
    return chat_engine

ContextChatEngine是 llama-index 专为 RAG 设计的引擎:收到问题后,会先从索引中检索相关文档片段(基于向量相似度),再将 “问题 + 文档片段 + 对话历史” 传给 LLM,确保回答基于文档

ChatMemoryBuffer用于保存对话历史,避免用户重复提问时需要重新输入上下文(如 “上文提到的 XX 是什么”)

四、前端交互:基于 Chainlit 的用户界面(app_ui.py

目标:实现用户可视化交互(聊天、文件上传),将 RAG 核心逻辑与用户操作连接起来。

4.1 应用初始化与认证

# app_ui.py
import chainlit as cl
from dotenv import load_dotenv
from base_rag import create_index, create_chat_engine
import os

# 加载环境变量
load_dotenv()

# 1. 认证控制:限制未授权访问
@cl.authorize_callback
def auth_callback(username: str, password: str):
    # 简单示例:仅允许admin/admin登录(实际可对接数据库)
    return username == "admin" and password == "admin"

# 2. 聊天启动时初始化
@cl.on_chat_start
async def on_chat_start():
    # 向用户发送欢迎消息
    await cl.Message(content="欢迎使用RAG聊天助手!请上传文档或直接提问~").send()
    
    # 加载已有的索引(若存在)
    index = create_index()
    # 创建聊天引擎并保存到用户会话(多用户隔离)
    chat_engine = create_chat_engine(index)
    cl.user_session.set("chat_engine", chat_engine)

@cl.authorize_callback:Chainlit 提供的认证机制,这里用简单的用户名密码校验,实际部署可替换为 OAuth 或数据库验证

cl.user_session:用于存储用户专属的聊天引擎,确保多用户同时使用时,各自的知识库和对话历史互不干扰(例如 A 用户上传的文档不会被 B 用户检索到)

4.2 消息处理(文件上传与提问)

# app_ui.py(续)
@cl.on_message
async def on_message(message: cl.Message):
    # 从会话中获取当前用户的聊天引擎
    chat_engine = cl.user_session.get("chat_engine")
    
    # 处理用户上传的文件(若有)
    if message.elements:
        documents = []
        for element in message.elements:
            # 只处理文件/图片(可扩展支持PDF、Markdown等)
            if element.type in ["file", "image"]:
                # 保存文件到临时路径
                file_path = f"./temp/{element.name}"
                os.makedirs("./temp", exist_ok=True)
                with open(file_path, "wb") as f:
                    f.write(element.content)
                documents.append(file_path)
        
        if documents:
            # 基于新文件更新索引
            await cl.Message(content="正在处理上传的文档...").send()
            index = create_index(documents=documents)
            # 更新会话中的聊天引擎
            chat_engine = create_chat_engine(index)
            cl.user_session.set("chat_engine", chat_engine)
            await cl.Message(content="文档处理完成,可以提问啦!").send()
            return  # 处理完文件后等待用户提问
    
    # 处理用户提问(流式返回回答)
    msg = cl.Message(content="")  # 初始化空消息用于流式输出
    await msg.send()
    
    # 调用聊天引擎处理问题,流式获取结果
    response = chat_engine.stream_chat(message.content)
    for token in response.response_gen:
        msg.content += token
        await msg.update()  # 实时更新消息内容(打字机效果)

文件上传流程:用户上传文件后,先保存到本地临时目录,再调用create_index更新索引,最后用新索引重建聊天引擎 —— 确保后续提问能检索到新文档

提问处理流程:通过stream_chat获取流式回答(逐 token 返回),再用msg.update()实时推送给用户,模拟 ChatGPT 的 “边生成边显示” 效果,提升体验

五、完整流程串联:从用户操作到回答生成

  1. 启动应用:运行chainlit run app_ui.py,Chainlit 加载环境变量并启动前端界面。
  2. 用户登录:通过admin/admin认证后进入聊天界面。
  3. 初始化on_chat_start加载已有索引,创建带记忆的聊天引擎,存入用户会话。
  4. 交互循环
    • 若用户上传文件:保存文件→更新索引→重建聊天引擎。
    • 若用户提问:聊天引擎先检索索引找相关文档→结合对话历史→调用 LLM 生成回答→流式返回给用户。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值