Zero code, use Dify and Laf to connect to the enterprise WeChat AI robot in two minutes

Original link: https://docs.dify.ai/v/zh-hans/use-cases/integrate-with-wecom-using-dify

Dify allows the creation of AI applications and provides secondary development capabilities. Here I will demonstrate the creation of an AI application for a legal question and answer assistant called "Zhifa". In this tutorial, I will guide you to connect to corporate WeChat for "Zhifa".

Preparation

  • Administrator permissions for Enterprise WeChat
  • A Dify account
  • A Laf cloud account
  • (Optional) An OpenAI API Key. If not, you can use the 200 call opportunities provided by Dify for free for testing.
  • (Optional) Create a new env.txt file on your computer and copy the following content into env.txt. In the next tutorial, we will fill in the relevant information into this file step by step. Steps that require saving information are highlighted.
WXWORK_TOKEN=""
WXWORK_AESKEY=""
WXWORK_CORPID=""
WXWORK_AGENTID=""
WXWORK_CORPSECRET=""
DIFY_APPTOKEN=""

Make an app on Dify

This chapter will introduce how to create a legal knowledge data set and associate the data set with applications.

Build legal knowledge data set

Check out the documentation at any time for more operations on building a dataset: [Dataset Management]

In order for "Know the Law" to understand more context, we need to create a database of legal knowledge.

  • **Import documents:**Import legal knowledge PDF documents from your computer.
  • Text segmentation and cleaning : The uploaded text needs to undergo secondary processing before it can be understood by the large language model. We don't need to pay attention to the specific implementation logic here, just select automatic segmentation, and then click "Save and Process".
  • **Text Embedding:** In about 30 seconds, the data set is created successfully. You can come back at any time to add more files to the database.

Built application

Check out the documentation at any time for more operations on creating an application [Create Application]

  • **Create application:** According to the instructions in the picture, create a conversational application and name it "Zhifa".
  • **Associated data set:** On the "Prompt Word Arrangement" page, add and select the data set you just created in the "Context" module.
  • **Publish the model:** After completing the associated data set, click "Publish" in the upper right corner of the page to make the model effective.
  • **Get API access key. **On the "Access API" page, create an API key and copy and save it as DIFY_APPTOKEN. Please be careful not to leak the key to anyone to avoid property damage.

Create an enterprise WeChat application

  • **Record enterprise information: **Enter the enterprise WeChat management background - My Enterprise, record the enterprise ID here asWXWORK_CORPID
  • **Create an enterprise WeChat application:** Enter the application management page, click [Create Application] to enter the creation page, fill in the application information and click [Create Application]. If you already have an existing application, you can skip this step.
  • **Record enterprise WeChat application information:** Click on the just created application on the application management page to enter the application details page. Record the AgentId and Secret here (you need to click the Get button to get it in the corporate WeChat chat window), which are WXWORK_AGENTID and WXWORK_CORPSECRET respectively.
  • **Enterprise WeChat application receives information:** On the application details page, click [Set API Reception] at the message receiving area.

On the API message receiving page, click the two [Randomly Get] buttons, which will automatically generate a Token and EncodingAESKey, which we record as WXWORK_TOKEN and WXWORK_AESKEY respectively. Note, do not close this page. We will fill in the URL after the Laf side is configured.

Create cloud functions on Laf Cloud

  • **Create a new Laf cloud application:** After entering Laf, click New to create a cloud application. Just choose the free plan here.
  • **Add dependencies:** Enterprise WeChat application needs to add @wecom/cryptotwo xml2jsdependencies. Once added, your dependency list should look like below.
  • **Add environment variables:** Starting from the second line, paste all the content collected in the above steps here and click Update.
  • **Create cloud function: **Click to create a cloud function, note that "Request Method" is checked POST, GETand click OK.

After creating the cloud function, delete the default code and paste all the code in the "Appendix" at the end of the article here.

  • **Publish cloud function:** After clicking publish, the cloud function will take effect.

Now paste the URL into the blank space just left on the [Set API Reception] page of the Enterprise WeChat backend, and then click Save.

  • **Configure IP whitelist:** Find the application you just created in WeChat Enterprise and send a message. As expected, no messages were received. This is because Enterprise WeChat blocks the IP of Laf Cloud by default.

Click on the log and you should see an error like this ' not allow to access from your ip'

Click to view the details of this log and record the Laf cloud IP given in the log.

Go back to the management background of Enterprise WeChat, click on the application you just created, and configure a feasible IP for the application.

Just fill in the IP recorded in the log just now.

Verify effect

  1. **Test chat:** Find the application you just created in WeChat Enterprise and send a message. You should now be able to receive push messages.

Quote

This in-depth reference is made to the following article, and I would like to thank the original author for his hard work. https://forum.laf.run/d/556/3

appendix

Enterprise WeChat application code- (pseudo streaming response)

import cloud from '@lafjs/cloud'
import { decrypt, getSignature } from '@wecom/crypto'
import xml2js from 'xml2js'

function genConversationKey(userName) {
  return `${process.env.WXWORK_AGENTID}:${userName}`
}

function genWxAppAccessTokenKey() {
  return `${process.env.WXWORK_AGENTID}:access-token`
}

async function getToken() {
  console.log('[getToken] called')

  const cache = cloud.shared.get(genWxAppAccessTokenKey())
  if (cache && cache.expires >= Date.now()) return cache.token

  const res = await cloud.fetch({
    url: 'https://qyapi.weixin.qq.com/cgi-bin/gettoken',
    method: 'GET',
    params: {
      corpid: process.env.WXWORK_CORPID,
      corpsecret: process.env.WXWORK_CORPSECRET,
    }
  })

  const token = res.data.access_token
  cloud.shared.set(genWxAppAccessTokenKey(), { token, expires: Date.now() + res.data.expires_in * 1000 })
  return token
}

async function sendWxMessage(message, user) {
  console.log('[sendWxMessage] called', user, message)

  const res = await cloud.fetch({
    url: 'https://qyapi.weixin.qq.com/cgi-bin/message/send',
    method: 'POST',
    params: {
      access_token: await getToken()
    },
    data: {
      "touser": user,
      "msgtype": "text",
      "agentid": process.env.WXWORK_AGENTID,
      "text": {
        "content": message
      },
      "safe": 0,
      "enable_id_trans": 0,
      "enable_duplicate_check": 0,
      "duplicate_check_interval": 1800
    },
  })
  console.log('[sendWxMessage] received', res.data)
}

async function sendDifyMessage(message, userName, onMessage) {
  console.log('[sendDifyMessage] called', message, userName)

  const conversationId = cloud.shared.get(genConversationKey(userName)) || null
  let newConversationId = ''
  let responseText = ''

  try {
    const response = await cloud.fetch({
      url: 'https://api.dify.ai/v1/chat-messages',
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.DIFY_APPTOKEN}`
      },
      data: {
        inputs: {},
        response_mode: "streaming",
        query: message,
        user: userName,
        conversation_id: conversationId
      },
      responseType: "stream"
    })

    let firstHalfMessage = ''
    response.data.on('data', (data) => {
      let message = data.toString()
      try {
        if (firstHalfMessage) {
          message += firstHalfMessage
          firstHalfMessage = ''
        }

        // 检查是不是sse协议
        if (!message.startsWith('data: ')) return

        const parsedChunk: Record<string, any> = JSON.parse(message.substring(6))

        if (!newConversationId) {
          newConversationId = parsedChunk.conversation_id
          cloud.shared.set(genConversationKey(userName), newConversationId)
        }
        const { answer } = parsedChunk
        responseText += answer

        // 伪流式响应
        if (answer.endsWith('\n\n') || (responseText.length > 120 && /[?。;!]$/.test(responseText))) {
          onMessage(responseText.replace('\n\n', ''))
          console.log('[sendDifyMessage] received', responseText, newConversationId)
          responseText = ''
        }
      } catch (e) {
        firstHalfMessage = message
        console.error('[sendDifyMessage] error', message)
      }

    })

    // stream结束时把剩下的消息全部发出去
    response.data.on('end', () => {
      onMessage(responseText.replace('\n\n', ''))
    })
  } catch (e) {
    console.error("[sendDifyMessage] error", e)
  }
}

async function asyncSendMessage(xml) {
  console.log('[asyncSendMessage] called', xml)

  if (xml.MsgType[0] !== 'text') return

  const message = xml.Content[0]
  const userName = xml.FromUserName[0]

  if (message === '/new') {
    // 重置conversation id
    cloud.shared.set(genConversationKey(userName), null)
    sendWxMessage('新建成功,开始新的对话吧~~', userName)
    return
  }

  sendWxMessage('AI思考中, 请耐心等待~~', userName)

  try {
    sendDifyMessage(message, userName, (message) => {
      sendWxMessage(message, userName)
    })
  }
  catch (e) {
    console.error('[sendDifyMessage] error', e)
    sendWxMessage('接口请求失败,请联系管理员查看错误信息', userName)
  }
}

export default async function (ctx: FunctionContext) {
  const { query } = ctx
  const { msg_signature, timestamp, nonce, echostr } = query
  const token = process.env.WXWORK_TOKEN
  const key = process.env.WXWORK_AESKEY
  console.log('[main] called', ctx.method, ctx.request.url)

  // 签名验证专用
  if (ctx.method === 'GET') {
    const signature = getSignature(token, timestamp, nonce, echostr)
    if (signature !== msg_signature) {
      return { message: '签名验证失败', code: 401 }
    }
    const { message } = decrypt(key, echostr)
    return message
  }

  const payload = ctx.body.xml
  const encrypt = payload.encrypt[0]
  const signature = getSignature(token, timestamp, nonce, encrypt)
  if (signature !== msg_signature) {
    return { message: '签名验证失败', code: 401 }
  }

  const { message } = decrypt(key, encrypt)
  const {
    xml
  } = await xml2js.parseStringPromise(message)
  // 由于GPT API耗时较久,这里提前返回,防止企业微信超时重试,后续再手动调用发消息接口
  ctx.response.sendStatus(200)

  await asyncSendMessage(xml)

  return { message: true, code: 0 }
}

Guess you like

Origin blog.csdn.net/alex_yangchuansheng/article/details/132560912