原文:如何使用 OpenAI 和 Python 构建 AI 助手

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/946add4c7036e89ccc8d6d3ede00c323.png

Canva 上的图片。

客户最常向我提出的一个问题是,“我如何使用我的数据制作一个定制的聊天机器人?”六个月前,这可能需要数月时间来开发,但今天,情况并不一定如此。在这篇文章中,我提供了一个使用 OpenAI 的助手和微调 API 创建定制的 AI 的逐步指南。为每种方法提供了 Python示例代码


聊天机器人与助手

在深入示例代码之前,我想简要区分 AI 聊天机器人和助手。虽然这些术语经常互换使用,但在这里,我使用它们来表示不同的事物。

聊天机器人是你可以与之进行对话的 AI,而AI 助手是能够使用工具的聊天机器人。工具可以是网络浏览、计算器、Python 解释器或其他任何可以扩展聊天机器人功能的工具[1]。

例如,如果你使用 ChatGPT 的免费版本,那它就是一个聊天机器人,因为它只提供基本的聊天功能。然而,如果你使用 ChatGPT 的付费版本,那它就是一个助手,因为它包含了网络浏览、知识检索和图像生成等功能。

嵌入视频链接

助手 API

虽然构建 AI 助手(即 AI 代理)并不是一个新想法,但 OpenAI 的新助手 API 提供了一种简单直接的方式来创建这类 AI。在这里,我将使用该 API 来制作一个配备知识检索(即 RAG)功能的 YouTube 评论回复器,该功能来自我的 Medium 文章。以下示例代码可在本帖的GitHub 仓库中找到。

基础助手

我们首先导入 Python 库并设置与 OpenAI API 的通信。

from openai import OpenAI
from sk import my_sk # import secret key from .py file

client = OpenAI(api_key=my_sk)

注意,对于这一步,你需要一个 OpenAI API 密钥。如果你没有 API 密钥或者不知道如何获取,我在一篇之前的文章中介绍了如何操作。在这里,我在一个名为 sk.py 的单独的 Python 文件中定义了我的秘密密钥,该文件在上面的代码块中被导入。

现在我们可以创建一个基本的助手(技术上是一个聊天机器人,因为没有使用工具)。这可以通过一行代码完成,但我为了可读性使用了更多行。

intstructions_string = "ShawGPT, functioning as a virtual data science 
consultant on YouTube, communicates in clear, accessible language, escalating 
to technical depth upon request. 
It reacts to feedback aptly and concludes with its signature '–ShawGPT'. 
ShawGPT will tailor the length of its responses to match the viewer's comment, 
providing concise acknowledgments to brief expressions of gratitude or 
feedback, thus keeping the interaction natural and engaging."

assistant = client.beta.assistants.create(
    name="ShawGPT",
    description="Data scientist GPT for YouTube comments",
    instructions=intstructions_string,
    model="gpt-4-0125-preview"
)

如上图所示,我们可以设置助手的名称描述说明模型。与助手性能最相关的输入是说明模型。开发良好的说明(即提示工程)是一个迭代的过程,但值得花些时间。此外,我使用了最新的可用版本的 GPT-4。然而,也有更旧(且更便宜)的模型可供选择[2]。

“助手”设置好后,我们可以向它发送消息以生成响应。这在下面的代码块中完成。

# create thread (i.e. object that handles conversation between user and assistant)
thread = client.beta.threads.create()

# add a user message to the thread
message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="Great content, thank you!"
)

# send message to assistant to generate a response
run = client.beta.threads.runs.create(
  thread_id=thread.id,
  assistant_id=assistant.id,
)

上述代码块中发生了一些事情。首先,我们创建了一个线程对象。这处理用户和助手之间的消息传递,从而避免了我们需要编写样板代码来执行这一操作。接下来,我们向线程中添加了一条用户消息。这些是我们用例中的 YouTube 评论。然后,最后,我们通过 run 对象将线程发送给助手以生成响应。

几秒钟后,我们从助手那里得到了以下响应:

You're welcome! I'm glad you found it helpful. If you have any more questions 
or topics you're curious about, feel free to ask. –ShawGPT

虽然这个回答可能看起来不错,但这不是我会说的话。让我们看看我们如何通过所谓的少样本提示来改进助手。

少样本提示

少样本提示是指我们在助手的说明中包含输入输出示例,使其能够从中学习。在这里,我将 3 个(真实)评论和响应追加到之前的指令字符串中。

intstructions_string_few_shot = """ShawGPT, functioning as a virtual data 
science consultant on YouTube, communicates in clear, accessible language, 
escalating to technical depth upon request. 
It reacts to feedback aptly and concludes with its signature '–ShawGPT'. 
ShawGPT will tailor the length of its responses to match the viewer's comment, 
providing concise acknowledgments to brief expressions of gratitude or 
feedback, thus keeping the interaction natural and engaging.

Here are examples of ShawGPT responding to viewer comments.

Viewer comment: This was a very thorough introduction to LLMs and answered many questions I had. Thank you.
ShawGPT: Great to hear, glad it was helpful :) -ShawGPT

Viewer comment: Epic, very useful for my BCI class
ShawGPT: Thanks, glad to hear! -ShawGPT

Viewer comment: Honestly the most straightforward explanation I've ever watched. Super excellent work Shaw. Thank you. It's so rare to find good communicators like you!
ShawGPT: Thanks, glad it was clear -ShawGPT"""

注意,我在每个示例中添加了签名“— ShawGPT”(即使这些并不是我实际的响应)。这很重要,以便从助手那里得到期望的行为。

使用更新后的说明,我们可以以前同样的方式创建一个新的助手。

assistant = client.beta.assistants.create(
    name="ShawGPT",
    description="Data scientist GPT for YouTube comments",
    instructions=intstructions_string_few_shot,
    model="gpt-4-0125-preview"
)

将相同的测试评论(即“内容很好,谢谢!”)传递给这个助手,我们现在得到以下响应:

You're welcome, happy to hear you found it useful! -ShawGPT

虽然这(仍然)比我的典型回答要长一些,但它比我们之前看到的纯助手要好得多。

让我们尝试另一个(更技术性的)问题。我们可以使用之前相同的语法将用户消息传递给助手,但现在让我们问,“什么是肥尾性?

这是助手的响应。

Fat-tailedness is a concept from statistics that refers to the properties of a 
particular type of probability distribution. In a fat-tailed distribution, the 
tails (the far ends of the distribution, representing extreme values far from 
the mean) are heavier or "fatter" than they would be in a normal (Gaussian) 
distribution. This means there's a higher likelihood of extreme outcomes 
occurring compared to what you would expect if the data followed a normal 
distribution.

In practical terms, if a dataset is described as having a fat-tailed 
distribution, it indicates that rare, extreme events (the ones in the tails) 
happen more frequently than would be predicted by models based on normal 
distributions. This concept is extremely important in fields like finance, 
economics, and risk management, where underestimating the likelihood of extreme
events can have significant consequences.

Fat-tailed distributions contrast with thin-tailed distributions, like the 
normal distribution, where extreme values are less likely, and most of the 
probabilities are concentrated around the mean. Understanding whether a dataset
follows a fat-tailed distribution can significantly impact how we model and 
predict phenomena, especially when it comes to assessing risks and anticipating
rare but impactful events. -ShawGPT

虽然(再次)这个响应是合理的,但它读起来更像是来自 ChatGPT 的内容,而不是我可能会说的话。特别是,它使用与我在我的系列中使用的定义不同的厚尾定义(即比正态分布有更重尾部的分布)。

为了提高助手响应的这方面,我们可以给它访问我写的关于厚尾现象的文章中的知识的能力。

知识检索

虽然设置一个 RAG 系统包括一些非平凡步骤(即文档分块、设置向量数据库、构建语义搜索功能、将搜索结果融合到上下文窗口中),但在助手的 API 中,整个过程被简化为 2 个步骤

首先,我们上传我们的文档以供检索。其次,我们为我们的助手添加了检索功能。

在下面的代码块中,我将我的关于量化厚尾的文章上传到我的 OpenAI 账户。为此,我们需要指定文件路径/名称和文件的目的。在这里,我们将目的设置为"assistants"。另一个选项是"fine-tune",我们很快就会看到。

file = client.files.create(
  file=open("articles/4 Ways to Quantify Fat Tails with Python _ by Shaw Talebi _ Towards Data Science.pdf", "rb"),
  purpose="assistants"
)

接下来,我们可以创建一个与之前类似的新助手,但现在允许检索并指定助手可用的文件。虽然在这里我只使用一个文档进行检索,但我们可以通过在fileids*列表中包含相关的文件 ID 来指定多达 20 个文件。

assistant = client.beta.assistants.create(
    name="ShawGPT",
    description="Data scientist GPT for YouTube comments",
    instructions=intstructions_string_few_shot,
    tools=[{"type": "retrieval"}],
    file_ids=[file.id],
    model="gpt-4-0125-preview"
)

将相同的技术问题传递给助手,我们得到以下响应。

Fat-tailedness describes the degree to which rare events significantly 
contribute to the aggregate statistics of a distribution. This concept 
highlights that in certain distributions, a small number of observations can 
disproportionately influence the overall picture. A classic example is the 
Pareto distribution, often referenced in the context of wealth distribution, 
where a small proportion of individuals can hold the majority of wealth. In 
statistical terms, distributions range from thin-tailed (e.g., Gaussian, where 
extreme events are highly improbable) to very fat-tailed (e.g., Pareto, where 
extreme events are less rare than in Gaussian distributions).

Fat-tailedness is not about a binary categorization of distributions as either 
"Power Law" or not but recognizes a continuum or spectrum of "tail heaviness." 
This understanding offers a more nuanced view, allowing for more accurate and 
precise categorization of data distributions. Identifying and quantifying the 
fat-tailed nature of a dataset is important in fields like finance, insurance, 
risk management, and social sciences, where understanding the likelihood and 
impact of extreme events is crucial.

In practical terms, fat-tailedness can be quantified using different heuristics,
which may involve mathematical computations such as the Power Law Tail Index, 
Kurtosis, Log-normal's σ, and Taleb's κ metric. Each of these measures offers 
a different lens through which to assess the fat-tailed nature of a 
distribution, thereby providing useful insights into the behavior of extreme 
events within the dataset -ShawGPT

这个响应与我思考(和解释)厚尾现象的方式非常接近。助手成功地将其响应中融入了文章中的关键概念。例如,用罕见事件来定义厚尾现象,厚尾现象存在于一个谱上,以及四种测量它们的启发式方法。

到目前为止,我们已经通过**提示工程知识检索**来创建我们的助手,已经取得了相当大的进展。然而,响应仍然不完全像我写的样子。为了进一步改进助手的这一方面,我们可以转向微调。

微调 API

虽然提示工程可能是编程助手的简单方法,但并不总是明显如何最好地指导模型以展示所需的行为。在这些情况下,微调模型可能是有利的。

微调是指我们使用特定任务的额外示例来训练一个预存在的模型。在 OpenAI 微调 API 中,这包括提供用户-助手消息配对[3]。

对于 YouTube 评论回复用例,这意味着收集观众评论(即用户消息)及其相关回复(即助手消息)的配对。

尽管这个额外的数据收集过程使得微调在前期工作更加繁重,但它可以显著提高模型性能[3]。在这里,我将介绍针对这个特定用例的微调过程。

数据准备

为了生成用户-助手消息配对,我手动浏览了过去的 YouTube 评论,并将它们复制粘贴到一个电子表格中。然后,我将这个电子表格导出为.csv 文件(可在GitHub 仓库找到)。

虽然这个.csv 文件包含了微调所需的全部关键数据,但它不能直接使用。我们必须首先将其转换成特定的格式,然后才能传递给 OpenAI API。

更具体地说,我们需要生成一个**.jsonl 文件**,这是一个文本文件,其中每一行对应一个 JSON 格式的训练示例。如果你是 Python 用户且不熟悉 JSON,你可以将其想象成一个字典(即由键值对组成的数据结构)[4]。

为了将我们的.csv 文件转换为必要的.jsonl 格式,我首先为每种类型的评论创建了 Python 列表。这是通过逐行读取原始.csv 文件并将每条消息存储在相应的列表中完成的。

import csv
import json
import random

comment_list = []
response_list = []

with open('data/YT-comments.csv', mode ='r') as file:
    file = csv.reader(file)

    # read file line by line
    for line in file:
        # skip first line
        if line[0]=='Comment':
            continue

        # append comments and responses to respective lists
        comment_list.append(line[0])
        response_list.append(line[1] + " -ShawGPT")

接下来,为了创建.jsonl 文件,我们必须创建一个字典列表,其中每个元素对应一个训练示例。这些字典的是"messages",是(又一次)与系统、用户和助手消息相对应的字典列表。下面给出了这个数据结构的可视化概述。

<…/Images/4a960b1fe9ffef933111be55c2f9e38e.png>

微调训练数据格式概述 [4]。图片由作者提供。

以下是将我们的_comment*list*_response*list*对象转换为示例列表的 Python 代码。这是通过逐个遍历_comment*list*_response*list*,并在每一步创建三个字典来完成的。

这些分别对应于系统、用户和助手消息,其中系统消息是我们通过少量提示来制作助手时使用的相同指令,而用户/助手消息来自各自的列表。然后,这些字典被存储在一个列表中,作为特定训练示例的值。

example_list = []

for i in range(len(comment_list)):
    # create dictionaries for each role/message    
    system_dict = {"role": "system", "content": intstructions_string_few_shot}
    user_dict = {"role": "user", "content": comment_list[i]}
    assistant_dict = {"role": "assistant", "content": response_list[i]}

    # store dictionaries into list
    messages_list = [system_dict, user_dict, assistant_dict]

    # create dictionary for ith example and add it to example_list
    example_list.append({"messages": messages_list})

在此过程结束时,我们有一个包含 59 个元素的列表,对应于 59 个用户-助手示例对。帮助评估模型性能的另一步是将这 59 个示例分成两个数据集,一个用于训练模型,另一个用于评估其性能

这在下面的代码块中完成,我在 59 个示例中随机抽取了 9 个,并将它们存储在一个名为 validation_data_list 的新列表中。然后,这些示例从 example_list 中移除,它将作为我们的训练数据集。

# create train/validation split
validation_index_list = random.sample(range(0, len(example_list)-1), 9)

validation_data_list = [example_list[index] for index in validation_index_list]

for example in validation_data_list:
    example_list.remove(example)

最后,当我们的训练和验证数据集准备就绪后,我们可以将它们写入.jsonl 文件。这可以通过以下方式完成。

# write examples to file
with open('data/training-data.jsonl', 'w') as training_file:
    for example in example_list:
        json.dump(example, training_file)
        training_file.write('n')

with open('data/validation-data.jsonl', 'w') as validation_file:
    for example in validation_data_list:
        json.dump(example, validation_file)
        validation_file.write('n')

精细调优作业

数据准备就绪后,我们可以分两步运行精细调优作业。首先,我们将训练和验证文件上传到我们的 OpenAI 账户。其次,我们运行训练过程 [3]。

我们上传文件的方式与设置助手文档检索时相同,但现在将文件用途设置为“精细调优”。这适用于下面的训练和验证数据集。

# upload fine-tuning files
training_file = client.files.create(
  file = open("data/training-data.jsonl", "rb"),
  purpose = "fine-tune"
)

validation_file = client.files.create(
  file = open("data/validation-data.jsonl", "rb"),
  purpose = "fine-tune"
)

现在,我们可以运行精细调优作业。为此,我们需要指定要精细调优的训练文件和模型。可用于精细调优的最先进模型是 gpt-3.5-turbo,我在这里使用它。我还设置了一个后缀,它将用户定义的字符串添加到精细调优模型的 ID 中。下面的代码展示了这一点。

client.fine_tuning.jobs.create(
    training_file = training_file.id,
    validation_file = validation_file.id,
    suffix = "ShawGPT",
    model = "gpt-3.5-turbo"
)

训练过程大约需要 15 分钟。完成后,我们可以通过以下方式使用精细调优的模型:通过 completions API,如下所示。

test_comment = "Great content, thank you!"

response = client.chat.completions.create(
    model="ft:gpt-3.5-turbo-0613:personal:shawgpt:8mUeVreo",
    messages=[
    {"role": "system", "content": intstructions_string_few_shot},
    {"role": "user", "content": test_comment}
    ]
)

注意到我们上面代码块中生成的响应与我们之前的方式不同。这是因为精细调优的模型目前不支持在 Assistants API 中使用

最大的缺点是,如果我们想通过工具(例如,将其变成一个助手)增强精细调优的模型,我们必须通过 LangChain 或 LlamaIndex(例如)之类的库来构建这些功能。

虽然这使得开发一个精细调优的助手更加费时,但我们看到的默认响应更接近我所说的内容。以下是对测试评论的一些响应。

Thanks, I appreciate it! -ShawGPT
Thanks, glad you liked it! -ShawGPT
Glad it was helpful! -ShawGPT

让我们看看它如何处理之前相同的同技术问题,即“什么是肥尾性?

Good question! The fat tail phenomenon represents the size of outlier (extreme) 
events relative to a normal (Gaussian) distribution. In other words, there's a 
greater probability of extreme events occurring compared to a normal 
distribution. -ShawGPT

虽然模型在定义“肥尾”时使用的术语与我不同,但响应的长度和风格比我们在 RAG 之前的 Assistants API 中看到的要好得多。这表明,如果我们向这个精细调优模型添加 RAG,它将生成比之前看到的显著更好的响应。

YouTube-Blog/LLMs/ai-assistant-openai at main · ShawhinT/YouTube-Blog

接下来是什么?

构建定制的 AI 助手比以往任何时候都要简单。在这里,我们看到了通过 OpenAI 的助手 API 创建 AI 助手的一种简单方法,以及如何通过他们的微调 API 微调模型。

虽然 OpenAI 目前拥有开发此处讨论的 AI 助手类型的最先进模型,但这些模型被锁定在其 API 之后,这限制了我们可以用它们构建什么/如何构建。

因此,一个自然的问题是,我们如何可能使用开源解决方案开发类似的系统。这将在本系列的下一篇文章中介绍,我将讨论如何使用 QLoRA 微调模型 并通过 RAG 增强聊天机器人。

更多关于 LLMs 👇

大型语言模型 (LLMs)


资源

连接: 我的网站 | 预约通话

社交平台: YouTube 🎥 | LinkedIn | Instagram

支持: 买我一杯咖啡 ☕️

免费获取我写的每一篇新故事


[1] OpenAI 助手 API

[2] 助手 API 中的可用模型

[3] 微调指南

[4] 微调数据准备

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐