一首歌颂Slack - 如果Slack是一款游戏,会怎样?

ChatGPT4 generated image

你知道吗?Slack最初是以Tiny Speck的名义作为一款游戏创业项目。他们的抱负很大:创造一款“疯狂、美丽、有价值”的游戏,既在创意上能引起终端用户共鸣,也在财务上对投资者有回报。虽然Tiny Speck团队在很大程度上实现了他们既定目标,但最终产品却演变成了意外之物:Slack,一个由游戏梦想的残骸中诞生的工具。

这个 Slack 变革的故事深受启发。这证明了一个团队,致力于建造出令人瞩目的东西,可以在完全意料之外的方式上转变并取得成功。但是,如果你和我一样,或许你会忍不住思考这个问题,如果 Slack 实际上是一个游戏而不是我们今天所钟爱的平台,会怎样呢?

我确定你正在想,“Oliver,你喝了太多的蛋酒”(我发誓我没有)。不过,我请求你容忍我进行这个实验。以最基本的方式思考Slack,并问自己——当我们谈论Slack时,我们大多数人会想到什么?花点时间思考一下,我给你一两分钟...

完成了?太好了 — 消息传递,当然啦!

我们可以通过消息传递做什么呢?呃……很多事情。事实上——一些最早的游戏是基于文本的。想象一下,在Slack中整合了像《Zork》这样经典的基于文本的冒险游戏。如果你不熟悉《Zork》,它是一款经典的基于文本的冒险游戏,最早在1970年代末和1980年代初发行,并且在互动小说类型的游戏中具有最大的影响力之一。

Zork

但是我们如何将其付诸实践呢?嗯,为什么不使用2023年的最流行词汇呢?生成式人工智能!

在不改变HTML结构的前提下,将以下英文文本翻译为简体中文: 进入ChatGPT。将ChatGPT等人工智能解决方案集成到Slack等平台中并非没有挑战。处理提示历史和线程等问题有时需要大量的工程资源来解决。然而,对我们来说,幸运的是,OpenAI正在积极应对这些挑战。在其开发者大会上,OpenAI推出了Assistants API,一项新的服务,旨在通过开发者在应用程序中创建“代理人式体验”。用OpenAI的话来说,强调了四个关键方面*。

  1. 助手可以通过具体指令调用OpenAI的模型来调整他们的个性和能力。
  2. 助手可以同时访问多个工具。这些工具可以是OpenAI托管的工具,比如代码解释器和知识检索,或者是您自己建立/托管的工具(通过函数调用)。
  3. 助手可以访问持久线程。线程通过存储消息历史记录并在对话对于模型的上下文长度过长时将其截断,从而简化AI应用开发。您只需要创建一个线程,然后在用户回复时简单地将消息附加到它上面。
  4. 助手可以访问多种格式的文件 - 无论是作为它们自己的创建的一部分,还是作为助手和用户之间的交流中的一部分。当使用工具时,助手还可以创建文件(例如图片、电子表格等),并在创建的消息中引用文件。

*https://platform.openai.com/docs/assistants/how-it-works/objects*

这就是Slack、游戏和人工智能相交的有趣之处。虽然我喜欢Zork,但我更是Dungeons and Dragons(D&D)的铁杆粉丝,所以当我设想在Slack中创建一个AI驱动的地牢主宰时,这并不令人惊讶。想象一下,一个人工智能编织D&D冒险,将心爱的游戏与喜爱的平台融合在一起,探索科技与创造力相遇的新领域。

好的,所以我们只需要建造它...

我从OpenAI这一边入手,创建了我的第一个助手。功劳归功于OpenAI,出乎意料地容易。接着,为了让它能够表现得像一个合格的“地下城大师”,我不得不给它一些指示并决定它的配置。

Assistant configuration

开始安排说明,我决定寻求Reddit的帮助。我想很多人可能和我有完全相同的想法,事实证明我是对的!没必要重复发明轮子,而且我不是一个专家级的提示工程师,真是开心的一天。

u/Weary-Brother-4257 发布了这篇文章在 r/ChatGPT 大约一年前,我决定使用他们的提示来写我的指引。

你可能也注意到了,如果你有一个敏锐的眼光,这是第二个版本,例如“v2”的助理。这是因为我最初也上传了官方的D&D地牢主指南——但是在测试一段时间后,我发现它引起了更多的困惑,所以我决定暂时放弃它(也许将来会重新考虑这个想法)。

正如您所看到的,目前我并不需要任何特定的工具,尽管我对它们有一些想法...(也许将来会用得上)。

Variation of openAI original model published https://platform.openai.com/docs/assistants/how-it-works/objects

接下来,我们需要在Slack端开始建设。现在,我对这个项目有一些很大的想法,但是意识到我还有一份工作要做,这意味着我不能每个小时都花在这上面。所以,我决定制作一个更加简化的MVP版本。为此,我决定创建一个非常“简化”的版本,有些人可能称之为“D&D”,但更公平的称呼可能是某种奇怪的Zork和D&D的混合。例如,我考虑过的一个想法是使用斜杠命令创建一个掷骰功能,以及在App首页上显示一些角色信息,但是,我们将从这个版本开始...

好了,别再发牢骚了。谈到构建方面,我决定使用Slack Bolt框架。虽然我们支持下一代平台应用的TypeScript,但实话实说——我对TypeScript的掌握并不是那么熟悉,所以我决定使用Node.js来构建,用老牌的Bolt框架来进行。

为了开始,我决定我们需要通过斜杠命令启动游戏的过程。这将触发一个模态窗口,在此窗口中,我们将给玩家三个选择: 1. 玩家是谁(玩家可以在Slack中选择用户) 2. 他们想要玩哪些职业(以增加游戏的多样性) 3. 他们希望故事的主要背景是哪个流派(同样,为了增加多样性)

Initiating a new game in Slack

除了发送我们的初始提示给助手(我们稍后会谈到这一部分),收集这些输入还有另一个目的。具体来说,它允许我们获取用户ID,以便在模态框提交后邀请他们加入我们正在创建的私人频道。这样可以使其他用户的环境更加整洁。

为了处理通道的任何命名冲突,我们添加:

// Generate a random channel name
const randomChannelName = `channel-name-${Math.floor(Math.random() * 10000)}`;

在ChatGPT方面,一旦我们提交模态框,我们还会传递用户输入的有效载荷以及一些额外的提示给助手。

// Function to make a request to the OpenAI API
async function generateCampaignDetails(selectedUserIDs, selectedClasses, selectedGenre, channelId) {
// Create a new WebClient instance using the bot token
const web = new WebClient(process.env.BOT_TOKEN);
try {
// Send a message from the assistant to the thread
await web.chat.postMessage({
channel: channelId,
text: "Text that acknowledges the users was successful in setting up the channel",
});
// Retrieve the assistant's details
const assistant = await openai.beta.assistants.retrieve("assistant_id");
// Create a new thread
const thread = await openai.beta.threads.create();
// Add a message to the thread
const message = await openai.beta.threads.messages.create(
thread.id,
{
role: "user",
content: `additional prompt to the assistant`,
}
);
// Run the thread
const run = await openai.beta.threads.runs.create(
thread.id,
{
assistant_id: assistant.id,
instructions: "additional prompt to the assistant"
}
);

现在,我们肯定想要通过Slack的事件监听API,特别是app_mention功能,实现与AI Dungeon Master的来回交互。这种方法简化了将游戏集成到Slack中,使其无缝且直观。每当用户希望发送提示给ChatGPT助手时,他们只需在消息前添加'@slackdungeonmaster',然后消息会转发给助手。为了给助手足够的时间来回应,我还实现了一个延迟,在获取回复并发送回Slack频道之前。不可否认,为了确保回复得到正确呈现,一些额外的格式调整是必要的。

const { App } = require('@slack/bolt');
const { WebClient } = require('@slack/web-api');
const OpenAIAPI = require('openai');
require('dotenv').config();

const app = new App({
token: process.env.BOT_TOKEN,
appToken: process.env.APP_TOKEN,
socketMode: true,
});

const openai = new OpenAIAPI({ key: process.env.OPENAI_API_KEY });

// Function to process and format the OpenAI response
function processOpenAIResponse(response) {
return response.map((item) => {
const textValue = item.text.value
.replace(/\[{"type":"text","text":{"value":"/g, '')
.replace(/"}}]/g, '')
.replace(/[\[\]!]/g, '') // Remove !, [, and ] characters
.replace(/\(/g, ' '); // Replace ( with a space

return {
type: 'section',
text: {
type: 'mrkdwn',
text: textValue.replace(/\n\n/g, '\n \n'), // Replace for double new line
},
};
});
}

// Asynchronously process 'app_mention' events
async function processAppMention(event, client) {
const messageText = event.text;
const channelId = event.channel;

console.log(`Message text: ${messageText}, Channel ID: ${channelId}`); // Log message details

try {
await client.reactions.add({
channel: channelId,
timestamp: event.ts,
name: 'dungeonmaster',
});

const thinkingMessage = await client.chat.postMessage({
channel: channelId,
text: 'Slack Dungeon Master is thinking...',
});

const threadId = 'YOUR THREAD ID';
const assistantId = 'YOUR ASSISTANT ID';

try {
const assistant = await openai.beta.assistants.retrieve(assistantId);

await openai.beta.threads.messages.create(threadId, {
role: 'user',
content: messageText,
});

const run = await openai.beta.threads.runs.create(threadId, {
assistant_id: assistant.id,
instructions: 'Please generate images with some prompt I want it to be structured so that we put ![image](url of image)',
});

await new Promise((resolve) => setTimeout(resolve, 30000));

const messagesResponse = await openai.beta.threads.messages.list(threadId);
const messages = messagesResponse.body.data;
const assistantMessage = messages.find((message) => message.role === 'assistant');

if (assistantMessage) {
const assistantResponseContent = assistantMessage.content;
await client.chat.postMessage({
channel: channelId,
text: 'Here is the response from ChatGPT:',
blocks: processOpenAIResponse(assistantResponseContent),
});

await client.reactions.add({
channel: channelId,
timestamp: thinkingMessage.ts,
name: 'dndemoji',
});

await client.chat.delete({
channel: channelId,
ts: thinkingMessage.ts,
});
} else {
console.error('Assistant response not found in the messages');
}
} catch (error) {
console.error('Error sending to ChatGPT:', error);
}
} catch (error) {
console.error('Error in handling app_mention:', error);
}
}

// Enhanced Event handler for 'app_mention'
app.event('app_mention', async ({ event, client }) => {
console.log('Received an app_mention event', event); // Log the received event

// Start the processing in a non-blocking way
processAppMention(event, client).catch(error => {
console.error('Error in async app_mention processing:', error);
});
});

// Start the Bolt app
const startApp = async () => {
await app.start();
console.log('⚡️ Bolt app started');
};

// Export the function to start the app
module.exports = {
startApp,
};

这大致就是我启动和运行它的方式。至于结果?你自己看吧!

这就像是从瓶子里释放出一个魔灯——只不过这个魔灯更喜欢操控故事情节,而不是实现愿望。谁能想到,我们曾经的简朴的Slack,在游戏世界里只是一个像素化的梦想,会变成一个数字冒险的舞台?所以,你就有了:一个游戏存在于工作空间中,或者说是一个工作空间存在于游戏中?界限变得模糊不清,而老实说,这正是其中的一半乐趣。至于我,我要去看看我的由Slack驱动的地牢主宰是否会接受"提高生产力"作为一项有效的任务奖励。游戏开始吧!

**本文的部分内容已使用ChatGPT进行了增强处理。

2024-01-04 04:11:52 AI中文站翻译自原文