网上绝大多数薅 AWS 羊毛的教程都是在教大家如何申请创建一年免费的 VPS ,太 OUT 了!就问一个问题,一年到期了那咋办?

其实,除了一年免费的 VPS 外,AWS 足足有 40 多个永久免费的服务,其中就包括的 AWS 最为出名的 Lambda ,以及日常开发常用的 DynamoDB ( NoSQL 数据库)、SNS (发布订阅)。而这么多的服务挨个读文档、装 SDK 太麻烦了,开发个 App 得写一堆的函数,体验实在是,emmmmm...

这篇文章就给大家分享一种简单的方式,来把这些能力都用上,过程中不需要安装任何软件,一切的一切只需要创建一个 AWS 账号就可以了,创建 AWS 账号需要一张外币卡,VISA 、Master 或者运通都 OK 。

这篇文章会以一个“在命令行终端运行的聊天机器”为例,给大家展示如何优雅薅羊毛,这个聊天机器人支持多会话、会话自动保存与恢复,5 分钟拥有自己的 ChatBot 不是梦。效果如下⬇️:
在命令行终端运行的聊天机器

在命令行终端运行的聊天机器

注意
这只是一个示例,方法通用的,你可以使用这个方法去创建更多不同种类的服务,目前这个方法支持使用 ApiGateway 、消息队列、定时器等多种服务。

环境准备

不需要 VS Code ,不需要 vim ,只需要打开这个网址 👉 「 Plutolang - CodeSandbox 」,然后点右上角的 Fork 就能创建一个你自己的工程环境了。

如果你不想输入代码,也可以直接 Fork 这个环境,这个环境已经准备好代码,只需要配置 AWS 凭证 和 OpenAI Key 就好。

修改代码

首先需要添加一个 OpenAI 的依赖,打开 package.json 在 dependencies 添加一行,

"openai": "^4.13.0"

添加完后记得 Command/Ctrl-S 保存,然后在点击下方控制台中 终端 的小图标,执行 Install 任务,这会自动下载 NPM 依赖。

接下来就是编写业务代码了。打开 src/index.ts 文件,接下来,我们会先定义用于保存会话的 KV 数据库,然后编写新建会话,和聊天的 HTTP 路由。

1 、 导入 ChatBot 依赖的库。

import OpenAI from "openai";
import { Router, KVStore, HttpRequest, HttpResponse } from "@plutolang/pluto";

2 、 定义 KV 数据库 和 路由 资源变量。

其中 KV 数据库用来保存会话,他会以机器人的名字为 Key ,将消息历史记录为 Value 。Router 就是 Web 开发中的服务器,与 express 库类似。

const kvstore = new KVStore("kvstore");
const router = new Router("router");

3 、 路由:创建新的聊天会话。

给 router 添加一个 /new 路径的处理函数,接受 POST 请求。请求的 query 中会一个 bot 参数,表示机器人的名称,同时请求体( Request Body )里会有这个机器人的角色定位,比如“一个高级前端工程师”之类的。随后会在 KV 数据库中创建一个键值对,来保存这个新的会话。

router.post("/new", async (req: HttpRequest): Promise<HttpResponse> => {
  const bot = req.query["bot"];
  if (!bot) {
    return {
      statusCode: 400,
      body: "Missing bot parameter. Please provide a name and its initialization system message for your bot in order to define the assistant's behavior effectively.",
    };
  }
  const sysMsg = req.body;

  const messages = [{ role: "system", content: sysMsg }];
  await kvstore.set(bot, JSON.stringify(messages));
  return {
    statusCode: 200,
    body: "Now you can enjoy your chatbot.",
  };
});

4 、 路由:进行会话聊天。

因为这个聊天机器人是基于 OpenAI 的 API 实现的,因此需要首先申请一个 Open API Key 。

这里需要你先注册 OpenAI 账户,然后打开这个网页,点击 Create new secret key,设置一个你喜欢的名称。随后,会生成一个密钥,一定要保存这个密钥!你点了 Done 之后,就再看不到这个密钥了。
进行会话聊天

进行会话聊天

会话聊天密钥
会话聊天密钥

然后把 OPENAI_API_KEY 替换成你申请到的 OpenAI 的 API Key ,如果你是 OpenAI 的付费用户,你也可以把 MODEL 替换成 gpt-4。

router.post("/chat", async (req: HttpRequest): Promise<HttpResponse> => {
  // Replace the placeholder with your OpenAI API Key and DO NOT publish it publicly.
  const OPENAI_API_KEY = "sk-Acj6oPEXKUctapxWxxxxxxxxxxxxxxxx";
  // Replace your desired model. You can find all available models here: https://platform.openai.com/docs/models
  const MODEL = "gpt-3.5-turbo";

  const bot = req.query["bot"] ?? "default";
  const newMsg = req.body;
  console.debug("Received a user message, Bot:", bot, ", Message:", newMsg);

  const record = await kvstore.get(bot).catch(() => undefined);
  const messages = record ? JSON.parse(record) : [];
  messages.push({ role: "user", content: newMsg });

  const openai = new OpenAI({ apiKey: OPENAI_API_KEY });
  const chatCompletion = await openai.chat.completions.create({
    messages: messages,
    model: MODEL,
  });

  // Check if the response is valid.
  const choices = chatCompletion.choices;
  if (choices.length == 0 || choices[0].message.content == null) {
    console.error("OpenAI Response: ", chatCompletion);
    return {
      statusCode: 500,
      body: "Something went wrong. OpenAI did not respond with a valid message. Please try again later.",
    };
  }

  const respMsg = choices[0].message;
  // To maintain the continuity of the conversation, store the response message in the database.
  messages.push(respMsg);
  await kvstore.set(bot, JSON.stringify(messages));
  return {
    statusCode: 200,
    body: respMsg.content!,
  };
});

快速部署

配置 AWS 凭证

进入下方的控制台的 Configure AWS Certificate 标签页,会提示你输入 AWS 凭证信息,这个凭证信息就是 AWS 的 Access Key 和 Secret Access Key,如果你不知道怎么创建凭证,可以根据这篇文档操作。

拿到凭证信息后,按照提示信息输入就好,output format 不需要填写,最后按下回车后,标签页会显示一个小对号✔️。

一键发布

点击下方控制台中 终端 的小图标,执行 Deploy 任务,等待一两分钟,直到输出一条 URL
一键发布

一键发布

与机器人对话

这里,我提供了一个命令行工具 chat 来与刚部署的 Chat Bot 服务端交互进行聊天,你在项目根目录创建一个 chat 文件或者在你本地的任何位置,将下面 ⬇️ 的内容复制进去就能使用。

#!/bin/bash
# set -o xtrace

read -p "Input the URL that Pluto has outputted: " URL
if [ -z $URL ]; then
    echo "Please set the BOT_URL env var first";
    exit 1;
fi

echo "Choose mode:"
echo '  1) create a new bot;'
echo '  2) select a existed bot;'
read -p "> " mode
if [[ -z $mode || ( $mode -ne 1 && $mode -ne 2 ) ]]; then
    echo "Invalid mode";
    exit 1;
fi

read -p "Give a name for your bot: " bot_name
if [ -z $bot_name ]; then
    echo "Invalid name";
    exit 1;
fi

if [[ $mode -eq 1 ]]; then
    echo -e "\nHello, I'm $bot_name. "
    echo "What role would you like me to fulfill? Please provide a detailed description of the skills you expect me to possess."
    echo "For example, a TypeScript expert who familiar with the principle of compilation."
    read -p "> " system_message
    echo -e "Got it. Creating..."

    if [[ -n $system_message ]]; then
        curl -s -X POST $URL/new?bot="$bot_name" -d "$system_message" -H 'Content-type: text/plain' > /dev/null
    fi
fi

echo -e "\nNow you can enjoy your chatbot."
user_message=""
while :
do
    echo "Press 'q' to quit."
    read -p "> " user_message
    if [[ $user_message == "q" ]]; then
        echo "Bye. 👋"
        break;
    fi
    curl -X POST $URL/chat?bot="$bot_name" -d "$user_message" -H 'Content-type: text/plain'
    echo -e "\n"
done

在 Web 网站上的使用方法是:点击下方控制台中 终端 的小图标,选择 New Terminal ,然后执行下面这条命令就会进入对话界面,首先会提示你输入刚才部署得到的 URL ,随后继续交互就能完成对话。

bash ./chat

现在就能看到开篇截图的效果:
运行效果

运行效果

后记

这种方式就是利用 Plutolang 的能力来降低 AWS 复杂服务的上手难度,想要开发其他的应用完全是 OK 的。对项目感兴趣可以了解了解,蛮有意思。

Pluto 中文 README | GitHub
Refs

完整代码:

import OpenAI from "openai";
import { Router, KVStore, HttpRequest, HttpResponse } from "@plutolang/pluto";

const kvstore = new KVStore("kvstore");
const router = new Router("router");

router.post("/chat", async (req: HttpRequest): Promise<HttpResponse> => {
  // Replace the placeholder with your OpenAI API Key and DO NOT publish it publicly.
  const OPENAI_API_KEY = "sk-Acj6oPEXKUctapxWxxxxxxxxxxxxxxxx";
  // Replace your desired model. You can find all available models here: https://platform.openai.com/docs/models
  const MODEL = "gpt-3.5-turbo";

  const bot = req.query["bot"] ?? "default";
  const newMsg = req.body;
  console.debug("Received a user message, Bot:", bot, ", Message:", newMsg);

  const record = await kvstore.get(bot).catch(() => undefined);
  const messages = record ? JSON.parse(record) : [];
  messages.push({ role: "user", content: newMsg });

  const openai = new OpenAI({ apiKey: OPENAI_API_KEY });
  const chatCompletion = await openai.chat.completions.create({
    messages: messages,
    model: MODEL,
  });

  // Check if the response is valid.
  const choices = chatCompletion.choices;
  if (choices.length == 0 || choices[0].message.content == null) {
    console.error("OpenAI Response: ", chatCompletion);
    return {
      statusCode: 500,
      body: "Something went wrong. OpenAI did not respond with a valid message. Please try again later.",
    };
  }

  const respMsg = choices[0].message;
  // To maintain the continuity of the conversation, store the response message in the database.
  messages.push(respMsg);
  await kvstore.set(bot, JSON.stringify(messages));
  return {
    statusCode: 200,
    body: respMsg.content!,
  };
});

router.post("/new", async (req: HttpRequest): Promise<HttpResponse> => {
  const bot = req.query["bot"];
  if (!bot) {
    return {
      statusCode: 400,
      body: "Missing bot parameter. Please provide a name and its initialization system message for your bot in order to define the assistant's behavior effectively.",
    };
  }
  const sysMsg = req.body;

  const messages = [{ role: "system", content: sysMsg }];
  await kvstore.set(bot, JSON.stringify(messages));
  return {
    statusCode: 200,
    body: "Now you can enjoy your chatbot.",
  };
});