【RAG 项目实战 06】使用 LangChain 结合 Chainlit 实现文档问答

【RAG 项目实战 06】使用 LangChain 结合 Chainlit 实现文档问答


NLP Github 项目:


[!NOTE] 问题汇总

  1. 如何添加rag-demo能力
  2. 如何将rag-demo的单轮问答转化为多轮问答
  3. 如何改造rag-demo的提示词:将 StringPrompt 转化为 ChatPrompt
  4. 如何拼接和组合不同的Prompt
  5. 如何修改Hub中的Prompt

一、文档问答流程

检索式问答的功能示意图:
检索式问答系统的功能示意

检索式问答的系统流程图:

二、文档问答系统的核心模块

2.1 环境配置

# @Author:青松  
# 公众号:FasterAI  
# Python, version 3.10.14  
# Pytorch, version 2.3.0  
# Chainlit, version 1.1.301

2.2 文档分块

使用文档分割器 RecursiveCharacterTextSplitter 将用户上传的文件分割文本块

# 配置文件分割器,每个块1000个token,重复100个
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)

files = None

# 等待用户上传文件
while files is None:
	files = await cl.AskFileMessage(
		content="Please upload a text file to begin!",
		accept=["text/plain"],
		max_size_mb=20,
		timeout=180,
	).send()

file = files[0]

# 发送处理文件的消息
msg = cl.Message(content=f"Processing `{file.name}`...", disable_feedback=True)
await msg.send()

with open(file.path, "r", encoding="utf-8") as f:
	text = f.read()

# 将文件分割成文本块
texts = text_splitter.split_text(text)

2.3 建立索引

# 将文件分割成文本块
texts = text_splitter.split_text(text)

# 为每个文本块添加元数据信息
metadatas = [{"source": f"{i}-pl"} for i in range(len(texts))]

# 使用异步方式创建 Chroma 向量数据库
vectorstore = await cl.make_async(Chroma.from_texts)(
	texts, embeddings_model, metadatas=metadatas
)

2.4 创建RAG链

# 创建会话历史记录
message_history = ChatMessageHistory()

# 使用 ConversationBufferMemory 记忆组件存储历史记录
memory = ConversationBufferMemory(
	memory_key="chat_history",
	output_key="answer",
	chat_memory=message_history,
	return_messages=True,
)

# todo:即将弃用,使用 create_history_aware_retriever 结合 create_retrieval_chain 替换
# 使用 Chroma vector store 创建一个检索链
chain = ConversationalRetrievalChain.from_llm(
	llm,
	chain_type="stuff",
	retriever=vectorstore.as_retriever(),
	memory=memory,
	return_source_documents=True,
)

cl.user_session.set("chain", chain)

2.5 检索生成

@cl.on_message
async def on_message(message: cl.Message):
    """ 监听用户消息事件 """

    chain = cl.user_session.get("chain")  # type: ConversationalRetrievalChain
    cb = cl.AsyncLangchainCallbackHandler()

    # todo 即将弃用,需要替换
    res = await chain.acall(message.content, callbacks=[cb])

    # 大模型检索生成的答案
    answer = res["answer"]
    await cl.Message(content=answer).send()

三、效果展示

启动程序:

chainlit run rag_app.py -w

系统截图:

RAG

结合聊天历史RAG

四、完整代码

# @Author:青松
# 公众号:FasterAI
# Python, version 3.10.14
# Pytorch, version 2.3.0
# Chainlit, version 1.1.301

import chainlit as cl
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ChatMessageHistory, ConversationBufferMemory
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceBgeEmbeddings

import llm_util
from common import Constants

# 获取大模型实例
llm = llm_util.get_llm(Constants.MODEL_NAME['QianFan'])

# 获取文本嵌入模型
model_name = "BAAI/bge-small-zh-v1.5"
encode_kwargs = {"normalize_embeddings": True}
embeddings_model = HuggingFaceBgeEmbeddings(
    model_name=model_name, encode_kwargs=encode_kwargs
)

# 配置文件分割器,每个块1000个token,重复100个
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)


@cl.on_chat_start
async def on_chat_start():
    """ 监听会话开始事件 """

    await send_welcome_msg()

    files = None

    # 等待用户上传文件
    while files is None:
        files = await cl.AskFileMessage(
            content="Please upload a text file to begin!",
            accept=["text/plain"],
            max_size_mb=20,
            timeout=180,
        ).send()

    file = files[0]

    # 发送处理文件的消息
    msg = cl.Message(content=f"Processing `{file.name}`...", disable_feedback=True)
    await msg.send()

    with open(file.path, "r", encoding="utf-8") as f:
        text = f.read()

    # 将文件分割成文本块
    texts = text_splitter.split_text(text)

    # 为每个文本块添加元数据信息
    metadatas = [{"source": f"{i}-pl"} for i in range(len(texts))]

    # 使用异步方式创建 Chroma 向量数据库
    vectorstore = await cl.make_async(Chroma.from_texts)(
        texts, embeddings_model, metadatas=metadatas
    )

    # 创建会话历史记录
    message_history = ChatMessageHistory()

    # 使用 ConversationBufferMemory 记忆组件存储历史记录
    memory = ConversationBufferMemory(
        memory_key="chat_history",
        output_key="answer",
        chat_memory=message_history,
        return_messages=True,
    )

    # todo:即将弃用,使用 create_history_aware_retriever 结合 create_retrieval_chain 替换
    # 使用 Chroma vector store 创建一个检索链
    chain = ConversationalRetrievalChain.from_llm(
        llm,
        chain_type="stuff",
        retriever=vectorstore.as_retriever(),
        memory=memory,
        return_source_documents=True,
    )

    # 通知用户文件已处理完成,更新当前窗口的内容
    msg.content = f"Processing `{file.name}` done. You can now ask questions!"
    await msg.update()

    cl.user_session.set("chain", chain)


@cl.on_message
async def on_message(message: cl.Message):
    """ 监听用户消息事件 """

    chain = cl.user_session.get("chain")  # type: ConversationalRetrievalChain
    cb = cl.AsyncLangchainCallbackHandler()

    # todo 即将弃用
    res = await chain.acall(message.content, callbacks=[cb])

    # 大模型的回答
    answer = res["answer"]
    await cl.Message(content=answer).send()


async def send_welcome_msg():
    image = cl.Image(url="https://qingsong-1257401904.cos.ap-nanjing.myqcloud.com/wecaht.png")

    # 发送一个图片
    await cl.Message(
        content="**青松** 邀你关注 **FasterAI**, 让每个人的 AI 学习之路走的更容易些!立刻扫码开启 AI 学习、面试快车道 **(^_^)** ",
        elements=[image],
    ).send()


@cl.password_auth_callback
def auth_callback(username: str, password: str):
    """ 持久化客户端聊天历史代码,不需要请删除 """
    if (username, password) == ("admin", "admin"):
        return cl.User(
            identifier="admin", metadata={"role": "admin", "provider": "credentials"}
        )
    else:
        return None

【动手学 RAG】系列文章:


【动手部署大模型】系列文章:

本文由mdnice多平台发布

posted @   青松^_^  阅读(60)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
点击右上角即可分享
微信分享提示