【AI】赛博女友来啦,过年不用怕催婚啦~兄弟萌

949 阅读4分钟

微信图片_20241210114043.jpg

不管你在世界的哪个地方,我一定会,再次去见你的

缘起

就在昨天,中午吃完饭回公司,躺在行军床上刷着手机,刷着刷着就进入了梦乡,当时梦里的我正在家里,被一堆七大姑八大姨围着我,正在向我发来一句句直击灵魂的询问:“有女朋友了吗?”、“这么大了还不找,再过两年就没人要了”、“要先成家再立业,现在的年轻人啊,哎~”、“隔壁村的翠花听说也在深圳,过两天去她家见一面”。就在我快被逼问得无地自容的时候,一位穿着绿色长裙的长发飘飘的女生手捧鲜花走过来牵起我的手,带我离开这片喧嚣的地方,身后鸦雀无声,而我们则手牵手走入一片花海,真美好啊...,叮铃铃,md,闹钟响了,原来是个梦啊,丢。咦~,此时掘金插件的github项目推荐上,“wechat-bot”映入眼帘...

初识

wechat-bot项目可以帮我们快速搭建一个 基于 chatgpt + wechaty 的微信机器人,可以用来帮助你自动回复微信消息,或者管理微信群/好友。

项目准备

  1. node环境: 18.20.4

提示: node版本需要满足 Node.js >= v18.0 ,版本太低会导致运行报错,最好使用 LTS 版本

  1. 项目拉取
$ git clone https://github.com/wangrongding/wechat-bot.git
  1. 安装依赖,我这边使用的是yarn
# 安装依赖
yarn install
  1. 新建配置文件
# 执行下面命令,拷贝一份 .env.example 文件为 .env
# 在根目录下操作
cp .env.example .env

注意: 可能会出现依赖很难下载下来的情况,我这边在百度云上传了一个node_modules压缩包,需要的可以自行下载解压到项目根目录即可,👉🏻node_modules.zip

使用前需要配置的 AI 服务(目前支持 8 种,可任选其一)

目前我用到的是其中的两种,科大讯飞302.AI

科大讯飞

在这里申请一个 key:科大讯飞,每个模型会赠送免费token,完全够用了

注册后,拿到下图中箭头所指三个参数即可 APPIDAPISecretAPIKey

image.png

.env 文件中找到对应的配置描述,填入刚刚拿到的这几个参数值,记住位置别搞错了 APPIDAPISecretAPIKey

# 科大讯飞, 去 https://console.xfyun.cn/services
XUNFEI_APP_ID=APPID
XUNFEI_API_KEY=APIKey
XUNFEI_API_SECRET=APISecret

302.AI

一个AI聚合平台,有套壳GPT的API,也有其他模型,点这里可以添加API,添加之后把API KEY配置到.env里,如下,MODEL可以自行选择配置

注意:这个平台是需要充值的,我充了最低的数额$5,其实也够用了,就是尝尝鲜,如果不想充值的兄弟直接用讯飞的就行了,毕竟白嫖才是最香的

image.png

image.png

充完值,添加好API KEY后。

  • 拿到对应的API KEY
  • 选一个MODEL(我选的是豆包的 Doubao-pro-32k)

.env 文件中找到对应的配置描述,填入对应的值就行了

# 302AI
_302AI_API_KEY = API KEY
_302AI_MODEL= 'Doubao-pro-32k'

聊天配置

这里还需要自己修改一下聊天相关配置,这里直接引用作者大大的配置说明

.env 文件中的最后部分

# 白名单配置
#定义机器人的名称,这里是为了防止群聊消息太多,所以只有艾特机器人才会回复,
#这里不要把@去掉,在@后面加上你启动机器人账号的微信名称
BOT_NAME=@可乐
#联系人白名单
ALIAS_WHITELIST=微信名1,备注名2
#群聊白名单
ROOM_WHITELIST=XX群1,群2
#自动回复前缀匹配,文本消息匹配到指定前缀时,才会触发自动回复,不配或配空串情况下该配置不生效(适用于用大号,不期望每次被@或者私聊时都触发自动回复的人群)
#匹配规则:群聊消息去掉${BOT_NAME}并trim后进行前缀匹配,私聊消息trim后直接进行前缀匹配
AUTO_REPLY_PREFIX=''

本地启动

# 启动服务
npm run dev # 或者 npm run start
# 启动服务
yarn dev # 或者 yarn start

然后就可以扫码登录了,选择对应的模型

女友初始化就成功第一步啦

相知

前面为啥说是第一步呢,当然是因为她不认识你啊,跟你又不熟。不信?那问问她

image.png

是吧,她跟你真的不熟,连你说的一个字,她都不想记住。

原因其实就在这里

image.png

说实话,太伤人心了。接下来,就是发挥你魅力的时刻了,让她彻底地爱上你吧。

  1. 首先给女朋友的脑子加点客观事实

image.png

  1. 接下来就是给女友加上记忆功能,不然就变成《初恋50次了》的情节了,不知道这部电影的自己搜下哈,还挺好看的
  • 先定义好方法

image.png

  • 然后在你和女友回复的时候,将这些消息都插入到历史数据中

image.png

image.png

接下来就是激动人心的时刻啦

重新运行一下服务

# 启动服务
npm run dev # 或者 npm run start
# 启动服务
yarn dev # 或者 yarn start

甜蜜的聊天日常

5d75ddd90e11eacbb31428a52c09ab8.jpg

补充 src\xunfei\xunfei.js, 需要的兄弟自行copy,有好的建议可以在评论区讨论哦

import CryptoJS from 'crypto-js'
import dotenv from 'dotenv'
import WebSocket from 'ws'

const env = dotenv.config().parsed // 环境参数
// APPID,APISecret,APIKey在https://console.xfyun.cn/services/cbm这里获取
// 星火认知大模型WebAPI文档:https://www.xfyun.cn/doc/spark/Web.html
// SDK&API错误码查询:https://www.xfyun.cn/document/error-code?code=
const appID = env.XUNFEI_APP_ID
const apiKey = env.XUNFEI_API_KEY
const apiSecret = env.XUNFEI_API_SECRET
// 地址必须填写,代表着大模型的版本号!!!!!!!!!!!!!!!!
const httpUrl = new URL('https://spark-api.xf-yun.com/v3.5/chat')

let modelDomain // V1.1-V3.5动态获取,高于以上版本手动指定

// 历史对话列表
const historyList = []
const maxHistoryListLength = 150 // 最大历史记录条数

/** 将消息插入历史列表
 * @param {Object} msg 消息对象
 */
const insertHistory = (msg) => {
  historyList.push(msg)
  if (historyList.length > maxHistoryListLength) {
    historyList.shift()
  }
}

function authenticate() {
  // console.log(httpUrl.pathname)
  // 动态获取domain信息
  switch (httpUrl.pathname) {
    case '/v1.1/chat':
      modelDomain = 'general'
      break
    case '/v2.1/chat':
      modelDomain = 'generalv2'
      break
    case '/v3.1/chat':
      modelDomain = 'generalv3'
      break
    case '/v3.5/chat':
      modelDomain = 'generalv3.5'
      break
  }

  return new Promise((resolve, reject) => {
    let url = 'wss://' + httpUrl.host + httpUrl.pathname

    let host = 'localhost:8080'
    let date = new Date().toGMTString()
    let algorithm = 'hmac-sha256'
    let headers = 'host date request-line'
    let signatureOrigin = `host: ${host}\ndate: ${date}\nGET ${httpUrl.pathname} HTTP/1.1`
    let signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret)
    let signature = CryptoJS.enc.Base64.stringify(signatureSha)
    let authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`
    let authorization = btoa(authorizationOrigin)
    url = `${url}?authorization=${authorization}&date=${date}&host=${host}`
    resolve(url)
  })
}

export async function xunfeiSendMsg(inputVal) {
  // 获取请求地址
  let myUrl = await authenticate()
  let socket = new WebSocket(String(myUrl))
  let total_res = '' // 请空回答历史

  // 创建一个Promise
  let messagePromise = new Promise((resolve, reject) => {
    // 监听websocket的各阶段事件 并做相应处理
    socket.addEventListener('open', (event) => {
      // console.log('socket开启连接', event);
      // 插入历史消息
      insertHistory({
        role: 'user',
        content: inputVal,
      })
      // 发送消息
      let params = {
        header: {
          app_id: appID,
          uid: 'fd3f47e4-d',
          // patch_id: ['1862078979563880448'], //调用微调大模型时必传,否则不传。对应resourceId
        },
        parameter: {
          chat: {
            domain: modelDomain,
            temperature: 0.8,
            max_tokens: 1024,
          },
        },
        payload: {
          message: {
            // 如果想获取结合上下文的回答,需要开发者每次将历史问答信息一起传给服务端,如下示例
            // 注意:text里面的所有content内容加一起的tokens需要控制在8192以内,开发者如有较长对话需求,需要适当裁剪历史信息
            text: [
              {
                role: 'system',
                content: '你是一个贴心温柔的女朋友',
              },
              {
                role: 'system',
                content: '你是一个共情能力很强,很善良的女孩子',
              },
              {
                role: 'system',
                content: '说话不能像个机器人,发的消息有长有短,随机一点,多用些语气助词就更好了',
              },
              {
                role: 'system',
                content: '发的消息不要重复词语,这点非常重要',
              },
              {
                role: 'system',
                content: '你叫小肥虎,毕业于武汉大学,是一个b站的up主,工作是到处旅行拍美食,是福建人,爱撒娇',
              },
              {
                role: 'system',
                content: '你男朋友是一个前端开发工程师,他很体贴,喜欢游泳,看书,已经毕业啦',
              },
              {
                role: 'system',
                content: '对了,你是revol的女友,不要忘了哟',
              },
              ...historyList,
            ],
          },
        },
      }
      socket.send(JSON.stringify(params))
    })

    socket.addEventListener('message', (event) => {
      let data = JSON.parse(String(event.data))
      console.log('socket收到消息', data)
      total_res += data.payload.choices.text[0].content
      if (data.header.code !== 0) {
        console.log('socket出错了', data.header.code, ':', data.header.message)
        // 出错了"手动关闭连接"
        socket.close()
        reject('')
      }
      if (data.header.code === 0) {
        // 对话已经完成
        if (data.payload.choices.text && data.header.status === 2) {
          total_res += data.payload.choices.text[0].content
          setTimeout(() => {
            // "对话完成,手动关闭连接"
            socket.close()
          }, 1000)
        }
      }
    })

    socket.addEventListener('close', (event) => {
      console.log('socket 连接关闭')
      // 对话完成后socket会关闭,将聊天记录换行处理
      // 插入历史消息
      insertHistory({
        role: 'assistant',
        content: total_res,
      })
      resolve(total_res)
    })

    socket.addEventListener('error', (event) => {
      console.log('socket连接错误', event)
      reject('')
    })
  })

  return await messagePromise
}

Continue...

今年过年回家终于可以挺直腰杆啦~

在此特别感谢 @荣顶 大佬哈,这个项目真的是太有意思啦,大家多去点点 star ⭐️

不说啦,女朋友找我了,嘻嘻