先总结
- 请你用自己的话简述一下这个基于文档的QA(问答)系统的实现流程?
QA系统需要先加载load为LangChain能够读取的形式。再把信息切分成文档块,将切分好的文档块存储进向量数据库中。使用时,去数据库中去检索相关性高的文档块,传递给LLM生成答案。
- LangChain支持很多种向量数据库,你能否用另一种常用的向量数据库Chroma来实现这个任务?
- LangChain支持很多种大语言模型,你能否用HuggingFace网站提供的开源模型 google/flan-t5-x1 代替GPT-3.5完成这个任务?
代码细节
"""
本文件是【用LangChain快速构建基于“易速鲜花”本地知识库的智能问答系统】章节的配套代码,课程链接:https://juejin.cn/book/7387702347436130304/section/7388069933021986856
您可以点击最上方的“运行“按钮,直接运行该文件;更多操作指引请参考Readme.md文件。
"""
# 1.Load 导入Document Loaders
import os
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.document_loaders import Docx2txtLoader
from langchain_community.document_loaders import TextLoader
from typing import Dict, List, Any
from langchain.embeddings.base import Embeddings
from langchain.pydantic_v1 import BaseModel
from volcenginesdkarkruntime import Ark
# os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'
# os.environ["OPENAI_BASE_URL"] = 'OpenAI 的 API URL'
# 加载Documents 文件都存在documents中了
base_dir = "./OneFlower" # 文档的存放目录
documents = []
for file in os.listdir(base_dir):
# 构建完整的文件路径
file_path = os.path.join(base_dir, file)
if file.endswith(".pdf"):
loader = PyPDFLoader(file_path)
documents.extend(loader.load())
elif file.endswith(".docx"):
loader = Docx2txtLoader(file_path)
documents.extend(loader.load())
elif file.endswith(".txt"):
loader = TextLoader(file_path)
documents.extend(loader.load())
# 2.Split 将Documents切分成块以便后续进行嵌入和向量存储
#定义切分器 操作documents对象 个人认为
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=10)
chunked_documents = text_splitter.split_documents(documents)
# 3.Store 将分割嵌入并存储在矢量数据库Qdrant中
from langchain_community.vectorstores import Qdrant
class DoubaoEmbeddings(BaseModel, Embeddings):
client: Ark = None
api_key: str = ""
model: str
def __init__(self, **data: Any):
super().__init__(**data)
if self.api_key == "":
self.api_key = os.environ["OPENAI_API_KEY"]
self.client = Ark(
base_url=os.environ["OPENAI_BASE_URL"],
api_key=self.api_key
)
def embed_query(self, text: str) -> List[float]:
"""
生成输入文本的 embedding.
Args:
texts (str): 要生成 embedding 的文本.
Return:
embeddings (List[float]): 输入文本的 embedding,一个浮点数值列表.
"""
embeddings = self.client.embeddings.create(model=self.model, input=text)
return embeddings.data[0].embedding
def embed_documents(self, texts: List[str]) -> List[List[float]]:
return [self.embed_query(text) for text in texts]
class Config:
arbitrary_types_allowed = True
#Qdrant.from_documents()参数 文档对象,嵌入的类对象,存储形式 收集名字
vectorstore = Qdrant.from_documents(
documents=chunked_documents, # 以分块的文档
embedding=DoubaoEmbeddings(
model=os.environ["EMBEDDING_MODELEND"],
), # 用OpenAI的Embedding Model做嵌入
location=":memory:", # in-memory 存储
collection_name="my_documents",
) # 指定collection_name
# 4. Retrieval 准备模型和Retrieval链
import logging # 导入Logging工具
from langchain_openai import ChatOpenAI # ChatOpenAI模型
from langchain.retrievers.multi_query import (
MultiQueryRetriever,
) # MultiQueryRetriever工具
from langchain.chains import RetrievalQA # RetrievalQA链
# 设置Logging
logging.basicConfig()
logging.getLogger("langchain.retrievers.multi_query").setLevel(logging.INFO)
# 实例化一个大模型工具 - OpenAI的GPT-3.5
llm = ChatOpenAI(model=os.environ["LLM_MODELEND"], temperature=0)
# 实例化一个MultiQueryRetriever
# 框架写好的类吧 应该是
retriever_from_llm = MultiQueryRetriever.from_llm(
retriever=vectorstore.as_retriever(), llm=llm
)
# 实例化一个RetrievalQA链
qa_chain = RetrievalQA.from_chain_type(llm, retriever=retriever_from_llm)
# 5. Output 问答系统的UI实现
from flask import Flask, request, render_template
app = Flask(__name__) # Flask APP
@app.route("/", methods=["GET", "POST"])
def home():
if request.method == "POST":
# 接收用户输入作为问题
question = request.form.get("question")
# RetrievalQA链 - 读入问题,生成答案
result = qa_chain({"query": question})
# 把大模型的回答结果返回网页进行渲染
return render_template("index.html", result=result)
return render_template("index.html")
if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True, port=5000)
这里代码很像spring的Controller层 功能也差不多吧
总结来说,这段代码创建了一个简单的 Web 应用程序,它通过 Flask 框架提供了一个用户界面,允许用户输入问题并获取基于
RetrievalQA 链的回答。
IO入门
思考题
- 在上面的示例中,format_instructions,也就是输出格式是怎样用output_parser构建出来的,又是怎样传递到提示模板中的?
- 加入了partial_variables,也就是输出解析器指定的format_instructions之后的提示,为什么能够让模型生成结构化的输出?你可以打印出这个提示,一探究竟。
- 使用输出解析器后,调用模型时有没有可能仍然得不到所希望的输出?也就是说,模型有没有可能仍然返回格式不够完美的输出?
1.2format_instructions 是通过 StructuredOutputParser 的 get_format_instructions() 方法构建出来的。这个方法会返回一个字符串,描述了如何格式化输出,以便模型能够生成符合预期结构的响应。返回值format_instructions又作为变量传入了prompt。在循环中,在格式化提示prompt.format使用。这样,提示模板中就包含了如何格式化输出的说明,模型在生成响应时就会遵循这些说明。
3.是的,即使使用了输出解析器,模型仍然有可能返回格式不够完美的输出。这可能是由于以下几个原因:
- 模型的训练数据中没有包含足够的格式化输出的示例,导致模型在生成输出时没有遵循指定的格式。
- 模型可能没有完全理解格式化指令,或者在生成输出时忽略了这些指令。
- 模型可能受到输入数据的影响,导致生成的输出不符合预期的格式。
为了减少这种情况的发生,可以采取以下措施:
- 在训练模型时,确保提供足够的格式化输出的示例,以便模型能够学习到正确的格式。
- 在使用模型时,提供清晰明确的格式化指令,帮助模型理解输出的格式要求。
- 对模型的输出进行后处理,以确保输出符合预期的格式。
如果模型仍然返回格式不够完美的输出,可以考虑调整模型的参数、更换模型或者重新训练模型。
代码细节
最简易实现 template串传入PromptTemplate.from_template。初始化大模型,并用 prompt.format(flower_name=["东雪莲"], price="114514")赋值,最后用model.invoke(input)得到输出
本文件是【模型I/O:输入提示、调用模型、解析输出】章节的配套代码,课程链接:https://juejin.cn/book/7387702347436130304/section/7396583376915005480
您可以点击最上方的“运行“按钮,直接运行该文件;更多操作指引请参考Readme.md文件。
"""
# 导入LangChain中的提示模板
import os
from langchain.prompts import PromptTemplate
# 创建原始模板
template = """您是一位专业的鲜花店文案撰写员。\n
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
"""
# 根据原始模板创建LangChain提示模板
prompt = PromptTemplate.from_template(template)
# 打印LangChain提示模板的内容
print(prompt)
# 设置OpenAI API Key
# os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'
# 导入LangChain中的OpenAI模型接口
from langchain_openai import OpenAI, ChatOpenAI
# 创建模型实例
# model = OpenAI(model_name='gpt-3.5-turbo-instruct')
model = ChatOpenAI(model=os.environ.get("LLM_MODELEND"))
# 输入提示
input = prompt.format(flower_name=["东雪莲"], price="114514")
# 得到模型的输出
output = model.invoke(input)
# 打印输出内容
print(output)
我们也可以通过循环复用模版 。就像c++的模版一样往里传值就行了,代码是不变的。 我们甚至可以换模型model = HuggingFaceHub(repo_id="google/flan-t5-large") 对比使用传统chat模型。
"""
本文件是【模型I/O:输入提示、调用模型、解析输出】章节的配套代码,课程链接:https://juejin.cn/book/7387702347436130304/section/7396583376915005480
您可以点击最上方的“运行“按钮,直接运行该文件;更多操作指引请参考Readme.md文件。
"""
from httpx import Response
from openai import OpenAI # 导入OpenAI
import os
# openai.api_key = '你的OpenAI API Key' # API Key
prompt_text = "您是一位专业的鲜花店文案撰写员。对于售价为{}元的{},您能提供一个吸引人的简短描述吗?" # 设置提示
flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]
# 循环调用Text模型的Completion方法,生成文案
for flower, price in zip(flowers, prices):
prompt = prompt_text.format(price, flower)
# response = openai.completions.create(
# model="gpt-3.5-turbo-instruct",
# prompt=prompt,
# max_tokens=100
# )
client = OpenAI()
response = client.chat.completions.create(
model=os.environ.get("LLM_MODELEND"),
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": prompt},
],
max_tokens=100,
)
# print(response.choices[0].text.strip()) # 输出文案
print(response.choices[0].message.content)
如果我们想处理输出。需要定义解析器。 首先定义输出结构,我们希望模型生成的答案包含两部分:鲜花的描述文案(description)和撰写这个文案的原因(reason)。所以我们定义了一个名为response_schemas的列表,其中包含两个ResponseSchema对象,分别对应这两部分的输出。
根据这个列表,我通过StructuredOutputParser.from_response_schemas方法创建了一个输出解析器。
然后,我们通过输出解析器对象的get_format_instructions()方法获取输出的格式说明(format_instructions),再根据原始的字符串模板和输出解析器格式说明创建新的提示模板(这个模板就整合了输出解析结构信息)。再通过新的模板生成模型的输入,得到模型的输出。此时模型的输出结构将尽最大可能遵循我们的指示,以便于输出解析器进行解析。
对于每一个鲜花和价格组合,我们都用 output_parser.parse(output) 把模型输出的文案解析成之前定义好的数据格式,也就是一个Python字典,这个字典中包含了description 和 reason 这两个字段的值。 最后,把所有信息整合到一个pandas DataFrame对象中(需要安装Pandas库)。这个DataFrame对象中包含了flower、price、description 和 reason 这四个字段的值。其中,description 和 reason 是由 output_parser 从模型的输出中解析出来的,flower 和 price 是我们自己添加的。
我们可以打印出DataFrame的内容,也方便地在程序中处理它,比如保存为下面的CSV文件。因为此时数据不再是模糊的、无结构的文本,而是结构清晰的有格式的数据。输出解析器在这个过程中的功劳很大。