手把手教你在飞书中搭建机器人

前言

大家好,我是潇潇雨声。飞书是一款在国内广受欢迎的企业内部管理和协同工具,同时也可以作为一个强大的个人知识管理工具。在本文中,我将帮助你迅速创建一个飞书对话机器人,并嵌入 chatGPT 的功能。这个机器人可以直接回答你的问题,也可以在群聊中被@,从而以 chatGPT 的方式提供回应。通过这样的操作,你的飞书机器人将迅速蜕变成一个支持 chatGPT 的智能助手。

一,进入飞书开发者后台进行配置

1. 首先登录到飞书的开发者后台,创建一个名为 ChatGPT 的应用

开发者后台:https://open.feishu.cn/app alt

alt

2. 保存 App ID 和 App Secret 的信息

alt

3. 为创建好的应用添加机器人能力

alt

4. 为机器人赋予权限

在权限管理页面进行权限配置,需要开通如下 6 个权限:

  • im:message
  • im:message.group_at_msg
  • im:message.group_at_msg:readonly
  • im:message.p2p_msg
  • im:message.p2p_msg:readonly
  • im:message:send_as_bot
alt

二,创建 AirCode 项目

1. 登录 AirCode,创建一个新的 Nodejs v18 的项目,项目名可以根据你的需要填写,例如填写 ChatGPT。

AirCode 网址:https://aircode.io/

alt
alt

注意:不要选上 TypeScipt

将以下的代码替换掉 hello.js 中的代码

// @version 0.0.9 调整了 axios 的报错的输出,以便于调试。const aircode = require("aircode");const lark = require("@larksuiteoapi/node-sdk");var axios = require("axios");const EventDB = aircode.db.table("event");const MsgTable = aircode.db.table("msg"); // 用于保存历史会话的表// 如果你不想配置环境变量,或环境变量不生效,则可以把结果填写在每一行最后的 "" 内部const FEISHU_APP_ID = process.env.APPID || ""; // 飞书的应用 IDconst FEISHU_APP_SECRET = process.env.SECRET || ""; // 飞书的应用的 Secretconst FEISHU_BOTNAME = process.env.BOTNAME || ""; // 飞书机器人的名字const OPENAI_KEY = process.env.KEY || ""; // OpenAI 的 Keyconst OPENAI_MODEL = process.env.MODEL || "gpt-3.5-turbo"; // 使用的模型const OPENAI_MAX_TOKEN = process.env.MAX_TOKEN || 1024; // 最大 token 的值const client = new lark.Client({  appId: FEISHU_APP_ID,  appSecret: FEISHU_APP_SECRET,  disableTokenCache: false,});// 日志辅助函数,请贡献者使用此函数打印关键日志function logger(param) {  console.debug(`[CF]`, param);}// 回复消息async function reply(messageId, content) {  try{    return await client.im.message.reply({    path: {      message_id: messageId,    },    data: {      content: JSON.stringify({        text: content,      }),      msg_type: "text",    },  });  } catch(e){    logger("send message to feishu error",e,messageId,content);  }}// 根据sessionId构造用户会话async function buildConversation(sessionId, question) {  let prompt = [];  // 从 MsgTable 表中取出历史记录构造 question  const historyMsgs = await MsgTable.where({ sessionId }).find();  for (const conversation of historyMsgs) {      // {"role": "system", "content": "You are a helpful assistant."},      prompt.push({"role": "user", "content": conversation.question})      prompt.push({"role": "assistant", "content": conversation.answer})  }  // 拼接最新 question  prompt.push({"role": "user", "content": question})  return prompt;}// 保存用户会话async function saveConversation(sessionId, question, answer) {  const msgSize =  question.length + answer.length  const result = await MsgTable.save({    sessionId,    question,    answer,    msgSize,  });  if (result) {    // 有历史会话是否需要抛弃    await discardConversation(sessionId);  }}// 如果历史会话记录大于OPENAI_MAX_TOKEN,则从第一条开始抛弃超过限制的对话async function discardConversation(sessionId) {  let totalSize = 0;  const countList = [];  const historyMsgs = await MsgTable.where({ sessionId }).sort({ createdAt: -1 }).find();  const historyMsgLen = historyMsgs.length;  for (let i = 0; i < historyMsgLen; i++) {    const msgId = historyMsgs[i]._id;    totalSize += historyMsgs[i].msgSize;    countList.push({      msgId,      totalSize,    });  }  for (const c of countList) {    if (c.totalSize > OPENAI_MAX_TOKEN) {      await MsgTable.where({_id: c.msgId}).delete();    }  }}// 清除历史会话async function clearConversation(sessionId) {  return await MsgTable.where({ sessionId }).delete();}// 指令处理async function cmdProcess(cmdParams) {  switch (cmdParams && cmdParams.action) {    case "/help":      await cmdHelp(cmdParams.messageId);      break;    case "/clear":      await cmdClear(cmdParams.sessionId, cmdParams.messageId);      break;    default:      await cmdHelp(cmdParams.messageId);      break;  }  return { code: 0 }}// 帮助指令async function cmdHelp(messageId) {  helpText = `ChatGPT 指令使用指南Usage:    /clear    清除上下文    /help     获取更多帮助  `  await reply(messageId, helpText);}// 清除记忆指令async function cmdClear(sessionId, messageId) {  await clearConversation(sessionId)  await reply(messageId, "✅记忆已清除");}// 通过 OpenAI API 获取回复async function getOpenAIReply(prompt) {  var data = JSON.stringify({    model: OPENAI_MODEL,    messages: prompt  });  var config = {    method: "post",    maxBodyLength: Infinity,    url: "https://api.openai.com/v1/chat/completions",    headers: {      Authorization: `Bearer ${OPENAI_KEY}`,      "Content-Type": "application/json",    },    data: data,    timeout: 50000  };  try{      const response = await axios(config);      if (response.status === 429) {        return '问题太多了,我有点眩晕,请稍后再试';      }      // 去除多余的换行      return response.data.choices[0].message.content.replace("\n\n", "");  }catch(e){     logger(e.response.data)     return "问题太难了 出错了. (uДu〃).";  }}// 自检函数async function doctor() {  if (FEISHU_APP_ID === "") {    return {      code: 1,      message: {        zh_CN: "你没有配置飞书应用的 AppID,请检查 & 部署后重试",        en_US:          "Here is no FeiSHu APP id, please check & re-Deploy & call again",      },    };  }  if (!FEISHU_APP_ID.startsWith("cli_")) {    return {      code: 1,      message: {        zh_CN:          "你配置的飞书应用的 AppID 是错误的,请检查后重试。飞书应用的 APPID 以 cli_ 开头。",        en_US:          "Your FeiShu App ID is Wrong, Please Check and call again. FeiShu APPID must Start with cli",      },    };  }  if (FEISHU_APP_SECRET === "") {    return {      code: 1,      message: {        zh_CN: "你没有配置飞书应用的 Secret,请检查 & 部署后重试",        en_US:          "Here is no FeiSHu APP Secret, please check & re-Deploy & call again",      },    };  }  if (FEISHU_BOTNAME === "") {    return {      code: 1,      message: {        zh_CN: "你没有配置飞书应用的名称,请检查 & 部署后重试",        en_US:          "Here is no FeiSHu APP Name, please check & re-Deploy & call again",      },    };  }  if (OPENAI_KEY === "") {    return {      code: 1,      message: {        zh_CN: "你没有配置 OpenAI 的 Key,请检查 & 部署后重试",        en_US: "Here is no OpenAI Key, please check & re-Deploy & call again",      },    };  }  if (!OPENAI_KEY.startsWith("sk-")) {    return {      code: 1,      message: {        zh_CN:          "你配置的 OpenAI Key 是错误的,请检查后重试。OpenAI 的 KEY 以 sk- 开头。",        en_US:          "Your OpenAI Key is Wrong, Please Check and call again. FeiShu APPID must Start with cli",      },    };  }  return {    code: 0,    message: {      zh_CN:      "✅ 配置成功,接下来你可以在飞书应用当中使用机器人来完成你的工作。",      en_US:      "✅ Configuration is correct, you can use this bot in your FeiShu App",    },    meta: {      FEISHU_APP_ID,      OPENAI_MODEL,      OPENAI_MAX_TOKEN,      FEISHU_BOTNAME,    },  };}async function handleReply(userInput, sessionId, messageId, eventId) {  const question = userInput.text.replace("@_user_1", "");  logger("question: " + question);  const action = question.trim();  if (action.startsWith("/")) {    return await cmdProcess({action, sessionId, messageId});  }  const prompt = await buildConversation(sessionId, question);  const openaiResponse = await getOpenAIReply(prompt);  await saveConversation(sessionId, question, openaiResponse)  await reply(messageId, openaiResponse);  // update content to the event record  const evt_record = await EventDB.where({ event_id: eventId }).findOne();  evt_record.content = userInput.text;  await EventDB.save(evt_record);  return { code: 0 };}module.exports = async function (params, context) {  // 如果存在 encrypt 则说明配置了 encrypt key  if (params.encrypt) {    logger("user enable encrypt key");    return {      code: 1,      message: {        zh_CN: "你配置了 Encrypt Key,请关闭该功能。",        en_US: "You have open Encrypt Key Feature, please close it.",      },    };  }  // 处理飞书开放平台的服务端校验  if (params.type === "url_verification") {    logger("deal url_verification");    return {      challenge: params.challenge,    };  }  // 自检查逻辑  if (!params.hasOwnProperty("header") || context.trigger === "DEBUG") {    logger("enter doctor");    return await doctor();  }  // 处理飞书开放平台的事件回调  if ((params.header.event_type === "im.message.receive_v1")) {    let eventId = params.header.event_id;    let messageId = params.event.message.message_id;    let chatId = params.event.message.chat_id;    let senderId = params.event.sender.sender_id.user_id;    let sessionId = chatId + senderId;    // 对于同一个事件,只处理一次    const count = await EventDB.where({ event_id: eventId }).count();    if (count != 0) {      logger("skip repeat event");      return { code: 1 };    }    await EventDB.save({ event_id: eventId });    // 私聊直接回复    if (params.event.message.chat_type === "p2p") {      // 不是文本消息,不处理      if (params.event.message.message_type != "text") {        await reply(messageId, "暂不支持其他类型的提问");        logger("skip and reply not support");        return { code: 0 };      }      // 是文本消息,直接回复      const userInput = JSON.parse(params.event.message.content);      return await handleReply(userInput, sessionId, messageId, eventId);    }    // 群聊,需要 @ 机器人    if (params.event.message.chat_type === "group") {      // 这是日常群沟通,不用管      if (        !params.event.message.mentions ||        params.event.message.mentions.length === 0      ) {        logger("not process message without mention");        return { code: 0 };      }      // 没有 mention 机器人,则退出。      if (params.event.message.mentions[0].name != FEISHU_BOTNAME) {        logger("bot name not equal first mention name ");        return { code: 0 };      }      const userInput = JSON.parse(params.event.message.content);      return await handleReply(userInput, sessionId, messageId, eventId);    }  }  logger("return without other log");  return {    code: 2,  };};

2. 安装 axios 以及@larksuiteoapi/node-sdk 依赖

alt

3. 配置以下四个环境变量

  • APPID:飞书的应用 ID
  • SECRET: 飞书的应用的 Secret
  • BOTNAME:飞书机器人的名字
  • KEY: OpenAI 的 Key

4. 开始部署

alt

部署成功的话,下方控制台显示内容如下

alt

4. 复制生成的链接

alt

5. 将刚刚复制的地址填充到请求地址中

alt

6. 添加接受信息的事件

alt

7. 最好填写点击版本号和更新说明以及申请理由,满足格式即可,最后点击确定,机器人就上线了。

alt

8. 打开应用

alt

9. 效果如下

alt

如果觉得我的分享对您有帮助,请关注我。创作不易,您的三连就是对我最大的支持。

alt

本文由 mdnice 多平台发布

版权声明:本文为博主作者:潇潇雨声迟原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/qq_60308100/article/details/135176205

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
心中带点小风骚的头像心中带点小风骚普通用户
上一篇 2024年5月6日
下一篇 2024年5月6日

相关推荐