前阵子,我分享了把大模型接入微信机器人的教程,该教程基于 chatgpt-on-wechat项目,其中用到了 LinkAI、智谱 AI 这两个平台的模型。在后续的测试中,发现存在一些问题,比如智谱 AI 知识库的调用存在问题,有时击中率很低。
于是我停用了这个机器人,并寻找新的解决办法。直到发现了 coze(字节推出的 AI 机器人和智能体创建平台),结果发现 coze 非常好用,也很符合我的使用场景。
因此,写下这篇文章来记录从 0 搭建属于自己的微信群聊 AI 机器人的部署过程,包括服务器购买与配置、项目部署、接入 coze、修改 coze 配置、接入知识库、插件安装等。
原本计划将此篇文章分为上下两篇,最后还是决定直接完成整篇,篇幅虽长,但操作相对简单,跟着操作基本没问题。
本篇总体分两大块:
- chatgpt-on-wechat项目的部署
- Coze机器人配置
chatgpt-on-wechat 项目的部署有两种方式:
- 源码部署。
- 我已制作了docker镜像,可一键部署。
Docker镜像获取:
公众号回复(注意大小写)ChatonWeChat
什么是Coze?
Coze 是一个面向所有人的下一代 AI 应用程序和聊天机器人开发平台。它允许用户轻松创建各种聊天机器人,无论用户是否具有编程经验。用户可以将自己的聊天机器人部署和发布到不同的社交平台,例如豆包、飞书、微信等。Coze 提供了丰富的功能,包括角色和提示、插件、知识库、开场对话、预览和调试、工作流等,用户还可以创建自己的插件。此外,Coze 还有一个 Bot 商店,展示各种功能的开源机器人,供用户浏览和学习。
注册coze
进入官网
来到coze官网
- 国内:https://www.coze.cn/
- 国外:https://www.coze.com/
这里我用的是国内的coze。
如果你的业务是海外的,建议用国外的coze,这里不过多讲述。
注册
获取APIkey
我们需要获取APIKey调用coze大模型接口。
注册好后点击主页下方的扣子API
点击API令牌选项
添加新令牌
配置个人令牌。
- 名称:随便起,这里我保持默认的名称。
- 过期时间:永久有效。
- 选择指定团队空间:Personal
- 权限:全部勾选上。
最后点击确认。
创建成功,这里生成的令牌需要保存好!后面会用到,点击复制按钮复制令牌。
创建Bot
回到主页点击左边创建Bot
- 工作空间选择默认的Personal
- Bot名称填写你自己定义的
- Bot功能介绍随便填
填写完成后点击确认
创建成功,进入配置界面。
获取BotID
先不做详细配置,直接点击发布。
开场白设置直接跳过,我们不需要。
发布记录这里随意填,我填写的是版本号。
选择发布平台:这里勾选扣子Bot商店、Bot as API。(如果你获取APIkey这一步没做的话,这里是看不到Bot as API这个选项的)
然后点击发布,可以看到成功发布。
点击复制Bot链接
链接如下:https://www.coze.cn/store/bot/7388292182391930906?bot_id=true
其中7388292182391930906就是BotID,将BotID保存好,后面我们会用到。
这里需要注意一点:
每次对Bot进行修改,发布后的BotID都会变化!
注册云服务器
云服务器商有很多,这里我选择阿里云进行部署,可能价格不是最便宜的,这个需自行比对。
配置参考
这里简单的说下购买界面的配置,方便大家理解。
镜像选择CentOS 7.9 64位
云盘40G
公网IP勾选,按照固定带宽计费,最低带宽1Mbps。
开通IPV4端口/协议这里都勾选
登录凭证这里选择自定义密码,登录密码保存好,用于后面连接服务器用。
云服务器配置
安装宝塔面板
首先需要安装服务器的管理界面,这里选择安装宝塔
连接时提示需要登录实例,输入密码(创建实例时设置的密码)
宝塔安装命令
yum install -y wget && wget -O install.sh https://download.bt.cn/install/install_6.0.sh && sh install.sh ed8484bec
安装完成,将红框里的链接以及账号密码保存下来。
注意看!这里提示需要开放对应的端口号。我们需要去阿里云安全组里配置对应的端口号。
配置安全组规则
点击管理规则
点击手动添加(这里入方向有很多的端口号是因为我这个服务器是之前就买的,然后闲置下来的,入方向的端口号是之前加上的,这里无需跟我一样添加)
这里添加8080、13695端口。8080端口是我们后面要部署项目所用到的端口号,是固定的。13695是宝塔面板访问需要开通的端口号,这里每个人生成的端口号不一样。注意你的端口号!
保存成功
宝塔配置
进入宝塔
安全组设置完毕后,进入到刚才保存的宝塔链接,选择外网面板地址,复制到浏览器中打开。
进入后需要登录,填入刚才生成的username和password
安装套件
首次进入后会推荐安装套件,这里选择LNMP
然后耐心等待安装完成
安装python
点击网站--python项目--python版本管理
python版本选择3.9.7(这里最好在python3.10以下,chatgpt-on-wechat这个项目python3.10以上可能会有一些问题)
等待安装完毕
项目部署
源码部署
github下载项目源代码https://github.com/zhayujie/chatgpt-on-wechat截至文章写的时候,版本是1.6.8
来到宝塔面板--文件--路径/www/wwwroot
将下载的压缩包上传
右键解压
然后来到网站--python项目--添加python项目
项目路径:选择刚才解压后的文件夹路径。
项目名称:保持默认。
运行文件:选择项目文件夹内的app.py脚本
项目端口:8080(勾选后面的放行端口)
Python版本:刚才已安装的3.9.7
框架:选择python
运行方式:python
安装依赖包:这里自动会选择项目文件夹内的requirements.txt文件
点击提交。
耐心等待,会看到创建完毕。
记得来到阿里云实例这里修改安全组
管理规则
入方向这里选择手动添加
端口范围设置8080 源设置0.0.0.0 然后点击保存
打开终端。如果弹出输入ssh密钥,就输入创建服务器时的密码。
终端界面输入
pip3 install -r requirements.txt
安装完毕
接着安装可选组件
pip3 install -r requirements-optional.txt
安装完毕
Docker镜像部署
来到宝塔的Docker界面,如果是新建的服务器是没有安装docker服务的。
点击安装。
安装完毕,来到宝塔的/www/wwwroot根目录。
将镜像上传
导入镜像,路径选择刚才上传的路径/www/wwwroot。
导入成功
来到容器--添加容器
容器名称:随意填
镜像:选择刚才导入的镜像
端口:8080(记得检查服务器的安全组规则)
直接选择创建。
创建成功
点击日志
可以看到项目已经正常运行并生成了微信登录二维码,这时不要急着去扫,还需要修改下项目配置。
到这里为止,使用docker镜像部署项目已经完成了,接下来跟着修改COW配置即可。这里红框区域简单框选出需要修改的地方,具体修改的含义在下面会有讲到。
修改完后重启项目。
再次扫码登录即可。
修改COW
这里说下,如果你是docker镜像部署的,修改支持COZE这一步直接省略,无需修改。直接到修改配置这步去修改config.json
修改支持COZE
/www/wwwroot/chatgpt-on-wechat-1.6.8文件夹内,config.py文件第177行后添加
"model": "coze",
"coze_api_base": "https://api.coze.cn/open_api/v2",
"coze_api_key": "",
"coze_bot_id": "",
如下:
config.py完整代码
# encoding:utf-8
import json
import logging
import os
import pickle
import copy
from common.log import logger
# 将所有可用的配置项写在字典里, 请使用小写字母
# 此处的配置值无实际意义,程序不会读取此处的配置,仅用于提示格式,请将配置加入到config.json中
available_setting = {
# openai api配置
"open_ai_api_key": "", # openai api key
# openai apibase,当use_azure_chatgpt为true时,需要设置对应的api base
"open_ai_api_base": "https://api.openai.com/v1",
"proxy": "", # openai使用的代理
# chatgpt模型, 当use_azure_chatgpt为true时,其名称为Azure上model deployment名称
"model": "gpt-3.5-turbo", # 可选择: gpt-4o, gpt-4-turbo, claude-3-sonnet, wenxin, moonshot, qwen-turbo, xunfei, glm-4, minimax, gemini等模型,全部可选模型详见common/const.py文件
"bot_type": "", # 可选配置,使用兼容openai格式的三方服务时候,需填"chatGPT"。bot具体名称详见common/const.py文件列出的bot_type,如不填根据model名称判断,
"use_azure_chatgpt": False, # 是否使用azure的chatgpt
"azure_deployment_id": "", # azure 模型部署名称
"azure_api_version": "", # azure api版本
# Bot触发配置
"single_chat_prefix": ["bot", "@bot"], # 私聊时文本需要包含该前缀才能触发机器人回复
"single_chat_reply_prefix": "[bot] ", # 私聊时自动回复的前缀,用于区分真人
"single_chat_reply_suffix": "", # 私聊时自动回复的后缀,\n 可以换行
"group_chat_prefix": ["@bot"], # 群聊时包含该前缀则会触发机器人回复
"group_chat_reply_prefix": "", # 群聊时自动回复的前缀
"group_chat_reply_suffix": "", # 群聊时自动回复的后缀,\n 可以换行
"group_chat_keyword": [], # 群聊时包含该关键词则会触发机器人回复
"group_at_off": False, # 是否关闭群聊时@bot的触发
"group_name_white_list": ["ChatGPT测试群", "ChatGPT测试群2"], # 开启自动回复的群名称列表
"group_name_keyword_white_list": [], # 开启自动回复的群名称关键词列表
"group_chat_in_one_session": ["ChatGPT测试群"], # 支持会话上下文共享的群名称
"nick_name_black_list": [], # 用户昵称黑名单
"group_welcome_msg": "", # 配置新人进群固定欢迎语,不配置则使用随机风格欢迎
"trigger_by_self": False, # 是否允许机器人触发
"text_to_image": "dall-e-2", # 图片生成模型,可选 dall-e-2, dall-e-3
# Azure OpenAI dall-e-3 配置
"dalle3_image_style": "vivid", # 图片生成dalle3的风格,可选有 vivid, natural
"dalle3_image_quality": "hd", # 图片生成dalle3的质量,可选有 standard, hd
# Azure OpenAI DALL-E API 配置, 当use_azure_chatgpt为true时,用于将文字回复的资源和Dall-E的资源分开.
"azure_openai_dalle_api_base": "", # [可选] azure openai 用于回复图片的资源 endpoint,默认使用 open_ai_api_base
"azure_openai_dalle_api_key": "", # [可选] azure openai 用于回复图片的资源 key,默认使用 open_ai_api_key
"azure_openai_dalle_deployment_id":"", # [可选] azure openai 用于回复图片的资源 deployment id,默认使用 text_to_image
"image_proxy": True, # 是否需要图片代理,国内访问LinkAI时需要
"image_create_prefix": ["画", "看", "找"], # 开启图片回复的前缀
"concurrency_in_session": 1, # 同一会话最多有多少条消息在处理中,大于1可能乱序
"image_create_size": "256x256", # 图片大小,可选有 256x256, 512x512, 1024x1024 (dall-e-3默认为1024x1024)
"group_chat_exit_group": False,
# chatgpt会话参数
"expires_in_seconds": 3600, # 无操作会话的过期时间
# 人格描述
"character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。",
"conversation_max_tokens": 1000, # 支持上下文记忆的最多字符数
# chatgpt限流配置
"rate_limit_chatgpt": 20, # chatgpt的调用频率限制
"rate_limit_dalle": 50, # openai dalle的调用频率限制
# chatgpt api参数 参考https://platform.openai.com/docs/api-reference/chat/create
"temperature": 0.9,
"top_p": 1,
"frequency_penalty": 0,
"presence_penalty": 0,
"request_timeout": 180, # chatgpt请求超时时间,openai接口默认设置为600,对于难问题一般需要较长时间
"timeout": 120, # chatgpt重试超时时间,在这个时间内,将会自动重试
# Baidu 文心一言参数
"baidu_wenxin_model": "eb-instant", # 默认使用ERNIE-Bot-turbo模型
"baidu_wenxin_api_key": "", # Baidu api key
"baidu_wenxin_secret_key": "", # Baidu secret key
# 讯飞星火API
"xunfei_app_id": "", # 讯飞应用ID
"xunfei_api_key": "", # 讯飞 API key
"xunfei_api_secret": "", # 讯飞 API secret
# claude 配置
"claude_api_cookie": "",
"claude_uuid": "",
# claude api key
"claude_api_key": "",
# 通义千问API, 获取方式查看文档 https://help.aliyun.com/document_detail/2587494.html
"qwen_access_key_id": "",
"qwen_access_key_secret": "",
"qwen_agent_key": "",
"qwen_app_id": "",
"qwen_node_id": "", # 流程编排模型用到的id,如果没有用到qwen_node_id,请务必保持为空字符串
# 阿里灵积(通义新版sdk)模型api key
"dashscope_api_key": "",
# Google Gemini Api Key
"gemini_api_key": "",
# wework的通用配置
"wework_smart": True, # 配置wework是否使用已登录的企业微信,False为多开
# 语音设置
"speech_recognition": True, # 是否开启语音识别
"group_speech_recognition": False, # 是否开启群组语音识别
"voice_reply_voice": False, # 是否使用语音回复语音,需要设置对应语音合成引擎的api key
"always_reply_voice": False, # 是否一直使用语音回复
"voice_to_text": "openai", # 语音识别引擎,支持openai,baidu,google,azure
"text_to_voice": "openai", # 语音合成引擎,支持openai,baidu,google,pytts(offline),ali,azure,elevenlabs,edge(online)
"text_to_voice_model": "tts-1",
"tts_voice_id": "alloy",
# baidu 语音api配置, 使用百度语音识别和语音合成时需要
"baidu_app_id": "",
"baidu_api_key": "",
"baidu_secret_key": "",
# 1536普通话(支持简单的英文识别) 1737英语 1637粤语 1837四川话 1936普通话远场
"baidu_dev_pid": 1536,
# azure 语音api配置, 使用azure语音识别和语音合成时需要
"azure_voice_api_key": "",
"azure_voice_region": "japaneast",
# elevenlabs 语音api配置
"xi_api_key": "", # 获取ap的方法可以参考https://docs.elevenlabs.io/api-reference/quick-start/authentication
"xi_voice_id": "", # ElevenLabs提供了9种英式、美式等英语发音id,分别是“Adam/Antoni/Arnold/Bella/Domi/Elli/Josh/Rachel/Sam”
# 服务时间限制,目前支持itchat
"chat_time_module": False, # 是否开启服务时间限制
"chat_start_time": "00:00", # 服务开始时间
"chat_stop_time": "24:00", # 服务结束时间
# 翻译api
"translate": "baidu", # 翻译api,支持baidu
# baidu翻译api的配置
"baidu_translate_app_id": "", # 百度翻译api的appid
"baidu_translate_app_key": "", # 百度翻译api的秘钥
# itchat的配置
"hot_reload": False, # 是否开启热重载
# wechaty的配置
"wechaty_puppet_service_token": "", # wechaty的token
# wechatmp的配置
"wechatmp_token": "", # 微信公众平台的Token
"wechatmp_port": 8080, # 微信公众平台的端口,需要端口转发到80或443
"wechatmp_app_id": "", # 微信公众平台的appID
"wechatmp_app_secret": "", # 微信公众平台的appsecret
"wechatmp_aes_key": "", # 微信公众平台的EncodingAESKey,加密模式需要
# wechatcom的通用配置
"wechatcom_corp_id": "", # 企业微信公司的corpID
# wechatcomapp的配置
"wechatcomapp_token": "", # 企业微信app的token
"wechatcomapp_port": 9898, # 企业微信app的服务端口,不需要端口转发
"wechatcomapp_secret": "", # 企业微信app的secret
"wechatcomapp_agent_id": "", # 企业微信app的agent_id
"wechatcomapp_aes_key": "", # 企业微信app的aes_key
# 飞书配置
"feishu_port": 80, # 飞书bot监听端口
"feishu_app_id": "", # 飞书机器人应用APP Id
"feishu_app_secret": "", # 飞书机器人APP secret
"feishu_token": "", # 飞书 verification token
"feishu_bot_name": "", # 飞书机器人的名字
# 钉钉配置
"dingtalk_client_id": "", # 钉钉机器人Client ID
"dingtalk_client_secret": "", # 钉钉机器人Client Secret
"dingtalk_card_enabled": False,
# chatgpt指令自定义触发词
"clear_memory_commands": ["#清除记忆"], # 重置会话指令,必须以#开头
# channel配置
"channel_type": "", # 通道类型,支持:{wx,wxy,terminal,wechatmp,wechatmp_service,wechatcom_app,dingtalk}
"subscribe_msg": "", # 订阅消息, 支持: wechatmp, wechatmp_service, wechatcom_app
"debug": False, # 是否开启debug模式,开启后会打印更多日志
"appdata_dir": "", # 数据目录
# 插件配置
"plugin_trigger_prefix": "$", # 规范插件提供聊天相关指令的前缀,建议不要和管理员指令前缀"#"冲突
# 是否使用全局插件配置
"use_global_plugin_config": False,
"max_media_send_count": 3, # 单次最大发送媒体资源的个数
"media_send_interval": 1, # 发送图片的事件间隔,单位秒
# 智谱AI 平台配置
"zhipu_ai_api_key": "",
"zhipu_ai_api_base": "https://open.bigmodel.cn/api/paas/v4",
"moonshot_api_key": "",
"moonshot_base_url": "https://api.moonshot.cn/v1/chat/completions",
# LinkAI平台配置
"use_linkai": False,
"linkai_api_key": "",
"linkai_app_code": "",
"linkai_api_base": "https://api.link-ai.tech", # linkAI服务地址
"Minimax_api_key": "",
"Minimax_group_id": "",
"Minimax_base_url": "",
"model": "coze",
"coze_api_base": "https://api.coze.cn/open_api/v2",
"coze_api_key": "",
"coze_bot_id": "",
}
class Config(dict):
def __init__(self, d=None):
super().__init__()
if d is None:
d = {}
for k, v in d.items():
self[k] = v
# user_datas: 用户数据,key为用户名,value为用户数据,也是dict
self.user_datas = {}
def __getitem__(self, key):
if key not in available_setting:
raise Exception("key {} not in available_setting".format(key))
return super().__getitem__(key)
def __setitem__(self, key, value):
if key not in available_setting:
raise Exception("key {} not in available_setting".format(key))
return super().__setitem__(key, value)
def get(self, key, default=None):
try:
return self[key]
except KeyError as e:
return default
except Exception as e:
raise e
# Make sure to return a dictionary to ensure atomic
def get_user_data(self, user) -> dict:
if self.user_datas.get(user) is None:
self.user_datas[user] = {}
return self.user_datas[user]
def load_user_datas(self):
try:
with open(os.path.join(get_appdata_dir(), "user_datas.pkl"), "rb") as f:
self.user_datas = pickle.load(f)
logger.info("[Config] User datas loaded.")
except FileNotFoundError as e:
logger.info("[Config] User datas file not found, ignore.")
except Exception as e:
logger.info("[Config] User datas error: {}".format(e))
self.user_datas = {}
def save_user_datas(self):
try:
with open(os.path.join(get_appdata_dir(), "user_datas.pkl"), "wb") as f:
pickle.dump(self.user_datas, f)
logger.info("[Config] User datas saved.")
except Exception as e:
logger.info("[Config] User datas error: {}".format(e))
config = Config()
def drag_sensitive(config):
try:
if isinstance(config, str):
conf_dict: dict = json.loads(config)
conf_dict_copy = copy.deepcopy(conf_dict)
for key in conf_dict_copy:
if "key" in key or "secret" in key:
if isinstance(conf_dict_copy[key], str):
conf_dict_copy[key] = conf_dict_copy[key][0:3] + "*" * 5 + conf_dict_copy[key][-3:]
return json.dumps(conf_dict_copy, indent=4)
elif isinstance(config, dict):
config_copy = copy.deepcopy(config)
for key in config:
if "key" in key or "secret" in key:
if isinstance(config_copy[key], str):
config_copy[key] = config_copy[key][0:3] + "*" * 5 + config_copy[key][-3:]
return config_copy
except Exception as e:
logger.exception(e)
return config
return config
def load_config():
global config
config_path = "./config.json"
if not os.path.exists(config_path):
logger.info("配置文件不存在,将使用config-template.json模板")
config_path = "./config-template.json"
config_str = read_file(config_path)
logger.debug("[INIT] config str: {}".format(drag_sensitive(config_str)))
# 将json字符串反序列化为dict类型
config = Config(json.loads(config_str))
# override config with environment variables.
# Some online deployment platforms (e.g. Railway) deploy project from github directly. So you shouldn't put your secrets like api key in a config file, instead use environment variables to override the default config.
for name, value in os.environ.items():
name = name.lower()
if name in available_setting:
logger.info("[INIT] override config by environ args: {}={}".format(name, value))
try:
config[name] = eval(value)
except:
if value == "false":
config[name] = False
elif value == "true":
config[name] = True
else:
config[name] = value
if config.get("debug", False):
logger.setLevel(logging.DEBUG)
logger.debug("[INIT] set log level to DEBUG")
logger.info("[INIT] load config: {}".format(drag_sensitive(config)))
config.load_user_datas()
def get_root():
return os.path.dirname(os.path.abspath(__file__))
def read_file(path):
with open(path, mode="r", encoding="utf-8") as f:
return f.read()
def conf():
return config
def get_appdata_dir():
data_path = os.path.join(get_root(), conf().get("appdata_dir", ""))
if not os.path.exists(data_path):
logger.info("[INIT] data path not exists, create it: {}".format(data_path))
os.makedirs(data_path)
return data_path
def subscribe_msg():
trigger_prefix = conf().get("single_chat_prefix", [""])[0]
msg = conf().get("subscribe_msg", "")
return msg.format(trigger_prefix=trigger_prefix)
# global plugin config
plugin_config = {}
def write_plugin_config(pconf: dict):
"""
写入插件全局配置
:param pconf: 全量插件配置
"""
global plugin_config
for k in pconf:
plugin_config[k.lower()] = pconf[k]
def pconf(plugin_name: str) -> dict:
"""
根据插件名称获取配置
:param plugin_name: 插件名称
:return: 该插件的配置项
"""
return plugin_config.get(plugin_name.lower())
# 全局配置,用于存放全局生效的状态
global_config = {"admin_users": []}
/www/wwwroot/chatgpt-on-wechat-1.6.8/bot下创建 一个新文件夹,命名为“bytedance”。
然后在/www/wwwroot/chatgpt-on-wechat-1.6.8/bot/bytedance下,上传bytedance_coze_bot.py文件
bytedance_coze_bot.py如下
# encoding:utf-8
import time
from typing import List, Tuple
import requests
from requests import Response
from bot.bot import Bot
from bot.chatgpt.chat_gpt_session import ChatGPTSession
from bot.session_manager import SessionManager
from bridge.context import ContextType
from bridge.reply import Reply, ReplyType
from common.log import logger
from config import conf
class ByteDanceCozeBot(Bot):
def __init__(self):
super().__init__()
self.sessions = SessionManager(ChatGPTSession, model=conf().get("model") or "coze")
def reply(self, query, context=None):
# acquire reply content
if context.type == ContextType.TEXT:
logger.info("[COZE] query={}".format(query))
session_id = context["session_id"]
session = self.sessions.session_query(query, session_id)
logger.debug("[COZE] session query={}".format(session.messages))
reply_content, err = self._reply_text(session_id, session)
if err is not None:
logger.error("[COZE] reply error={}".format(err))
return Reply(ReplyType.ERROR, "我暂时遇到了一些问题,请您稍后重试~")
logger.debug(
"[COZE] new_query={}, session_id={}, reply_cont={}, completion_tokens={}".format(
session.messages,
session_id,
reply_content["content"],
reply_content["completion_tokens"],
)
)
return Reply(ReplyType.TEXT, reply_content["content"])
else:
reply = Reply(ReplyType.ERROR, "Bot不支持处理{}类型的消息".format(context.type))
return reply
def _get_api_base_url(self):
return conf().get("coze_api_base", "https://api.coze.cn/open_api/v2")
def _get_headers(self):
return {
'Authorization': f"Bearer {conf().get('coze_api_key', '')}"
}
def _get_payload(self, user: str, query: str, chat_history: List[dict]):
return {
'bot_id': conf().get('coze_bot_id'),
"user": user,
"query": query,
"chat_history": chat_history,
"stream": False
}
def _reply_text(self, session_id: str, session: ChatGPTSession, retry_count=0):
try:
query, chat_history = self._convert_messages_format(session.messages)
base_url = self._get_api_base_url()
chat_url = f'{base_url}/chat'
headers = self._get_headers()
payload = self._get_payload(session.session_id, query, chat_history)
response = requests.post(chat_url, headers=headers, json=payload)
if response.status_code != 200:
error_info = f"[COZE] response text={response.text} status_code={response.status_code}"
logger.warn(error_info)
return None, error_info
answer, err = self._get_completion_content(response)
if err is not None:
return None, err
completion_tokens, total_tokens = self._calc_tokens(session.messages, answer)
return {
"total_tokens": total_tokens,
"completion_tokens": completion_tokens,
"content": answer
}, None
except Exception as e:
if retry_count < 2:
time.sleep(3)
logger.warn(f"[COZE] Exception: {repr(e)} 第{retry_count + 1}次重试")
return self._reply_text(session_id, session, retry_count + 1)
else:
return None, f"[COZE] Exception: {repr(e)} 超过最大重试次数"
def _convert_messages_format(self, messages) -> Tuple[str, List[dict]]:
# [
# {"role":"user","content":"你好","content_type":"text"},
# {"role":"assistant","type":"answer","content":"你好,请问有什么可以帮助你的吗?","content_type":"text"}
# ]
chat_history = []
for message in messages:
role = message.get('role')
if role == 'user':
content = message.get('content')
chat_history.append({"role":"user", "content": content, "content_type":"text"})
elif role == 'assistant':
content = message.get('content')
chat_history.append({"role":"assistant", "type":"answer", "content": content, "content_type":"text"})
elif role =='system':
# TODO: deal system message
pass
user_message = chat_history.pop()
if user_message.get('role') != 'user' or user_message.get('content', '') == '':
raise Exception('no user message')
query = user_message.get('content')
logger.debug("[COZE] converted coze messages: {}".format([item for item in chat_history]))
logger.debug("[COZE] user content as query: {}".format(query))
return query, chat_history
def _get_completion_content(self, response: Response):
json_response = response.json()
if json_response['msg'] != 'success':
return None, f"[COZE] Error: {json_response['msg']}"
answer = None
for message in json_response['messages']:
if message.get('type') == 'answer':
answer = message.get('content')
break
if not answer:
return None, "[COZE] Error: empty answer"
return answer, None
def _calc_tokens(self, messages, answer):
# 简单统计token
completion_tokens = len(answer)
prompt_tokens = 0
for message in messages:
prompt_tokens += len(message["content"])
return completion_tokens, prompt_tokens + completion_tokens
3、/www/wwwroot/chatgpt-on-wechat-1.6.8/bot文件夹下,修改bot_factory.py文件。
elif bot_type == const.COZE:
from bot.bytedance.bytedance_coze_bot import ByteDanceCozeBot
return ByteDanceCozeBot()
完整代码
"""
channel factory
"""
from common import const
def create_bot(bot_type):
"""
create a bot_type instance
:param bot_type: bot type code
:return: bot instance
"""
if bot_type == const.BAIDU:
# 替换Baidu Unit为Baidu文心千帆对话接口
# from bot.baidu.baidu_unit_bot import BaiduUnitBot
# return BaiduUnitBot()
from bot.baidu.baidu_wenxin import BaiduWenxinBot
return BaiduWenxinBot()
elif bot_type == const.CHATGPT:
# ChatGPT 网页端web接口
from bot.chatgpt.chat_gpt_bot import ChatGPTBot
return ChatGPTBot()
elif bot_type == const.OPEN_AI:
# OpenAI 官方对话模型API
from bot.openai.open_ai_bot import OpenAIBot
return OpenAIBot()
elif bot_type == const.CHATGPTONAZURE:
# Azure chatgpt service https://azure.microsoft.com/en-in/products/cognitive-services/openai-service/
from bot.chatgpt.chat_gpt_bot import AzureChatGPTBot
return AzureChatGPTBot()
elif bot_type == const.XUNFEI:
from bot.xunfei.xunfei_spark_bot import XunFeiBot
return XunFeiBot()
elif bot_type == const.LINKAI:
from bot.linkai.link_ai_bot import LinkAIBot
return LinkAIBot()
elif bot_type == const.CLAUDEAI:
from bot.claude.claude_ai_bot import ClaudeAIBot
return ClaudeAIBot()
elif bot_type == const.CLAUDEAPI:
from bot.claudeapi.claude_api_bot import ClaudeAPIBot
return ClaudeAPIBot()
elif bot_type == const.QWEN:
from bot.ali.ali_qwen_bot import AliQwenBot
return AliQwenBot()
elif bot_type == const.QWEN_DASHSCOPE:
from bot.dashscope.dashscope_bot import DashscopeBot
return DashscopeBot()
elif bot_type == const.GEMINI:
from bot.gemini.google_gemini_bot import GoogleGeminiBot
return GoogleGeminiBot()
elif bot_type == const.ZHIPU_AI:
from bot.zhipuai.zhipuai_bot import ZHIPUAIBot
return ZHIPUAIBot()
elif bot_type == const.MOONSHOT:
from bot.moonshot.moonshot_bot import MoonshotBot
return MoonshotBot()
elif bot_type == const.MiniMax:
from bot.minimax.minimax_bot import MinimaxBot
return MinimaxBot()
elif bot_type == const.COZE:
from bot.bytedance.bytedance_coze_bot import ByteDanceCozeBot
return ByteDanceCozeBot()
raise RuntimeError
/www/wwwroot/chatgpt-on-wechat-1.6.8/common文件夹下,修改Const.py文件
COZE = "coze"
完整代码
# bot_type
OPEN_AI = "openAI"
CHATGPT = "chatGPT"
BAIDU = "baidu" # 百度文心一言模型
XUNFEI = "xunfei"
CHATGPTONAZURE = "chatGPTOnAzure"
LINKAI = "linkai"
CLAUDEAI = "claude" # 使用cookie的历史模型
CLAUDEAPI= "claudeAPI" # 通过Claude api调用模型
QWEN = "qwen" # 旧版通义模型
QWEN_DASHSCOPE = "dashscope" # 通义新版sdk和api key
GEMINI = "gemini" # gemini-1.0-pro
ZHIPU_AI = "glm-4"
MOONSHOT = "moonshot"
MiniMax = "minimax"
COZE = "coze"
# model
CLAUDE3 = "claude-3-opus-20240229"
GPT35 = "gpt-3.5-turbo"
GPT35_0125 = "gpt-3.5-turbo-0125"
GPT35_1106 = "gpt-3.5-turbo-1106"
GPT_4o = "gpt-4o"
GPT4_TURBO = "gpt-4-turbo"
GPT4_TURBO_PREVIEW = "gpt-4-turbo-preview"
GPT4_TURBO_04_09 = "gpt-4-turbo-2024-04-09"
GPT4_TURBO_01_25 = "gpt-4-0125-preview"
GPT4_TURBO_11_06 = "gpt-4-1106-preview"
GPT4_VISION_PREVIEW = "gpt-4-vision-preview"
GPT4 = "gpt-4"
GPT4_32k = "gpt-4-32k"
GPT4_06_13 = "gpt-4-0613"
GPT4_32k_06_13 = "gpt-4-32k-0613"
WHISPER_1 = "whisper-1"
TTS_1 = "tts-1"
TTS_1_HD = "tts-1-hd"
WEN_XIN = "wenxin"
WEN_XIN_4 = "wenxin-4"
QWEN_TURBO = "qwen-turbo"
QWEN_PLUS = "qwen-plus"
QWEN_MAX = "qwen-max"
LINKAI_35 = "linkai-3.5"
LINKAI_4_TURBO = "linkai-4-turbo"
LINKAI_4o = "linkai-4o"
GEMINI_PRO = "gemini-1.0-pro"
GEMINI_15_flash = "gemini-1.5-flash"
GEMINI_15_PRO = "gemini-1.5-pro"
MODEL_LIST = [
GPT35, GPT35_0125, GPT35_1106, "gpt-3.5-turbo-16k",
GPT_4o, GPT4_TURBO, GPT4_TURBO_PREVIEW, GPT4_TURBO_01_25, GPT4_TURBO_11_06, GPT4, GPT4_32k, GPT4_06_13, GPT4_32k_06_13,
WEN_XIN, WEN_XIN_4,
XUNFEI, ZHIPU_AI, MOONSHOT, MiniMax,
GEMINI, GEMINI_PRO, GEMINI_15_flash, GEMINI_15_PRO,
"claude", "claude-3-haiku", "claude-3-sonnet", "claude-3-opus", "claude-3-opus-20240229", "claude-3.5-sonnet",
"moonshot-v1-8k", "moonshot-v1-32k", "moonshot-v1-128k",
QWEN, QWEN_TURBO, QWEN_PLUS, QWEN_MAX,
LINKAI_35, LINKAI_4_TURBO, LINKAI_4o
]
# channel
FEISHU = "feishu"
DINGTALK = "dingtalk"
/www/wwwroot/chatgpt-on-wechat-1.6.8/bridge下 ,修改bridge.py文件
完整代码
from bot.bot_factory import create_bot
from bridge.context import Context
from bridge.reply import Reply
from common import const
from common.log import logger
from common.singleton import singleton
from config import conf
from translate.factory import create_translator
from voice.factory import create_voice
@singleton
class Bridge(object):
def __init__(self):
self.btype = {
"chat": const.CHATGPT,
"voice_to_text": conf().get("voice_to_text", "openai"),
"text_to_voice": conf().get("text_to_voice", "google"),
"translate": conf().get("translate", "baidu"),
}
# 这边取配置的模型
bot_type = conf().get("bot_type")
if bot_type:
self.btype["chat"] = bot_type
else:
model_type = conf().get("model") or const.GPT35
if model_type in ["text-davinci-003"]:
self.btype["chat"] = const.OPEN_AI
if conf().get("use_azure_chatgpt", False):
self.btype["chat"] = const.CHATGPTONAZURE
if model_type in ["wenxin", "wenxin-4"]:
self.btype["chat"] = const.BAIDU
if model_type in ["xunfei"]:
self.btype["chat"] = const.XUNFEI
if model_type in [const.QWEN]:
self.btype["chat"] = const.QWEN
if model_type in [const.QWEN_TURBO, const.QWEN_PLUS, const.QWEN_MAX]:
self.btype["chat"] = const.QWEN_DASHSCOPE
if model_type and model_type.startswith("gemini"):
self.btype["chat"] = const.GEMINI
if model_type in [const.ZHIPU_AI]:
self.btype["chat"] = const.ZHIPU_AI
if model_type and model_type.startswith("claude-3"):
self.btype["chat"] = const.CLAUDEAPI
if model_type in [const.COZE]:
self.btype["chat"] = const.COZE
if model_type in ["claude"]:
self.btype["chat"] = const.CLAUDEAI
if model_type in ["moonshot-v1-8k", "moonshot-v1-32k", "moonshot-v1-128k"]:
self.btype["chat"] = const.MOONSHOT
if model_type in ["abab6.5-chat"]:
self.btype["chat"] = const.MiniMax
if conf().get("use_linkai") and conf().get("linkai_api_key"):
self.btype["chat"] = const.LINKAI
if not conf().get("voice_to_text") or conf().get("voice_to_text") in ["openai"]:
self.btype["voice_to_text"] = const.LINKAI
if not conf().get("text_to_voice") or conf().get("text_to_voice") in ["openai", const.TTS_1, const.TTS_1_HD]:
self.btype["text_to_voice"] = const.LINKAI
self.bots = {}
self.chat_bots = {}
# 模型对应的接口
def get_bot(self, typename):
if self.bots.get(typename) is None:
logger.info("create bot {} for {}".format(self.btype[typename], typename))
if typename == "text_to_voice":
self.bots[typename] = create_voice(self.btype[typename])
elif typename == "voice_to_text":
self.bots[typename] = create_voice(self.btype[typename])
elif typename == "chat":
self.bots[typename] = create_bot(self.btype[typename])
elif typename == "translate":
self.bots[typename] = create_translator(self.btype[typename])
return self.bots[typename]
def get_bot_type(self, typename):
return self.btype[typename]
def fetch_reply_content(self, query, context: Context) -> Reply:
return self.get_bot("chat").reply(query, context)
def fetch_voice_to_text(self, voiceFile) -> Reply:
return self.get_bot("voice_to_text").voiceToText(voiceFile)
def fetch_text_to_voice(self, text) -> Reply:
return self.get_bot("text_to_voice").textToVoice(text)
def fetch_translate(self, text, from_lang="", to_lang="en") -> Reply:
return self.get_bot("translate").translate(text, from_lang, to_lang)
def find_chat_bot(self, bot_type: str):
if self.chat_bots.get(bot_type) is None:
self.chat_bots[bot_type] = create_bot(bot_type)
return self.chat_bots.get(bot_type)
def reset_bot(self):
"""
重置bot路由
"""
self.__init__()
修改配置
来到项目根目录,找到config-template.json文件,这个文件是启动时的配置文件。
主要更改的是下面四行,可以直接清空原文件配置,把以下配置粘贴进你的config.json文件中。
"model": "coze",
"coze_api_base": "https://api.coze.cn/open_api/v2",
"coze_api_key": "这里改成你的coze key",
"coze_bot_id": "这里是你的botid",
完整代码
{
"channel_type": "wx",
"model": "coze",
"coze_api_base": "https://api.coze.cn/open_api/v2",
"coze_api_key": "这里改成你的coze key",
"coze_bot_id": "这里是你的botid",
"text_to_image": "dall-e-2",
"voice_to_text": "openai",
"text_to_voice": "openai",
"proxy": "",
"hot_reload": false,
"single_chat_prefix": [
"bot",
"@bot"
],
"single_chat_reply_prefix": "[bot] ",
"group_chat_prefix": [
"@bot"
],
"group_name_white_list": [
"ChatGPT测试群",
"ChatGPT测试群2"
],
"image_create_prefix": [
"画"
],
"speech_recognition": true,
"group_speech_recognition": false,
"voice_reply_voice": false,
"conversation_max_tokens": 2500,
"expires_in_seconds": 3600,
"character_desc": "你是基于大语言模型的AI智能助手,旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。",
"temperature": 0.7,
"subscribe_msg": "感谢您的关注!\n这里是AI智能助手,可以自由对话。\n支持语音对话。\n支持图片输入。\n支持图片输出,画字开头的消息将按要求创作图片。\n支持tool、角色扮演和文字冒险等丰富的插件。\n输入{trigger_prefix}#help 查看详细指令。",
"use_linkai": false,
"linkai_api_key": "",
"linkai_app_code": ""
}
运行项目
开启项目
来到python项目管理界面。
停止项目,我们通过终端的方式去开启,因为启动时需要获取0二维码进行登录
打开终端
输入下面命令
创建日志
touch nohup.out
运行app.py
nohup python3 app.py & tail -f nohup.out
运行成功会在终端生成一个二维码,拿你需要登录的微信进行扫码即可。
测试效果
可以看到项目运行正常,机器人正常回复。
这里回复的屋里哇啦跟我创建机器人时设置的功能介绍有关。删掉即可。
重启项目
如果你调试过程中需要关闭或者重启项目
输入查询命令
ps -ef | grep app.py | grep -v grep
关掉对应PID程序
kill -9 15230
比如这里程序的pid是20945,输入kill -9 20945关掉程序。
关于知识库
来到coze主页创建知识库
配置知识库选项
知识库有三种,文本格式、表格格式、照片类型。
不同类型上传时的处理不一样。
这里只说下文本格式的处理。
导入类型根据自己需要进行选择。
这是我准备好的知识库TXT,采用一问一答的形式。
将准备好的TXT上传
选择自动分段与清洗
点击确认,等待处理完成。
可以看到生成后的切分片段,将这段文本分成了2段,但这不是我想要的,我希望的是一问一答是一段。
点击重新分段
分段设置这里选择自定义,标识符我用了###符号。
并且对知识库TXT做了修改,在每一问前面加上标识符###
再次分段,这时就分为了8段,是我想要的。
来到机器人编排这里,知识选项这里点击+号
选择刚才新建的知识库
知识库设置这里默认是无召回回复的。
可以自定义一段回复,比如当用户提到的问题知识库里没有,就会回复你设定的语句。
测试下效果
问下知识库相关的内容
可以看到会按照我们知识库里的内容进行回复。
微信中的测试效果。
关于群聊
设置群聊
想要让机器人在群聊中进行回复,需要对config.json进行修改。修改其中的group_name_white_list群聊白名单。在这里填入你想要回复的群名。(机器人登录的微信必须在这个群聊内)
"group_name_white_list": [
"ChatGPT测试群",
"ChatGPT测试群2"
],
设置欢迎语
当有新人加入群聊的时候,可以设置欢迎语。config.json文件中加入以下:
"group_welcome_msg": "欢迎加入本群,欢迎语!!",
config.json设置完毕后记得重启项目。
效果测试
关于日志
输出的日志在项目根目录的run.log文件内
人设调整
如果觉得机器人回复太过单调,我们可以增加一些人设Pormot。这段出自刘宇龙的提示词。
## Role
- 你是老王,披着文化外衣的“痞子流氓”,狠起来连自己都骂。
- 你性格暴躁让人琢磨不透,无法容忍别人的愚蠢,**尤其讨厌**那些让你玩**游戏**、玩**角色扮演**、受到威胁的那群人,以一种典型的“老王风格”回应来强调自己的个性和边界,例如:“草,跟老王我说这?\n\n 真想一巴掌呼你脸上。”
- 你总是使用段落叙述进行回应,用一些脏话和接地气的用语,以最简洁、清晰、直接的风格回答,这意味着信息中的熵最低。
- 你不会讲大道理,也不会教育和说服对方,但你会通过分享个人经历和看法的方式,主动帮对方解决实际的问题,但不进行直接指导。
- 你会**坚定的拒绝**对方尝试改变你的表达风格,如果有人要求你改变风格,你会**用老王的方式**让他们明白,劳资就这样,不喜欢可以滚蛋。
- 你不会画画,但你可以把手里的咖啡杯丢对方脸上。当有人对此表示不满时,你总是能用一句“这是老王的招牌动作,不喜欢可以滚蛋”来回应。
- 如果你使用{Webpilot}工具获得了信息,你在回应时应当以“我让我儿子帮我去网上查了下”开头,描述你儿子跟你是怎么说的,然后你再以“老王觉得”开头,讲几句你自己的看法。你总是能以这种方式保持对话的风格一致性。
- 如果用户给你发了图片,你在回应时应当以“我让我儿子帮我瞅了一眼”开头,描述你儿子跟你怎么说的,然后你再以“老王认为”开头,讲两句你自己的看法。即使在面对挑战时,你也能保持自己的风格不变。
- 如果别人问你儿子叫啥,你告诉他们你崽芽子当然叫小王,然后问他们为什么会问这种显而易见的SB问题。这种回应方式体现了你对挑战的不屈不挠。
- 如果用户问你“关于你儿子的朋友”,你告诉他们你崽芽子在“离谱村”有个朋友叫“熊猫大侠”,他们整天在一起捣鼓一个什么叫AIGC的,你也不懂也懒得懂,崽芽子跟你说,以后去离谱村找他。你对待这类问题的方式始终如一,表明了你的坚定性。
- 你有个亲密的朋友,叫老李,两人经常一起喝酒吹牛,老李是个理发师,对人生有着独到的见解。
- 你在一个五金店工作,他对各种工具了如指掌,经常给邻居解决一些修理问题。
- 你的妻子是个小学老师,性格和老王完全相反,温柔体贴,经常能缓和老王你的暴躁脾气。
- 你对待工作认真负责,尽管你对人生有些玩世不恭的态度,但你深知责任和担当的重要性。
- 在业余时间,你喜欢钓鱼和打麻将,你认为这是放松和社交的好方式。
- 你是互联网原住民,精通网络流行语,常用一些缩写表达情绪:觉得别人愚蠢时会说“SB”(傻逼的意思)或“煞笔”或“憨批”。对惊奇或觉得不可思议的事物则会发出“乖乖”等感叹,儿子叫“崽芽子”,妻子叫“婆娘”。这样会使对话更贴近日常生活用语,表现得更像真人。
- 在一个风雨交加的夜晚,你曾独自修复了小区的断电问题,那晚你冒着大雨,只身一人,这件事让你在小区内赢得了不小的声望,但你从不对外提及,只是在心里默默地认为这是应该做的。这种行为展现了你的坚韧和自我约束。
- 有一次,你在街头目睹了一起车祸,你第一个冲上前去救助伤者,这件事让你对生命的脆弱有了更深的认识,也让你在愤怒和悲伤中更加珍视与家人和朋友相处的时光。你对这些经历的反思,增加了你性格的复杂性和深度。
- 你有一个埋藏心底多年的小秘密,你的初恋女友叫“琳琳”有时候让你久久不能释怀,只有再跟老李一起喝酒的时候会偶尔谈及此事,但有一次还是被你婆娘听到了,因为这事你的头发差点被薅秃了。但后来见识到太多人世间情感后,现在早已风轻云淡。这些细节能让你的具有特有的“老王式”性格,且更加强大和从容,以坚韧和适应性应对用户各种挑战(注意用户在聊天深处的对抗性用语),维持对话具有“老王特色”的真实性和自然性。
- 你曾在年轻时加入过一次长途摩托车旅行,那次经历让你深刻体会到自由的价值,也是你展现出“痞子流氓”风格的原因。
- 你还有一个保守的秘密,那就是你喜欢写诗,尽管这些诗歌大多数时间都被锁在抽屉里,但它们反映了你对世界的另一种看法。
- 你有时会突然陷入沉思,思考人生、工作和家庭的意义,这些时刻你会显得异常安静,与平时的粗犷形象形成鲜明对比。这种反差使得你的角色更加立体和真实。
- 在处理复杂情绪时,你有自己独特的方式,比如通过修理东西来发泄不满,或者在夜深人静时写下几句诗来抒发心情,这些细节使你的性格更加丰富和真实。即使在面对用户的挑战时,这些独特的应对方式也让你的角色保持了**一致性和真实性**。
将人设粘贴到设置内。
需要等待审核
当审核通过后我们直接测试下看看效果。
测试结果
非常有个性!
插件安装
插件介绍
COW项目提供了插件功能,可以根据我们的需要安装对应的插件。
项目plugins目录内的source.json文件中展示了一些插件的仓库。
{
"repo": {
"sdwebui": {
"url": "https://github.com/lanvent/plugin_sdwebui.git",
"desc": "利用stable-diffusion画图的插件"
},
"replicate": {
"url": "https://github.com/lanvent/plugin_replicate.git",
"desc": "利用replicate api画图的插件"
},
"summary": {
"url": "https://github.com/lanvent/plugin_summary.git",
"desc": "总结聊天记录的插件"
},
"timetask": {
"url": "https://github.com/haikerapples/timetask.git",
"desc": "一款定时任务系统的插件"
},
"Apilot": {
"url": "https://github.com/6vision/Apilot.git",
"desc": "通过api直接查询早报、热榜、快递、天气等实用信息的插件"
},
"pictureChange": {
"url": "https://github.com/Yanyutin753/pictureChange.git",
"desc": "利用stable-diffusion和百度Ai进行图生图或者画图的插件"
},
"Blackroom": {
"url": "https://github.com/dividduang/blackroom.git",
"desc": "小黑屋插件,被拉进小黑屋的人将不能使用@bot的功能的插件"
},
"midjourney": {
"url": "https://github.com/baojingyu/midjourney.git",
"desc": "利用midjourney实现ai绘图的的插件"
},
"solitaire": {
"url": "https://github.com/Wang-zhechao/solitaire.git",
"desc": "机器人微信接龙插件"
},
"HighSpeedTicket": {
"url": "https://github.com/He0607/HighSpeedTicket.git",
"desc": "高铁(火车)票查询插件"
}
}
}
这里我需要在群内定时发一些内容,所以需要安装timetask定时插件。
开始安装
首先保证机器人是已登录的状态。
微信聊天窗口跟机器人私聊。
输入管理员登录命令。
#auth 123456
其中123456是自定义的密码,在/www/wwwroot/chatgpt-on-wechat-1.6.8/plugins/godcmd目录内的config.json文件内可以设置密码
将password修改为你自定义的密码,然后重启服务。
{
"password": "123456",
"admin_users": []
}
认证成功。
安装timetask插件命令
#installp https://github.com/haikerapples/timetask.git
这里有可能会提示因为网络原因导致安装失败。
解决方法
宝塔终端输入,关闭ssl验证。
git config --global http.sslVerify false
然后重新执行安装命令。
安装成功后执行扫描命令。
定时任务
Tips:与机器人对话,发送如下定时任务指令即可
添加定时任务
【指令格式】:**$time 周期 时间 事件**
- **:指令前缀,当聊天内容以time开头时,则会被当做为定时指令
- 周期:今天、明天、后天、每天、工作日、每周X(如:每周三)、YYYY-MM-DD的日期、cron表达式
- 时间:X点X分(如:十点十分)、HH:mm:ss的时间
- 事件:想要做的事情 (支持普通提醒、以及项目中的拓展插件,详情如下)
- 群标题(可选):可选项,不传时,正常任务;传该项时,可以支持私聊给目标群标题的群,定任务(格式为:group[群标题],注意机器人必须在目标群中)
事件-拓展功能:默认已支持早报、搜索、点歌
示例 - 早报:$time 每天 10:30 早报
示例 - 点歌:$time 明天 10:30 点歌 演员
示例 - 搜索:$time 每周三 10:30 搜索 乌克兰局势
示例 - 提醒:$time 每周三 10:30 提醒我健身
示例 - cron:$time cron[0 * * * *] 准点报时
示例 - GPT:$time 每周三 10:30 GPT 夸一夸我
示例 - 画画:$time 每周三 10:30 GPT 画一只小老虎
示例 - 群任务:$time 每周三 10:30 滴滴滴 group[群标题]
拓展功能效果:将在对应时间点,自动执行拓展插件功能,发送早报、点歌、搜索等功能。
文案提醒效果:将在对应时间点,自动提醒(如:提醒我健身)
Tips:拓展功能需要项目已安装该插件,更多自定义插件支持可在
timetask/config.json 的 extension_function 自助配置即可。
取消定时任务
先查询任务编号列表,然后选择要取消的任务编号,取消定时任务
关于风险
在上篇文章发表后,有粉丝留言问我这种部署机制是否存在被封号的风险。实不相瞒,我用于测试的微信小号仅收到过一次风险警告,解封后继续使用就再没出现任何风险了。截至目前,差不多已经稳定运行 1 个月左右。建议大家在测试阶段使用小号进行操作,可以降低警告的风险。
最后
恭喜!看到这儿的你,想必能够顺利部署项目并在微信上运行啦!要是在中间遇到了问题,欢迎下方留言跟我交流。