TLDR:这个工作流程可以回应客户的反馈信息,并将其解析为优先支持票,使用GPT4 + 狸猫(开源)。
谁会从中受益? 对于任何希望将GPT4的能力与复杂工作流程无缝集成的人来说,这非常有用。
这篇文章有多高级? 需要基本的Python知识,但也仅限于此。狸猫负责其余的事情。
这篇文章不会做什么。 我们不会获取推文(请查看其API),也不会发布到您的客户支持服务(这在很大程度上取决于技术堆栈)。
介绍
GPT4已经席卷了世界。潜在的用例范围从聊天机器人到分类器,再到数据分析以及现在是视觉。
其中一个挑战是创建需要结构化输出的应用程序,因为语言模型返回的是自然文本输出。
此外,零食风格的LLM众所周知存在错位(语言模型可能以不同方式理解任务)和产生错觉。为了解决这个问题,我们创建了狸猫,一种简单方法来创建对齐的LLM驱动的应用程序,并确保结构化和类型化输出。
在这里,我将演示如何使用狸猫在20分钟内创建以下内容:
- (客户支持)聊天机器人
- (客户支持)分类器,以及
- 可靠的结构化对象以登录数据库。
案例简介
在这个示例用例中,我们将创建一个既充当客户支持聊天机器人,又在内部系统中创建潜在问题的工作流程。特别地,我们假设一个发给公司支持帐户的推文作为输入。工作流程将对推文作出共情回应,并对其进行分类,以确定是否需要公司支持团队进一步采取行动。如果该推文确实需要进一步行动,那么将创建一个支持票,您可以将其保存到内部数据库以进行进一步的处理。
狸猫将在此示例中用于创建回应、分类推文并在需要时创建支持票。我们能够在20分钟内构建这个Python应用程序。现在,我们想向您展示如何做到同样。如果您迫不及待,可以在以下链接中找到仓库和用例:
狸猫是什么,为什么它对此用例很有用?
狸猫是一个用于轻松构建LLM驱动应用程序的开源库,几乎不需要LLM或提示知识。狸猫的有用特性,尤其适用于此用例,包括:
- 类型感知:输出将始终遵循用户指定的类型提示结构(在LLM中处理这些是一场噩梦,因为输出是自由文本)。输出可以指定为Python基本类型(整数、列表、字典)或更复杂的类型,如Pydantic类或Literals。
- 语言模型行为很容易通过示例进行对齐(遵循上下文学习的思想)。添加这些对齐语句不是必需的,但通常可以显著提高模型的性能。添加模拟示例就像在记事本中写下它们一样简单。
- 狸猫执行自动模型蒸馏,即教师模型(GPT-4)自动蒸馏为一个更小的精细调整模型,可以在不损失性能的情况下使成本和延迟降低多达15倍。
这些功能与该用例相关,原因如下:
- LLM的输出将被其他系统在下游使用。这要求输出始终具有类型以确保不会出现故障,并且不会有数据导致的运行时错误。这非常重要,因为工作流会创建需要记录到数据库中的结构化票据对象,而数据库期望按照给定的模式提供数据。
- 什么应该(和不应该)采取行动是主观而不明显的。此外,语言模型对客户反馈的响应方式非常重要(语气和信息必须正确,以免惹怒已经遇到问题的客户)。因此,关注一致性是至关重要的,因为您希望确保LLM了解什么是适当的回应,并且与员工处理这些问题的方式相一致,之后的任何进一步操作(您不希望错过客户请求)都与之对齐。将语言模型与这些问题保持一致是确保适合生产使用的性能的唯一方法。
- 在生产环境中,潜在的支持票据和反馈数量庞大。因此,通过缩减成本15倍可以节省大量开支并且对长期使用具有激励作用,尤其是因为性能将保持不变,并且工作流不会受到潜在的将来对GPT4的版本更改的影响。
为了确定项目范围,我们将使用以下要求
首先,我们假设以下一般工作流程:
- 用户发送关于产品或服务的反馈消息到Twitter帐户。
- LLM分析反馈并以尽可能亲切的方式回复(或者尽力而为),即聊天机器人方面。
- 根据客户反馈,LLM将把反馈分类为“需要采取行动”或“不需要采取行动”,即分类器方面。
- 如果需要采取行动,那么聊天机器人将创建一个客户票据对象,以便稍后在下游应用程序中使用。
我们将在此用例中使用OpenAI的GPT4。首先,我们需要使用您的OpenAI API密钥设置一些环境变量。为此,我们应该创建一个.env文件并将其添加到目录中。稍后将读取.env文件并正确解析环境变量。
OPENAI_API_KEY=sk-XXX
这就是配置Tanuki所需的全部内容!接下来,让我们看看如何构建这个用例。
构建工作流程
如前所述,如果成本不是一个问题,您可以在提示的情况下使用GPT4,尽管获得类型化的输出需要额外的工作。如果您有几周的时间,您甚至可以微调一个开源的LLM来处理此任务。相反,我们将使用Tanuki在20分钟内完成此任务。
工作的第一步是安装Tanuki
pip install tanuki.py
然后,让我们制定一些基础工作。我们可以假设输入的推文对象如下:
from pydantic import BaseModelclass Tweet(BaseModel): """ 推文对象 名称是用户的帐户 文本是他们发送的推文 id是唯一的分类器 """ name: str text: str id: str
然后,我们将创建一个response
Pydantic对象,将给出的推文发布回用户,如果需要人工处理输入消息,则还需要创建一个SupportTicket
对象以保存到数据库中
from typing import Literal, Optionalclass Response(BaseModel): """ 响应对象,其中response属性是发送给客户的响应 requires_ticket是一个布尔值,指示传入的推文是一个问题还是一个直接问题 需要人工干预和行动 """ requires_ticket: bool response: str class SupportTicket(BaseModel): """ 支持票据对象,其中 issue是客户遇到的问题的简要描述 urgency传达了团队对问题的紧急程度 """ issue: str urgency: Literal["low", "VoAGI", "high"]
现在,我们已经完成了基础工作,可以开始创建实际的函数来为我们完成所有繁重的工作。
首先,我们创建一个函数,从传入的tweet
对象创建Response
对象。我们将tweet指定为输入参数,将Response
指定为输出类型提示。指定类型提示非常重要,因为它会告诉执行函数的语言模型应该创建什么作为最终输出。Tanuki还会确保输出符合类型提示,因此我们可以安全地说,由于不正确的对象或不可靠的输出而导致的任何故障都不会发生。
接下来,在函数的文档字符串中添加LLM需要执行的摘要,并添加@tanuki.patch
装饰器。这确保了classify_and_respond
的输出具有良好的类型,以便进行进一步分析。
import tanuki@tanuki.patchdef classify_and_respond(tweet: Tweet) -> Response: """ 以同理心和友善的方式回应客户支持推文文字。告知对问题的关注,并说明是否有支持团队应该解决的直接问题,或者是一个问题,团队将对其进行回应。 """
为了确保可靠的性能并添加对齐语句以引导LLM性能输出给用户的内容,我们将创建另一个名为align_respond
的函数,并使用@tanuki.align
装饰器。
在align_respond
中,我们将通过显示有效输入和输出的示例进行LLM输出的对齐。这个对齐将:
- 向LLM展示如何回应客户的请求
- 显示客户的请求需要记录下来(并创建一个内部工单)的请求。
这些对齐将使用Python的assert语句将其作为输入和期望输出进行模拟。下面是聊天机器人输出对象的一些对齐语句示例:
@tanuki.aligndef align_respond(): input_tweet_1 = Tweet(name = "Laia Johnson", text = "I really like the new shovel but the handle broke after 2 days of use. Can I get a replacement?", id = "123") assert classify_and_respond(input_tweet_1) == Response( requires_ticket=True, response="嗨,很抱歉听到这个问题。我们将尽快给您更换,您能告诉我们您的订单号吗?" ) input_tweet_2 = Tweet(name = "Keira Townsend", text = "I hate the new design of the iphone. It is so ugly. I am switching to Samsung", id = "10pa") assert classify_and_respond(input_tweet_2) == Response( requires_ticket=False, response="嗨,很抱歉听到这个问题。我们会考虑您的反馈,并告知产品团队。" ) input_tweet_3 = Tweet(name = "Thomas Bell", text = "@Amazonsupport. I have a question about ordering, do you deliver to Finland?", id = "test") assert classify_and_respond(input_tweet_3) == Response( requires_ticket=True, response="嗨,感谢您的反馈。问题将发送给我们的支持团队,他们将尽快给您回复。" ) input_tweet_4 = Tweet(name = "Jillian Murphy", text = "Just bought the new goodybox and so far I'm loving it!", id = "009") assert classify_and_respond(input_tweet_4) == Response( requires_ticket=False, response="嗨,感谢您的联系。我们很高兴您喜欢这个产品。" )
上述的assert语句大大降低了产生幻觉和意外故障的可能性,将LLM与预期行为对齐。我喜欢将此视为“面向测试的对齐”(针对LLMs的面向测试的开发)。
对于第二部分(即支持工单的创建和记录),需要完全相同的操作。通过使用补丁和对齐函数遵循相同的结构,我们有以下内容:
@tanuki.patchdef create_support_ticket(tweet_text: str) -> SupportTicket: """ 使用推文文本创建一个支持工单,以保存到内部数据库 创建需要采取的行动简要概述以及问题的紧急程度 """@tanuki.aligndef align_supportticket(): input_tweet_1 = "I really like the new shovel but the handle broke after 2 days of use. Can I get a replacement?" assert create_support_ticket(input_tweet_1) == SupportTicket( issue="需要替换产品,因为手柄断了", urgency = "高" ) input_tweet_2 = "@Amazonsupport. I have a question about ordering, do you deliver to Finland?" assert create_support_ticket(input_tweet_2) == SupportTicket( issue="找出并回答我们当前是否为芬兰提供送货服务", urgency="低" ) input_tweet_3 = "Just bought the new goodybox and so far I'm loving it! The cream package was slightly damaged however, would need that to be replaced" assert create_support_ticket(input_tweet_3) == SupportTicket( issue="需要替换一个新的面霜,因为包装稍微损坏了", urgency="VoAGI" )
为了将所有内容整合在一起,我们创建一个analyse_and_respond()
函数来创建响应并在需要时支持工单,然后我们就完成了!
def analyse_and_respond(tweet: Tweet) -> tuple[Optional[SupportTicket], Response]: # 获取响应 response = classify_and_respond(tweet) # 如果响应需要一个工单,则创建一个工单 if response.requires_ticket: support_ticket = create_support_ticket(tweet.text) return response, support_ticket return response, None
所有这些的完整最终代码应该是这样的:
from dotenv import load_dotenvload_dotenv()from pydantic import BaseModelfrom typing import Literal, Optionalimport tanukiclass Tweet(BaseModel): """ 推文对象 其中name是用户的账户名 issue是他们发送的消息 """ name: str text: str id: strclass Response(BaseModel): """ 响应对象,其中response属性是发送给客户的响应 requires_ticket是一个布尔值,指示传入的推文是一个问题还是需要人工干预和处理的直接问题 """ requires_ticket: bool response: str class SupportTicket(BaseModel): """ 支持工单对象,其中 issue是客户问题的简要描述 urgency表示团队应该多么紧急地回应这个问题 """ issue: str urgency: Literal["低", "VoAGI", "高"]# 响应创建@tanuki.patchdef classify_and_respond(tweet: Tweet) -> Response: """ 以共情和友善的方式回应客户支持推文的文本。 传达你关心这个问题,如果问题是一个需要支持团队修复的直接问题或者一个问题,团队将对此进行回复。 """@tanuki.aligndef align_respond(): input_tweet_1 = Tweet(name = "Laia Johnson", text = "我非常喜欢这把新铲子,但使用2天后手柄折断了。我可以换一个吗?", id = "123") assert classify_and_respond(input_tweet_1) == Response( requires_ticket=True, response="您好,很抱歉听到这个问题。我们会尽快给您换一个,请您告诉我们您的订单号码吗?" ) input_tweet_2 = Tweet(name = "Keira Townsend", text = "我讨厌iphone的新款设计。它太丑了。我要换到三星", id = "10pa") assert classify_and_respond(input_tweet_2) == Response( requires_ticket=False, response="您好,很抱歉听到这个问题。我们将考虑这个问题,并向产品团队反馈" ) input_tweet_3 = Tweet(name = "Thomas Bell", text = "@Amazonsupport。我有一个有关订购的问题,你们能送到芬兰吗?", id = "test") assert classify_and_respond(input_tweet_3) == Response( requires_ticket=True, response="您好,感谢您的询问。问题将送到我们的支持团队,他们会尽快回复您" ) input_tweet_4 = Tweet(name = "Jillian Murphy", text = "刚刚购买了新的福袋,到目前为止我非常喜欢它!", id = "009") assert classify_and_respond(input_tweet_4) == Response( requires_ticket=False, response="您好,感谢您的询问。我们很高兴听到您喜欢我们的产品" )# 支持工单创建@tanuki.patchdef create_support_ticket(tweet_text: str) -> SupportTicket: """ 使用推文文本创建一个支持工单,以保存到内部数据库 创建一个需要采取的行动的简短摘要和问题的紧急程度 """@tanuki.aligndef align_supportticket(): input_tweet_1 = "我非常喜欢这把新铲子,但使用2天后手柄折断了。我可以换一个吗?" assert create_support_ticket(input_tweet_1) == SupportTicket( issue="手柄折断,需要更换产品", urgency = "高" ) input_tweet_2 = "@Amazonsupport。我有一个有关订购的问题,你们能送到芬兰吗?" assert create_support_ticket(input_tweet_2) == SupportTicket( issue="查明并回答我们当前是否送货到芬兰", urgency="低" ) input_tweet_3 = "刚刚购买了新的福袋,到目前为止,我非常喜欢它!但是,奶油包装稍微有些损坏,我需要被更换" assert create_support_ticket(input_tweet_3) == SupportTicket( issue="需要一个新的奶油,因为包装稍微损坏", urgency="VoAGI" ) # 工作流的最终函数def analyse_and_respond(tweet: Tweet) -> tuple[Optional[SupportTicket], Response]: # 获取响应 response = classify_and_respond(tweet) # 如果响应需要一个工单,则创建一个工单 if response.requires_ticket: support_ticket = create_support_ticket(tweet.text) return response, support_ticket return response, None
而且就是这样!现在,我们可以通过几个例子来测试工作流程。
def main(): """ 这个函数分析传入的推文并返回响应输出,如果需要的话还返回一个票输出 """ # 首先,调用aligns注册对齐语句 align_respond() align_supportticket() input_tweet_1 = Tweet(name = "Jack Bell", text = "Bro @Argos为什么我的订单没有送到?我两周前订购的。糟糕的服务", id = "1") response, ticket = analyse_and_respond(input_tweet_1) print(response) # requires_ticket=True # response="嗨,Jack,很抱歉听到这个问题。我们会立即调查,并尽快回复您。" print(ticket) # issue="顾客的订单在两周后没有送到" # urgency='高' input_tweet_2 = Tweet(name = "Casey Montgomery", text = "@Argos交货时间是3周,但承诺是1周。不是一个粉丝。", id = "12") response, ticket = analyse_and_respond(input_tweet_2) print(response) # requires_ticket=True # response="嗨,Casey,很抱歉听说您的交货有延误。我们将调查这个问题,并尽快回复您。" print(ticket) # issue='交货时间比承诺的时间长' # urgency='VoAGI' input_tweet_3 = Tweet(name = "Jacks Parrow", text = "@Argos新的标志看起来相当丑,不知道他们为什么改变", id = "1123") response, ticket = analyse_and_respond(input_tweet_3) print(response) # requires_ticket=False # response="嗨,Jacks Parrow,很抱歉您不喜欢新的标志。我们将把您的反馈传达给相关团队。感谢您告诉我们。" print(ticket) # None
看起来效果如预期!输出看起来很好,遵循我们对其使用的语气,并在需要时创建适当紧急性的票。
所有这些只花了不到半个小时来创建。
接下来是什么
虽然这只是一个小例子,但它展示了开发人员如何轻松地使用Tanuki创建LLM驱动的函数和应用程序。
如果您对此感兴趣,并且想要了解更多信息(或者更好的是参与其中),请加入我们的Discord。这只是我们创建的一个用例,您可以在这里找到其他用例(例如,从网页抓取创建结构化数据,从自然文本描述待办事项创建待办事项列表应用程序,屏蔽深刻的语言等等)
如果您有任何问题,请在评论区留言或在我们的Discord上与我联系。很快与您交谈!