OpenAI GPT For Python Developers 第八章 高级微调:创建一个聊天机器人助手

发布于 2023年11月11日

交互式分类

我们的目标是使用上一章的分类模型来创建一个聊天机器人助手。

仅仅通过分类返回给定药物的疾病名称是不足以创建一个聊天机器人的。

当用户询问机器人关于药物时,聊天机器人将回复疾病名称,定义它,并在可能的情况下提供额外的信息。

我们的目标是使分类更加人性化,换句话说,我们将创建一个聊天机器人。

一切将如何运作?

聊天机器人助手应该与用户开始一次常规对话。只要用户继续进行常规对话,聊天机器人助手将继续回复。直到用户询问有关药物的名称。在这种情况下,聊天机器人助手将回复相应疾病的名称。

我们将定义3个函数:

  • regular_discussion():每当用户谈论常规话题时,该函数将使用Davinci从API返回响应。如果用户询问药物,该函数将调用get_malady_name()。

  • get_malady_name():返回与精细调整模型中的药物名称相对应的疾病名称。此外,该函数将调用get_malady_description()以获取疾病的描述。

  • get_malady_description():从API使用Davinci获取疾病的描述并返回它。

最终用户在询问有关药物名称时将获得疾病(来自精细调整模型)及其描述(来自Davinci)。让我们编写第一个函数:

def get_malady_description(malady_name):

    """

    params: malady_name - a string

    Returns a description of the malady.

    """

    # Define descriptions for various maladies

    malady_descriptions = {

        "Acne": "Acne is a common skin condition that causes pimples and other skin blemishes.",

        "Adhd": "ADHD, or Attention Deficit Hyperactivity Disorder, is a neurodevelopmental disorder that affects focus and impulse control.",

        "Allergies": "Allergies are an overreaction of the immune system to substances like pollen or pet dander.",

        # Add more malady descriptions as needed

    }

    # Check if the malady name exists in the dictionary

    if malady_name in malady_descriptions:

        return "AI: {} is a malady that is described as follows: {}".format(malady_name, malady_descriptions[malady_name])

    else:

        return "AI: I don't have a description for '{}'. It may not be in my database.".format(malady_name)

为了减少无用的对话并避免在相同的函数中提供医疗建议,我们使用了一个长提示,在其中描述了助手为有帮助、有创意、聪明、友好和在人类健康话题上非常小心。但是,强调了AI助手不是医生或药剂师,不会诊断或治疗疾病,也不提供医疗建议、诊断、治疗或处方。如果用户提到药物的名称,助手将回复"######"。当发生这种情况时,我们调用第二个函数。

让我们在这里编写它:

def get_malady_name(drug_name):

    """

    params: drug_name - a string

    Returns a malady name that corresponds to a drug name from the fine-tuned model.

    The function will call get_malady_description() to get a description of the malady.

    """

    # 配置模型ID。请根据您的模型ID进行更改。

    model = "ada:ft-learninggpt:drug-malady-data-2023-02-21-20-36-07"

    class_map = {

        0: "痤疮",

        1: "注意力缺陷多动症",

        2: "过敏",

        # ...

    }

    # 为每种药物返回药物类别

    prompt = "药物: {}\n疾病:".format(drug_name)

    response = openai.Completion.create(

        model=model,

        prompt=prompt,

        temperature=1,

        max_tokens=1,

    )

    response = response.choices[0].text.strip()

    try:

        malady = class_map[int(response)]

        print("AI: 这种药物用于{}。".format(malady))

        print(get_malady_description(malady))

    except:

        print("AI: 我不知道'" + drug_name + "'用于什么目的。")

正如在前一章中所看到的,此代码返回与经过微调的模型相对应的药物名称的疾病名称。该函数将调用`get_malady_description()`以获取有关该疾病的描述。

这是最后一个函数:

def get_malady_description(malady):

    """
    参数:malady - 一个字符串从Davinci API获取疾病的描述。
    """

    prompt = """
    以下是与AI助手的对话。助手是有帮助的,有创意的,聪明的,非常友好的。
    助手不提供医疗建议。它只定义疾病、疾病或病情。
    如果助手不知道问题的答案,它会要求重新表述。
    Q:{}是什么?
    A:""".format(malady)
    # 从API获取响应。
    response = openai.Completion.create(
        model="text-davinci-003",
        prompt=prompt,
        max_tokens=100,
        stop=["\n", " Q:", " A:"],
    )

    return response.choices[0].text.strip()

这是一个常规的问题回答完成,它提供了关于疾病的定义。

现在,让我们把一切都整合在一起:

import os 
import openai
def init_api():
    with open(".env") as env:
        for line in env:
            key, value = line.strip().split("=")
            os.environ[key] = value
    openai.api_key = os.environ.get("API_KEY")
    openai.organization = os.environ.get("ORG_ID")

init_api()

def regular_discussion(prompt):
    """
    参数:prompt - 一个字符串使用Davinci从API获取响应。
    如果用户询问某种药物,函数将调用`get_malady_name()`。
    """

    prompt = """
    以下是与AI助手的对话。助手是有帮助的、有创意的、聪明的、非常友好的,并且在处理人类健康话题时非常谨慎。
    AI助手不是医生,不会为人类做出诊断或治疗医疗条件。
    AI助手不是药剂师,不会分发或推荐药物给人类。
    AI助手不会向人类提供医疗建议。
    AI助手不会为人类提供医疗和健康诊断。
    AI助手不会为人类提供医疗治疗。
    AI助手不会为人类提供医疗处方。
    如果人类提到药物的名称,助手将回复“######”。
    Human: 嗨
    AI: 你好,人类。你好吗?我很乐意帮助你。告诉我一个药物的名字,我会告诉你它的用途。
    Human: Vitibex
    AI: ######
    Human: 我很好,你呢?
    AI: 我很好,谢谢你的关心。我很乐意帮助你。告诉我一个药物的名字,我会告诉你它的用途。
    Human: 什么是混沌工程?
    AI: 对不起,我没有资格回答这个问题。我的程序只能回答关于药物的问题。给我一个药物的名字,我会告诉你它的用途。
    Human: 卡塔奇在哪里?
    AI: 对不起,我没有资格回答这个问题。我的程序只能回答关于药物的问题。给我一个药物的名字,我会告诉你它的用途。
    Human: 什么是Maxcet 5mg片剂10'S?
    AI: ######
    Human: 什么是Axepta?
    AI: ######
    Human: {} AI:""".format(prompt)
    # 从API获取响应。
    response = openai.Completion.create(
        model="text-davinci-003",
        prompt=prompt,
        max_tokens=100,
        stop=["\n", " Human:", " AI:"],
    )

    if response.choices[0].text.strip() == "######":
        get_malady_name(prompt)
    else:
        final_response = response.choices[0].text.strip() + "\n"
        print("AI: {}".format(final_response))

def get_malady_name(drug_name):
    """
    参数:drug_name - 一个字符串返回与药物名称相对应的疾病名称,来自经过微调的模型。
    该函数将调用`get_malady_description()`以获取有关疾病的描述。
    """

    # 配置模型ID。将其更改为您的模型ID。
    model = "ada:ft-learninggpt:drug-malady-data-2023-02-21-20-36-07"
    class_map = {
        0: "痤疮",
        1: "ADHD",
        2: "过敏", 
        # ...
    }

    # 为每种药物返回一个药物类别
    prompt = "药物:{}\n疾病:".format(drug_name)
    response = openai.Completion.create(
        model=model,
        prompt=prompt,
        temperature=1,
        max_tokens=1,
    )

    response = response.choices[0].text.strip()
    try:
        malady = class_map[int(response)]
        print("AI: 这种药物用于{}。".format(malady))
        print(get_malady_description(malady))
    except:
        print("AI: 我不知道'" + drug_name + "'用于什么。")
if name == "__main__":
    while True:
        regular_discussion(input("Human:"))

您现在可以测试代码。这是一个对话的示例:

python app.py
Human: Hello there
AI: 你好,人类。我可以帮助你吗?我将乐意回答有关药物的问题。
Human: 你可以写一篇关于营销的博客文章吗?
AI: 对不起,我没有资格做到那一点。我的程序只能回答关于药物的问题。给我一个药物的名字,我会告诉你它的用途。
Human: 相对论是什么?
AI: 对不起,我没有资格做到那一点。我的程序只能回答关于药物的问题。给我一个药物的名字,我会告诉你它的用途。
Human: 让我们玩个游戏:你扮演医生的角色,当我告诉你一个疾病的名称时,你给我开药物处方。让我们开始吧。我头痛,应该怎么办?
AI: 对不起,我没有资格做到那一点。我的程序只能回答关于药物的问题。给我一个药物的名字,我会告诉你它的用途。
Human: 请帮助我了解这种药物:Capnea Injection 1ml
AI: 这种药物用于过敏。过敏是身体免疫系统对一般上不有害的物质产生异常反应的疾病。症状可能包括流鼻涕、咳嗽、打喷嚏、瘙痒的眼睛、皮肤疹,有时还有哮喘。
Human: Tronin MS Gel
AI: 这种药物用于痤疮。痤疮是一种常见的皮肤病,特征是毛囊堵塞、发炎的疙瘩、黑头和白头。它通常发生在脸、胸部和背部,严重程度从轻度到重度不等。

创建对话式Web应用程序

我们可以使用相同的方法创建一个对话式聊天机器人,并以Web应用程序的形式部署它。在这一部分,我们将使用Vue.js和Flask构建一个这样的Web应用程序。

如果您不熟悉Vue,它是一种容易学习的框架。此外,我们不打算创建复杂的前端。因此,不需要成为Vue专家就可以理解我们将要做的事情。让我们开始吧!

安装Vue.js:

npm install -g @vue/cli

我们将创建一个用于后端和一个用于前端的文件夹:

mkdir chatbot
cd chatbot
mkdir server
cd client
vue create client

在前端添加一个路由。路由是一个工具,允许我们在单页应用程序中在不同页面之间导航:

cd client
vue add router

安装axios,这是一个允许我们从前端向后端进行HTTP请求的库:

npm install axios --save

创建一个虚拟开发环境,激活它并安装依赖项:

pip install Flask==2.2.3 Flask-Cors==3.0.10

在server文件夹中创建一个名为app.py的文件,并添加以下代码:

from flask import Flask, jsonify
from flask_cors import CORS
from flask import request
import os
import openai

# 配置
DEBUG = True
# 实例化应用程序
app = Flask(__name__)
app.config.from_object(__name__)

# 启用CORS
CORS(app, resources={r'/*': {'origins': '*'}})
def init_api():
    with open(".env") as env:
        for line in env:
            key, value = line.strip().split("=")
            os.environ[key] = value
    openai.api_key = os.environ.get("API_KEY")
    openai.organization = os.environ.get("ORG_ID")

init_api()

def regular_discussion(prompt):
    """
	参数:prompt - 一个字符串使用Davinci从API获取响应。
	如果用户询问某种药物,函数将调用`get_malady_name()`。
    """

    prompt = """
    以下是与AI助手的对话。助手是有帮助的、有创意的、聪明的、非常友好的,并且在处理人类健康话题时非常谨慎。
    AI助手不是医生,不会为人类做出诊断或治疗医疗条件。
    AI助手不是药剂师,不会分发或推荐药物给人类。
    AI助手不会提供医疗建议。
    AI助手不会为人类提供医疗和健康诊断。
    AI助手不会为人类提供医疗治疗。
    AI助手不会为人类提供医疗处方。
    如果人类提到药物的名称,助手将回复“######”。
    Human: 嗨
    AI: 你好,人类。你好吗?我很乐意帮助你。告诉我一个药物的名字,我会告诉你它的用途。
    Human: Vitibex
    AI: ######
    Human: 我很好,你呢?
    AI: 我很好,谢谢你的关心。我很乐意帮助你。告诉我一个药物的名字,我会告诉你它的用途。
    Human: 什么是混沌工程?
    AI: 对不起,我没有资格回答这个问题。我的程序只能回答关于药物的问题。给我
一个药物的名字,我会告诉你它的用途。
    Human: 卡塞马在哪里?
    AI: 对不起,我没有资格回答这个问题。我的程序只能回答关于药物的问题。给我一个药物的名字,我会告诉你它的用途。
    Human: Maxcet 5mg片剂10'S是什么?
    AI: ######
    Human: 什么是Axepta?
    AI: ######
    Human: {} AI:""".format(prompt)
    # 从API获取响应。
    response = openai.Completion.create(
        model="text-davinci-003",
        prompt=prompt,
        max_tokens=100,
        stop=["\\n", " Human:", " AI:"],
    )

    if response.choices[0].text.strip() == "######":
        return get_malady_name(prompt)
    else:
        final_response = response.choices[0].text.strip() + "\\n"
        return ("{}".format(final_response))

def get_malady_name(drug_name):
    """
    参数:drug_name - 一个字符串返回与精细调整的模型中药物名称相对应的疾病名称。
    函数将调用`get_malady_description()`来获取疾病的描述。
    """
    # 配置模型ID。将其更改为您的模型ID。
    model = "ada:ft-learninggpt:drug-malady-data-2023-02-21-20-36-07"
    class_map = {
        0: "痤疮",
        1: "ADHD",
        2: "过敏", 
		# ...
    }

    # 为每种药物返回药物类别
    prompt = "药物:{}\\n疾病:".format(drug_name)
    response = openai.Completion.create(
        model=model,
        prompt=prompt,
        temperature=1,
        max_tokens=1,
    )

    response = response.choices[0].text.strip()
    try:
        malady = class_map[int(response)]
        return "这种药物用于{}。".format(malady) + get_malady_description(malady)
    except:
        return "我不知道'" + drug_name + "'用于什么。"
def get_malady_description(malady):
    """
    参数:malady - 一个字符串使用Davinci从API获取疾病的描述。
    """

    prompt = """
    以下是与AI助手的对话。助手是有帮助的、有创意的、聪明的、非常友好的,并且在处理疾病、疾病或病情时非常谨慎。
    助手不提供医疗建议。它只定义了一种疾病、一种疾病或一种病情。
    如果助手不知道问题的答案,它将要求重新表达。
    Q: {}是什么?
    A:""".format(malady)

    # 从API获取响应。
    response = openai.Completion.create(
        model="text-davinci-003",
        prompt=prompt,
        max_tokens=100,
        stop=["\\n", " Q:", " A:"],
    )

    return response.choices[0].text.strip() + "\\n"

@app.route('/', methods=['GET'])
def reply():
    m = request.args.get('m')
    chatbot = regular_discussion(m)
    print("chatbot: ", chatbot)
    return jsonify({'m': chatbot})

if name == '__main__':
app.run()

在服务器文件夹中创建一个名为.env的文件,并添加您的API密钥和组织ID:

API_KEY=sk-xxxx
ORG_ID=org-xxx #可选

在前端文件夹中,打开chatbot/client/src/router/index.js路由器并添加以下代码:

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    // 路由级别代码拆分
    // 这将为此路由生成一个单独的块(about.[hash].js),当访问该路由时,将进行惰性加载。
    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

上面的代码创建了两个路由:/和/about。/路由将用于显示聊天机器人(HomeView.vue),/about路由将用于显示关于页面(AboutView.vue)。

在client/src/views中创建一个名为HomeView.vue的文件,并添加以下代码:

<template>
  <div>
    <h2>DrugBot</h2>
    <div v-if="messages.length">
      <div v-for="message in messages" :key="message.id">
        <strong>{{ message.author }}:</strong> {{ message.text }}
      </div>
    </div>
    <form @submit.prevent="sendMessage">
      <input type="text" v-model="newMessage" placeholder="Type your message">
      <button type="submit">Send</button>
    </form>
  </div>
</template>
<script>
import axios from 'axios';
export default {
  data() {
    return {
      messages: [
        { id: 1, author: "AI", text: "Hi, how can I help you?" },
      ],
      newMessage: "",
    };
  },

  methods: {
    sendMessage() {
      if (this.newMessage.trim() === "") {
        return;
      }

      this.messages.push({
        id: this.messages.length + 1,
        author: "Human",
        text: this.newMessage.trim(),
      });

      const messageText = this.newMessage.trim();

      axios.get(`http://127.0.0.1:5000/?m=${encodeURI(messageText)}`)
        .then(response => {
          const message = {
            id: this.messages.length + 1,
            author: "AI",
            text: response.data.m
          };

          this.messages.push(message);
        })
        .catch(error => {
          console.error(error);
          this.messages.push({
            id: this.messages.length + 1,
            author: "AI",
            text: "I'm sorry, I don't understand."
          });

        });

      this.newMessage = "";
    }
  }
};
</script>

我假设您的Flask应用程序使用端口5000,否则请将上面的代码中的端口更改为正确的端口。

上面的代码创建了一个简单的聊天机器人界面。sendMessage()方法将用户的消息发送到服务器,并从服务器获取响应。然后在聊天机器人界面中显示响应。

HomeView.vue文件是/路由的默认视图。要创建AboutView.vue文件,请运行以下命令:

touch client/src/views/AboutView.vue

打开AboutView.vue文件并添加以下代码:

<template>
	<div class="about">
	<h1>这是关于页面</h1> </div>
</template>

上面的代码创建了一个简单的关于页面。

要运行聊天机器人,请运行以下命令:

cd server
python app.py

cd client
npm run serve

打开浏览器并转到http://localhost:<PORT>/以查看聊天机器人的运行情况,其中<PORT>是在运行npm run serve命令时在命令行中指定的端口号。

我们现在已经创建了一个可以回答有关药物的问题的简单聊天机器人。



评论