乐趣区

关于人工智能:终于我们把-CEO-炒了让-ChatGPT-出任-CEO

⚠️ FBI Warning:本文纯属作者自娱自乐,数字人的观点不代表 CEO 自己的观点,请大家不要上当受骗!!

哪个公司的 CEO 不想领有一个本人的数字克隆?

设想🤔一下,如果 CEO 数字克隆上线了,那他是不是就能够 一天约见 100 个投资人 了?把他接入企业官网公众号后盾作为客服,24 小时不吃饭不睡觉不喝水给用户答疑解惑,想想就很刺激!感觉 CEO 在给我打工

环界云的 CEO 做到了!先来看看成果:

怎么样,你也想领有一个本人的数字克隆么?问题不大,跟着我操作。

首先你须要筹备本人的语料,咱们 CEO 的语料就是来自 各种同性交友大会 的演讲内容,如果你的语料不够多,那就得本人想方法了。

当然,本文提供的办法不仅仅实用于数字克隆,你能够基于任意专有知识库来打造一个公有畛域的专家或者客服,而后再对接到公众号,它不香吗?

筹备工作

已认证的微信公众号

首先你须要有 一个微信公众号 ,而且是 曾经认证 的公众号,因为公众号强制要求服务器每次必须在 15s 以内回复音讯,公众号平台在发送申请到服务器后,如果 5s 内没收到回复,会再次发送申请等待 5 秒,如果还是没有收到申请,最初还会发送一次申请,所以服务器必须在 15s 以内实现音讯的解决。如果超过 15s 还没有返回怎么办?那就超时了,用户将永远都收不到这条音讯。

如果你想冲破 15s 限度怎么办?

  • 如果是已认证的公众号,能够间接应用客服音讯进行回复,它的原理是通过 POST 一个 JSON 数据包来发送音讯给普通用户。客服音讯就厉害了,只有在 48 小时以内 都能够回复。具体可查看微信官网文档。
  • 如果是未认证的公众号,并不能齐全解决 15s 限度的问题,然而能够优化。这里提供一个思路,你能够应用流式响应来缓解这个限度,先与 OpenAI 建设连贯,再一个字符一个字符获取生成的文本,最初将所获取的文本列表拼接成回复文本。能缓解申请超时的关键在于:建设连贯的工夫个别状况下不会超过 15s,所以只有在给定的工夫内,胜利建设连贯,根本就能返回内容(15s 之后接管到多少文本就返回多少文本)。尽管有可能会呈现回复内容被截断的状况,但总比你回复不了强吧?

本文给出的办法是基于微信客服音讯进行回复,所以须要一个已认证的公众号。如果是未认证的公众号,就须要你本人钻研流式响应了,本文不做赘述。

FastGPT

其次你须要注册一个 FastGPT 账号。它是一个 ChatGPT 平台我的项目,目前曾经集成了 ChatGPT、GPT4 和 Claude,能够应用任意文原本训练本人的知识库

🌐 注册链接:https://fastgpt.run/?inviterId=64215e9914d068bf840141d0

知识库

注册完 FastGPT 后,你能够间接填写本人的 API Key 进行应用,也能够在 FastGPT 平台充值应用。

接下来点击侧栏的数据库图标进入知识库界面,而后点击“+”号新建一个知识库。

点击「导入」,能够看到有 3 种办法来导入知识库。

如果你有多个文本文件,能够间接抉择「文本 / 文件拆分」进行导入,模式倡议选「QA 拆分」,也能够间接分段。

导入之后,就会开始训练,训练实现后的成果:

Laf

最初你还须要一个平台来开发你的利用,那当然是 Laf 啦。据环界云 CEO 数字克隆所说👀,Laf 是一个 Serverless 框架,能够用来疾速开发具备 AI 能力的分布式应用,助你像写博客一样写代码,随时随地疾速公布上线利用。真⭕五分钟上线 CEO 数字克隆!

🌐 Laf 注册链接:https://laf.run

编写云函数

所有工作准备就绪后,开始动笔写 亿点点 代码。

先新建利用,间接新建收费的进行测试:

点击「+」新建云函数:

而后将上面的云函数代码间接复制粘贴到 Web IDE 中:

import cloud from '@lafjs/cloud';
import * as crypto from 'crypto';

// 公众号配置
const appid = 'wxb1833715d8f0809d'
const appsecret = 'fd76ce714a8083112100c2160b2f2c5d'
const wxToken = 'test';
// fastgpt 配置
const apikey = "63f9a14228d2a688d8dc9e1b-xsyvfby3cui09tfcvxen3"
const modelId = "642adec15f01d67d4613efdb"

// 创立数据库连贯并获取 Message 汇合
const db = cloud.database();
const _ = db.command
const Message = db.collection('messages')

// 解决接管到的微信公众号音讯
export async function main(event) {// const res = await cloud.fetch.post(` https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${await getAccess_token()}`, {
  //   button: [
  //     {
  //       "type": "click",
  //       "name": "清空记录",
  //       "key": "CLEAR"
  //     },
  //   ]
  // })
  const {signature, timestamp, nonce, echostr} = event.query;

  // 验证音讯是否非法,若不非法则返回错误信息
  if (!verifySignature(signature, timestamp, nonce, wxToken)) {return 'Invalid signature';}
  // 如果是首次验证,则返回 echostr 给微信服务器
  if (echostr) {return echostr;}

  // -------------- 注释开始

  const payload = event.body.xml;
  const sessionId = payload.fromusername[0]

  console.log(payload)
  // 点击了清空记录
  if (payload.msgtype[0] === 'event' && payload.eventkey[0] === 'CLEAR') {console.log(1111)
    await Message.where({sessionId: sessionId}).remove({multi: true})
    await replyBykefu('记录已清空', sessionId)
    return 'clear record'
  }

  // 仅做文本音讯例子
  if (payload.msgtype[0] !== 'text') return 'no text'
  const newMessage = {msgid: payload.msgid[0],
    question: payload.content[0].trim(),
    username: payload.fromusername[0],
    sessionId,
    createdAt: Date.now()}

  await replyText(newMessage, payload.fromusername[0])

  return 'success'
}

// 解决文本回复音讯
async function replyText(message, touser) {const { question, sessionId, msgid} = message;

  // 反复的内容,不回复
  const {data: msg} = await Message.where({msgid: message.msgid}).getOne()
  if (msg) return

  console.log("收到用户音讯", touser, message)

  // 立刻增加一条待回复记录 
  await Message.add(message);

  // 回复提醒
  await replyBykefu("🤖机器人正在思考🤔中...", sessionId)
  await changesState(sessionId)

  const reply = await getFastGptReply(question, sessionId);

  const {answer} = reply;

  await Message.where({msgid: message.msgid}).update({answer,});

  // return answer;
  await replyBykefu(answer, touser)
}

// 获取微信公众号 ACCESS_TOKEN
async function getAccess_token() {const shared_access_token = await cloud.shared.get("mp_access_token")

  if (shared_access_token && shared_access_token.access_token && shared_access_token.exp > Date.now()) {return shared_access_token.access_token}
  // ACCESS_TOKEN 不存在或者已过期
  // 获取微信公众号 ACCESS_TOKEN
  const mp_access_token = await cloud.fetch.get(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${appsecret}`)
  mp_access_token.data.access_token && cloud.shared.set("mp_access_token", {
    access_token: mp_access_token.data.access_token,
    exp: Date.now() + 7100 * 1000})
  return mp_access_token.data.access_token
}

// 公众号客服回复文本音讯
export async function replyBykefu(message, touser) {
  // 判断是否为中文字符
  function isChinese(char) {return /[\u4e00-\u9fa5]/.test(char)  // 判断是否是中文字符
  }
  // 拆分文本长度
  function splitText(text) {let result = []
    let len = text.length
    let index = 0
    while (index < len) {
      let part = ''
      let charCount = 0
      while (charCount < 800 && index < len) {let char = text[index]
        charCount++
        part += char
        if (isChinese(char)) charCount++  // 中文字符计数 +1
        index++
      }
      result.push(part)
    }
    return result
  }
  // 定义休眠函数
  function sleep(ms) {return new Promise(resolve => setTimeout(resolve, ms)) };

  const access_token = await getAccess_token()
  let text = splitText(message)
  let len = splitText(message).length

  try {for (let i = 0; i < len; i++) {let part = text[i]  // 获取第 i 段
      await sleep(1000)
      // 回复音讯
      const res = await cloud.fetch.post(`https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${access_token}`, {
        "touser": touser,
        "msgtype": "text",
        "text":
        {"content": part}
      })
    }
  } catch (err) {console.log(err)
  }
}

// 批改公众号回复状态
export async function changesState(touser) {const access_token = await getAccess_token()
  // 批改正在输出的状态
  const res = await cloud.fetch.post(`https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token=${access_token}`, {
    "touser": touser,
    "command": "Typing"
  })
}

// 校验微信服务器发送的音讯是否非法
export function verifySignature(signature, timestamp, nonce, token) {const arr = [token, timestamp, nonce].sort();
  const str = arr.join('');
  const sha1 = crypto.createHash('sha1');
  sha1.update(str);
  return sha1.digest('hex') === signature;
}

// 返回组装 xml
export function toXML(payload, content) {const timestamp = Date.now();
  const {tousername: fromUserName, fromusername: toUserName} = payload;
  return `
      <xml>
        <ToUserName><![CDATA[${toUserName}]]></ToUserName>
        <FromUserName><![CDATA[${fromUserName}]]></FromUserName>
        <CreateTime>${timestamp}</CreateTime>
        <MsgType><![CDATA]></MsgType>
        <Content><![CDATA[${content}]]></Content>
      </xml>
      `
}

// 调用 fastgpt 答复
async function getFastGptReply(question, sessionId) {const res = await db.collection('messages')
    .where({sessionId})
    .get()

  // 获取最多 10 组上下文
  const list = res.data.slice(-10)
  const prompts = list.map((item) => [{
    obj: "Human",
    value: item.question || ''
  }, {
    obj: "AI",
    value: item.answer || ''
  }]).concat({
    obj: "Human",
    value: question
  }).flat()

  const config = {
    method: 'post', // 设置申请办法为 POST
    url: 'https://fastgpt.run/api/openapi/chat/chat', // 设置申请地址
    headers: { // 设置申请头信息
      apikey,
      'Content-Type': 'application/json'
    },
    data: { // 设置申请体数据
      modelId,
      isStream: false,
      prompts
    }
  }
  try {const ret = await cloud.fetch(config)
    console.log("fastgpt 响应", ret.data)
    return {answer: ret.data.data || ret.data || ''}
  } catch (e) {console.log("出错了", e.response)
    return {error: "问题太难了 出错了. (uДu〃).",
    }
  }
}

整个云函数的调用流程如下:

❶ 当收到微信公众号音讯时,首先调用 main 函数。在 main 函数中,首先验证音讯是否非法,如果不非法则返回错误信息。如果是首次验证,则返回 echostr 给微信服务器。

❷ 接着依据音讯类型进行解决。对于文本音讯,调用 replyText 函数进行解决。

❸ 在 replyText 函数 中,首先查看是否为反复的内容,如果是则不回复。而后将用户发送的问题存入数据库,并回复提示信息给用户,示意机器人正在思考中。

❹ 接下来调用 getFastGptReply 函数 获取 FastGPT 的答复。在 getFastGptReply 函数中,首先从数据库中获取最多 10 组上下文信息,而后将问题和上下文信息一起发送给 FastGPT。接管到 FastGPT 的答复后返回给 replyText 函数。

❺ 回到 replyText 函数 ,将 FastGPT 返回的答复更新到数据库中,并通过客服接口将答复发送给用户。在发送答复之前,会调用 changesState 函数 批改公众号回复状态为正在输出中。

❻ 调用 replyBykefu 函数 通过微信公众号客服接口发送文本音讯给用户。在 replyBykefu 函数中,首先依据文本长度拆分成多段,并逐段发送给用户。

先不要改变代码中的任何内容,前面会通知你如何批改。

点击「公布」:

最初复制已公布的函数地址:

配置微信公众号

这一步咱们须要在微信公众号平台上配置开发者信息,并将服务器地址设置为部署好的云函数服务地址。步骤如下:

首先登录微信公众平台,点开左侧的「设置与开发」,点击「根本设置」,而后点击「服务器配置」,服务器配置那里点击批改配置:

将之前的云函数服务地址复制到「服务器 URL」中,下边的 Token 与云函数代码中的 token 保持一致,下边的 EncodingAESKey 点击右侧随机生成就行,而后点击提交:

返回 token 校验胜利即可。

获取公众号的 AppID 和 AppSecret:

这一步的操作请务必不要遗记!!!你须要把 laf.run 的 IP 地址全副增加到 IP 白名单中:

laf.run 域名的 IP 地址可通过以下命令获取:

$ dig +short laf.run
112.124.8.17
120.26.163.28
112.124.9.83
47.97.22.68
112.124.9.194
114.55.179.67
114.55.177.246
120.27.246.172
120.26.161.248
47.97.5.237

把获取到的 AppID 和 AppSecret 填写到 Laf 云函数中,而后点击「公布」:

最初在公众号平台点击「启用」即可。

配置 FastGPT

接下来开始配置 FastGPT,首先新建一个 API Key:

而后新建一个利用:

而后抉择须要关联的知识库:

能够依据本人的需要设置一下温度、搜寻模式和零碎提醒词,最终点击「保留批改」。

获取利用的 modelId:

将你获取的 API Key 和 modelId 填写到 Laf 云函数中,批改实现后点击公布:

到公众号里测试一下:

完满👀

当然,接入数字 CEO 只是图个乐呵,演示完了就撤了。目前 Laf 公众号真正接入的是 Laf 专有模型,能够答复与 Laf 相干的任何问题,感兴趣的小伙伴能够去体验一下,公众号的名字是:Laf 开发者

QA

如果发送音讯后无响应,能够先去 Laf 控制台的日志中查看是否收到用户音讯,有上面的提醒代表是失常的(可能须要点下搜寻能力刷新进去)。

如果收到了音讯,然而没有回复,八成是公众号没有发送客服音讯权限。对应是下图的权限:

退出移动版