环境:dify 14.2 docker版本;
模型:glm-4-flash;
工具:dify-on-wechat,timetask;
一、背景
在上一篇《零成本——用 Dify 打造微信虚拟恋人》中,我们顺利地借助 Dify 与 dify-on-wechat 成功打造出了私人定制版的虚拟恋人,且可通过微信实时在线。然而,其存在一定缺陷,即表现得不够主动积极,仅会在被询问时进行回答。今日,我们将进行进阶玩法,以使虚拟恋人能够积极主动地发起对话。
二、实现思路
Timetask 是一款可在dify-on-wechat上运行的插件,其支持自定义定时任务,包括自定义时间、轮询周期等,且具备动态添加任务、取消任务以及查看任务列表等功能,是一款功能强大的定时任务系统插件。通过“dify-on-wechat”,我们引入“Timetask”插件。凭借“Timetask”插件的能力,能够实现定时触发指定问句或内容,进而实现虚拟恋人主动向我们发起对话。
三、实现步骤
-
安装timetask插件:进入dify-on-wechat服务的插件文件夹中,docker环境下在/app/plugins中。
cd /app/plugins git clone https://github.com/haikerapples/timetask.git cd timetask
-
进入timetask文件夹中,按需修改配置文件 config.json,可以不用改,就用默认的;
{ #定时任务前缀(以该前缀时,会被定时任务插件捕获) "command_prefix": "$time", #是否开启debug(会输出日志) "debug": false, #检测频率(默认1秒一次,注意不建议修改!!如果任务带秒钟,则可能会被跳过) "time_check_rate": 1, #Excel中迁移任务的时间(默认在凌晨4点将Excel 任务列表sheet 中失效的任务 迁移至 -> 历史任务sheet中) "move_historyTask_time": "04:00:00", #是否每个任务回复前,均 路由查询一遍是否能被其他插件解释,若会被解释,则使用解释内容回复;否则继续查询是否开启了拓展功能,如果均不可被消费,则最终使用原始内容兜底 #比如 $time 今天 13:35 搜索股票,到达目标时间,则会将 “搜索股票”的关键词默认路由到其他插件查询一遍,如果可以被其他插件解释,则再会使用使用解释后的内容回复。 #定时内容可自由设定,比如 “搜索股票”、“$tool 查询天气”,只要你的工程的插件可以解释关键字即可(前面2个内容为示例,是否可以成功取决于你工程是否有识别该关键字的插件) "is_open_route_everyReply": true, #是否开启拓展功能(开启后,会识别项目中已安装的插件,如果命中 extension_function中的前缀,则会将消息路由转发给目标插件) "is_open_extension_function": true, #支持的拓展功能列表(理论上 已安装的插件,均支持路由转发,其他插件可自主配置,参考早报的配置方式) "extension_function": [ { # 触发词 "key_word": "早报", # 路由插件的 指令前缀 "func_command_prefix":"$tool " }, { "key_word": "点歌", "func_command_prefix": "$" }, { "key_word": "搜索", "func_command_prefix": "$tool google " }, { # 触发词 "key_word": "GPT", "func_command_prefix": "GPT" } ] }
-
重点: timetask项目作者已经很久没有维护了,在这个项目代码里有个bug,需要我们手动处理下,不然无法成功使用,打开TimeTaskTool.py文件,第80行和81行需要注释掉:
-
定制任务:通过 timetask 的配置文件我们可以看到,timetask 支持用户自定义功能,以实现不同的能力。这里,我们以定时发送天气预报为例,实现每天早上 08:00,虚拟恋人主动向我们发送当日天气预报和穿衣建议。发送的内容我们通过 dify 工作流实现并自动写入数据库中(参考《dify 实现数据自动写入数据库》),然后编写 dify-on-wechat 的天气插件,实现调用数据库中的天气信息,并将天气插件加入 timetask 的配置文件中,实现关键词调用。
编写天气插件,在/app/plugins中新建文件夹,命名为DifyTools,文件夹中新增文件DifyTools.py,这个插件可以实现当用户问句是“天气日报”时,自动查询数据库中天气预报的内容;
import os import json import requests from common.log import logger import plugins from bridge.context import ContextType from bridge.reply import Reply, ReplyType from plugins import * import config import mysql.connector # 导入用于连接MySQL数据库的库 @plugins.register(name="DifyTools", desc="根据不同指令获取对应数据库内容并回复", version="1.0", author="vhql", desire_priority=501) class DifyTools(Plugin): def __init__(self): super().__init__() self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context logger.info(f"[{__class__.__name__}] initialized") def get_help_text(self, **kwargs): return "输入“天气日报”获取对应最新天气。" def on_handle_context(self, e_context): if e_context['context'].type == ContextType.TEXT: content = e_context["context"].content.strip() if content.startswith("天气日报"): logger.info(f"[{__class__.__name__}] 收到消息: {content}") self.fetch_weather_news(e_context) def fetch_weather_news(self, e_context): return self._fetch_news_from_table(e_context, "weather_new_report") def _fetch_news_from_table(self, e_context, table_name): """ 从指定的数据库表中获取最近一条数据,并进行相应处理。 :param e_context: 消息上下文对象,包含回复等相关信息 :param table_name: 要查询数据的数据库表名 :return: 无 """ db_config = { "host": "192.168.3.86", "port": "3306", "user": "user", "password": "password", "charset": "utf8", "database": "database" } try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor() query = f"SELECT date, text FROM {table_name} ORDER BY date DESC LIMIT 1" cursor.execute(query) result = cursor.fetchone() if result: date, text = result self.construct_reply([{"date": date, "text": text}], e_context) else: logger.error(f"未查询到{table_name.split('_')[0]}天气数据") self.send_error_reply(e_context, f"获取{table_name.split('_')[0]}天气失败,暂无数据。") cursor.close() conn.close() except Exception as e: logger.error(f"数据库操作抛出异常: {e}") self.send_error_reply(e_context, f"请求{table_name.split('_')[0]}天气失败,请稍后再试。") def construct_reply(self, newslist, e_context): reply = Reply() reply.type = ReplyType.TEXT content = e_context["context"].content.strip() if content.startswith("天气日报"): reply_prefix = "📢 天气预报:\n\n" else: reply_prefix = "" # 构造回复内容 reply.content = reply_prefix for i, news_item in enumerate(newslist, 1): date = news_item.get('date', '未知日期') text = news_item.get('text', '').replace("**", "").replace("#", "") reply.content += f"⌚️更新时间:{date}\n\n{text}" e_context["reply"] = reply e_context.action = EventAction.BREAK_PASS def send_error_reply(self, e_context, message): reply = Reply() reply.type = ReplyType.TEXT reply.content = message e_context["reply"] = reply e_context.action = EventAction.BREAK_PASS
新增文件__init__.py
from .DifyTools import *
-
重启dify-on-wechat服务,会自动安装timetask和DifyTools插件,和虚拟恋人发送#help timetask可以查看timetask功能介绍。
-
和虚拟恋人发送 “$time 每天 06:50:00 天气日报”,以后每天早上虚拟恋人都会主动和你发送当天的天气预报啦!
关注微信公众号【红岸解码室】,发送“天气预报”,获取天气预报DSL文件!