构建RAG应用-day04-将LLM 接入 LangChain 构建检索问答链 部署知识库助手
llm接入langchain
示例-llm作为翻译助手
接入chatgpt到langchain,使用普通写法和Langchain的表达式语言写法 (LCEL)。
import os
import openai
from dotenv import load_dotenv, find_dotenv
from langchain_openai import ChatOpenAI
from langchain.prompts.chat import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
def init_openai():
openai.base_url = 'https://api.chatanywhere.tech/v1'
_ = load_dotenv(find_dotenv())
openai_api_key = os.environ['OPENAI_API_KEY']
llm = ChatOpenAI(temperature=0.0, base_url=openai.base_url, openai_api_key=openai_api_key, streaming=True)
return llm
def init_prompt():
template = "你是一个翻译助手,可以帮助我将 {input_language} 翻译成 {output_language}."
human_template = "{text}"
chat_prompt = ChatPromptTemplate.from_messages([
("system", template),
("human", human_template),
])
return chat_prompt
if __name__ == '__main__':
# # 普通写法
# # 初始化llm
# llm = init_openai()
# # 初始化prompt
# chat_prompt = init_prompt()
# text = "我带着比身体重的行李,\
# 游入尼罗河底,\
# 经过几道闪电 看到一堆光圈,\
# 不确定是不是这里。\
# "
# messages = chat_prompt.format_messages(input_language="中文", output_language="英文", text=text)
# # 调用llm
# output = llm.invoke(messages)
# # 输出解析
# output_parser = StrOutputParser()
# result = output_parser.invoke(output)
# print(result)
# Langchain的表达式语言写法 (LCEL)
llm = init_openai()
chat_prompt = init_prompt()
output_parser = StrOutputParser()
# 类似于 Unix 管道运算符,它将不同的组件链接在一起,将一个组件的输出作为下一个组件的输入。
chain = chat_prompt | llm | output_parser
chain.invoke({
"input_language": "中文",
"output_language": "英文",
"text": "我带着比身体重的行李,\
游入尼罗河底,\
经过几道闪电 看到一堆光圈,\
不确定是不是这里。\
"})
构建问答链
使用langchain+chatgpt+zhipu embedding 构建应用,并且使用langchain记忆功能。
from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQA
import openai
from langchain_openai import ChatOpenAI
from embed import ZhipuAIEmbeddings
import os
from dotenv import load_dotenv, find_dotenv
from langchain.vectorstores.chroma import Chroma
from langchain.memory import ConversationBufferMemory
class VectorDB:
embedding = ZhipuAIEmbeddings()
persist_directory = 'data_base/vector_db/chroma'
slice = 20
def __init__(self, sliced_docs: list = None):
if sliced_docs is None:
self.vectordb = Chroma(embedding_function=self.embedding, persist_directory=self.persist_directory)
else:
self.vectordb = Chroma.from_documents(
documents=sliced_docs[:self.slice], # 为了速度,只选择前 20 个切分的 doc 进行生成;使用千帆时因QPS限制,建议选择前 5 个doc
embedding=self.embedding,
persist_directory=self.persist_directory # 允许我们将persist_directory目录保存到磁盘上
)
def persist(self):
self.vectordb.persist()
print(f"向量库中存储的数量:{self.vectordb._collection.count()}")
def sim_search(self, query, k=3):
sim_docs = self.vectordb.similarity_search(query, k=k)
for i, sim_doc in enumerate(sim_docs, start=1):
print(f"检索到的第{i}个内容: \n{sim_doc.page_content[:200]}", end="\n--------------\n")
return sim_docs
def mmr_search(self, query, k=3):
mmr_docs = self.vectordb.max_marginal_relevance_search(query, k=k)
for i, sim_doc in enumerate(mmr_docs, start=1):
print(f"MMR 检索到的第{i}个内容: \n{sim_doc.page_content[:200]}", end="\n--------------\n")
return mmr_docs
def init_openai():
openai.base_url = 'https://api.chatanywhere.tech/v1'
_ = load_dotenv(find_dotenv())
openai_api_key = os.environ['OPENAI_API_KEY']
llm = ChatOpenAI(temperature=0.0, base_url=openai.base_url, openai_api_key=openai_api_key, streaming=True)
return llm
if __name__ == '__main__':
llm = init_openai()
vectordb = VectorDB().vectordb
memory = ConversationBufferMemory(
memory_key="chat_history", # 与 prompt 的输入变量保持一致。
return_messages=True, # 将以消息列表的形式返回聊天记录,而不是单个字符串
output_key="result"
)
template = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答
案。最多使用三句话。尽量使答案简明扼要。总是在回答的最后说“谢谢你的提问!”。
{context}
问题: {question}
"""
QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context", "question"],
template=template)
qa_chain = RetrievalQA.from_chain_type(llm,
retriever=vectordb.as_retriever(),
return_source_documents=True,
memory=memory,
chain_type_kwargs={"prompt": QA_CHAIN_PROMPT})
question_1 = "什么是南瓜书?"
question_2 = "王阳明是谁?"
# result = qa_chain({"query": question_1})
# print("大模型+知识库后回答 question_1 的结果:")
# print(result["result"])
question = "我可以学习到关于提示工程的知识吗?"
result = qa_chain({"query": question})
print(result['result'])
部署知识库助手
使用streamlit部署知识库助手。
import openai
import streamlit as st
from langchain_openai import ChatOpenAI
import os
from langchain_core.output_parsers import StrOutputParser
from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQA
from embed import ZhipuAIEmbeddings
from langchain.vectorstores.chroma import Chroma
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv()) # read local .env file
openai.base_url = 'https://api.chatanywhere.tech/v1'
zhipuai_api_key = os.environ['ZHIPUAI_API_KEY']
def generate_response(input_text, openai_api_key):
llm = ChatOpenAI(temperature=0.7, openai_api_key=openai_api_key, base_url=openai.base_url)
output = llm.invoke(input_text)
output_parser = StrOutputParser()
output = output_parser.invoke(output)
# st.info(output)
return output
def get_vectordb():
# 定义 Embeddings
embedding = ZhipuAIEmbeddings()
# 向量数据库持久化路径
persist_directory = 'data_base/vector_db/chroma'
# 加载数据库
vectordb = Chroma(
persist_directory=persist_directory, # 允许我们将persist_directory目录保存到磁盘上
embedding_function=embedding
)
return vectordb
# 带有历史记录的问答链
def get_chat_qa_chain(question: str, openai_api_key: str):
vectordb = get_vectordb()
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0, openai_api_key=openai_api_key, base_url=openai.base_url)
memory = ConversationBufferMemory(
memory_key="chat_history", # 与 prompt 的输入变量保持一致。
return_messages=True # 将以消息列表的形式返回聊天记录,而不是单个字符串
)
retriever = vectordb.as_retriever()
qa = ConversationalRetrievalChain.from_llm(
llm,
retriever=retriever,
memory=memory
)
result = qa({"question": question})
return result['answer']
# 不带历史记录的问答链
def get_qa_chain(question: str, openai_api_key: str):
vectordb = get_vectordb()
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0, openai_api_key=openai_api_key, base_url=openai.base_url)
template = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答
案。最多使用三句话。尽量使答案简明扼要。总是在回答的最后说“谢谢你的提问!”。
{context}
问题: {question}
"""
QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context", "question"],
template=template)
qa_chain = RetrievalQA.from_chain_type(llm,
retriever=vectordb.as_retriever(),
return_source_documents=True,
chain_type_kwargs={"prompt": QA_CHAIN_PROMPT})
result = qa_chain({"query": question})
return result["result"]
# Streamlit 应用程序界面
def main():
st.title('🦜🔗 动手学大模型应用开发')
openai_api_key = st.sidebar.text_input('OpenAI API Key', type='password')
# 添加一个选择按钮来选择不同的模型
# selected_method = st.sidebar.selectbox("选择模式", ["qa_chain", "chat_qa_chain", "None"])
selected_method = st.radio(
"你想选择哪种模式进行对话?",
["None", "qa_chain", "chat_qa_chain"],
captions=["不使用检索问答的普通模式", "不带历史记录的检索问答模式", "带历史记录的检索问答模式"])
# 用于跟踪对话历史
if 'messages' not in st.session_state:
st.session_state.messages = []
messages = st.container(height=300)
if prompt := st.chat_input("Say something"):
# 将用户输入添加到对话历史中
st.session_state.messages.append({"role": "user", "text": prompt})
if selected_method == "None":
# 调用 respond 函数获取回答
answer = generate_response(prompt, openai_api_key)
elif selected_method == "qa_chain":
answer = get_qa_chain(prompt, openai_api_key)
elif selected_method == "chat_qa_chain":
answer = get_chat_qa_chain(prompt, openai_api_key)
# 检查回答是否为 None
if answer is not None:
# 将LLM的回答添加到对话历史中
st.session_state.messages.append({"role": "assistant", "text": answer})
# 显示整个对话历史
for message in st.session_state.messages:
if message["role"] == "user":
messages.chat_message("user").write(message["text"])
elif message["role"] == "assistant":
messages.chat_message("assistant").write(message["text"])
if __name__ == "__main__":
main()