员工助手小项目&&IO入门

125 阅读8分钟

先总结

导图.png

  1. 请你用自己的话简述一下这个基于文档的QA(问答)系统的实现流程?

QA系统需要先加载load为LangChain能够读取的形式。再把信息切分成文档块,将切分好的文档块存储进向量数据库中。使用时,去数据库中去检索相关性高的文档块,传递给LLM生成答案。

  1. LangChain支持很多种向量数据库,你能否用另一种常用的向量数据库Chroma来实现这个任务?
  2. 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)

image.png 这里代码很像spring的Controller层 功能也差不多吧

image.png

image.png

image.png

image.png 总结来说,这段代码创建了一个简单的 Web 应用程序,它通过 Flask 框架提供了一个用户界面,允许用户输入问题并获取基于 RetrievalQA 链的回答。

image.png

IO入门

image.png

image.png

image.png

思考题

  1. 在上面的示例中,format_instructions,也就是输出格式是怎样用output_parser构建出来的,又是怎样传递到提示模板中的?
  2. 加入了partial_variables,也就是输出解析器指定的format_instructions之后的提示,为什么能够让模型生成结构化的输出?你可以打印出这个提示,一探究竟。
  3. 使用输出解析器后,调用模型时有没有可能仍然得不到所希望的输出?也就是说,模型有没有可能仍然返回格式不够完美的输出?

1.2format_instructions 是通过 StructuredOutputParser 的 get_format_instructions() 方法构建出来的。这个方法会返回一个字符串,描述了如何格式化输出,以便模型能够生成符合预期结构的响应。返回值format_instructions又作为变量传入了prompt。在循环中,在格式化提示prompt.format使用。这样,提示模板中就包含了如何格式化输出的说明,模型在生成响应时就会遵循这些说明。

3.是的,即使使用了输出解析器,模型仍然有可能返回格式不够完美的输出。这可能是由于以下几个原因:

  1. 模型的训练数据中没有包含足够的格式化输出的示例,导致模型在生成输出时没有遵循指定的格式。
  2. 模型可能没有完全理解格式化指令,或者在生成输出时忽略了这些指令。
  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文件。因为此时数据不再是模糊的、无结构的文本,而是结构清晰的有格式的数据。输出解析器在这个过程中的功劳很大