通过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======);
    
相关推荐
资深web全栈开发17 小时前
深度对比 LangChain 8 种文档分割方式:从逻辑底层到选型实战
深度学习·自然语言处理·langchain
FranzLiszt184717 小时前
基于One API 将本地 Ollama 模型接入 FastGPT
langchain·fastgpt·rag·ollama·one api
zuozewei20 小时前
零基础 | 基于LangChain的角色扮演聊天机器人实现
python·langchain·机器人
猫头虎20 小时前
Claude Code 永动机:ralph-loop 无限循环迭代插件详解(安装 / 原理 / 最佳实践 / 避坑)
ide·人工智能·langchain·开源·编辑器·aigc·编程技术
言之。1 天前
LangChain短期内存系统
microsoft·langchain
xinxin本尊1 天前
使用langchain创建一个ReAct能力的agent
langchain
言之。1 天前
LangChain 模型模块使用详解
python·langchain·flask
田井中律.1 天前
AI大模型之Agent,RAG,LangChain(二)
langchain
linmoo19862 天前
Langchain4j 系列之十一 - 工具调用(AI Services)
人工智能·langchain·工具·langchain4j·toolcall·tool calling