|
| 1 | +--- |
| 2 | +title: "利用 Dify 构建基于 GPT-4 Turbo 的智能 Agent,实现医疗微信机器人" |
| 3 | +author: gscfwid |
| 4 | +categories: article |
| 5 | +tags: |
| 6 | + - dify |
| 7 | + - gpt4-turbo |
| 8 | +image: /assets/2024/03-wechatbot-with-wechaty-dify-gpt4/bp-post.webp |
| 9 | +--- |
| 10 | + |
| 11 | +> 作者: [gscfwid](https://github.com/gscfwid/),An anesthetist in a big ship of mainland. |
| 12 | +
|
| 13 | +大家好,我是一名医生,同时也是一个技术爱好者。今天我想和大家分享一下我最近的一个项目——利用 Dify 构建基于 GPT-4 Turbo 的智能 Agent,实现高级微信聊天机器人。 |
| 14 | + |
| 15 | +## 为什么选择微信机器人 |
| 16 | + |
| 17 | +首先,我想说一下为什么要做这个机器人。作为一名医生,我的一项重要工作是随访病人,了解他们术后的恢复情况。我们医院每年有 7-8 万的手术量,人工电话随访是非常不现实的。而且,现在运营商对电话的限制也比较严格。我发现,对于病人来说,微信可能是一个更容易接受的随访方式,因为它不会显得打扰到他们的生活。 |
| 18 | + |
| 19 | +## 为什么选择 Wechaty |
| 20 | + |
| 21 | +然而,市面上大多数微信机器人框架都是基于 web 协议的,而目前 web 协议基本处于不可用的状态。例如,虽然 wechaty 可以利用 UOS 上保留的 web 协议,但它无法获取永久的 ID、备注、tag 等数据,这对随访工作非常不利。在尝试了 padlocal 协议后,我觉得它非常适合我的需求。 |
| 22 | + |
| 23 | +## 为什么选择 Dify |
| 24 | + |
| 25 | +接下来,我想说一下为什么选择使用 Dify 来构建这个机器人。首先,我希望这个机器人不仅能完成随访任务,还能成为一个面向病人的医学科普机器人。随着大语言模型的爆发,这个想法变得非常容易实现。利用 wechaty 和大模型的 API,我很快就构思出了一个初步的框架。 |
| 26 | + |
| 27 | +Dify 是一个知名的大模型 Agent 平台,它对 API 的封装要比 OpenAI 官方的 API 更加友好,例如在 prompt 的构建和对话线程的保持方面。虽然 OpenAI 也可以构建 assistant,但是保持对话似乎没有那么容易。另外,Dify 平台本身也构建了一些插件,比如 Google 搜索,可以很容易集成到 API 中。因此,我最终选择了 Dify 作为我的开发平台。 |
| 28 | +以上就是我开发这个微信医疗随访机器人的一些背景和思路。作为一个医生和技术小白,我希望通过分享自己的项目经历,能给大家带来一些启发和思考。在接下来的部分,我会和大家聊聊这个机器人的一些技术细节,欢迎大家提出宝贵的意见和建议。 |
| 29 | + |
| 30 | +## 通过 Dify 创建基于 GPT-4 Turbo 的模型 |
| 31 | + |
| 32 | +Dify 提供了一个简单易用的界面,让我可以快速地创建和测试模型。 |
| 33 | +首先,我在 Dify 平台上创建了一个新的应用,并选择了 GPT-4 Turbo 作为基础模型。在这个初始阶段,我暂时没有使用任何自定义的 prompt 或插件,只是想先做一个简单的测试,看看模型的性能如何。 |
| 34 | + |
| 35 | +创建应用后,Dify 会自动生成一个 API 密钥(API key),我们可以使用这个密钥来调用 Dify 的 API 接口,与我们创建的智能对话模型进行交互。 |
| 36 | + |
| 37 | +## 使用 Wechaty 实现微信机器人 |
| 38 | + |
| 39 | +有了智能对话模型,接下来我们需要一个平台来实现微信机器人,将模型集成到微信中。这里我选择了 Wechaty。Wechaty 是一个开源的对话机器人 SDK,支持个人微信号,使用 Node.js 和 TypeScript 构建。 |
| 40 | + |
| 41 | +下面是我的代码实现,主要分为以下几个部分: |
| 42 | + |
| 43 | +首先,我使用 wechaty-puppet-padlocal 作为 Wechaty 的 Puppet Provider,它通过 iPad 协议连接微信,相比 Web 协议更加稳定可靠。然后使用 WechatyBuilder 来构建我们的机器人实例。 |
| 44 | + |
| 45 | +```javascript |
| 46 | +// 初始化Wechaty |
| 47 | +const { PuppetPadlocal } = require("wechaty-puppet-padlocal"); |
| 48 | +const { WechatyBuilder } = require("wechaty"); |
| 49 | + |
| 50 | +const puppet = new PuppetPadlocal({ |
| 51 | + token: process.env.PUPPET_PADLOCAL_TOKEN, |
| 52 | +}); |
| 53 | + |
| 54 | +const bot = WechatyBuilder.build({ puppet, name: "test" }); |
| 55 | +``` |
| 56 | + |
| 57 | +接下来调用 Dify API 的核心函数。我使用 axios 库发送 POST 请求到 Dify 的 API 端点,传入用户的输入消息、对话 ID 等参数,并通过 API Key 进行身份验证。Dify 会返回智能对话模型生成的回复。 |
| 58 | + |
| 59 | +```javascript |
| 60 | +// 调用Dify API的函数 |
| 61 | +const difyApiKey = process.env.DIFY_API_KEY; |
| 62 | +const difyApiUrl = "https://api.dify.ai/v1/chat-messages"; |
| 63 | + |
| 64 | +async function sendMessage(message, userName) { |
| 65 | + // ... |
| 66 | + try { |
| 67 | + const response = await axios.post( |
| 68 | + difyApiUrl, |
| 69 | + { |
| 70 | + inputs: {}, |
| 71 | + query: message, |
| 72 | + response_mode: "streaming", |
| 73 | + conversation_id: conversationData.conversationId, |
| 74 | + user: userName, |
| 75 | + files: [], |
| 76 | + }, |
| 77 | + { |
| 78 | + headers: { |
| 79 | + Authorization: `Bearer ${difyApiKey}`, |
| 80 | + "Content-Type": "application/json", |
| 81 | + }, |
| 82 | + } |
| 83 | + ); |
| 84 | + // Process response... |
| 85 | + } catch (error) { |
| 86 | + if (error.response) { |
| 87 | + console.error( |
| 88 | + "Dify API responded with status code:", |
| 89 | + error.response.status |
| 90 | + ); |
| 91 | + } else if (error.request) { |
| 92 | + console.error("No response received from Dify API:", error.request); |
| 93 | + } else { |
| 94 | + console.error("Error setting up request to Dify API:", error.message); |
| 95 | + } |
| 96 | + // Handle error appropriately... |
| 97 | + } |
| 98 | + // ... |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +最后,我监听 Wechaty 的 message 事件,当收到用户在群聊中@机器人的消息时,提取出消息内容,调用 sendMessage 函数获取智能回复,然后通过 room.say 将回复发送到群聊中。 |
| 103 | + |
| 104 | +```javascript |
| 105 | +// 监听消息事件 |
| 106 | +bot.on("message", async (message) => { |
| 107 | + // 获取发消息人的信息 |
| 108 | + const id = message.talker().id; |
| 109 | + const room = message.room(); |
| 110 | + const userName = message.name(); |
| 111 | + const text = message.text(); |
| 112 | + // 判断它是否在我已经创建好的SQLite数据库中 |
| 113 | + if (!room) { |
| 114 | + const query = "SELECT * FROM contacts WHERE id = ?"; |
| 115 | + try { |
| 116 | + const row = await db.get(query, [id]); |
| 117 | + if (row != undefined) { |
| 118 | + const reply = await sendMessage(text, userName); |
| 119 | + await message.talker().say(reply); |
| 120 | + } |
| 121 | + } catch (err) { |
| 122 | + console.error(err.message); |
| 123 | + } |
| 124 | + } |
| 125 | +}); |
| 126 | +``` |
| 127 | + |
| 128 | +这里需要强调的一点是,我利用 Dify API 中的 conversation_id 来实现保持对话的功能。这部分代码主要在 sendMessage 函数中: |
| 129 | + |
| 130 | +```javascript |
| 131 | +const conversationMap = new Map(); // 创建一个键值对来保存提问者的信息和conversation_id |
| 132 | +const CONVERSATION_EXPIRATION = 5 * 60 * 1000; // 设定conversation的保持时间,设定为5分钟 |
| 133 | +async function sendMessage(message, userName) { |
| 134 | + // ... |
| 135 | + let conversationData = conversationMap.get(userName); |
| 136 | + const timestamp = Date.now(); |
| 137 | + |
| 138 | + // 如果会话不存在或已过期,则创建新的会话 |
| 139 | + if ( |
| 140 | + !conversationData || |
| 141 | + timestamp - conversationData.timestamp > CONVERSATION_EXPIRATION |
| 142 | + ) { |
| 143 | + conversationData = { conversationId: null, timestamp }; |
| 144 | + conversationMap.set(userName, conversationData); |
| 145 | + } |
| 146 | + |
| 147 | + const response = await axios.post( |
| 148 | + difyApiUrl, |
| 149 | + { |
| 150 | + // ... |
| 151 | + conversation_id: conversationData.conversationId, |
| 152 | + user: userName, |
| 153 | + // ... |
| 154 | + } |
| 155 | + // ... |
| 156 | + ); |
| 157 | + |
| 158 | + // 更新会话ID和时间戳 |
| 159 | + conversationData.timestamp = timestamp; |
| 160 | + |
| 161 | + // ... |
| 162 | + // 下面的代码是由于使用了stream模式来获取Dify的response,所以我需要遍历它的每个回复,找到最终的回复内容 |
| 163 | + for (const line of lines) { |
| 164 | + if (line.startsWith("data:")) { |
| 165 | + const data = JSON.parse(line.slice(5).trim()); |
| 166 | + if (data.event === "agent_thought") { |
| 167 | + // ... |
| 168 | + conversationData.conversationId = data.conversation_id; |
| 169 | + } |
| 170 | + } |
| 171 | + } |
| 172 | + // ... |
| 173 | +} |
| 174 | +``` |
| 175 | + |
| 176 | +通过这种方式,我们可以为每个用户维护一个独立的对话上下文,实现多轮对话。当用户在一定时间内(这里设置为 5 分钟)继续发送消息时,就可以保持上下文连贯;如果超过了这个时间,就会开始一个新的对话。 |
| 177 | + |
| 178 | +接下来的设置就都是在 Dify 平台了,目前我还在制作中。我设想是上传从网络中下载的科普文章作为知识库,限定 GPT-4 只在知识库中作答。Anyway,就不属于技术讨论的范畴了。 |
| 179 | + |
| 180 | +以上就是利用 Dify 和 Wechaty 实现微信智能对话机器人的核心代码。通过 Dify 强大的对话模型和 Wechaty 方便的微信集成,我们可以快速搭建一个实用的医疗科普机器人。当然,这只是一个基础版本,我们还可以继续添加更多功能,如自定义 prompt、知识库搜索等,来进一步提升机器人的智能化水平。 |
0 commit comments