将 ChatGPT 引入你的飞书

将 ChatGPT 引入你的飞书

1. 创建一个飞书开放平台应用,并获取到 APPID 和 Secret

访问 开发者后台,创建一个名为 ChatGPT 的应用,并上传应用头像。创建完成后,访问【凭证与基础信息】页面,复制 APPID 和 Secret 备用。

将 ChatGPT 引入你的飞书_第1张图片

2. 开启机器人能力

打开应用的机器人应用功能

将 ChatGPT 引入你的飞书_第2张图片

3. 访问 AirCode ,创建一个新的项目

登录 AirCode ,创建一个新的 Node.js v16 的项目,项目名可以根据你的需要填写,可以填写 ChatGPT

将 ChatGPT 引入你的飞书_第3张图片

4. 复制本项目下的 event.js 的源码内容,并粘贴到 Aircode 当中

访问ChatGPT-Feishu/event.js at master · bestony/ChatGPT-Feishu (github.com),复制代码

// @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,
  };
};

将 ChatGPT 引入你的飞书_第4张图片

并把代码粘贴到 AIrcode 默认创建的 hello.js。然后点击顶部的 deploy,完成第一次部署。

将 ChatGPT 引入你的飞书_第5张图片

部署成功后,可以在下方看到。

将 ChatGPT 引入你的飞书_第6张图片

5. 安装所需依赖

这个开发过程中,我们使用了飞书开放平台官方提供的 SDK,以及 axios 来完成调用。点击页面左下角的包管理器,安装 axios@larksuiteoapi/node-sdk。安装完成后,点击上方的部署,使其生效。

将 ChatGPT 引入你的飞书_第7张图片

6. 配置环境变量

接下来我们来配置环境变量,你需要配置三个环境变量 APPIDSECRETBOTNAME,APPID 填写你刚刚在飞书开放平台获取的 APPID,SECRET 填写你在飞书开放平台获取到的 SECRET,BOTNAME 填写你的机器人的名字。

image-20230210013355689

配置完成后,点击上方的 Deploy 按钮部署,使这些环境变量生效。

将 ChatGPT 引入你的飞书_第8张图片

会变成这样的

将 ChatGPT 引入你的飞书_第9张图片

7. 获取 OpenAI 的 KEY,并配置环境变量

访问 Account API Keys - OpenAI API ,点击这里的 Create new secret key,创建一个新的 key,并保存备用。

将 ChatGPT 引入你的飞书_第10张图片

重新回到 Aircode,配置一个名为 KEY 的环境变量,并填写你刚刚生成的 Key。配置完成后,点击部署使其生效。

将 ChatGPT 引入你的飞书_第11张图片

8. 开启权限并配置事件

访问开放平台页面,开通如下 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

将 ChatGPT 引入你的飞书_第12张图片

然后回到 AirCode,复制函数的调用地址。

将 ChatGPT 引入你的飞书_第13张图片

然后回到事件订阅界面,添加事件。

将 ChatGPT 引入你的飞书_第14张图片

9. 发布版本,等待审核

上述这些都配置完成后,你的机器人就配置好了,接下来只需要在飞书开放平台后台找到应用发布,创建一个全新的版本并发布版本即可。

问开放平台页面,开通如下 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

将 ChatGPT 引入你的飞书_第15张图片

然后回到 AirCode,复制函数的调用地址。

将 ChatGPT 引入你的飞书_第16张图片

然后回到事件订阅界面,添加事件。

在这里插入图片描述

9. 发布版本,等待审核

上述这些都配置完成后,你的机器人就配置好了,接下来只需要在飞书开放平台后台找到应用发布,创建一个全新的版本并发布版本即可。

文章转载自:将 ChatGPT 引入你的飞书 - 飞书应用开发教程 (feishu.io)

你可能感兴趣的:(人工智能)