04-不使用LangChain的链实现历史消息的RAG应用

摘要

在上文中,学习了使用内置链LCELAgent的方式构建带有历史消息记录的RAG应用。除了基于Agent的构建方式外,其他两种构建方式大体流程类似。基本都是先构建Prompt模板,然后使用LCEL内置链的形式,将大模型、Prompt等组装起来,无论是LCEL或内置链,都是基于LangChain的去完成的。

为了进一步理解各步骤执行情况,本文将不使用LangChain的去构建RAG模型,进一步去体会。

LLM类

简单实现LLM的初始化,并带有返回模型的功能,模型包括:LLMEmbedding。代码如下:

from langchain_openai import ChatOpenAI
from langchain_community.embeddings import ZhipuAIEmbeddings

from dotenv import load_dotenv
import os
load_dotenv()
class LLMModel:
    def __init__(self):
        self.api_key = os.getenv("api_key")
        self.base_url = os.getenv("base_url")
        self.zhipu_api_key = os.getenv("ZHIPU_API_KEY")
        self.zhipu_embedding_model = os.getenv("ZHIPU_EMBEDDING_MODEL")

    def get_llm_model(self):
        return ChatOpenAI(model = "deepseek-chat", api_key = self.api_key, base_url = self.base_url)

    def get_embedding_model(self):
        return ZhipuAIEmbeddings(api_key = self.zhipu_api_key, model = self.zhipu_embedding_model)

向量库Chroma类

初始化向量库,同时需要具备获取检索器,文档的增、删功能,具体如下:

from langchain_chroma import Chroma
from llm_model import LLMModel
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyPDFLoader

class ChromaDB:

    def __init__(self):
        self.db = Chroma(
            embedding_function=LLMModel().get_embedding_model(),
            persist_directory="./chroma_db"
        )
	# 获取检索器
    def get_retriever(self):
        return self.db.as_retriever(
            search_type = "mmr"
        )
	
    def add_docs(self,docs):
        for doc in docs:
            self.db.add_documents([doc])
    # 加载PDF文档并向量化存储
    def load_save_documents(self,file_path):
        # todo 验证路径合法性
        loader = PyPDFLoader(file_path)
        spliter = RecursiveCharacterTextSplitter(
            chunk_size=100,
            chunk_overlap=20,
            is_separator_regex=True,
            length_function=len
        )

        docs = []
        for doc in loader.lazy_load():
            docs.append(doc)

        doc_after_split = spliter.split_documents(docs)
        self.add_docs(doc_after_split)
	# 根据ID删除文档
    def delete_docs(self,ids):
        self.db.delete(ids)

构建历史消息持久化存储

不使用LangChain的BaseChatMessageHistory类来存储历史消息,使用自定义类存储,此处简单使用csv文件进行演示,实现历史消息存储和查询功能,使用session_id区分会话。代码如下:

import os
import pandas as pd

class ChatHistory:
    def __init__(self, chat_history_file_path='./chat_history.csv'):
        self.chat_history_file_path = chat_history_file_path

    def save_chat_history(self,session_id, user_message,bot_message,):
        """
        保存聊天历史
        :param chat_history_file_path: 聊天历史存储地址
        :param user_message: 用户输入的消息
        :param bot_message: llm回复的消息
        :return:
        """
        df = pd.DataFrame({'session_id':str(session_id),'user_message': [user_message], 'bot_message': [bot_message]})

        if os.path.exists(self.chat_history_file_path):
            df.to_csv(self.chat_history_file_path, mode='a', header=False, index=False)
        else:
            df.to_csv(self.chat_history_file_path, mode='w', header=True, index=False)

    # 根据session_id查询对话历史信息
    def load_chat_history(self, session_id):
        """
        根据session_id查询对话历史信息
        :param session_id: 会话ID
        :return:
        """
        if os.path.exists(self.chat_history_file_path):
            df = pd.read_csv(self.chat_history_file_path)
            df = df.loc[df['session_id'] == str(session_id)]
        else:
            df = pd.DataFrame(columns=['session_id', 'user_message', 'bot_message'])
        return df

构建带有历史消息的ChatBot类

自定义问答ChatBot类,具备保存、查询历史消息,基于历史消息进行问答的功能。代码如下:

from llm_model import LLMModel
from chat_history import ChatHistory
from langchain_core.messages import HumanMessage,SystemMessage

class ChatBot:

    def __init__(self):
        self.llm_model = LLMModel()
        self.ch = ChatHistory()
        self.llm = self.llm_model.get_llm_model()

    def add_chat_record(self, session_id, question, answer):
        self.ch.save_chat_history(session_id, question, answer)

    def get_chat_record(self, session_id):
        history_pd = self.ch.load_chat_history(session_id)
        history = []
        if len(history_pd) > 0:
            for _, row in history_pd.iterrows():
                list_ = row.tolist()
                dic_ = {"human_message": list_[1], "ai_meaasge": list_[2]}
                history.append(dic_)
        return history

    def get_answer_with_session(self, session_id, question):
        history = self.get_chat_record(session_id)
        self.system_prompt = f"你是一个深度学习的专家,你需要根据用户的输入,以及用户上下文:{history},理解用户问题,给出通俗易懂,且简洁的回复。如果用户上下文不存在,可以忽略上下文理解用户问题。如果用户问题与上下文没有关联密切关联,可以忽略上下文理解问题。"
        response = self.llm.invoke([HumanMessage(content = question), SystemMessage(content = self.system_prompt)])
        bot_message = response.content
        self.add_chat_record(session_id, question, bot_message)
        return response

总结:

在上述代码中,并没有使用到LangChain的链机制,在get_answer_with_session方法中进行了手动实现数据流转。

构建带有历史消息的RAG应用

ChatBot作为基类,重写get_answer_with_session方法,实现知识库检索功能。代码如下:

from chat_bot import ChatBot
from langchain_core.messages import HumanMessage,SystemMessage
from chroma_db import ChromaDB
class RAGChatBot(ChatBot):

    def __init__(self):
        super().__init__()
        self.chroma_db = ChromaDB()
        self.retriever = self.chroma_db.get_retriever()

    def get_context(self, question, session_id):

        print("开始查询历史消息记录!")
        history = self.get_chat_record(session_id)
        print(f"历史记录为:{history}")
        print("———————————————————————————————————————")
        self.contextualize_system_prompt = f"给定一个聊天历史和最新的用户问题,该最新的用户问题可能需要引用聊天历史中的上下文,制定一个独立的问题," \
                                           f"可以在没有聊天历史的情况下理解。不要回答任何问题,只要在需要时重新表述最新问题,否则就原样返回。聊天历史如下:{history}"
        print("开始重构用户问题!")
        response = self.llm.invoke([HumanMessage(content=question), SystemMessage(content=self.contextualize_system_prompt)])

        print(f"重构后的用户提问为:{response.content}")
        print("———————————————————————————————————————")

        print("开始查询知识库!")
        context = self.retriever.invoke(response.content)

        return context

    def get_answer_with_session(self, session_id, question):
        """
            根据session_id和question,获取answer
        """
        history = self.get_chat_record(session_id)
        context = self.get_context(question, session_id)
        context_format = "\n\n".join(doc.page_content for doc in context)
        print(f"知识库查询结果为:{context}")
        print("———————————————————————————————————————")
        self.system_prompt = f"你是一个基于深度学习数据融合的专家,你需要根据用户的输入、用户历史会话:{history}、以及用户上下文:{context_format},理解用户问题,给出通俗易懂,且简洁的回复。"
        response = self.llm.invoke([HumanMessage(content=question), SystemMessage(content=self.system_prompt)])
        self.add_chat_record(session_id, question, response.content)
        return response
posted @ 2025-06-04 18:21  AfroNicky  阅读(31)  评论(0)    收藏  举报