通过langchain的LCEL创建带历史感知的检索链

  • 前言:使用langchain搭建agent,很多时候我们需要记录用户提问的上下文,通过提问的上下文,到知识库中检索出对于问题的内容。交给大模型来整理输出。

怎么做

  1. 创建llm
  2. 创建momery 来存储,用户的提问和llm的回答。让llm具有记忆
  3. 加载企业内部文档库中的文档 ,将加载到的文档内容进行拆分 注意:这里拆分也分很多种方式:可以按符号递归拆分 ,可以按语义拆分 ,可以按字符串长度拆分等等...
  4. 使用文本潜入模型进行向量化对拆分好的文档进行向量化
  5. 将向量化后的List[Document]再存入到向量数据库
  6. 定义历史感知的prompt,获取历史chat_history和用户输入input传递给prompt在通过LCEL(langchain 表达式语言)传递给llm。通过llm给基于历史上下文和当前输入的input,独立理解和输出为一个相关问题。 什么是LCEL呢?
    • 新版本的langchain表达式语言。统一调用方式,让chain之间调用更方便。(采用了和nodejs类似的管道符操作)
    • 特点 :所有实现Runnable(可被调用,批处理,流处理,转换和组合的工作单元)接口的对象,都可以用管道符号"|"来进行串行调用,前一个的结果,作为后一个的输入。
    1. langchia_core.runnable模块内有LCEL操作的方法。
    • RunnableLambda:将python可调用对象(和js一样python中function也是对象)转换为Runnable对象,经过RunnableLambda处理后就可以使用管道符号了。
    • RunnableParallel:RunnableParallel是与RunnableSequence并列的两个主要组合原语之一。它会并发调用Runnables,并为每个Runnable提供相同的输入,每个Runnable运行结果,并返回一个map
    • RunnableSequence:串行调用Runnables
    • RunnablePassthrough:将参数透传,不做处理
    1. 使用LCEL操作,管道符号"|"最后一个可以是通函数,因为最后一个 对象会被langchain做隐式转换为Runnable对象
  7. 通过向量数据库实例生成检索器 ,检索器调用问题检索出相关文档 注意:这里检索的方式有很多种:相似性检索mmr检索多轮检索上下文压缩检索等等,根据自己的需要使用不同的检索方式
  8. 将检索结果的文档拼接作为参数context,历史记录参数chat_history, 用户输入参数input传递给问答链qa_chain。
  9. 定义qa_chain的qa_prompt接受参数,通过llm生产答案
  10. 通过StrOutputParser将结果格式化输出为字符串

一:创建llm

新版本langchain创建llm可以使用init_chat_model,从from langchain_core.model import init_chat_model中引入

python 复制代码
 from langchain_core.model import init_chat_model;
 
 llm = init_chat_model(
     model="deepseek-chat",
     api_key="******",
     base_url="https://api.deepseek.com/v1",
 )

创建memory

python 复制代码
from langchain_core.chat_history import BaseChatMessageHistory;
from langchain_community.chat_message_history import ChatMessageHistory;

store={};
def get_session_history(session_id): -> BaseChatMessageHistory
    if session_id not in store:
        store[session_id] = new ChatMessageHistory();
    return store[session_id];

通过加载器加载文档

很多场景下使用langchain搭建的agent都需要到企业私有文档库中加载内容,作为agent的知识库来回答和分析问题

python 复制代码
from typing import List;
from langchain_core.document_loader import Document;
from langchain_community.documents_loader import WebBaseLoader;


def load_documents(url): -> str
    loader = WebBaseLoader(url);
    text = loader.load();
text = load_documents("https://lilianweng.github.io/posts/2023-06-23-agent/")

使用splitter切割文档并向量化

python 复制代码
from langchain_text_splitter import RecursiveCharacterTextSplitter;

def spliter_text(text: str): -> List[Document]
    splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200);
    docuemnts = splitter.split_documents(text);

documents = spliter_text(text)

存储到向量数据库

python 复制代码
import os
os.environ["USER_AGENT"] = "langchain-retriever/1.0.0"
from langchain_community.vectorStores import Chroma;
from langchain_community.embeddings import DashScopeEmbedding;

embedding = DashScopeEmbedding(
  model="text-embedding-v3",
  dashscope_api_key="*******",
)

def create_and_load_vector_store(docs: List[Document], persist_directory):
    vertor_store = None;
    embedding = 
    if os.path.exists(persist_directory):
        vertor_store = Chroma(embedding_function=embedding, persist_directory=persist_directory);
        vector_store.add_documents(docs);
    else:
        vector_store = Chroma.from_documets(dosc, embedding=embedding, persist_directory);
        
    return vertor_store;

创建历史对话感知链

python 复制代码
from langchain_core.prompt import ChatPromptTemplate, MessagesPlaceholder;
contextualize_q_prompt = ChatPromptTemplate.from_messages[
    ("system": ("""
      "给定聊天历史和最新的用户问题,"
      "该问题可能引用聊天历史中的上下文,"
      "重新构造一个可以在没有聊天历史的情况下理解的独立问题。"
      "如果需要,不要回答问题,只需重新构造问题并返回。"
    """))
    MessagesPlaceholder(variable_name="chat_history"),
    ("human": "{input}")
]

contextualize_q_chain = contextualize_q_prompt | llm;

创建一个问答链

python 复制代码
from langchain_core.prompt import ChatPromptTemplate, MessagesPlaceholder;

qa_prompt=ChatPromptTemplate.from_messages([
    ("system": ("""
      你是一个助手,根据用户的问题,从给定的文档中检索相关信息,并返回答案:
      如果查询到的信息与用户的问题不相关,请返回"查询到的信息与用户的问题不相关"
      回答尽量精简,不超过3句话
      \n\n
     {context})
    """))
   MessagesPlaceholder(variable_name="chat_history"),
   ("user": "{input}"),
])

qa_chain = qa_prompt | llm;

使用LCEL组合

python 复制代码
from langchain_core.runnable import RunnableLambda, RunnableParallel, 
RunnablePassthrough, RunnableMessageHistoryMessage;
from langchain_core.message import BaseMessage;
from langchain_core.out_parsers import StrOutputParser;
# 返回的事AImessgae,是一个对象。所以这里取出文本内容。当然也可以把get_new_question替换为StrOutputParser();也是一样的
def get_new_question(message: BaseMessage):
    return message.content;
 
def formate_doc(documents: List[Document]): -> str
    return ("").join([doc.pageContent for doc in documents])
# 因为retriver_documents函数不是一个Runnable对象,所以下面在使用改函数时,使用了RunnableLambda包裹为一个Runnable对象。这样就可以使用"|"操作了
def retriver_documents(x: dict):
   vector_store=create_and_load_vector_store(documents, "./langchain_retriever_lcel.db"))
   retriever = vector_store.as_retriever(search_type="mmr", config={"k": 4, "fetch_k": 20});
   input = x.get("input", "");
   re_create_question = x.get("re_create_question", input);
   docs = retriver.invoke(re_create_question);
   return docs;

def create_history_awaver_chain()
    
    history_awaver_chain = RunnableParallel({
        "input": RunnablePassthrough,
        "chat_history": lambda x: x.get("chay_history", ""),
    })
    | RunnableParallel({
        "re_create_question": contextualize_q_chain | get_new_question,
        "input": lambda x: x.get("input", ""),
        "chat_history": lambda x: x.get("chat_history", ""),
      })
    | RunnableParallel({
         # 这里context后面的值为什么不用retriver.invoke(x.get("re_create_question"))呢?
         # 应为retriver.invoke(x.get("re_create_question"))返回的是一个List[message],而不是一个Runnable对象,所以这里用RunnableLambda来包裹函数,将返回的结果转为Runnable对象,不然使用管道符号"|"会报错。
         "context": lambda x: RunnableLambda(retriver_documents) | formate_doc, # 这里的formate_doc也可以改为StrOutputParser(),也是一样的效果
         "input": lambda x: x.get("input", ""),
         "chat_history": lambda x: x.get("chat_history", ""),
      })
    | qa_chain
    | StrOutputParser()
    
    return history_awaver_chain;
    
chain=create_history_awaver_chain();
converstion_chain=RunnableMessageHistoryMessage(
    chain,
    get_session_history=get_session_history,
    imput_message_key="input",
    history_message_key="chat_history",
)
result = converstion_chain.invoke({"input": "什么是任务分解?"}, config={"configurable": {"session_id": "123"});

print(result, '=====result======);

result1 = converstion_chain.invoke({"input": "我刚问了什么问题?"}, config={"configurable": {"session_id": "123"});

print(result1, '=====result======);
    
相关推荐
JaydenAI2 小时前
[拆解LangChain执行引擎] ManagedValue——一种特殊的只读虚拟通道
python·langchain
OPEN-Source3 小时前
大模型实战:搭建一张“看得懂”的大模型应用可观测看板
人工智能·python·langchain·rag·deepseek
一切尽在,你来5 小时前
1.4 LangChain 1.2.7 核心架构概览
人工智能·langchain·ai编程
一切尽在,你来5 小时前
1.3 环境搭建
人工智能·ai·langchain·ai编程
蛇皮划水怪12 小时前
深入浅出LangChain4J
java·langchain·llm
、BeYourself13 小时前
LangChain4j 流式响应
langchain
、BeYourself13 小时前
LangChain4j之Chat and Language
langchain
qfljg15 小时前
langchain usage
langchain
kjkdd19 小时前
6.1 核心组件(Agent)
python·ai·语言模型·langchain·ai编程
渣渣苏1 天前
Langchain实战快速入门
人工智能·python·langchain