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