LLM 接入 LangChain
。LangChain 内置了 OpenAI、LLAMA 等大模型的调用接口,并通过允许用户自定义 LLM 类型,来提供强大的可扩展性
基于 LangChain 调用 ChatGPT
基于 LangChain 调用百度文心
基于 LangChain 调用讯飞星火
基于 LangChain 调用智谱 GLM
由于 langchain 中提供的 ChatGLM 已不可用,因此我们需要自定义一个 LLM
基于以下代码
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from typing import Any, List, Mapping, Optional, Dict
from langchain_core.callbacks.manager import CallbackManagerForLLMRun
from langchain_core.language_models.llms import LLM
from zhipuai import ZhipuAI
import os
# 继承自 langchain.llms.base.LLM
class ZhipuAILLM(LLM):
# 默认选用 glm-4
model: str = "glm-4"
# 温度系数
temperature: float = 0.1
# API_Key
api_key: str = None
def _call(self, prompt : str, stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any):
client = ZhipuAI(
api_key = self.api_key
)
def gen_glm_params(prompt):
'''
构造 GLM 模型请求参数 messages
请求参数:
prompt: 对应的用户提示词
'''
messages = [{"role": "user", "content": prompt}]
return messages
messages = gen_glm_params(prompt)
response = client.chat.completions.create(
model = self.model,
messages = messages,
temperature = self.temperature
)
if len(response.choices) > 0:
return response.choices[0].message.content
return "generate answer error"
# 首先定义一个返回默认参数的方法
@property
def _default_params(self) -> Dict[str, Any]:
"""获取调用API的默认参数。"""
normal_params = {
"temperature": self.temperature,
}
# print(type(self.model_kwargs))
return {**normal_params}
@property
def _llm_type(self) -> str:
return "Zhipu"
@property
def _identifying_params(self) -> Mapping[str, Any]:
"""Get the identifying parameters."""
return {**{"model": self.model}, **self._default_params}
调用 GLM
# 需要下载源码
from zhipuai_llm import ZhipuAILLM
from dotenv import find_dotenv, load_dotenv
import os
# 读取本地/项目的环境变量。
# find_dotenv()寻找并定位.env文件的路径
# load_dotenv()读取该.env文件,并将其中的环境变量加载到当前的运行环境中
# 如果你设置的是全局的环境变量,这行代码则没有任何作用。
_ = load_dotenv(find_dotenv())
# 获取环境变量 API_KEY
api_key = os.environ["ZHIPUAI_API_KEY"] #填写控制台中获取的 APIKey 信息
zhipuai_model = ZhipuAILLM(model = "glm-4", temperature = 0.1, api_key = api_key) #model="glm-4-0520",
print(zhipuai_model("你好,请你自我介绍一下!"))
构建检索问答链
加载向量数据库
import sys
sys.path.append("../beginner's lab") # 将父目录放入系统路径中
# 使用智谱 Embedding API,注意,需要将上一章实现的封装代码下载到本地
from zhipuai_embedding import ZhipuAIEmbeddings
from langchain.vectorstores.chroma import Chroma
from dotenv import load_dotenv, find_dotenv
import os
_ = load_dotenv(find_dotenv()) # read local .env file
zhipuai_api_key = os.environ['ZHIPUAI_API_KEY']
# 定义 Embeddings
embedding = ZhipuAIEmbeddings()
# 向量数据库持久化路径
persist_directory = 'data_base/vector_db/chroma'
# 加载数据库
vectordb = Chroma(
persist_directory=persist_directory, # 允许我们将persist_directory目录保存到磁盘上
embedding_function=embedding
)
# print(f"向量库中存储的数量:{vectordb._collection.count()}")
# question = "什么是prompt engineering?"
# docs = vectordb.similarity_search(question,k=3)
# print(f"检索到的内容数:{len(docs)}")
# for i, doc in enumerate(docs):
# print(f"检索到的第{i}个内容: \n {doc.page_content}", end="\n-----------------------------------------------------\n")
构建检索问答链
//前几次提问,大模型 + 知识库的回答是用英文,神奇
# 需要下载源码
from zhipuai_llm import ZhipuAILLM
from dotenv import find_dotenv, load_dotenv
import os
# 读取本地/项目的环境变量。
# find_dotenv()寻找并定位.env文件的路径
# load_dotenv()读取该.env文件,并将其中的环境变量加载到当前的运行环境中
# 如果你设置的是全局的环境变量,这行代码则没有任何作用。
_ = load_dotenv(find_dotenv())
# 获取环境变量 API_KEY
api_key = os.environ["ZHIPUAI_API_KEY"] #填写控制台中获取的 APIKey 信息
llm = ZhipuAILLM(model = "glm-4", temperature = 0.1, api_key = api_key) #model="glm-4-0520",
# print(zhipuai_model("你好,请你自我介绍一下!"))
from langchain.prompts import PromptTemplate
template = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答
案。最多使用三句话。尽量使答案简明扼要。总是在回答的最后说“谢谢你的提问!”。
{context}
问题: {question}
"""
QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],
template=template)
from langchain.chains import RetrievalQA
qa_chain = RetrievalQA.from_chain_type(llm,
retriever=vectordb.as_retriever(),
return_source_documents=True,
chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})
question_1 = "什么是南瓜书?"
question_2 = "王阳明是谁?"
result = qa_chain({"query": question_1})
print("大模型+知识库后回答 question_1 的结果:")
print(result["result"])
result = qa_chain({"query": question_2})
print("大模型+知识库后回答 question_2 的结果:")
print(result["result"])
"""
大模型+知识库后回答 question_1 的结果:
I don't know what the "Pumpkin Book" refers to. Thanks for your question!
大模型+知识库后回答 question_2 的结果:
王阳明是明代著名的哲学家、军事家、教育家,提出了“知行合一”的哲学思想,影响深远。他的学说被后人称为阳明学或心学。谢谢
你的提问!
"""
prompt_template = """请回答下列问题:{}""".format(question_1)
### 基于大模型的问答
print(llm.predict(prompt_template))
prompt_template = """请回答下列问题:{}""".format(question_2)
### 基于大模型的问答
print(llm.predict(prompt_template))
添加历史对话的记忆功能
记忆 Memory
介绍 LangChain 中的储存模块,即如何将先前的对话嵌入到语言模型中的,使其具有连续对话的能力。我们将使用 ConversationBufferMemory
,它保存聊天消息历史记录的列表,这些历史记录将在回答问题时与问题一起传递给聊天机器人,从而将它们添加到上下文中
对话检索链 Conversation Retrieval Chain
# 前略
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory(
memory_key="chat_history", # 与 prompt 的输入变量保持一致。
return_messages=True # 将以消息列表的形式返回聊天记录,而不是单个字符串
)
from langchain.chains import ConversationalRetrievalChain
retriever=vectordb.as_retriever()
qa = ConversationalRetrievalChain.from_llm(
llm,
retriever=retriever,
memory=memory
)
question = "我可以学习到关于提示工程的知识吗?"
result = qa({"question": question})
print(result['answer'])
# question = "为什么这门课需要教这方面的知识?"
# result = qa({"question": question})
# print(result['answer'])
question = "继续"
result = qa({"question": question})
print(result['answer'])
In my example, return is OK.
But when the question is “你知道我的上一个问题或者你的上一个回答吗”, it seems that glm has no idea. Weird indeed.
部署知识库助手
Streamlit 是一种快速便捷的方法,可以直接在 Python 中通过友好的 Web 界面演示机器学习模型。在本课程中,我们将学习_如何使用它为生成式人工智能应用程序构建用户界面_
Streamlit 简介
Streamlit
是一个用于快速创建数据应用程序的开源 Python 库。它的设计目标是让数据科学家能够轻松地将数据分析和机器学习模型转化为具有交互性的 Web 应用程序,而无需深入了解 Web 开发。它不需要你去编写任何客户端代码(HTML/CSS/JS),只需要编写普通的 Python 模块
Streamlit 提供了一组简单而强大的基础模块,用于构建数据应用程序:
-
st.write():这是最基本的模块之一,用于在应用程序中呈现文本、图像、表格等内容。
-
st.title()、st.header()、st.subheader():这些模块用于添加标题、子标题和分组标题,以组织应用程序的布局。
-
st.text()、st.markdown():用于添加文本内容,支持 Markdown 语法。
-
st.image():用于添加图像到应用程序中。
-
st.dataframe():用于呈现 Pandas 数据框。
-
st.table():用于呈现简单的数据表格。
-
st.pyplot()、st.altair_chart()、st.plotly_chart():用于呈现 Matplotlib、Altair 或 Plotly 绘制的图表。
-
st.selectbox()、st.multiselect()、st.slider()、st.text_input():用于添加交互式小部件,允许用户在应用程序中进行选择、输入或滑动操作。
-
st.button()、st.checkbox()、st.radio():用于添加按钮、复选框和单选按钮,以触发特定的操作
构建应用程序
run in console, and see your web app!
//unfortunately, i don’t have an openai api key.
try glm, but history record function is shit.
After countless trial, I ultimately discovered a case when it remembered his last answer.
Indeed, this history record function is far from good.
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
import sys
sys.path.append("../beginner's lab") # 将父目录放入系统路径中
from zhipuai_embedding import ZhipuAIEmbeddings
from zhipuai_llm import ZhipuAILLM
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
zhipuai_api_key = os.environ['ZHIPUAI_API_KEY']
def generate_response(input_text, zhipuai_api_key):
llm = ZhipuAILLM(model = "glm-4", temperature = 0.1, api_key = zhipuai_api_key)
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" # ensure it's correct!
# 加载数据库
vectordb = Chroma(
persist_directory=persist_directory, # 允许我们将persist_directory目录保存到磁盘上
embedding_function=embedding
)
return vectordb
#带有历史记录的问答链
def get_chat_qa_chain(question:str,zhipuai_api_key:str):
vectordb = get_vectordb()
llm = ZhipuAILLM(model = "glm-4", temperature = 0.1, api_key = zhipuai_api_key)
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,zhipuai_api_key:str):
vectordb = get_vectordb()
llm = ZhipuAILLM(model = "glm-4", temperature = 0.1, api_key = zhipuai_api_key)
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('🦜🔗 动手学大模型应用开发')
zhipuai_api_key = st.sidebar.text_input('zhipuai 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, zhipuai_api_key)
elif selected_method == "qa_chain":
answer = get_qa_chain(prompt,zhipuai_api_key)
elif selected_method == "chat_qa_chain":
answer = get_chat_qa_chain(prompt,zhipuai_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()
streamlit run streamlit_app.py
课后思考
听说 LangChain 过于抽象,或将被淘汰
学习了 python 的类型提示语法
学到这里,对于自建 LLM 应用有了一些概念,不像之前只会空想
自己尝试,效率很低,不知道运行异常的真正原因
看来编程素养不够,代码抄来不懂,调试无从下手