本公众号已接入了 AI 绘画工具 Midjourney,可以让你轻松画出很多“大师”级的作品。同时还接入了 AI 聊天机器人,支持 GPT、Claude 以及 Laf 专有模型,可通过指令来随意切换模型。欢迎前来*戏调**


<<< 左右滑动见更多 >>>
❝
⚠️ FBI Warning:本文纯属作者自娱自乐,数字人的观点不代表 CEO 本人的观点,请大家不要上当受骗!!
哪个公司的 CEO 不想拥有一个自己的数字克隆?
想象一下,如果 CEO 数字克隆上线了,那他是不是就可以 一天约见 100 个投资人 了?把他接入企业官方公众号后台作为客服,24 小时不吃饭不睡觉不喝水给用户答疑解惑,想想就很刺激!感觉 CEO 在给我打工 ✅
环界云的 CEO 做到了!先来看看效果:


怎么样,你也想拥有一个自己的数字克隆么?问题不大,跟着*操我**作。
首先你需要准备自己的语料,我们 CEO 的语料就是来自 各种同*交性**友大会 的演讲内容,如果你的语料不够多,那就得自己想办法了。
当然,本文提供的方法不仅仅适用于数字克隆,你可以基于任意专有知识库来打造一个私有领域的专家或者客服,然后再对接到公众号,它不香吗?
准备工作
已认证的微信公众号
首先你需要有 一个微信公众号 ,而且是 已经认证 的公众号,因为公众号强制要求服务器每次必须在 15s 以内回复消息,公众号平台在发送请求到服务器后,如果 5s 内没收到回复,会再次发送请求等候 5 秒,如果还是没有收到请求,最后还会发送一次请求,所以服务器必须在 15s 以内完成消息的处理。如果超过 15s 还没有返回怎么办?那就超时了,用户将永远都收不到这条消息。
如果你想突破 15s 限制怎么办?
- 如果是已认证的公众号,可以直接使用客服消息进行回复,它的原理是通过 POST 一个 JSON 数据包来发送消息给普通用户。客服消息就厉害了,只要在 48 小时以内 都可以回复。具体可查看 微信官方文档[1] 。
- 如果是未认证的公众号,并不能完全解决 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 中:
importcloudfrom'@lafjs/cloud';
import*ascryptofrom'crypto';
//公众号配置
constappid='wxb1833715d8f0809d'
constappsecret='fd76ce714a8083112100c2160b2f2c5d'
constwxToken='test';
//fastgpt配置
constapikey="63f9a14228d2a688d8dc9e1b-xsyvfby3cui09tfcvxen3"
constmodelId="642adec15f01d67d4613efdb"
//创建数据库连接并获取Message集合
constdb=cloud.database();
const_=db.command
constMessage=db.collection('messages')
//处理接收到的微信公众号消息
exportasyncfunctionmain(event){
//constres=awaitcloud.fetch.post(`https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${awaitgetAccess_token()}`,{
//button:[
//{
//"type":"click",
//"name":"清空记录",
//"key":"CLEAR"
//},
//]
//})
const{signature,timestamp,nonce,echostr}=event.query;
//验证消息是否合法,若不合法则返回错误信息
if(!verifySignature(signature,timestamp,nonce,wxToken)){
return'Invalidsignature';
}
//如果是首次验证,则返回echostr给微信服务器
if(echostr){
returnechostr;
}
//--------------正文开始
constpayload=event.body.xml;
constsessionId=payload.fromusername[0]
console.log(payload)
//点击了清空记录
if(payload.msgtype[0]==='event'&&payload.eventkey[0]==='CLEAR'){
console.log(1111)
awaitMessage.where({sessionId:sessionId}).remove({multi:true})
awaitreplyBykefu('记录已清空',sessionId)
return'clearrecord'
}
//仅做文本消息例子
if(payload.msgtype[0]!=='text')return'notext'
constnewMessage={
msgid:payload.msgid[0],
question:payload.content[0].trim(),
username:payload.fromusername[0],
sessionId,
createdAt:Date.now()
}
awaitreplyText(newMessage,payload.fromusername[0])
return'success'
}
//处理文本回复消息
asyncfunctionreplyText(message,touser){
const{question,sessionId,msgid}=message;
//重复的内容,不回复
const{data:msg}=awaitMessage.where({msgid:message.msgid}).getOne()
if(msg)return
console.log("收到用户消息",touser,message)
//立即添加一条待回复记录
awaitMessage.add(message);
//回复提示
awaitreplyBykefu("机器人正在思考中...",sessionId)
awaitchangesState(sessionId)
constreply=awaitgetFastGptReply(question,sessionId);
const{answer}=reply;
awaitMessage.where({msgid:message.msgid}).update({
answer,
});
//returnanswer;
awaitreplyBykefu(answer,touser)
}
//获取微信公众号ACCESS_TOKEN
asyncfunctiongetAccess_token(){
constshared_access_token=awaitcloud.shared.get("mp_access_token")
if(shared_access_token&&shared_access_token.access_token&&shared_access_token.exp>Date.now()){
returnshared_access_token.access_token
}
//ACCESS_TOKEN不存在或者已过期
//获取微信公众号ACCESS_TOKEN
constmp_access_token=awaitcloud.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
})
returnmp_access_token.data.access_token
}
//公众号客服回复文本消息
exportasyncfunctionreplyBykefu(message,touser){
//判断是否为中文字符
functionisChinese(char){
return/[\u4e00-\u9fa5]/.test(char)//判断是否是中文字符
}
//拆分文本长度
functionsplitText(text){
letresult=[]
letlen=text.length
letindex=0
while(index<len){
letpart=''
letcharCount=0
while(charCount<800&&index<len){
letchar=text[index]
charCount++
part+=char
if(isChinese(char))charCount++//中文字符计数+1
index++
}
result.push(part)
}
returnresult
}
//定义休眠函数
functionsleep(ms){returnnewPromise(resolve=>setTimeout(resolve,ms))};
constaccess_token=awaitgetAccess_token()
lettext=splitText(message)
letlen=splitText(message).length
try{
for(leti=0;i<len;i++){
letpart=text[i]//获取第i段
awaitsleep(1000)
//回复消息
constres=awaitcloud.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)
}
}
//修改公众号回复状态
exportasyncfunctionchangesState(touser){
constaccess_token=awaitgetAccess_token()
//修改正在输入的状态
constres=awaitcloud.fetch.post(`https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token=${access_token}`,{
"touser":touser,
"command":"Typing"
})
}
//校验微信服务器发送的消息是否合法
exportfunctionverifySignature(signature,timestamp,nonce,token){
constarr=[token,timestamp,nonce].sort();
conststr=arr.join('');
constsha1=crypto.createHash('sha1');
sha1.update(str);
returnsha1.digest('hex')===signature;
}
//返回组装xml
exportfunctiontoXML(payload,content){
consttimestamp=Date.now();
const{tousername:fromUserName,fromusername:toUserName}=payload;
return`
<xml>
<ToUserName><![CDATA[${toUserName}]]></ToUserName>
<FromUserName><![CDATA[${fromUserName}]]></FromUserName>
<CreateTime>${timestamp}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[${content}]]></Content>
</xml>
`
}
//调用fastgpt回答
asyncfunctiongetFastGptReply(question,sessionId){
constres=awaitdb.collection('messages')
.where({sessionId})
.get()
//获取最多10组上下文
constlist=res.data.slice(-10)
constprompts=list.map((item)=>[{
obj:"Human",
value:item.question||''
},{
obj:"AI",
value:item.answer||''
}]).concat({
obj:"Human",
value:question
}).flat()
constconfig={
method:'post',//设置请求方法为POST
url:'https://fastgpt.run/api/openapi/chat/chat',//设置请求地址
headers:{//设置请求头信息
apikey,
'Content-Type':'application/json'
},
data:{//设置请求体数据
modelId,
isStream:false,
prompts
}
}
try{
constret=awaitcloud.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 地址可通过以下命令获取:
nbsp;dig+shortlaf.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 相关的任何问题,感兴趣的小伙伴可以去体验一下
QA
如果发送消息后无响应,可以先去 Laf 控制台的日志中检查是否收到用户消息,有下面的提示代表是正常的(可能需要点下搜索才能刷新出来)。

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

引用链接
[1] 微信官方文档: https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Service_Center_messages.html
关于 Laf
Laf 是一款为所有开发者打造的集函数、数据库、存储为一体的云开发平台,助你像写博客一样写代码,随时随地发布上线应用!3 分钟上线 ChatGPT 应用!
GitHub: https://github.com/labring/laf
官网(国内): https://laf.run
官网(海外): https://laf.dev
开发者论坛: https://forum.laf.run
关注 Laf 公众号与我们一同成长
