前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手教你在飞书中搭建机器人

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

作者头像
用户10517932
发布2023-12-24 08:30:15
5870
发布2023-12-24 08:30:15
举报
文章被收录于专栏:929KC929KC
前言

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

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

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

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

alt
alt
alt
alt

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

alt
alt

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

alt
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
alt

二,创建 AirCode 项目

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

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

alt
alt
alt
alt

注意:不要选上 TypeScipt

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

代码语言:javascript
复制
// @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 || ""; // 飞书的应用 ID
const FEISHU_APP_SECRET = process.env.SECRET || ""; // 飞书的应用的 Secret
const FEISHU_BOTNAME = process.env.BOTNAME || ""; // 飞书机器人的名字
const OPENAI_KEY = process.env.KEY || ""; // OpenAI 的 Key
const 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
alt

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

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

4. 开始部署

alt
alt

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

alt
alt

4. 复制生成的链接

alt
alt

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

alt
alt

6. 添加接受信息的事件

alt
alt

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

alt
alt

8. 打开应用

alt
alt

9. 效果如下

alt
alt

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

本文由 mdnice 多平台发布

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-12-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一,进入飞书开发者后台进行配置
    • 1. 首先登录到飞书的开发者后台,创建一个名为 ChatGPT 的应用
      • 2. 保存 App ID 和 App Secret 的信息
        • 3. 为创建好的应用添加机器人能力
          • 4. 为机器人赋予权限
          • 二,创建 AirCode 项目
            • 1. 登录 AirCode,创建一个新的 Nodejs v18 的项目,项目名可以根据你的需要填写,例如填写 ChatGPT。
              • 2. 安装 axios 以及@larksuiteoapi/node-sdk 依赖
                • 3. 配置以下四个环境变量
                  • 4. 开始部署
                    • 4. 复制生成的链接
                      • 5. 将刚刚复制的地址填充到请求地址中
                        • 6. 添加接受信息的事件
                          • 7. 最好填写点击版本号和更新说明以及申请理由,满足格式即可,最后点击确定,机器人就上线了。
                            • 8. 打开应用
                              • 9. 效果如下
                              相关产品与服务
                              对话机器人
                              对话机器人(Conversation Robot,ICR),是基于人工智能技术,面向企业场景的 AI 服务,可应用于智能客服、服务咨询、业务办理等场景。本产品旨在帮助企业快速构建,满足自身业务诉求的对话机器人,从而减少企业人力成本或解决服务不及时问题。用户可通过对话机器人用户端引擎,实现高准确率的对话服务。
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                              http://www.vxiaotou.com