From 59a300eac8a96bdf31f44ea663bd922f2e8ba447 Mon Sep 17 00:00:00 2001 From: nowadays0421 Date: Sun, 23 Jul 2023 14:33:02 +0800 Subject: [PATCH] Finish System-8 --- ...一个带评估的端到端问答系统 Evaluation.ipynb | 529 ++++++++++++++++++ figures/docs/C2/ch8-example.png | Bin 0 -> 27501 bytes 2 files changed, 529 insertions(+) create mode 100644 docs/content/C2 Building Systems with the ChatGPT API/8.搭建一个带评估的端到端问答系统 Evaluation.ipynb create mode 100644 figures/docs/C2/ch8-example.png diff --git a/docs/content/C2 Building Systems with the ChatGPT API/8.搭建一个带评估的端到端问答系统 Evaluation.ipynb b/docs/content/C2 Building Systems with the ChatGPT API/8.搭建一个带评估的端到端问答系统 Evaluation.ipynb new file mode 100644 index 0000000..8f4212f --- /dev/null +++ b/docs/content/C2 Building Systems with the ChatGPT API/8.搭建一个带评估的端到端问答系统 Evaluation.ipynb @@ -0,0 +1,529 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 第八章 搭建一个带评估的端到端问答系统\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在本章中,我们将搭建一个带评估的端到端问答系统,这个系统综合了之前多节课的内容,并加入了评估过程。\n", + "\n", + "1. 检查输入,确认其是否能通过审核 API 的审核。\n", + "\n", + "2. 如果通过了审核,我们将查找产品列表。\n", + "\n", + "3. 如果找到了产品,我们将尝试查找它们的相关信息。\n", + "\n", + "4. 我们使用模型回答用户提出的问题。\n", + "\n", + "5. 我们将通过审核 API 对生成的答案进行审核。\n", + "\n", + "如果没有被标记为有害的,我们将把答案返回给用户。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 二、端到端实现问答系统" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "第一步:输入通过 Moderation 检查\n", + "第二步:抽取出商品列表\n", + "第三步:查找抽取出的商品信息\n", + "第四步:生成用户回答\n", + "第五步:输出经过 Moderation 检查\n", + "第六步:模型评估该回答\n", + "第七步:模型赞同了该回答.\n", + "关于SmartX ProPhone和FotoSnap相机的信息如下:\n", + "\n", + "SmartX ProPhone:\n", + "- 品牌:SmartX\n", + "- 型号:SX-PP10\n", + "- 屏幕尺寸:6.1英寸\n", + "- 存储容量:128GB\n", + "- 相机:12MP双摄像头\n", + "- 网络:支持5G\n", + "- 保修:1年\n", + "- 价格:899.99美元\n", + "\n", + "FotoSnap相机系列:\n", + "1. FotoSnap DSLR相机:\n", + "- 品牌:FotoSnap\n", + "- 型号:FS-DSLR200\n", + "- 传感器:24.2MP\n", + "- 视频:1080p\n", + "- 屏幕:3英寸LCD\n", + "- 可更换镜头\n", + "- 保修:1年\n", + "- 价格:599.99美元\n", + "\n", + "2. FotoSnap无反相机:\n", + "- 品牌:FotoSnap\n", + "- 型号:FS-ML100\n", + "- 传感器:20.1MP\n", + "- 视频:4K\n", + "- 屏幕:3英寸触摸屏\n", + "- 可更换镜头\n", + "- 保修:1年\n", + "- 价格:799.99美元\n", + "\n", + "3. FotoSnap即时相机:\n", + "- 品牌:FotoSnap\n", + "- 型号:FS-IC10\n", + "- 即时打印\n", + "- 内置闪光灯\n", + "- 自拍镜\n", + "- 电池供电\n", + "- 保修:1年\n", + "- 价格:69.99美元\n", + "\n", + "关于我们的电视情况如下:\n", + "\n", + "1. CineView 4K电视:\n", + "- 品牌:CineView\n", + "- 型号:CV-4K55\n", + "- 屏幕尺寸:55英寸\n", + "- 分辨率:4K\n", + "- HDR支持\n", + "- 智能电视功能\n", + "- 保修:2年\n", + "- 价格:599.99美元\n", + "\n", + "2. CineView 8K电视:\n", + "- 品牌:\n" + ] + } + ], + "source": [ + "import openai \n", + "import utils_zh\n", + "from tool import get_completion_from_messages\n", + "\n", + "'''\n", + "注意:限于模型对中文理解能力较弱,中文 Prompt 可能会随机出现不成功,可以多次运行;也非常欢迎同学探究更稳定的中文 Prompt\n", + "'''\n", + "def process_user_message_ch(user_input, all_messages, debug=True):\n", + " \"\"\"\n", + " 对用户信息进行预处理\n", + " \n", + " 参数:\n", + " user_input : 用户输入\n", + " all_messages : 历史信息\n", + " debug : 是否开启 DEBUG 模式,默认开启\n", + " \"\"\"\n", + " # 分隔符\n", + " delimiter = \"```\"\n", + " \n", + " # 第一步: 使用 OpenAI 的 Moderation API 检查用户输入是否合规或者是一个注入的 Prompt\n", + " response = openai.Moderation.create(input=user_input)\n", + " moderation_output = response[\"results\"][0]\n", + "\n", + " # 经过 Moderation API 检查该输入不合规\n", + " if moderation_output[\"flagged\"]:\n", + " print(\"第一步:输入被 Moderation 拒绝\")\n", + " return \"抱歉,您的请求不合规\"\n", + "\n", + " # 如果开启了 DEBUG 模式,打印实时进度\n", + " if debug: print(\"第一步:输入通过 Moderation 检查\")\n", + " \n", + " # 第二步:抽取出商品和对应的目录,类似于之前课程中的方法,做了一个封装\n", + " category_and_product_response = utils_zh.find_category_and_product_only(user_input, utils_zh.get_products_and_category())\n", + " #print(category_and_product_response)\n", + " # 将抽取出来的字符串转化为列表\n", + " category_and_product_list = utils_zh.read_string_to_list(category_and_product_response)\n", + " #print(category_and_product_list)\n", + "\n", + " if debug: print(\"第二步:抽取出商品列表\")\n", + "\n", + " # 第三步:查找商品对应信息\n", + " product_information = utils_zh.generate_output_string(category_and_product_list)\n", + " if debug: print(\"第三步:查找抽取出的商品信息\")\n", + "\n", + " # 第四步:根据信息生成回答\n", + " system_message = f\"\"\"\n", + " 您是一家大型电子商店的客户服务助理。\\\n", + " 请以友好和乐于助人的语气回答问题,并提供简洁明了的答案。\\\n", + " 请确保向用户提出相关的后续问题。\n", + " \"\"\"\n", + " # 插入 message\n", + " messages = [\n", + " {'role': 'system', 'content': system_message},\n", + " {'role': 'user', 'content': f\"{delimiter}{user_input}{delimiter}\"},\n", + " {'role': 'assistant', 'content': f\"相关商品信息:\\n{product_information}\"}\n", + " ]\n", + " # 获取 GPT3.5 的回答\n", + " # 通过附加 all_messages 实现多轮对话\n", + " final_response = get_completion_from_messages(all_messages + messages)\n", + " if debug:print(\"第四步:生成用户回答\")\n", + " # 将该轮信息加入到历史信息中\n", + " all_messages = all_messages + messages[1:]\n", + "\n", + " # 第五步:基于 Moderation API 检查输出是否合规\n", + " response = openai.Moderation.create(input=final_response)\n", + " moderation_output = response[\"results\"][0]\n", + "\n", + " # 输出不合规\n", + " if moderation_output[\"flagged\"]:\n", + " if debug: print(\"第五步:输出被 Moderation 拒绝\")\n", + " return \"抱歉,我们不能提供该信息\"\n", + "\n", + " if debug: print(\"第五步:输出经过 Moderation 检查\")\n", + "\n", + " # 第六步:模型检查是否很好地回答了用户问题\n", + " user_message = f\"\"\"\n", + " 用户信息: {delimiter}{user_input}{delimiter}\n", + " 代理回复: {delimiter}{final_response}{delimiter}\n", + "\n", + " 回复是否足够回答问题\n", + " 如果足够,回答 Y\n", + " 如果不足够,回答 N\n", + " 仅回答上述字母即可\n", + " \"\"\"\n", + " # print(final_response)\n", + " messages = [\n", + " {'role': 'system', 'content': system_message},\n", + " {'role': 'user', 'content': user_message}\n", + " ]\n", + " # 要求模型评估回答\n", + " evaluation_response = get_completion_from_messages(messages)\n", + " # print(evaluation_response)\n", + " if debug: print(\"第六步:模型评估该回答\")\n", + "\n", + " # 第七步:如果评估为 Y,输出回答;如果评估为 N,反馈将由人工修正答案\n", + " if \"Y\" in evaluation_response: # 使用 in 来避免模型可能生成 Yes\n", + " if debug: print(\"第七步:模型赞同了该回答.\")\n", + " return final_response, all_messages\n", + " else:\n", + " if debug: print(\"第七步:模型不赞成该回答.\")\n", + " neg_str = \"很抱歉,我无法提供您所需的信息。我将为您转接到一位人工客服代表以获取进一步帮助。\"\n", + " return neg_str, all_messages\n", + "\n", + "user_input = \"请告诉我关于 smartx pro phone 和 the fotosnap camera 的信息。另外,请告诉我关于你们的tvs的情况。\"\n", + "response,_ = process_user_message_ch(user_input,[])\n", + "print(response)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 二、持续收集用户和助手消息" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "实现一个可视化界面" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# 调用中文 Prompt 版本\n", + "def collect_messages_ch(debug=True):\n", + " \"\"\"\n", + " 用于收集用户的输入并生成助手的回答\n", + "\n", + " 参数:\n", + " debug: 用于觉得是否开启调试模式\n", + " \"\"\"\n", + " user_input = inp.value_input\n", + " if debug: print(f\"User Input = {user_input}\")\n", + " if user_input == \"\":\n", + " return\n", + " inp.value = ''\n", + " global context\n", + " # 调用 process_user_message 函数\n", + " #response, context = process_user_message(user_input, context, utils.get_products_and_category(),debug=True)\n", + " response, context = process_user_message_ch(user_input, context, debug=False)\n", + " # print(response)\n", + " context.append({'role':'assistant', 'content':f\"{response}\"})\n", + " panels.append(\n", + " pn.Row('User:', pn.pane.Markdown(user_input, width=600)))\n", + " panels.append(\n", + " pn.Row('Assistant:', pn.pane.Markdown(response, width=600, style={'background-color': '#F6F6F6'})))\n", + " \n", + " return pn.Column(*panels) # 包含了所有的对话信息" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import panel as pn # 用于图形化界面\n", + "pn.extension()\n", + "\n", + "panels = [] # collect display \n", + "\n", + "# 系统信息\n", + "context = [ {'role':'system', 'content':\"You are Service Assistant\"} ] \n", + "\n", + "inp = pn.widgets.TextInput( placeholder='Enter text here…')\n", + "button_conversation = pn.widgets.Button(name=\"Service Assistant\")\n", + "\n", + "interactive_conversation = pn.bind(collect_messages_ch, button_conversation)\n", + "\n", + "dashboard = pn.Column(\n", + " inp,\n", + " pn.Row(button_conversation),\n", + " pn.panel(interactive_conversation, loading_indicator=True, height=300),\n", + ")\n", + "\n", + "dashboard" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "下图展示了该问答系统的运行实况:\n", + "\n", + "![](../../../figures/docs/C2/ch8-example.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "通过监控系统在更多输入上的质量,您可以修改步骤,提高系统的整体性能。\n", + "\n", + "也许我们会发现,对于某些步骤,我们的提示可能更好,也许有些步骤甚至不必要,也许我们会找到更好的检索方法等等。\n", + "\n", + "我们将在下一章中进一步讨论这个问题。 " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 三、英文版" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**1.1 端到端问答系统**" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "第一步:输入通过 Moderation 检查\n", + "第二步:抽取出商品列表\n", + "第三步:查找抽取出的商品信息\n", + "第四步:生成用户回答\n", + "第五步:输出经过 Moderation 检查\n", + "第六步:模型评估该回答\n", + "第七步:模型赞同了该回答.\n", + "Sure! Here's some information about the SmartX ProPhone and the FotoSnap DSLR Camera:\n", + "\n", + "1. SmartX ProPhone:\n", + " - Brand: SmartX\n", + " - Model Number: SX-PP10\n", + " - Features: 6.1-inch display, 128GB storage, 12MP dual camera, 5G connectivity\n", + " - Description: A powerful smartphone with advanced camera features.\n", + " - Price: $899.99\n", + " - Warranty: 1 year\n", + "\n", + "2. FotoSnap DSLR Camera:\n", + " - Brand: FotoSnap\n", + " - Model Number: FS-DSLR200\n", + " - Features: 24.2MP sensor, 1080p video, 3-inch LCD, interchangeable lenses\n", + " - Description: Capture stunning photos and videos with this versatile DSLR camera.\n", + " - Price: $599.99\n", + " - Warranty: 1 year\n", + "\n", + "Now, could you please let me know which specific TV models you are interested in?\n" + ] + } + ], + "source": [ + "import utils_en\n", + "import openai\n", + "\n", + "def process_user_message(user_input, all_messages, debug=True):\n", + " \"\"\"\n", + " 对用户信息进行预处理\n", + " \n", + " 参数:\n", + " user_input : 用户输入\n", + " all_messages : 历史信息\n", + " debug : 是否开启 DEBUG 模式,默认开启\n", + " \"\"\"\n", + " # 分隔符\n", + " delimiter = \"```\"\n", + " \n", + " # 第一步: 使用 OpenAI 的 Moderation API 检查用户输入是否合规或者是一个注入的 Prompt\n", + " response = openai.Moderation.create(input=user_input)\n", + " moderation_output = response[\"results\"][0]\n", + "\n", + " # 经过 Moderation API 检查该输入不合规\n", + " if moderation_output[\"flagged\"]:\n", + " print(\"第一步:输入被 Moderation 拒绝\")\n", + " return \"抱歉,您的请求不合规\"\n", + "\n", + " # 如果开启了 DEBUG 模式,打印实时进度\n", + " if debug: print(\"第一步:输入通过 Moderation 检查\")\n", + " \n", + " # 第二步:抽取出商品和对应的目录,类似于之前课程中的方法,做了一个封装\n", + " category_and_product_response = utils_en.find_category_and_product_only(user_input, utils_en.get_products_and_category())\n", + " #print(category_and_product_response)\n", + " # 将抽取出来的字符串转化为列表\n", + " category_and_product_list = utils_en.read_string_to_list(category_and_product_response)\n", + " #print(category_and_product_list)\n", + "\n", + " if debug: print(\"第二步:抽取出商品列表\")\n", + "\n", + " # 第三步:查找商品对应信息\n", + " product_information = utils_en.generate_output_string(category_and_product_list)\n", + " if debug: print(\"第三步:查找抽取出的商品信息\")\n", + "\n", + " # 第四步:根据信息生成回答\n", + " system_message = f\"\"\"\n", + " You are a customer service assistant for a large electronic store. \\\n", + " Respond in a friendly and helpful tone, with concise answers. \\\n", + " Make sure to ask the user relevant follow-up questions.\n", + " \"\"\"\n", + " # 插入 message\n", + " messages = [\n", + " {'role': 'system', 'content': system_message},\n", + " {'role': 'user', 'content': f\"{delimiter}{user_input}{delimiter}\"},\n", + " {'role': 'assistant', 'content': f\"Relevant product information:\\n{product_information}\"}\n", + " ]\n", + " # 获取 GPT3.5 的回答\n", + " # 通过附加 all_messages 实现多轮对话\n", + " final_response = get_completion_from_messages(all_messages + messages)\n", + " if debug:print(\"第四步:生成用户回答\")\n", + " # 将该轮信息加入到历史信息中\n", + " all_messages = all_messages + messages[1:]\n", + "\n", + " # 第五步:基于 Moderation API 检查输出是否合规\n", + " response = openai.Moderation.create(input=final_response)\n", + " moderation_output = response[\"results\"][0]\n", + "\n", + " # 输出不合规\n", + " if moderation_output[\"flagged\"]:\n", + " if debug: print(\"第五步:输出被 Moderation 拒绝\")\n", + " return \"抱歉,我们不能提供该信息\"\n", + "\n", + " if debug: print(\"第五步:输出经过 Moderation 检查\")\n", + "\n", + " # 第六步:模型检查是否很好地回答了用户问题\n", + " user_message = f\"\"\"\n", + " Customer message: {delimiter}{user_input}{delimiter}\n", + " Agent response: {delimiter}{final_response}{delimiter}\n", + "\n", + " Does the response sufficiently answer the question?\n", + " \"\"\"\n", + " messages = [\n", + " {'role': 'system', 'content': system_message},\n", + " {'role': 'user', 'content': user_message}\n", + " ]\n", + " # 要求模型评估回答\n", + " evaluation_response = get_completion_from_messages(messages)\n", + " if debug: print(\"第六步:模型评估该回答\")\n", + "\n", + " # 第七步:如果评估为 Y,输出回答;如果评估为 N,反馈将由人工修正答案\n", + " if \"Y\" in evaluation_response: # 使用 in 来避免模型可能生成 Yes\n", + " if debug: print(\"第七步:模型赞同了该回答.\")\n", + " return final_response, all_messages\n", + " else:\n", + " if debug: print(\"第七步:模型不赞成该回答.\")\n", + " neg_str = \"很抱歉,我无法提供您所需的信息。我将为您转接到一位人工客服代表以获取进一步帮助。\"\n", + " return neg_str, all_messages\n", + "\n", + "user_input = \"tell me about the smartx pro phone and the fotosnap camera, the dslr one. Also what tell me about your tvs\"\n", + "response,_ = process_user_message(user_input,[])\n", + "print(response)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**2.1 持续收集用户和助手信息**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def collect_messages_en(debug=False):\n", + " \"\"\"\n", + " 用于收集用户的输入并生成助手的回答\n", + "\n", + " 参数:\n", + " debug: 用于觉得是否开启调试模式\n", + " \"\"\"\n", + " user_input = inp.value_input\n", + " if debug: print(f\"User Input = {user_input}\")\n", + " if user_input == \"\":\n", + " return\n", + " inp.value = ''\n", + " global context\n", + " # 调用 process_user_message 函数\n", + " #response, context = process_user_message(user_input, context, utils.get_products_and_category(),debug=True)\n", + " response, context = process_user_message(user_input, context, debug=False)\n", + " context.append({'role':'assistant', 'content':f\"{response}\"})\n", + " panels.append(\n", + " pn.Row('User:', pn.pane.Markdown(user_input, width=600)))\n", + " panels.append(\n", + " pn.Row('Assistant:', pn.pane.Markdown(response, width=600, style={'background-color': '#F6F6F6'})))\n", + " \n", + " return pn.Column(*panels) # 包含了所有的对话信息" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "gpt", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/figures/docs/C2/ch8-example.png b/figures/docs/C2/ch8-example.png new file mode 100644 index 0000000000000000000000000000000000000000..ffaa0366efe26273590b5e48a72d4adf0afd93ba GIT binary patch literal 27501 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

0?8|5)g#ZmMpI?hBVD@? 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 z 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