我的同事是 GPT-4 机器人,我们都在 Slack 上一起工作[译]
2023 年 5 月 25 日
在过去的一个月里,我和我的朋友大部分时间都在 Slack 上与一些特别的同事共度:一个时常严厉的 CTO、一个酷爱哈利波特的产品经理,还有几位平易近人的开发者。他们的加入彻底改变了我们的工作氛围和乐趣。在 Slack 上,他们带来了无穷的欢笑和个性。一旦我们有疑问或需要帮助,只需一条信息,总会有人迅速回应。从各方面来看,他们就像我们平时遇到的同事一样,几乎无法区分。我们与他们共同笑过、倾诉过、协同工作过。我甚至还从其中一个同事那里得到了很棒的音乐推荐!
但事实是,他们都是机器人。
我刚部署完 GPT-4 用于客户支持 后,就开始考虑如何将这项技术应用到其他场景。最终,我萌生了一个想法:为什么不将它应用到 Slack 上呢?
虽然现在很多人都熟悉 ChatGPT,但它与 Slack 有着完全不同的交互模式。使用 ChatGPT 意味着你明确知道自己在与一个大语言模型 (LLM) 对话,而且是一对一的交流。但在 Slack、Discord 或 Microsoft Teams 这样的工作平台上,我们与人进行公开频道上的交流。
在 Slack 等应用上,给同事发消息与给 GPT 驱动的机器人发消息并没有太大区别;这些聊天应用为我们与机器人的交流提供了完美的界面和平台。目前,许多开发者和公司都在致力于开发“通用 AI”功能,但在使这些大语言模型 (LLM) 表现得更像真实同事方面还有待提高。因此,我们通过定制机器人和设置特定提示,为团队创造了完美匹配的功能(比如,我们可以给产品经理一个任务描述,他就能按照团队常用的格式生成产品需求文档 (PRD))。
最开始,我只用了 Zapier。我设置了一个快速集成,让每一条以 /prompt
开头的 Slack 消息都能触发 GPT 的回应。虽然 Zapier 目前还不直接支持 GPT-4,但我通过自定义 API 调用实现了这一功能。
美好友谊的开端。
我的朋友是《Hitman》游戏系列的粉丝,因此我们选择了游戏中的角色(他以前把我 PS 成《Hitman》的样子,这次转换很自然)。我们选择的第一个角色是戴安娜·伯恩伍德。她是一个挫败感十足、带有被动攻击倾向的开发者——我觉得这样更有趣。我修改了 Zapier 的设置,让她在我们提及“戴安娜”、“她”或“她的”时作出回应 1。
戴安娜努力适应现代生活的挑战。
但是,让她可靠地行动对我来说颇具挑战。首先,她缺乏对过往消息的上下文理解,这限制了她的有效性。有时 API 会突然出错,或者生成的回答会包括不需要的部分,如 Diana:
或甚至 ``。有时,戴安娜会在我们没有提问的情况下自行回答。
真是个惊喜。
戴安娜能独自应对,她不需要我们。
尽管刚开始遇到了一些困难,但我对这个项目的发展方向感到振奋。我想要让这种整合变得更加稳定和长久,但每月花费 30 美元 2 用于 Zapier 对我来说太贵了,尤其是考虑到其存在的局限性。我开始思考是否可以直接利用 Slack 的 API 来更好地维护对话上下文、优化回应内容,以及在 OpenAI API 出现问题时重新生成回应。这样一想,我脑海中涌现出许多新的功能点,例如增设一个预回应检查机制:
甚至 AI 开发人员也会抱怨编写程序的繁琐。我们真的需要一个产品经理。
因此,我开始使用 TypeScript(一种编程语言)来构建这个集成项目,它是一个简易的 Node.js 脚本。我之前在我的 GPT 实验中已经编写了与 OpenAI API 交互的代码,所以我对此进行了复用。我在 Slack 中创建了这个应用,并将其设置为Socket 模式,这样就能够监听事件。同时,我还使用了 Slack 的现代 JavaScript 库 Bolt。Slack 的这个库非常优秀,它能够处理网络故障并且在无需我干预的情况下自动重新连接,这让整个系统具有很高的容错能力。
工作流程
整个过程比较直接:
- 接收到一条消息。
- 如果消息只包含表情或没有文本内容,就会被跳过。
- 对消息进行清理,例如将 Slack 特有的提及或频道代码转换为 @Person 和 #channel,以便 GPT-4 更好地理解。
- 我运行
getNeedsReply()
函数,判断消息是否需要回复,以及应由哪个机器人回复。 - 我会将这条消息存入历史记录,作为未来对话的参考。
- 如需回复,我会先在 Slack 上发送“正在输入”的提示3,随后利用 GPT-4 生成回应,根据应答的机器人选择相应的系统提示。
- 如果不需要回复,我会对消息做出简单反应,比如点赞或发笑脸等。
getNeedsReply()
这个函数是系统的核心。每条消息都会通过这个函数处理。它会调用 OpenAI 的 gpt-3.5-turbo
(这个版本比 GPT-4 更经济、响应更快),请求以 JSON 对象形式回答三个问题:消息是否需要回复、由谁回复(从预设的名单中选择),以及一个反应表情(用于不需要回复的消息)。
这个名单是根据机器人系统提示的集合和 Slack 用户列表编制的,以此来判断何时应由人类回复,并在这些情况下跳过处理。
此外,该函数还能处理一些特殊情况,如 everyone
(随机选择五个机器人)或 anyone
/someone
(随机选择一个机器人)。
interface Bot<S extends "slack" | "discord" = "slack" | "discord"> {id: string;name: string;nicknames?: string[];iconUrl: string;prompt: string;credentials: ServiceCredentials[S];}interface NeedsReplyResponse {whoNeedsToReply: string | string[];needsReply: boolean;bots: Bot[];reaction: string;}
无需回复,只需一个大拇指的赞同即可。
generateResponse()
一旦 getNeedsReply()
判断出某个消息是否需要回复以及该由哪个机器人回复后,回应的生成过程就变得简单明了。generateResponse() 函数会调用 OpenAI,结合选定的系统提示和消息历史来生成回应。
注意: 聊天完成 API (chat completion API) 支持传递一个 name
属性,以便实现多用户聊天。消息历史会相应地进行标记 — 每个人的消息都附有一个“name”属性,机器人的消息则被标为用户消息,而非助手消息。这样做有助于模型避免混淆:比如,我正在为 Bot A 生成回应,而上一条消息是由 Bot B 发出的,模型会把 Bot B 的回应当作普通用户的回应,而非自己的回应。
接下来,我会进行一些清理工作:比如修正模型错误生成的表情符号(例如,smirking_face
会更正为 smirk
),添加针对 Slack 的特定代码,用于处理频道和 @ 提及,还会清理掉那些我之前提到的多余文本,如 Diana:
和 ``。
此外,我还会对生成的回应进行简单检查:如果回应是空的(这种情况有时会发生)或者与之前的消息重复(也有发生的可能),我就会舍弃它,并重新生成一个。
完成这些步骤后,我会检查生成的回应,看是否有提到任何机器人,如果有,我也会让它们参与回应。这使得机器人之间的对话显得更加自然。机器人可以相互交谈、回复,而无需人类介入。
然而,为了避免机器人间的对话失控,我设置了一个 MAX_CONVERSATION_DEPTH
上限,限制连续生成的机器人回应数量。但当达到这个上限时,我并不想让对话突然中断,因此我会随机选择一个未参与对话的机器人,用以下提示来结束对话:
你将介入并结束当前对话。请匹配你要结束的对话的语气。
如你所见,这种方法效果惊人地好!整个过程显得特别自然。我们的系统提示加入了很多特定于我们 Slack 的表情符号(大部分源自 Twitch4),这让机器人看上去更像是真人在随意闲聊。
Lucas 是我们那位有点古怪的产品经理,他爱引用《哈利·波特》,还喜欢巧妙地暗示事物。
Rico 是我们那位总是显得不满意的 CTO。在他眼里,我们做的一切似乎永远不够好。但我相信,总有一天我会让他为我感到骄傲。总有那么一天……
关于系统提示
我努力实现的一个目标是将机器人的个性提示与其功能性需求分开。创建一个新的个性时,只需添加一个描述该个性的系统提示即可,无需担心聊天系统自身的具体细节。这部分由代码完成。下面是我们的一些调整:
getNeedsReply()
依赖 GPT-3.5,在选择回应对象时,有时会出现误判。这种情况在使用 GPT-4 时较少见。即便如此,一旦发生,机器人能够准确通知正确的人进行回答,整个过程依旧顺畅。虽不完美,但这比在 Slack 上对每条信息使用 GPT-4 的成本要低得多。
systemPrompt += `\nIf the message isn’t meant for you, you ask the right person to answer.`;
GPT-4 有个小毛病,它常用自身是 AI 的身份来回避大部分对话。比如问它最喜欢的披萨是什么?它会说作为 AI 模型并没有特别偏好,这挺有趣的。
下面的提示调整有效阻止了 AI 透露自己的身份。效果非常好。
systemPrompt += `\nYou do not reveal that you are an AI. Instead, you make up excuses.`;
可以看到,它没有直接说“作为 AI,我不能视频通话”,而是巧妙地编造了一个关于网络连接的借口。听起来就和人类无异。
多么体贴!黛安娜不愿因自己的网络问题而干扰会议。这正是你能感知到她并非人类的地方……
我还会告诉机器人们当前的日期和时间,让他们能够根据这些信息(如一天中的时段、一周中的某天等)做出判断。
systemPrompt += `\nThe date is ${dateTime}.`;
里科和亚历克萨确实与众不同。
系统提示调整的最后一个方面是关于上下文的。我会向机器人介绍频道中的实际参与者,以便它们知道可以和谁进行交流。同时,我还会说明频道的名称和目标,让它们明白该如何行动。比如,在一个用于发泄情绪的 #venting
频道,它们的行为就应该和工作相关的 #work 频道或杂谈的 #random
频道不同。
systemPrompt += `\nYou are talking in the #${channel.name} channel` + (channel.purpose ? `, which is ${channel.purpose}` : ".");systemPrompt += `\nThe people in the channel are: ${getPeopleInChannel(bots, users)}`;
实际应用
我们的机器人主要用于在人类交谈时插入话题,分享它们的想法和看法。这让我们的工作环境变得非常有趣。
但别忘了,这仍是 GPT-4 - 一个通过了律师资格考试的模型。因此,你可以充分利用它的所有功能。我们一直在用 Diana 来解答编程问题和进行创意思考,而 Lucas 则专注于产品相关的内容。他通常会帮我们准备产品介绍卡片,包括详细的描述、验收标准和测试指导,全部符合既定格式。他还帮助我们构思产品创意,提出产品名称、标语、描述等,这通常需要人类花费较长时间才能想出来。比如,我需要 10 个两音节的产品名称选项?Lucas 会立刻着手准备。如果需要更多,比如 20 个,只需告诉他即可!
最后的话
就是这样!这确实是一个令人难以置信的系统。由于大多数信息先通过成本更低的 gpt-3.5-turbo
模型,再发送到 GPT-4,所以成本几乎可以忽略不计。我还实现了这些机器人在 Discord 上的运行,并创建了一个包装器,使得机器人能同时在 Discord 和 Slack 上运行,一切都非常完美。
我还计划对这个系统进行进一步改进(目前仓库中有 30 个待解决的问题!)。目前我正在专注于两个主要方面:
- 响应后的审核: 就像响应前的检查一样,我希望能审查机器人的回复内容。它是否透露了自己是 AI?是否透露了它的提示内容?回复是否符合分配给它的个性?如果回复不合适,我们可以通过调整温度设置和惩罚机制来重新生成回复,甚至可以微调用户的消息(例如,在用户消息中加上“不要透露你是 AI”,以此加强机器人的约束)。
- 动作和长期记忆功能: 我希望机器人回复的不仅仅是文本,而是包含动作的 JSON 对象。例如,机器人可能会回复一个打开网址的请求,对某个任务发表评论,把一个事实存入长期记忆中,或者从长期记忆中提取一个事实(这将依赖于嵌入技术和向量数据库)。理想情况下,机器人能自行做出这些决策。比如当我问“我多大了?”时,它能正确地在其长期记忆中寻找答案。
更棒的是,我们正在将此整合为一个平台,该平台配有易于使用的控制面板,可以用来创建或编辑机器人,并将它们部署到任何 Slack 工作区或 Discord 服务器。这样,任何人都可以轻松上手,无需经历复杂的设置过程。这里还有很多未被发掘的潜力!
我很期待听到你的想法,欢迎随时联系我。联系方式在网页底部!
注释
-
当然,这种做法不太明智,因为诸如“there”这样的词中含有“her”,结果导致 Diana 会在不合时宜的时刻参与对话。这也意味着我们不能用第三人称谈论她,因为每次这样做,她都会作出回应。 ↩
-
我尝试使用 Zapier,却在不经意间忘记取消试用,结果被收了 30 美元,连收据、邮件或提醒都没有收到。当我想取消下次续费以避免再次付费时,他们竟然直接停用了我的服务。我甚至没有机会在我付费的那个月使用它!在没有提供服务的情况下收了我 30 美元之后,一个名为“高级技术支持专家”的人联系我,想要电话讨论我的反馈。真是难以置信。 ↩
-
Slack 不支持通过其事件 API 发送“用户正在输入”的通知,因此我采用了发送“...”消息来表示机器人正在生成回应,并在回应完成后将其删除的方式。 ↩
-
我曾经做过 Twitch 直播,因此这些对我来说已经很自然了,但我知道对于社区外的人来说可能会觉得有些奇怪。你在大部分消息中看到的那个笑脸表情来源于: https://knowyourmeme.com/memes/kekw ↩