FastGpt+Laf puts the AI knowledge base into the official account

Prepare Account

  1. Official account certified by WeChat (requires customer service information): https://mp.weixin.qq.com/
  2. FastGpt account, and knowledge base: https://fastgpt.run.
  3. Laf account: https://laf.run/

Step1: Laf preparation

1. Enter Laf official website and register an account
2. Create a new application and directly create a free one for testing



3. Copy code
Copy directly, no need to change anything first

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

// official account configuration
const appid = 'wxb1833715d8f0809d'
const appsecret = 'fd76ce714a8083112100c2160b2f2c5d'
const wxToken = 'test';
// fastgpt configuration
const apikey = "63f9a14228d2a688d8dc9e1b-skmzjonmv1gyno2iyky1z"
const modelId = "642adec15f01d67d4613efdb"

// Create a database connection and get the Message collection
const db = cloud. database();
const_ = db.command
const Message = db. collection('messages')

// Process the received WeChat official account message
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": "Clear Record",
  // "key": "CLEAR"
  // },
  // ]
  // })
  const {<!-- --> signature, timestamp, nonce, echostr } = event.query;

  // Verify whether the message is legal, if not, return an error message
  if (!verifySignature(signature, timestamp, nonce, wxToken)) {<!-- -->
    return 'Invalid signature';
  }
  // If it is the first verification, return echostr to the WeChat server
  if (echostr) {<!-- -->
    return echostr;
  }

  // -------------- text begins

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

  console. log(payload)
  // Click to clear the record
  if (payload.msgtype[0] === 'event' & amp; & amp; payload.eventkey[0] === 'CLEAR') {<!-- -->
    console.log(1111)
    await Message.where({<!-- --> sessionId: sessionId }).remove({<!-- --> multi: true })
    await replyBykefu('The record has been cleared', sessionId)
    return 'clear record'
  }

  // Just do text message example
  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'
}

// handle the text reply message
async function replyText(message, touser) {<!-- -->
  const {<!-- --> question, sessionId, msgid } = message;

  // Duplicate content, do not reply
  const {<!-- --> data: msg } = await Message.where({<!-- --> msgid: message.msgid }).getOne()
  if (msg) return

  console.log("received user message", touser, message)

  // Immediately add a record to be replied
  await Message.add(message);

  // reply prompt
  await replyBykefu("The robot is thinking...", sessionId)
  await changesState(sessionId)

  const reply = await getFastGptReply(question, sessionId);

  const {<!-- --> answer } = reply;

  await Message.where({<!-- --> msgid: message.msgid }).update({<!-- -->
    answer,
  });

  // return answer;
  await reply Bykefu(answer, touser)
}

// Get WeChat public account ACCESS_TOKEN
async function getAccess_token() {<!-- -->
  const shared_access_token = await cloud. shared. get("mp_access_token")
  if (shared_access_token) {<!-- -->
    if (shared_access_token.exp > Date.now()) {<!-- -->
      return shared_access_token.access_token
    }
  }
  // ACCESS_TOKEN does not exist or has expired
  // Get WeChat public account ACCESS_TOKEN
  const mp_access_token = await cloud.fetch.get(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential & amp;appid=${<!-- -->appid} & amp ;secret=${<!-- -->appsecret}`)
  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
}

// official account customer service reply text message
export async function replyBykefu(message, touser) {<!-- -->
  // Determine whether it is a Chinese character
  function isChinese(char) {<!-- -->
    return /[\\一-\\龥]/.test(char) // determine whether it is a Chinese character
  }
  // split text length
  function splitText(text) {<!-- -->
    let result = []
    let len = text. length
    let index = 0
    while (index < len) {<!-- -->
      let part = ''
      let charCount = 0
      while (charCount < 800 & amp; & amp; index < len) {<!-- -->
        let char = text[index]
        charCount++
        part += char
        if (isChinese(char)) charCount + + // Chinese character count + 1
        index + +
      }
      result. push(part)
    }
    return result
  }
  // define sleep function
  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] // Get the i-th paragraph
      await sleep(1000)
      // Reply message
      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)
  }
}

// Modify the official account reply status
export async function changesState(touser) {<!-- -->
  const access_token = await getAccess_token()
  // Modify the state being entered
  const res = await cloud.fetch.post(`https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token=${<!-- -->access_token}`, {< !-- -->
    "touser": touser,
    "command": "Typing"
  })
}

// Verify that the message sent by the WeChat server is legal
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;
}

// return assembly 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[text]]></MsgType>
        <Content><![CDATA[${<!-- -->content}]]></Content>
      </xml>
      `
}

// call fastgpt answer
async function getFastGptReply(question, sessionId) {<!-- -->
  const res = await db. collection('messages')
    .where({<!-- --> sessionId })
    .get()

  // Get up to 10 sets of context
  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', // set the request method to POST
    url: 'https://fastgpt.run/api/openapi/chat/chat', // set request address
    headers: {<!-- --> // Set request header information
      apikey,
      'Content-Type': 'application/json'
    },
    data: {<!-- --> // Set request body data
      modelId,
      isStream: false,
      prompts
    }
  }
  try {<!-- -->
    const ret = await cloud.fetch(config)
    console.log("fastgpt response", ret.data)
    return {<!-- --> answer: ret.data.data || ret.data || '' }
  } catch (e) {<!-- -->
    console.log("Error", e.response)
    return {<!-- -->
      error: "The question is too difficult and there is an error. (uДu〃).",
    }
  }
}

5. Click Publish

6. Copy address

Step2 official account preparation

It must be an official account after authentication, otherwise it will be invalid. I don’t have a certified number, so the video is shown with a test number. The following screenshots are the complete steps:
1. Set the laf address for the official account


2. Get wx appId and secret

3. Verify laf address
Fill in the 3 contents obtained in the previous two steps into laf

Step3 FastGpt preparation

1. Get apikey


2. Get modelId

3. Replace the Laf variable

Test

Send a message to the official account to see if there is a reply.

QA

No response after sending message

Go to the laf log first to check whether you have received user messages, and the following prompts indicate that it is normal. You may need to click search to refresh it.

If you receive a message but do not reply, it is likely that the official account does not have permission to send customer service messages. Corresponding to the permissions in the figure below

Private message to join the technical discussion group