2022-12-18 14:03:34 +08:00
# encoding:utf-8
2022-12-13 01:50:43 +08:00
import json
2023-04-04 14:02:14 +08:00
import logging
2022-12-13 01:50:43 +08:00
import os
2023-04-04 00:59:21 +08:00
import pickle
2022-12-13 01:50:43 +08:00
2023-04-17 01:00:08 +08:00
from common . log import logger
2023-03-26 03:31:29 +08:00
# 将所有可用的配置项写在字典里, 请使用小写字母
2023-03-27 14:40:19 +08:00
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 " ,
" use_azure_chatgpt " : False , # 是否使用azure的chatgpt
2023-04-17 01:00:08 +08:00
" azure_deployment_id " : " " , # azure 模型部署名称
2023-03-27 14:40:19 +08:00
# Bot触发配置
" single_chat_prefix " : [ " bot " , " @bot " ] , # 私聊时文本需要包含该前缀才能触发机器人回复
" single_chat_reply_prefix " : " [bot] " , # 私聊时自动回复的前缀,用于区分真人
" group_chat_prefix " : [ " @bot " ] , # 群聊时包含该前缀则会触发机器人回复
" group_chat_reply_prefix " : " " , # 群聊时自动回复的前缀
" 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测试群 " ] , # 支持会话上下文共享的群名称
2023-04-03 23:58:19 +08:00
" trigger_by_self " : False , # 是否允许机器人触发
2023-03-27 14:40:19 +08:00
" image_create_prefix " : [ " 画 " , " 看 " , " 找 " ] , # 开启图片回复的前缀
2023-04-17 01:00:08 +08:00
" concurrency_in_session " : 1 , # 同一会话最多有多少条消息在处理中, 大于1可能乱序
2023-04-17 01:24:39 +08:00
" image_create_size " : " 256x256 " , # 图片大小,可选有 256x256, 512x512, 1024x1024
2023-03-27 14:40:19 +08:00
# 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
2023-03-25 18:08:37 +08:00
" temperature " : 0.9 ,
" top_p " : 1 ,
" frequency_penalty " : 0 ,
" presence_penalty " : 0 ,
2023-04-17 01:00:08 +08:00
" request_timeout " : 60 , # chatgpt请求超时时间, openai接口默认设置为600, 对于难问题一般需要较长时间
" timeout " : 120 , # chatgpt重试超时时间, 在这个时间内, 将会自动重试
2023-03-27 14:40:19 +08:00
# 语音设置
" speech_recognition " : False , # 是否开启语音识别
" group_speech_recognition " : False , # 是否开启群组语音识别
" voice_reply_voice " : False , # 是否使用语音回复语音, 需要设置对应语音合成引擎的api key
2023-04-01 22:27:11 +08:00
" always_reply_voice " : False , # 是否一直使用语音回复
2023-04-11 19:01:40 +08:00
" voice_to_text " : " openai " , # 语音识别引擎, 支持openai,baidu,google,azure
2023-04-01 16:36:27 +08:00
" text_to_voice " : " baidu " , # 语音合成引擎, 支持baidu,google,pytts(offline),azure
# baidu 语音api配置, 使用百度语音识别和语音合成时需要
2023-03-27 14:40:19 +08:00
" baidu_app_id " : " " ,
" baidu_api_key " : " " ,
" baidu_secret_key " : " " ,
# 1536普通话(支持简单的英文识别) 1737英语 1637粤语 1837四川话 1936普通话远场
" baidu_dev_pid " : " 1536 " ,
2023-04-01 16:36:27 +08:00
# azure 语音api配置, 使用azure语音识别和语音合成时需要
" azure_voice_api_key " : " " ,
" azure_voice_region " : " japaneast " ,
2023-03-27 14:40:19 +08:00
# 服务时间限制, 目前支持itchat
" chat_time_module " : False , # 是否开启服务时间限制
" chat_start_time " : " 00:00 " , # 服务开始时间
" chat_stop_time " : " 24:00 " , # 服务结束时间
2023-03-25 18:08:37 +08:00
# itchat的配置
2023-03-27 14:40:19 +08:00
" hot_reload " : False , # 是否开启热重载
2023-03-25 19:14:20 +08:00
# wechaty的配置
2023-03-27 14:40:19 +08:00
" wechaty_puppet_service_token " : " " , # wechaty的token
2023-03-30 01:01:00 +08:00
# wechatmp的配置
2023-04-17 01:00:08 +08:00
" wechatmp_token " : " " , # 微信公众平台的Token
" wechatmp_port " : 8080 , # 微信公众平台的端口,需要端口转发到80或443
2023-04-22 02:29:50 +08:00
" wechatmp_app_id " : " " , # 微信公众平台的appID
" wechatmp_app_secret " : " " , # 微信公众平台的appsecret
" wechatmp_aes_key " : " " , # 微信公众平台的EncodingAESKey, 加密模式需要
2023-03-25 18:08:37 +08:00
# chatgpt指令自定义触发词
2023-04-17 01:00:08 +08:00
" clear_memory_commands " : [ " #清除记忆 " ] , # 重置会话指令,必须以#开头
2023-03-30 16:06:57 +08:00
# channel配置
2023-04-17 01:00:08 +08:00
" channel_type " : " wx " , # 通道类型,支持:{wx,wxy,terminal,wechatmp,wechatmp_service}
2023-04-04 14:02:14 +08:00
" debug " : False , # 是否开启debug模式, 开启后会打印更多日志
2023-04-17 01:24:39 +08:00
" appdata_dir " : " " , # 数据目录
2023-04-05 05:37:06 +08:00
# 插件配置
" plugin_trigger_prefix " : " $ " , # 规范插件提供聊天相关指令的前缀,建议不要和管理员指令前缀"#"冲突
2023-03-25 18:08:37 +08:00
}
2023-03-27 14:40:19 +08:00
2023-03-25 18:08:37 +08:00
class Config ( dict ) :
2023-04-17 01:00:08 +08:00
def __init__ ( self , d : dict = { } ) :
2023-04-04 00:59:21 +08:00
super ( ) . __init__ ( d )
# user_datas: 用户数据, key为用户名, value为用户数据, 也是dict
self . user_datas = { }
2023-03-25 18:08:37 +08:00
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 ) :
2023-03-27 14:40:19 +08:00
try :
2023-03-25 18:08:37 +08:00
return self [ key ]
except KeyError as e :
return default
except Exception as e :
raise e
2023-03-27 14:40:19 +08:00
2023-04-04 00:59:21 +08:00
# 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 :
2023-04-17 01:24:39 +08:00
with open ( os . path . join ( get_appdata_dir ( ) , " user_datas.pkl " ) , " rb " ) as f :
2023-04-04 00:59:21 +08:00
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 :
2023-04-17 01:24:39 +08:00
with open ( os . path . join ( get_appdata_dir ( ) , " user_datas.pkl " ) , " wb " ) as f :
2023-04-04 00:59:21 +08:00
pickle . dump ( self . user_datas , f )
logger . info ( " [Config] User datas saved. " )
except Exception as e :
logger . info ( " [Config] User datas error: {} " . format ( e ) )
2023-03-27 14:40:19 +08:00
2023-04-17 01:00:08 +08:00
2023-03-25 18:08:37 +08:00
config = Config ( )
2022-12-13 01:50:43 +08:00
2023-03-27 14:40:19 +08:00
2022-12-13 01:50:43 +08:00
def load_config ( ) :
global config
2023-03-23 00:36:41 +08:00
config_path = " ./config.json "
2022-12-19 01:25:34 +08:00
if not os . path . exists ( config_path ) :
2023-04-17 01:00:08 +08:00
logger . info ( " 配置文件不存在, 将使用config-template.json模板 " )
2023-03-25 10:02:19 +08:00
config_path = " ./config-template.json "
2022-12-19 01:25:34 +08:00
config_str = read_file ( config_path )
2023-03-26 02:31:38 +08:00
logger . debug ( " [INIT] config str: {} " . format ( config_str ) )
2023-03-26 02:00:35 +08:00
2022-12-19 01:25:34 +08:00
# 将json字符串反序列化为dict类型
2023-03-25 18:08:37 +08:00
config = Config ( json . loads ( config_str ) )
2023-03-25 10:02:19 +08:00
# 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 ( ) :
2023-03-26 03:31:29 +08:00
name = name . lower ( )
2023-03-25 18:08:37 +08:00
if name in available_setting :
2023-03-27 14:40:19 +08:00
logger . info (
2023-04-17 01:00:08 +08:00
" [INIT] override config by environ args: {} = {} " . format ( name , value )
)
2023-03-26 03:31:29 +08:00
try :
config [ name ] = eval ( value )
except :
2023-03-31 15:13:28 +08:00
if value == " false " :
config [ name ] = False
elif value == " true " :
config [ name ] = True
else :
config [ name ] = value
2023-03-25 10:02:19 +08:00
2023-04-04 15:59:56 +08:00
if config . get ( " debug " , False ) :
2023-04-04 14:02:14 +08:00
logger . setLevel ( logging . DEBUG )
2023-04-17 01:00:08 +08:00
logger . debug ( " [INIT] set log level to DEBUG " )
2023-04-04 14:02:14 +08:00
2022-12-19 01:25:34 +08:00
logger . info ( " [INIT] load config: {} " . format ( config ) )
2023-04-04 00:59:21 +08:00
config . load_user_datas ( )
2022-12-13 01:50:43 +08:00
2023-04-17 01:00:08 +08:00
2022-12-13 01:50:43 +08:00
def get_root ( ) :
2023-03-27 14:40:19 +08:00
return os . path . dirname ( os . path . abspath ( __file__ ) )
2022-12-13 01:50:43 +08:00
def read_file ( path ) :
2023-04-17 01:00:08 +08:00
with open ( path , mode = " r " , encoding = " utf-8 " ) as f :
2022-12-13 01:50:43 +08:00
return f . read ( )
def conf ( ) :
2023-02-05 01:30:46 +08:00
return config
2023-04-09 11:59:31 +08:00
2023-04-17 01:24:39 +08:00
def get_appdata_dir ( ) :
data_path = os . path . join ( get_root ( ) , conf ( ) . get ( " appdata_dir " , " " ) )
2023-04-09 11:59:31 +08:00
if not os . path . exists ( data_path ) :
logger . info ( " [INIT] data path not exists, create it: {} " . format ( data_path ) )
os . makedirs ( data_path )
2023-04-17 01:24:39 +08:00
return data_path