Theme
OpenAI API Configuration
"open_ai_api_key": "sk-xxxxxx", # OpenAI API key
OpenAI API base. When use_azure_chatgpt is true, you need to set the corresponding API base
"open_ai_api_base": "https://api.wlai.vip/v1",
# encoding:utf-8
import json
import logging
import os
import pickle
import copy
from common.log import logger
# Write all available configuration items in the dictionary, please use lowercase letters
# The configuration values here have no actual meaning. The program will not read the configuration here, only for format hints. Please add the configuration to config.json
available_setting = {
# OpenAI API configuration
"open_ai_api_key": "sk-xxxxxx", # OpenAI API key
# OpenAI API base. When use_azure_chatgpt is true, you need to set the corresponding API base
"open_ai_api_base": "https://api.wlai.vip/v1",
"proxy": "", # Proxy used by OpenAI
# ChatGPT model. When use_azure_chatgpt is true, its name is the model deployment name on Azure
"model": "gpt-3.5-turbo", # Optional: gpt-4o, pt-4o-mini, gpt-4-turbo, claude-3-sonnet, wenxin, moonshot, qwen-turbo, xunfei, glm-4, minimax, gemini and other models. See common/const.py for all available models
"bot_type": "", # Optional configuration. When using third-party services compatible with OpenAI format, fill in "chatGPT". See the bot_type listed in common/const.py for specific bot names. If not filled, it will be determined based on the model name
"use_azure_chatgpt": False, # Whether to use Azure ChatGPT
"azure_deployment_id": "", # Azure model deployment name
"azure_api_version": "", # Azure API version
# Bot trigger configuration
"single_chat_prefix": ["bot", "@bot"], # Text must contain this prefix in private chat to trigger bot reply
"single_chat_reply_prefix": "[bot] ", # Prefix for automatic reply in private chat, used to distinguish from real people
"single_chat_reply_suffix": "", # Suffix for automatic reply in private chat, \n can be used for line breaks
"group_chat_prefix": ["@bot"], # Containing this prefix in group chat will trigger bot reply
"no_need_at": False, # Whether @bot is not needed when replying in group chat
"group_chat_reply_prefix": "", # Prefix for automatic reply in group chat
"group_chat_reply_suffix": "", # Suffix for automatic reply in group chat, \n can be used for line breaks
"group_chat_keyword": [], # Containing this keyword in group chat will trigger bot reply
"group_at_off": False, # Whether to disable @bot trigger in group chat
"group_name_white_list": ["ChatGPT Test Group", "ChatGPT Test Group 2"], # List of group names with automatic reply enabled
"group_name_keyword_white_list": [], # List of group name keywords with automatic reply enabled
"group_chat_in_one_session": ["ChatGPT Test Group"], # Group names that support conversation context sharing
"nick_name_black_list": [], # User nickname blacklist
"group_welcome_msg": "", # Configure fixed welcome message for new members. If not configured, random style welcome will be used
"trigger_by_self": False, # Whether to allow the bot to trigger itself
"text_to_image": "dall-e-2", # Image generation model, optional: dall-e-2, dall-e-3
# Azure OpenAI DALL-E-3 configuration
"dalle3_image_style": "vivid", # Style for DALL-E-3 image generation, optional: vivid, natural
"dalle3_image_quality": "hd", # Quality for DALL-E-3 image generation, optional: standard, hd
# Azure OpenAI DALL-E API configuration. When use_azure_chatgpt is true, used to separate text reply resources from DALL-E resources
"azure_openai_dalle_api_base": "", # [Optional] Azure OpenAI resource endpoint for image reply, defaults to open_ai_api_base
"azure_openai_dalle_api_key": "", # [Optional] Azure OpenAI resource key for image reply, defaults to open_ai_api_key
"azure_openai_dalle_deployment_id":"", # [Optional] Azure OpenAI resource deployment id for image reply, defaults to text_to_image
"image_proxy": True, # Whether image proxy is needed. Required when accessing LinkAI from mainland China
"image_create_prefix": ["draw", "show", "find"], # Prefix to enable image reply
"concurrency_in_session": 1, # Maximum number of messages being processed in the same session. Greater than 1 may cause disorder
"image_create_size": "256x256", # Image size, optional: 256x256, 512x512, 1024x1024 (DALL-E-3 defaults to 1024x1024)
"group_chat_exit_group": False,
# ChatGPT session parameters
"expires_in_seconds": 3600, # Expiration time for inactive sessions
# Personality description
"character_desc": "You are ChatGPT, a large language model trained by OpenAI. You are designed to answer and solve a wide range of questions, and you can communicate with people in multiple languages.",
"conversation_max_tokens": 1000, # Maximum number of characters for context memory support
# ChatGPT rate limiting configuration
"rate_limit_chatgpt": 20, # ChatGPT call frequency limit
"rate_limit_dalle": 50, # OpenAI DALL-E call frequency limit
# ChatGPT API parameters. See 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 request timeout. OpenAI interface defaults to 600. Generally requires longer time for difficult questions
"timeout": 120, # ChatGPT retry timeout. Will automatically retry within this time
# Baidu Wenxin parameters
"baidu_wenxin_model": "eb-instant", # Default uses ERNIE-Bot-turbo model
"baidu_wenxin_api_key": "", # Baidu API key
"baidu_wenxin_secret_key": "", # Baidu secret key
"baidu_wenxin_prompt_enabled": False, # Enable prompt if you are using ernie character model
# Xunfei Spark API
"xunfei_app_id": "", # Xunfei application ID
"xunfei_api_key": "", # Xunfei API key
"xunfei_api_secret": "", # Xunfei API secret
"xunfei_domain": "", # Domain parameter for Xunfei model. Spark4.0 Ultra is 4.0Ultra. See other models at: https://www.xfyun.cn/doc/spark/Web.html
"xunfei_spark_url": "", # Request address for Xunfei model. Spark4.0 Ultra is wss://spark-api.xf-yun.com/v4.0/chat. See other models at: https://www.xfyun.cn/doc/spark/Web.html
# Claude configuration
"claude_api_cookie": "",
"claude_uuid": "",
# Claude API key
"claude_api_key": "",
# Tongyi Qianwen API. See documentation for access method: 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 used for process orchestration model. If qwen_node_id is not used, please keep it as an empty string
# Alibaba Lingji (Tongyi new SDK) model API key
"dashscope_api_key": "",
# Google Gemini API Key
"gemini_api_key": "",
# WeWork general configuration
"wework_smart": True, # Configure whether WeWork uses logged-in enterprise WeChat. False for multiple instances
# Voice settings
"speech_recognition": True, # Whether to enable speech recognition
"group_speech_recognition": False, # Whether to enable group speech recognition
"voice_reply_voice": False, # Whether to use voice to reply to voice. Need to set API key for corresponding voice synthesis engine
"always_reply_voice": False, # Whether to always use voice reply
"voice_to_text": "openai", # Speech recognition engine, supports openai, baidu, google, azure, xunfei, ali
"text_to_voice": "openai", # Speech synthesis engine, supports openai, baidu, google, azure, xunfei, ali, pytts(offline), elevenlabs, edge(online)
"text_to_voice_model": "tts-1",
"tts_voice_id": "alloy",
# Baidu speech API configuration. Required when using Baidu speech recognition and synthesis
"baidu_app_id": "",
"baidu_api_key": "",
"baidu_secret_key": "",
# 1536 Mandarin (supports simple English recognition) 1737 English 1637 Cantonese 1837 Sichuan dialect 1936 Mandarin far-field
"baidu_dev_pid": 1536,
# Azure speech API configuration. Required when using Azure speech recognition and synthesis
"azure_voice_api_key": "",
"azure_voice_region": "japaneast",
# ElevenLabs speech API configuration
"xi_api_key": "", # For API access method, see https://docs.elevenlabs.io/api-reference/quick-start/authentication
"xi_voice_id": "", # ElevenLabs provides 9 English accents (British, American, etc.) with IDs: "Adam/Antoni/Arnold/Bella/Domi/Elli/Josh/Rachel/Sam"
# Service time limit. Currently supports itchat
"chat_time_module": False, # Whether to enable service time limit
"chat_start_time": "00:00", # Service start time
"chat_stop_time": "24:00", # Service end time
# Translation API
"translate": "baidu", # Translation API, supports baidu
# Baidu translation API configuration
"baidu_translate_app_id": "", # Baidu translation API app ID
"baidu_translate_app_key": "", # Baidu translation API secret key
# itchat configuration
"hot_reload": False, # Whether to enable hot reload
# Wechaty configuration
"wechaty_puppet_service_token": "", # Wechaty token
# WeChat Official Account configuration
"wechatmp_token": "", # WeChat Official Account token
"wechatmp_port": 8080, # WeChat Official Account port. Requires port forwarding to 80 or 443
"wechatmp_app_id": "", # WeChat Official Account app ID
"wechatmp_app_secret": "", # WeChat Official Account app secret
"wechatmp_aes_key": "", # WeChat Official Account EncodingAESKey. Required for encryption mode
# WeChat Work general configuration
"wechatcom_corp_id": "", # Enterprise WeChat company corp ID
# WeChat Work app configuration
"wechatcomapp_token": "", # Enterprise WeChat app token
"wechatcomapp_port": 9898, # Enterprise WeChat app service port. No port forwarding needed
"wechatcomapp_secret": "", # Enterprise WeChat app secret
"wechatcomapp_agent_id": "", # Enterprise WeChat app agent ID
"wechatcomapp_aes_key": "", # Enterprise WeChat app AES key
# Feishu configuration
"feishu_port": 80, # Feishu bot listening port
"feishu_app_id": "", # Feishu bot application APP ID
"feishu_app_secret": "", # Feishu bot APP secret
"feishu_token": "", # Feishu verification token
"feishu_bot_name": "", # Feishu bot name
# DingTalk configuration
"dingtalk_client_id": "", # DingTalk bot Client ID
"dingtalk_client_secret": "", # DingTalk bot Client Secret
"dingtalk_card_enabled": False,
# ChatGPT command custom trigger words
"clear_memory_commands": ["#clear_memory"], # Reset session command. Must start with #
# Channel configuration
"channel_type": "", # Channel type, supports: {wx, wxy, terminal, wechatmp, wechatmp_service, wechatcom_app, dingtalk}
"subscribe_msg": "", # Subscribe message, supports: wechatmp, wechatmp_service, wechatcom_app
"debug": False, # Whether to enable debug mode. When enabled, more logs will be printed
"appdata_dir": "", # Data directory
# Plugin configuration
"plugin_trigger_prefix": "$", # Standard prefix for plugin chat-related commands. Recommend not conflicting with admin command prefix "#"
# Whether to use global plugin configuration
"use_global_plugin_config": False,
"max_media_send_count": 3, # Maximum number of media resources to send at once
"media_send_interval": 1, # Time interval for sending images, in seconds
# Zhipu AI platform configuration
"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 platform configuration
"use_linkai": False,
"linkai_api_key": "",
"linkai_app_code": "",
"linkai_api_base": "https://api.link-ai.tech", # LinkAI service address
"Minimax_api_key": "",
"Minimax_group_id": "",
"Minimax_base_url": "",
}
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: User data, key is username, value is user data, also a 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("Configuration file does not exist. Will use config-template.json template")
config_path = "./config-template.json"
config_str = read_file(config_path)
logger.debug("[INIT] config str: {}".format(drag_sensitive(config_str)))
# Deserialize JSON string to dict type
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):
"""
Write global plugin configuration
:param pconf: Complete plugin configuration
"""
global plugin_config
for k in pconf:
plugin_config[k.lower()] = pconf[k]
def pconf(plugin_name: str) -> dict:
"""
Get configuration by plugin name
:param plugin_name: Plugin name
:return: Configuration items for this plugin
"""
return plugin_config.get(plugin_name.lower())
# Global configuration for storing globally effective state
global_config = {"admin_users": []}