ð使用Python从您自己的PDF中创建GPT-3 bot
#网络开发人员 #python #ai #gpt3

Read the latest version of this article here

介绍

传统上,大多数聊天机器人都是If-Else块的树。有些人有一层复杂的方法,可以捕获用户意图,以决定应触发哪些功能。但是,该技术永远不够好,无法解决实际的查询。
在GPT-3之后,我们看到了大型语言模型能够提出近人类的答案。

在本教程中,我们将索引文档,在其顶部创建一个由GPT驱动的机器人,最后将其视为API。

上下文 + LLM

这些LLM(例如ChatGpt)未经企业数据培训。它们是通用模型,为聊天和完成句子而创建。

要解决这个问题,您不需要微调这些模型。相反,更好的解决方案是将一些知识与问题一起传递。让我给你一个例子。

我们“最高速度是多少?”到chatgpt:

question without question

question with question

两个答案都是正确的,但是,第二个答案对客户更有用。

这里的下一个挑战是获取知识,并将其与问题一起为LLM设置上下文。为此,我们可以使用猎犬。想象一下,您要了解有关汽车的所有可能信息的数据库。您可以执行词汇搜索并找到可能具有答案的行。然后,将这些行以及客户的问题传递给LLM。

这是完整流程的样子

Context+LLM chatbot flow

在上面的示例中,我们扫描了DB的单词“ top”和“ speed”,将结果串联在一起。为了获得更好的体验,我们还添加了一些指令(以保持答案简洁),并与客户提出的原始问题一起。将所有内容放在一起,传递给LLM并繁荣 - 我们有答案。

索引

在本练习中,我们将建立一个简单的机器人,回答有关梅赛德斯 - 奔驰A级豪华轿车的问题。

Get their brochure here

现在,公司没有关于产品信息的原始表。它们以上述文档的形式提供。

因此,第一步是创建数据库或搜索索引。要提取文本,我们将使用pypdf2:

pip install PyPDF2
from PyPDF2 import PdfReader
import csv

SOURCE_FILE = "The Mercedes-Benz A-Class Limousine.pdf"
OUTPUT_CSV = "Mercedes-Benz-A-Class-Limousine.csv"

reader = PdfReader(SOURCE_FILE)

n_pages = len(reader.pages)

header = False # file is empty, so no header row is present
for n in range(n_pages):
    page = reader.pages[n]
    text = page.extract_text()

    with open(OUTPUT_CSV, "a+") as f:
        writer = csv.DictWriter(f, fieldnames=["page", "content"])
        if not header:
            writer.writeheader()
            header = True # header row added

        writer.writerow({"page": n + 1, "content": text.lower()})
        f.close()
page 内容
1 新的梅赛德斯 - 奔驰A级豪华轿车。
2 *请阅读defleamer2最近的陈列室联系我们实时聊天设计技术...

这将创建一个CSV文件。要亲吻,我们将使用此CSV文件 + Pandas用作我们自己的Retriever版本的索引。

猎犬

让我们创建一个kude0类,该类将根据客户查询来检索结果。该类将具有以下功能:

  • 删除停止字 停止词是像“ a”,“”,“”等的常用词,这些词对搜索没有贡献。负面影响频率更大。例如,如果有人问您“汽车的最佳功能是什么”,那么您将忽略问题中的大多数单词,而只会寻找“功能”一词。
stopwords = [...] 
return [word for word in words if word not in stopwords]
  • 过滤顶行 一旦我们使用匹配的关键字提取所有行,我们将优先考虑那些关键字频率较高的人。考虑到LLM的令牌极限,这也变得重要。
df = pd.read_csv('Mercedes-Benz-A-Class-Limousine.csv')

# add a column frequency which will contain the sum of frequencies of keywords words
df["freq"] = sum([df['content'].str.count(k) for k in keywords])

# sort the dataframe by frequency of keywords
rows_sorted = df.sort_values(by="freq", ascending=False)
  • 检索 实际检索功能将负责根据客户查询为我们提供上下文。
query = "top speed"

# select all rows where the content column contains the words "top" or "speed"
df = df.loc[df['content'].str.contains("top|speed")]

return f"{''.join(top_rows['content'])}"

请注意,这是回猎犬的非常基本的实现。标准做法是使用ChromaDBAzure Cognitive Search之类的东西进行索引和检索。
另外,您可以获取停止单词的完整列表this GitHub gist

让我们把所有内容都放入班级:

import pandas as pd


class Retriever:
    def __init__(self, source, searchable_column):
        self.source = source
        self.searchable_column = searchable_column
        self.index = pd.read_csv(source)

    @property
    def _stopwords(self):
        return [
            "a",
            "an",
            "the",
            "i",
            "my",
            "this",
            "that",
            "is",
            "it",
            "to",
            "of",
            "do",
            "does", 
            "with",
            "and",
            "can",
            "will",
        ]

    def remove_stopwords(self, query):
        words = query.split(" ")
        return [word for word in words if word not in self._stopwords]

    def _top_rows(self, df, keywords):
        df = df.copy()
        df["freq"] = sum([df[self.searchable_column].str.count(k) for k in keywords])
        rows_sorted = df.sort_values(by="freq", ascending=False)
        return rows_sorted.head(5)

    def retrieve(self, query):
        keywords = self.remove_stopwords(query.lower())
        query_as_str_no_sw = "|".join(keywords)

        df = self.index
        df = df.loc[df[self.searchable_column].str.contains(query_as_str_no_sw)]
        top_rows = self._top_rows(df, keywords)

        return f"Context: {''.join(top_rows['content'])}"

现在,我们可以轻松实例化此类:

r = Retriever(
   source="Mercedes-Benz-A-Class-Limousine.csv", searchable_column="content"
)
context = r.retrieve(query)

LLM

下一步是将此上下文传递给LLM。我们将使用SOTA GPT-3.5-Turbo模型。为此,您需要一个OpenAI API键,您可以获得here

这是示例OpenAI API请求:

OAI_BASE_URL = "https://api.openai.com/v1/chat/completions"
API_KEY = "sky0urKey"

CONTENT_SYSTEM = "You're a salesman at Mercedes, based on the query, create a concise answer."

def llm_reply(prompt: str) -> str:
    data = {
        "messages": [
            {"role": "system", "content": CONTENT_SYSTEM},
            {"role": "user", "content": prompt},
        ],
        "model": "gpt-3.5-turbo",
    }

    response = requests.post(
        OAI_BASE_URL,
        headers={"Authorization": f"Bearer {API_KEY}"},
        json=data,
    ).json()

    reply = response["choices"][0]["message"]["content"]

    return reply
>>> llm_reply("hello")
"Hello! How can I assist you today?"

messages是字典列表。每个词典都有两个键和内容。 “系统”角色为机器人设定了角色,并且像指令一样工作。我们将通过角色“用户”传递消息。

让我们问一下关于A类豪华轿车的第一个问题:

query = "What is the size of alloy wheels?"

r = Retriever(
   source="Mercedes-Benz-A-Class-Limousine.csv", searchable_column="content"
)
context = r.retrieve(query)

reply = llm_reply(f"{context}\nquery:{query}")
>>> reply
"The size of the alloy wheels is 43.2 cm (17 inches)."

恭喜!您成功地从PDF文件创建了一个由GPT驱动的机器人。

FastAPI应用程序

让我们使用FastApi作为端点来公开我们的功能。

我们还安装了一个静态目录,并渲染模板将此API与UI集成。

requirements.txt

fastapi==0.78.0
pandas==1.5.3
pydantic==1.9.1
requests==2.27.1
uvicorn==0.17.6

bot.py

import pandas as pd
import requests

OAI_BASE_URL = "https://api.openai.com/v1/chat/completions"
API_KEY = "sky0urKey"
CONTENT_SYSTEM = "You're a salesman at Mercedes, based on the query, create a concise answer."

class Retriever:
    ...

def llm_reply(prompt: str) -> str:
    ...

main.py

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
from bot import Retriever, llm_reply
from pydantic import BaseModel


class ChatModel(BaseModel):
    query: str


templates = Jinja2Templates(directory="./")

app = FastAPI()

app.mount(
    "/static",
    StaticFiles(directory="./", html=True),
    name="static",
)


@app.get("/")
def root(request: Request):
    return templates.TemplateResponse("index.html", context={"request": request})


@app.post("/chat")
def chat(cm: ChatModel):
    query = cm.query

    r = Retriever(
        source="Mercedes-Benz-A-Class-Limousine.csv", searchable_column="content"
    )
    context = r.retrieve(query)
    prompt = context + "\n" + "Query:" + query
    return llm_reply(prompt=prompt)

我们将使用Uvicorn运行该应用程序:

uvicorn main:app --port 8080 --reload

您可以转到localhost:8080/docs并从Swagger UI发送示例请求,也可以发送卷曲请求:

curl -X 'POST' \
  'http://127.0.0.1:8080/chat' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "query": "What is the size of alloy wheel?"
}'

现在,API已经实时,您可以将其连接到前端。对于本教程,我使用了这个美丽的design on codepen(信用:Abadu)。

演示

链接到完整的源代码在下面附有。

挑战和下一步

  • retriever :正如我们所讨论的,猎犬的改进范围。对于例如,您可以实现拼写检查的函数,或将整个内容与某些基于云的服务(例如Azure认知服务)集成。

  • 环围栏:改进提示/系统指令,以便将机器人限制为仅回答与梅赛德斯相关的问题,并礼貌地拒绝其他问题。

  • 聊天历史记录:当前机器人不会发送历史消息。另外,它无法识别后续问题。例如,在问“我可以连接iPhone吗?”之后,我应该能够问“其他设备”(后续问题)。因此,您应该确定后续问题,并发送聊天历史记录。

参考和进一步阅读