ChatGpt : 基于OpenAI + Flask快速搭建个人领域内的Q/A问答接口—嵌入网站内知识

news2024/9/28 17:31:25

文章目录

    • 学习前言
    • OpenAI简介
    • Q/A问答接口实现流程
      • 1、网络爬虫
      • 2、构建嵌入索引
      • 3、使用嵌入构建Q/A问答系统
      • 4、基于flask框架进行接口封装
      • 5、接口测试使用

学习前言

最近ChatGpt太火热了,赶紧来了解一波相关情况…
目前来说ChatGpt只有2021年之前的知识,如果想让它回答比较新的知识或者自己领域内的知识还是不太行的,通过了解发现OpenAI提供了相关的API让我们能够快速实现,真不戳,来吧!!!
对了,还没用过的童鞋赶紧去注册一下 https://chat.openai.com/ ,体验一下Chatgpt的强大的语义理解和回复能力!!!

OpenAI简介

GPT是“Generative Pre-trained Transformer”的缩写,是一种基于Transformer模型的预训练语言模型,由OpenAI公司开发。
OpenAI是一个人工智能研究组织,成立于2015年,总部位于美国旧金山。它由一些全球顶尖的研究者、工程师和企业家共同创立。OpenAI在人工智能研究领域取得了很多重要成果,其中最具代表性的是其开发的GPT和GPT-2语言模型。当然,现在已经是GPT-3.5了。
OpenAI训练了很多擅长理解和生成文本的牛波一语言模型,并对外开放了 API 来供咱们调用这些模型,目前开放的模型api能够解决大部分语言类相关的的任务,666。

赶紧来实操一下吧,这么强大的文本语义理解能力!!!

Q/A问答接口实现流程

我们就简单利用一个网站的知识来让模型学习,并完成对我们问题的回答,并且封装成接口,便于调用…
在操作之前,主要已经注册了OpenAI的账号https://platform.openai.com/,这样才能拿到对应接口调用 Secret key,如下图这样

1、网络爬虫

爬取网站内的所有文本内容,如果担心会爬取到内容太多或者仅做测试。可以加上domain限制,这样不会链接到域外的超链接,下面代码中有些小细节可能需要做一些调整,比如说在爬取中英文的网站,内容的分隔符不同,需要调整;以及有些网站是基于JS的,那基于requests的爬取就无法动态获取JS的内容,可能就需要使用利用一些实例化浏览器的操作,其中还需要主要一些浏览器驱动匹配的问题。
本文的重点不在爬虫,所以这里就不重点的展开看,如果这部分有啥问题,可以评论私信我,尽力解答

import requests
import re
import urllib.request
from bs4 import BeautifulSoup
from collections import deque
from html.parser import HTMLParser
from urllib.parse import urlparse
import os

# Regex pattern to match a URL
HTTP_URL_PATTERN = r'^http[s]*://.+'

domain = "www.runoob.com" # <- put your domain to be crawled
full_url = "https://www.runoob.com/" # <- put your domain to be crawled with https or http

# Create a class to parse the HTML and get the hyperlinks
class HyperlinkParser(HTMLParser):
    def __init__(self):
        super().__init__()
        # Create a list to store the hyperlinks
        self.hyperlinks = []

    # Override the HTMLParser's handle_starttag method to get the hyperlinks
    def handle_starttag(self, tag, attrs):
        attrs = dict(attrs)

        # If the tag is an anchor tag and it has an href attribute, add the href attribute to the list of hyperlinks
        if tag == "a" and "href" in attrs:
            self.hyperlinks.append(attrs["href"])

# 将 URL 作为参数,打开 URL,并读取 HTML 内容。然后,它返回在该页面上找到的所有超链接。
# Function to get the hyperlinks from a URL
def get_hyperlinks(url):
    
    # Try to open the URL and read the HTML
    try:
        # Open the URL and read the HTML
        with urllib.request.urlopen(url) as response:

            # If the response is not HTML, return an empty list
            if not response.info().get('Content-Type').startswith("text/html"):
                return []
            
            # Decode the HTML
            html = response.read().decode('utf-8')
    except Exception as e:
        print(e)
        return []

    # Create the HTML Parser and then Parse the HTML to get hyperlinks
    parser = HyperlinkParser()
    parser.feed(html)

    return parser.hyperlinks


# 目标是仅爬取 OpenAI 域下的内容并对其编制索引
# Function to get the hyperlinks from a URL that are within the same domain
def get_domain_hyperlinks(local_domain, url):
    clean_links = []
    for link in set(get_hyperlinks(url)):
        clean_link = None

        # If the link is a URL, check if it is within the same domain
        if re.search(HTTP_URL_PATTERN, link):
            # Parse the URL and check if the domain is the same
            url_obj = urlparse(link)
            if url_obj.netloc == local_domain:
                clean_link = link

        # If the link is not a URL, check if it is a relative link
        else:
            if link.startswith("/"):
                link = link[1:]
            elif link.startswith("#") or link.startswith("mailto:"):
                continue
            clean_link = "https://" + local_domain + "/" + link

        if clean_link is not None:
            if clean_link.endswith("/"):
                clean_link = clean_link[:-1]
            clean_links.append(clean_link)

    # Return the list of hyperlinks that are within the same domain
    return list(set(clean_links))

# 网络抓取任务设置的最后一步。它跟踪访问过的 URL 以避免重复相同的页面,这些页面可能链接到站点上的多个页面。它还从没有 HTML 标记的页面中提取原始文本,并将文本内容写入特定于该页面的本地 .txt 文件
def crawl(url):
    # Parse the URL and get the domain
    local_domain = urlparse(url).netloc

    # Create a queue to store the URLs to crawl
    queue = deque([url])

    # Create a set to store the URLs that have already been seen (no duplicates)
    seen = set([url])

    # Create a directory to store the text files
    if not os.path.exists("text/"):
            os.mkdir("text/")

    if not os.path.exists("text/"+local_domain+"/"):
            os.mkdir("text/" + local_domain + "/")

    # Create a directory to store the csv files
    if not os.path.exists("processed"):
            os.mkdir("processed")

    # While the queue is not empty, continue crawling
    while queue:

        # Get the next URL from the queue
        url = queue.pop()
        print(url) # for debugging and to see the progress

        # Save text from the url to a <url>.txt file
        with open('text/'+local_domain+'/'+url[8:].replace("/", "_") + ".txt", "w", encoding="UTF-8") as f:

            # Get the text from the URL using BeautifulSoup
            soup = BeautifulSoup(requests.get(url).text, "html.parser")

            # Get the text but remove the tags
            text = soup.get_text()

            # If the crawler gets to a page that requires JavaScript, it will stop the crawl
            if ("You need to enable JavaScript to run this app." in text):
                print("Unable to parse page " + url + " due to JavaScript being required")
            
            # Otherwise, write the text to the file in the text directory
            f.write(text)

        # Get the hyperlinks from the URL and add them to the queue
        for link in get_domain_hyperlinks(local_domain, url):
            if link not in seen:
                queue.append(link)
                seen.add(link)

crawl(full_url)

最后爬取的效果就是生成整个文本文件,所以如果你已经有了自己的文本数据,甚至是可以不用爬虫这部分,直接手动去整理自己的数据都可以
在这里插入图片描述

2、构建嵌入索引

构建嵌入索引总体就是将上述爬取到的文本文件进行向量化嵌入。通常来说,一般选用csv文件进行转换嵌入,所以我们来把文本转成Pandas.Dataframe类型吧,这样更好的进行csv文件写入。

首先将文本数据按域为索引转成dataframe,其中要注意的是额外的间距和一些新的行会使文本混乱并使嵌入过程复杂化。因此要使用一些代码有助于删除其中一些字符,

import pandas as pd

def remove_newlines(serie):
    serie = serie.str.replace('\n', ' ')
    serie = serie.str.replace('\\n', ' ')
    serie = serie.str.replace('  ', ' ')
    serie = serie.str.replace('  ', ' ')
    return serie

# Create a list to store the text files
texts=[]

# Get all the text files in the text directory
for file in os.listdir("text/" + domain + "/"):

    # Open the file and read the text
    with open("text/" + domain + "/" + file, "r", encoding="UTF-8") as f:
        text = f.read()

        # Omit the first 11 lines and the last 4 lines, then replace -, _, and #update with spaces.
        texts.append((file[11:-4].replace('-',' ').replace('_', ' ').replace('#update',''), text))

# Create a dataframe from the list of texts
df = pd.DataFrame(texts, columns = ['fname', 'text'])

# Set the text column to be the raw text with the newlines removed
df['text'] = df.fname + ". " + remove_newlines(df.text)
df.to_csv('processed/scraped.csv')
df.head()

接着,将原始文本保存到 dataframe后的下一步是分词。此过程通过分解句子将输入文本拆分为词,这是自然语言处理常用的步骤,主要是因为在词向量转换时会对输入的句子分词数量上有要求,因此要进行分词得到分词的数量,对于一些较长分词数量的句子进行截断或者换行处理。

import tiktoken

# Load the cl100k_base tokenizer which is designed to work with the ada-002 model
tokenizer = tiktoken.get_encoding("cl100k_base")

df = pd.read_csv('processed/scraped.csv', index_col=0)
df.columns = ['title', 'text']

# Tokenize the text and save the number of tokens to a new column
df['n_tokens'] = df.text.apply(lambda x: len(tokenizer.encode(x)))

# Visualize the distribution of the number of tokens per row using a histogram
df.n_tokens.hist()


max_tokens = 500

# Function to split the text into chunks of a maximum number of tokens
def split_into_many(text, max_tokens = max_tokens):

    # Split the text into sentences
    sentences = text.split('. ')

    # Get the number of tokens for each sentence
    n_tokens = [len(tokenizer.encode(" " + sentence)) for sentence in sentences]
    
    chunks = []
    tokens_so_far = 0
    chunk = []

    # Loop through the sentences and tokens joined together in a tuple
    for sentence, token in zip(sentences, n_tokens):

        # If the number of tokens so far plus the number of tokens in the current sentence is greater 
        # than the max number of tokens, then add the chunk to the list of chunks and reset
        # the chunk and tokens so far
        if tokens_so_far + token > max_tokens:
            chunks.append(". ".join(chunk) + ".")
            chunk = []
            tokens_so_far = 0

        # If the number of tokens in the current sentence is greater than the max number of 
        # tokens, go to the next sentence
        if token > max_tokens:
            continue

        # Otherwise, add the sentence to the chunk and add the number of tokens to the total
        chunk.append(sentence)
        tokens_so_far += token + 1

    return chunks
    


shortened = []

# Loop through the dataframe
for row in df.iterrows():

    # If the text is None, go to the next row
    if row[1]['text'] is None:
        continue

    # If the number of tokens is greater than the max number of tokens, split the text into chunks
    if row[1]['n_tokens'] > max_tokens:
        shortened += split_into_many(row[1]['text'])
    
    # Otherwise, add the text to the list of shortened texts
    else:
        shortened.append( row[1]['text'] )

这样的话,具有长分词数量的句子就会被降低,以便更好的进行向量化。
在这里插入图片描述内容现在被分解的更小,可以向 OpenAI API 发送一个简单的请求,指定使用新的 text-embedding-ada-002 模型来向量化:

import openai

df['embeddings'] = df.text.apply(lambda x: openai.Embedding.create(input=x, engine='text-embedding-ada-002')['data'][0]['embedding'])

df.to_csv('processed/embeddings.csv')
df.head()

如果文本数量较多,需要等待3~5分钟

3、使用嵌入构建Q/A问答系统

首先将得到的向量化数组转成Numpy格式,因为Numpy格式能够更方便,更快速的进行处理

import numpy as np
from openai.embeddings_utils import distances_from_embeddings

df=pd.read_csv('processed/embeddings.csv', index_col=0)
df['embeddings'] = df['embeddings'].apply(eval).apply(np.array)

df.head()

现在数据已准备就绪,需要将问题转换为具有简单函数的嵌入。因为向量化搜索使用余弦距离比较数字向。这些向量可能相关,如果它们的余弦距离接近,则可能是问题的答案。OpenAI python 包有一个内置distances_from_embeddings函数,可以直接调用。

def create_context(
    question, df, max_len=1800, size="ada"
):
    """
    Create a context for a question by finding the most similar context from the dataframe
    """

    # Get the embeddings for the question
    q_embeddings = openai.Embedding.create(input=question, engine='text-embedding-ada-002')['data'][0]['embedding']

    # Get the distances from the embeddings
    df['distances'] = distances_from_embeddings(q_embeddings, df['embeddings'].values, distance_metric='cosine')


    returns = []
    cur_len = 0

    # Sort by distance and add the text to the context until the context is too long
    for i, row in df.sort_values('distances', ascending=True).iterrows():
        
        # Add the length of the text to the current length
        cur_len += row['n_tokens'] + 4
        
        # If the context is too long, break
        if cur_len > max_len:
            break
        
        # Else add it to the text that is being returned
        returns.append(row["text"])

    # Return the context
    return "\n\n###\n\n".join(returns)

文本被分解成更小的标记集,因此按升序循环并继续添加文本是确保完整答案的关键步骤。如果返回的内容多于所需,也可以将 max_len 修改为更小的值。

上一步只检索了与问题语义相关的文本块,因此它们可能包含答案,但不能保证。通过返回前 5 个最有可能的结果,可以进一步增加找到答案的机会。

然后,回答提示将尝试从检索到的上下文中提取相关事实,以便形成连贯的答案。如果没有相关答案,提示将返回“我不知道”。

可以使用完成端点创建问题的真实答案text-davinci-003。

def answer_question(
    df,
    model="text-davinci-003",
    question="Am I allowed to publish model outputs to Twitter, without a human review?",
    max_len=1800,
    size="ada",
    debug=False,
    max_tokens=150,
    stop_sequence=None
):
    """
    Answer a question based on the most similar context from the dataframe texts
    """
    context = create_context(
        question,
        df,
        max_len=max_len,
        size=size,
    )
    # If debug, print the raw model response
    if debug:
        print("Context:\n" + context)
        print("\n\n")

    try:
        # Create a completions using the question and context
        response = openai.Completion.create(
            prompt=f"Answer the question based on the context below, and if the question can't be answered based on the context, say \"I don't know\"\n\nContext: {context}\n\n---\n\nQuestion: {question}\nAnswer:",
            temperature=0,
            max_tokens=max_tokens,
            top_p=1,
            frequency_penalty=0,
            presence_penalty=0,
            stop=stop_sequence,
            model=model,
        )
        return response["choices"][0]["text"].strip()
    except Exception as e:
        print(e)
        return ""

完成了!一个工作的 Q/A 系统已经准备就绪,该系统具有从 OpenAI 网站嵌入的知识。可以进行一些快速测试以查看输出的质量:

answer_question(df, question="What day is it?", debug=False)
> "I don't know."

4、基于flask框架进行接口封装

主要基于上述我们得到的个人领域的向量化文件,然后将question进行接收,并利用answer_question完成回答,简单进行接口的定义:

  • 请求url
/get_answer
  • 请求方式
post
  • Content-type
Json
  • 请求参数
{
	'question':'how are you?'
}
  • 返回参数
{
  'decs': '成功',
  'code': 9000,
  'msg': 'fine thank you'
}

代码如下:

import pandas as pd
import numpy as np

from flask import Flask,jsonify,request

from create_context import answer_question

df = pd.read_csv('./processed/embeddings.csv', index_col=0)
df['embeddings'] = df['embeddings'].apply(eval).apply(np.array)

#创建一个服务,赋值给APP
app = Flask(__name__)
#指定接口访问的路径,支持什么请求方式get,post
@app.route('/get_answer',methods=['post'])

#json方式传参
def get_ss():
    # 获取带json串请求的username参数传入的值
    question = request.json.get('question')
    # 判断请求传入的参数是否在字典里
    try:
        msg = answer_question(df, question=question)
        code = 1000
        decs = '成功'
    except:
        code = 9000
        msg = None
        decs = 'openai服务返回异常'
    data = {
        'decs': decs,
        'code': code,
        'msg': msg
    }
    return jsonify(data)


app.run(host='0.0.0.0',port=8802,debug=True)

5、接口测试使用

测试正常
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/349401.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1.6 epoll实战使用

文章目录1、连接池2、epoll两种工作模式2.1、LT模式2.2、ET模式3、后端开发面试题4、epoll验证1、连接池 将每一个套接字和一块内存进行绑定&#xff0c;连接池就是一个结构体数组&#xff0c;通过链表来维护一个空闲连接。 1、ngx_get_connection(int fd)从空闲链表取一个空闲…

MySQL 中的事务详解

前言MySQL 中的事务操作&#xff0c;要么修改都成功&#xff0c;要么就什么也不做&#xff0c;这就是事务的目的。事务有四大特性 ACID&#xff0c;原子性&#xff0c;一致性&#xff0c;隔离性&#xff0c;持久性。A(Atomic),原子性&#xff1a;指的是整个数据库事务操作是不可…

vue2版本《后台管理模式》(下)

文章目录前言一、home 页以下都属于home子组件二、header 头部 组件二、Menu 页面三、Bread 面包屑四、Footer五 、分页器&#xff1a; Pageing![在这里插入图片描述](https://img-blog.csdnimg.cn/fbe9bb7e84a04ccda4d3fc9f4ab9c36b.png#pic_center)六、权限管理总结前言 这章…

MySQL中索引详解

目录 一.介绍 二.索引分类 三.MySQL的索引 介绍 普通索引 唯一索引 注意 主键索引 组合索引 全文索引 空间索引 删除索引 四.索引的原理 概述 Hash算法 二叉树 平衡二叉树 BTREE树 MyISAM引擎使用BTree 六.索引的特点 优点 缺点 创建索引原则 一.介绍 索引是通…

[翻译]GPDB中的文件空间与表空间

GPDB中的文件空间与表空间GreenPlum是一个快速、灵活、纯软件的分析数据处理引擎&#xff0c;具有一些工具和特性可以充分利用任意个数硬件或者虚拟环境用来部署集群。这里讨论的一个特性是使用文件空间将数据加载和查询活动与底层的IO卷匹配。一旦在集群中创建了一个物理文件空…

【C++】类与对象(三) 运算符重载 赋值重载 取地址及const取地址操作符重载

前言 本章我们接替前一章继续深入理解类的默认成员函数&#xff0c;赋值重载&#xff0c;取地址重载&#xff0c;及const取地址操作符重载 但是在讲剩下的三个默认成员函数之前&#xff0c;我们要先来了解运算符重载&#xff0c;因为赋值重载&#xff0c;取地址重载&#xff0c…

10分钟学会python对接【OpenAI API篇】

今天学习 OpenAI API&#xff0c;你将能够访问 OpenAI 的强大模型&#xff0c;例如用于自然语言的 GPT-3、用于将自然语言翻译为代码的 Codex 以及用于创建和编辑原始图像的 DALL-E。 首先获取生成 API 密钥 在我们开始使用 OpenAI API 之前&#xff0c;我们需要登录我们的 Op…

Linux 定时任务调度(crontab)整理,太实用了!

crontab命令用于设置周期性被执行的指令。该命令从标准输入设备读取指令&#xff0c;并将其存放于“crontab”文件中&#xff0c;以供之后读取和执行。可以使用crontab定时处理离线任务&#xff0c;比如每天凌晨2点更新数据等&#xff0c;经常用于系统任务调度。服务启动和关闭…

AWS攻略——创建VPC

文章目录创建一个可以外网访问的VPCCIDR主路由表DestinationTarget主网络ACL入站规则出站规则子网创建EC2测试连接创建互联网网关&#xff08;IGW&#xff09;编辑路由表参考资料在 《AWS攻略——VPC初识》一文中&#xff0c;我们在AWS默认的VPC下部署了一台可以SSH访问的机器实…

WRAN翻译

基于小波的图像超分辨残差注意力网络 Wavelet-based residual attention network for image super-resolution 代码&#xff1a; https://github.com/xueshengke/WRANSR-keras 摘要&#xff1a; 图像超分辨率技术是图像处理和计算机视觉领域的一项基础技术。近年来&#xff0c…

【流辰信息技术】做好数据管理,赋能行业全速提升产业效能

在经济快速发展的当下&#xff0c;正是各行各业大展拳脚&#xff0c;全力以赴奔赴产能提升的好契机。做好企业&#xff0c;不仅要有一颗发展雄心&#xff0c;而且还要学会运用正确的技术和发展战略&#xff0c;推动企业向前进。流辰信息技术是低代码开发领域里的服务商&#xf…

自动化测试高频面试题(含答案)

Hello&#xff0c;你们的好朋友来了&#xff01;今天猜猜我给大家带来点啥干货呢&#xff1f;最近很多小伙伴出去面试的时候经常会被问到跟自动化测试相关的面试题。所以&#xff0c;今天特意给大家整理了一些经常被公司问到的自动化测试相关的面试题。停&#xff0c;咱先收藏起…

「团队管理」前端开发者如何规划并构建UCD的中长期前端开发能力与团队

文章目录 前言一、个人规划1.1 技能和知识的提升1.2 用户研究和用户体验1.3 团队协作能力1.4 项目管理和交付能力1.5 技术创新和开发效率二、构建UCD团队2.1 人才招聘和培养2.2 角色定义和分工2.3 团队文化和价值观前言 UCD(用户中心设计)是一种基于用户需求的设计方法论,将…

五千字总结一枚测试妹纸不平凡的2022

大家好&#xff0c;我是美团程序员&#xff0c;一个混过大厂&#xff0c;待过创业公司&#xff0c;爱给开发同学提Bug的测试妹纸一枚。2022年&#xff0c;是工作的第六年&#xff0c;也是具有突破性成长的一年&#xff0c;一直挺喜欢六这个数字&#xff0c;果然不负期望&#x…

C控制语句(if,switch,goto)

一.if 1.if循环语句格式 if(expression1) statement1 else if(expression2) statement2 else if(expression3) statement3 . . . else statement(n) else if 可以使用也可以不是用。 这里我们用一个例子进行讲解 2.if else 注意事项 If else if else之间只允许有一条语句&…

Shell - 随时启动 + 固定时间启动脚本

一.引言 有一个线上任务需要在每 10 min内的 5min 后执行&#xff0c;例如 5:10、15:10 ...、55: 10&#xff0c;正常情况下需要查看 Clock Time&#xff0c;待时间到达 5min 后手动启动&#xff0c;下面实现随时启动脚本&#xff0c;定时在 x5:10 点执行。 二.实现 A.固定 5…

【第0天】SQL快速入门-了解MySQL存储引擎(SQL 小虚竹)

回城传送–》《32天SQL筑基》 文章目录零、前言一、什么是数据库引擎二、MYSQL中有哪些数据库引擎2.1、MyISAM2.2、Memoey2.3、InnoDB三、MyISAM和InnoDB的区别3.1、MYSQL版本支持默认引擎不同MyISAMInnoDB3.2、数据的存储结构不同MyISAMInnoDB3.3、存储空间的消耗不同MyISAMIn…

新闻稿写作指南

当你想要传达一则新闻&#xff0c;写一份新闻稿是非常必要的。新闻稿的目的是让读者了解某个事件或信息&#xff0c;以及提供与之相关的背景信息和重要细节。以下是新闻稿的写作指南&#xff0c;帮助你写出一份清晰、简洁、有价值的新闻稿。1、选择一个有新闻价值的主题你的新闻…

MySQL参数优化之join_buffer_size

1.查看当前值 show variables like %join_buffer_size%mysql默认该设置为128 或 256 或512k&#xff0c;各个版本有所出入 2.作用范围 在mysql中表和表进行join时候&#xff0c;无论是两个表之间还是多个表之间&#xff0c;join的情况大致分为下面几种情况 join key 有索引 …

leaflet 设置一个图层或者多个图层的透明度(075)

第075个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中如何设置一个图层或者多个图层的透明度,利用了layer的setOpacity方法。 直接复制下面的 vue+leaflet源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共137行)相关API参考:专…