Dify 允許建立 AI 應用,並提供二次開發的能力。這裡我將演示建立一個法律問答助手的 AI 應用,稱作「知法」。在本篇教學中,我將指導你為「知法」接入企業微信。
WXWORK_TOKEN=""
WXWORK_AESKEY=""
WXWORK_CORPID=""
WXWORK_AGENTID=""
WXWORK_CORPSECRET=""
DIFY_APPTOKEN=""
這一章節將會介紹如何建立一個法律知識的資料集,並將資料集和應用關聯起來。
隨時檢視檔案中關於搭建資料集的更多操作:【資料集管理】
為了讓「知法」瞭解到更多的上下文,我們需要建立一個法律知識的資料庫。
隨時檢視檔案中關於建立應用的更多操作 【建立應用】
DIFY_APPTOKEN
。請注意不要把金鑰洩漏給任何人,以免造成財產損失。WXWORK_CORPID
![](https://cdn.jsdelivr.us/gh/langgenius/dify-docs@main/zh_CN/.gitbook/assets/image (23).png">
在 API 接收訊息頁面,點一下兩個【隨機獲取】按鈕,它會自動生成一個 Token 和 EncodingAESKey,我們分別記為 WXWORK_TOKEN 和 WXWORK_AESKEY。注意,不要關掉這個頁面,Laf 側設定完畢後我們再來填寫 URL。
@wecom/crypto
, xml2js
兩個依賴。新增好後,你的依賴列表應該像下面一樣。POST
, GET
,點選確定。在建立好雲函數中,刪除預設的程式碼,並將文末「附錄」中的程式碼全部貼上到這裡。
現在把 URL 貼上到企業微信後臺【設定 API 接收】的頁面中剛剛留白的地方,然後點選儲存。
點選紀錄檔,應當能看到這樣一條報錯 'not allow to access from your ip
'
點選檢視這條紀錄檔詳情,記錄紀錄檔中給出的 Laf 雲 IP。
回到企業微信的管理後臺,點選剛剛建立的應用,為應用設定可行 IP。
在這裡把剛剛的紀錄檔中記錄的 IP 填入即可。
這篇深度參考以下文章,感謝原作者的辛勤付出。https://forum.laf.run/d/556/3
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%20(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',%20(data) => {
let message = data.toString()
try {
if%20(firstHalfMessage) {
message += firstHalfMessage
firstHalfMessage = ''
}
// 檢查是不是sse協定
if%20(!message.startsWith('data: ')) return
const parsedChunk: Record<string, any> = JSON.parse(message.substring(6))
if%20(!newConversationId) {
newConversationId = parsedChunk.conversation_id
cloud.shared.set(genConversationKey(userName), newConversationId)
}
const { answer } = parsedChunk
responseText += answer
// 偽流式響應
if%20(answer.endsWith('\n\n') ||%20(responseText.length > 120 && /[?。;!]$/.test(responseText))) {
onMessage(responseText.replace('\n\n', ''))
console.log('[sendDifyMessage] received', responseText, newConversationId)
responseText = ''
}
} catch%20(e) {
firstHalfMessage = message
console.error('[sendDifyMessage] error', message)
}
})
// stream結束時把剩下的訊息全部發出去
response.data.on('end',%20() => {
onMessage(responseText.replace('\n\n', ''))
})
} catch%20(e) {
console.error("[sendDifyMessage] error", e)
}
}
async function asyncSendMessage(xml) {
console.log('[asyncSendMessage] called', xml)
if%20(xml.MsgType[0] !== 'text') return
const message = xml.Content[0]
const userName = xml.FromUserName[0]
if%20(message === '/new') {
// 重置conversation id
cloud.shared.set(genConversationKey(userName), null)
sendWxMessage('新建成功,開始新的對話吧~~', userName)
return
}
sendWxMessage('AI思考中, 請耐心等待~~', userName)
try {
sendDifyMessage(message, userName,%20(message) => {
sendWxMessage(message, userName)
})
}
catch%20(e) {
console.error('[sendDifyMessage] error', e)
sendWxMessage('介面請求失敗,請聯絡管理員檢視錯誤資訊', userName)
}
}
export default async function%20(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%20(ctx.method === 'GET') {
const signature = getSignature(token, timestamp, nonce, echostr)
if%20(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%20(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 }
}