Merge branch 'master' into master

This commit is contained in:
Junyan Qin (Chin)
2025-01-21 10:38:04 +08:00
committed by GitHub
31 changed files with 412 additions and 152 deletions

View File

@@ -12,7 +12,7 @@ from .. import group
class LogsRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@self.route('', methods=['GET'])
@self.route('', methods=['GET'], auth_type=group.AuthType.USER_TOKEN)
async def _() -> str:
start_page_number = int(quart.request.args.get('start_page_number', 0))

View File

@@ -13,7 +13,7 @@ from .. import group
class PluginsRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@self.route('', methods=['GET'])
@self.route('', methods=['GET'], auth_type=group.AuthType.USER_TOKEN)
async def _() -> str:
plugins = self.ap.plugin_mgr.plugins()
@@ -23,14 +23,14 @@ class PluginsRouterGroup(group.RouterGroup):
'plugins': plugins_data
})
@self.route('/<author>/<plugin_name>/toggle', methods=['PUT'])
@self.route('/<author>/<plugin_name>/toggle', methods=['PUT'], auth_type=group.AuthType.USER_TOKEN)
async def _(author: str, plugin_name: str) -> str:
data = await quart.request.json
target_enabled = data.get('target_enabled')
await self.ap.plugin_mgr.update_plugin_switch(plugin_name, target_enabled)
return self.success()
@self.route('/<author>/<plugin_name>/update', methods=['POST'])
@self.route('/<author>/<plugin_name>/update', methods=['POST'], auth_type=group.AuthType.USER_TOKEN)
async def _(author: str, plugin_name: str) -> str:
ctx = taskmgr.TaskContext.new()
wrapper = self.ap.task_mgr.create_user_task(
@@ -44,7 +44,7 @@ class PluginsRouterGroup(group.RouterGroup):
'task_id': wrapper.id
})
@self.route('/<author>/<plugin_name>', methods=['DELETE'])
@self.route('/<author>/<plugin_name>', methods=['DELETE'], auth_type=group.AuthType.USER_TOKEN)
async def _(author: str, plugin_name: str) -> str:
ctx = taskmgr.TaskContext.new()
wrapper = self.ap.task_mgr.create_user_task(
@@ -59,13 +59,13 @@ class PluginsRouterGroup(group.RouterGroup):
'task_id': wrapper.id
})
@self.route('/reorder', methods=['PUT'])
@self.route('/reorder', methods=['PUT'], auth_type=group.AuthType.USER_TOKEN)
async def _() -> str:
data = await quart.request.json
await self.ap.plugin_mgr.reorder_plugins(data.get('plugins'))
return self.success()
@self.route('/install/github', methods=['POST'])
@self.route('/install/github', methods=['POST'], auth_type=group.AuthType.USER_TOKEN)
async def _() -> str:
data = await quart.request.json

View File

@@ -9,7 +9,7 @@ class SettingsRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@self.route('', methods=['GET'])
@self.route('', methods=['GET'], auth_type=group.AuthType.USER_TOKEN)
async def _() -> str:
return self.success(
data={
@@ -23,7 +23,7 @@ class SettingsRouterGroup(group.RouterGroup):
}
)
@self.route('/<manager_name>', methods=['GET'])
@self.route('/<manager_name>', methods=['GET'], auth_type=group.AuthType.USER_TOKEN)
async def _(manager_name: str) -> str:
manager = self.ap.settings_mgr.get_manager(manager_name)
@@ -44,7 +44,7 @@ class SettingsRouterGroup(group.RouterGroup):
}
)
@self.route('/<manager_name>/data', methods=['PUT'])
@self.route('/<manager_name>/data', methods=['PUT'], auth_type=group.AuthType.USER_TOKEN)
async def _(manager_name: str) -> str:
data = await quart.request.json
manager = self.ap.settings_mgr.get_manager(manager_name)

View File

@@ -9,7 +9,7 @@ from .. import group
class StatsRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@self.route('/basic', methods=['GET'])
@self.route('/basic', methods=['GET'], auth_type=group.AuthType.USER_TOKEN)
async def _() -> str:
conv_count = 0

View File

@@ -20,7 +20,7 @@ class SystemRouterGroup(group.RouterGroup):
}
)
@self.route('/tasks', methods=['GET'])
@self.route('/tasks', methods=['GET'], auth_type=group.AuthType.USER_TOKEN)
async def _() -> str:
task_type = quart.request.args.get("type")
@@ -31,7 +31,7 @@ class SystemRouterGroup(group.RouterGroup):
data=self.ap.task_mgr.get_tasks_dict(task_type)
)
@self.route('/tasks/<task_id>', methods=['GET'])
@self.route('/tasks/<task_id>', methods=['GET'], auth_type=group.AuthType.USER_TOKEN)
async def _(task_id: str) -> str:
task = self.ap.task_mgr.get_task_by_id(int(task_id))
@@ -40,7 +40,7 @@ class SystemRouterGroup(group.RouterGroup):
return self.success(data=task.to_dict())
@self.route('/reload', methods=['POST'])
@self.route('/reload', methods=['POST'], auth_type=group.AuthType.USER_TOKEN)
async def _() -> str:
json_data = await quart.request.json
@@ -51,7 +51,7 @@ class SystemRouterGroup(group.RouterGroup):
)
return self.success()
@self.route('/_debug/exec', methods=['POST'])
@self.route('/_debug/exec', methods=['POST'], auth_type=group.AuthType.USER_TOKEN)
async def _() -> str:
if not constants.debug_mode:
return self.http_status(403, 403, "Forbidden")

View File

@@ -13,14 +13,14 @@ identifier = {
'instance_create_ts': 0,
}
HOST_ID_FILE = os.path.expanduser('~/.qchatgpt/host_id.json')
INSTANCE_ID_FILE = 'res/instance_id.json'
HOST_ID_FILE = os.path.expanduser('~/.langbot/host_id.json')
INSTANCE_ID_FILE = 'data/labels/instance_id.json'
def init():
global identifier
if not os.path.exists(os.path.expanduser('~/.qchatgpt')):
os.mkdir(os.path.expanduser('~/.qchatgpt'))
if not os.path.exists(os.path.expanduser('~/.langbot')):
os.mkdir(os.path.expanduser('~/.langbot'))
if not os.path.exists(HOST_ID_FILE):
new_host_id = 'host_'+str(uuid.uuid4())

View File

@@ -197,5 +197,27 @@ class Application:
await self.plugin_mgr.load_plugins()
await self.plugin_mgr.initialize_plugins()
case core_entities.LifecycleControlScope.PROVIDER.value:
self.logger.info("执行热重载 scope="+scope)
llm_model_mgr_inst = llm_model_mgr.ModelManager(self)
await llm_model_mgr_inst.initialize()
self.model_mgr = llm_model_mgr_inst
llm_session_mgr_inst = llm_session_mgr.SessionManager(self)
await llm_session_mgr_inst.initialize()
self.sess_mgr = llm_session_mgr_inst
llm_prompt_mgr_inst = llm_prompt_mgr.PromptManager(self)
await llm_prompt_mgr_inst.initialize()
self.prompt_mgr = llm_prompt_mgr_inst
llm_tool_mgr_inst = llm_tool_mgr.ToolManager(self)
await llm_tool_mgr_inst.initialize()
self.tool_mgr = llm_tool_mgr_inst
runner_mgr_inst = runnermgr.RunnerManager(self)
await runner_mgr_inst.initialize()
self.runner_mgr = runner_mgr_inst
case _:
pass
pass

View File

@@ -24,6 +24,7 @@ required_paths = [
"data/scenario",
"data/logs",
"data/config",
"data/labels",
"plugins"
]

View File

@@ -23,6 +23,7 @@ class LifecycleControlScope(enum.Enum):
APPLICATION = "application"
PLATFORM = "platform"
PLUGIN = "plugin"
PROVIDER = "provider"
class LauncherTypes(enum.Enum):

View File

@@ -0,0 +1,25 @@
from __future__ import annotations
from .. import migration
@migration.migration_class("xai-config", 18)
class XaiConfigMigration(migration.Migration):
"""迁移"""
async def need_migrate(self) -> bool:
"""判断当前环境是否需要运行此迁移"""
return 'xai-chat-completions' not in self.ap.provider_cfg.data['requester']
async def run(self):
"""执行迁移"""
self.ap.provider_cfg.data['requester']['xai-chat-completions'] = {
"base-url": "https://api.x.ai/v1",
"args": {},
"timeout": 120
}
self.ap.provider_cfg.data['keys']['xai'] = [
"xai-1234567890"
]
await self.ap.provider_cfg.dump_config()

View File

@@ -0,0 +1,25 @@
from __future__ import annotations
from .. import migration
@migration.migration_class("zhipuai-config", 19)
class ZhipuaiConfigMigration(migration.Migration):
"""迁移"""
async def need_migrate(self) -> bool:
"""判断当前环境是否需要运行此迁移"""
return 'zhipuai-chat-completions' not in self.ap.provider_cfg.data['requester']
async def run(self):
"""执行迁移"""
self.ap.provider_cfg.data['requester']['zhipuai-chat-completions'] = {
"base-url": "https://open.bigmodel.cn/api/paas/v4",
"args": {},
"timeout": 120
}
self.ap.provider_cfg.data['keys']['zhipuai'] = [
"xxxxxxx"
]
await self.ap.provider_cfg.dump_config()

View File

@@ -7,7 +7,7 @@ from .. import migration
from ..migrations import m001_sensitive_word_migration, m002_openai_config_migration, m003_anthropic_requester_cfg_completion, m004_moonshot_cfg_completion
from ..migrations import m005_deepseek_cfg_completion, m006_vision_config, m007_qcg_center_url, m008_ad_fixwin_config_migrate, m009_msg_truncator_cfg
from ..migrations import m010_ollama_requester_config, m011_command_prefix_config, m012_runner_config, m013_http_api_config, m014_force_delay_config
from ..migrations import m015_gitee_ai_config, m016_dify_service_api, m017_dify_api_timeout_params
from ..migrations import m015_gitee_ai_config, m016_dify_service_api, m017_dify_api_timeout_params, m018_xai_config, m019_zhipuai_config
from ..migrations import m020_wecom_config

View File

@@ -105,7 +105,7 @@ class ChatMessageHandler(handler.MessageHandler):
await self.ap.ctr_mgr.usage.post_query_record(
session_type=query.session.launcher_type.value,
session_id=str(query.session.launcher_id),
query_ability_provider="QChatGPT.Chat",
query_ability_provider="LangBot.Chat",
usage=text_length,
model_name=query.use_model.name,
response_seconds=int(time.time() - start_time),

View File

@@ -50,17 +50,6 @@ class PlatformManager:
adapter=adapter
)
async def on_stranger_message(event: platform_events.StrangerMessage, adapter: msadapter.MessageSourceAdapter):
await self.ap.query_pool.add_query(
launcher_type=core_entities.LauncherTypes.PERSON,
launcher_id=event.sender.id,
sender_id=event.sender.id,
message_event=event,
message_chain=event.message_chain,
adapter=adapter
)
async def on_group_message(event: platform_events.GroupMessage, adapter: msadapter.MessageSourceAdapter):
await self.ap.query_pool.add_query(
@@ -96,12 +85,6 @@ class PlatformManager:
)
self.adapters.append(adapter_inst)
if adapter_name == 'yiri-mirai':
adapter_inst.register_listener(
platform_events.StrangerMessage,
on_stranger_message
)
adapter_inst.register_listener(
platform_events.FriendMessage,
on_friend_message

View File

@@ -9,6 +9,7 @@ from . import events
from ..provider.tools import entities as tools_entities
from ..core import app
from ..platform.types import message as platform_message
from ..platform import adapter as platform_adapter
def register(
@@ -113,6 +114,37 @@ class APIHost:
async def initialize(self):
pass
# ========== 插件可调用的 API主程序API ==========
def get_platform_adapters(self) -> list[platform_adapter.MessageSourceAdapter]:
"""获取已启用的消息平台适配器列表
Returns:
list[platform.adapter.MessageSourceAdapter]: 已启用的消息平台适配器列表
"""
return self.ap.platform_mgr.adapters
async def send_active_message(
self,
adapter: platform_adapter.MessageSourceAdapter,
target_type: str,
target_id: str,
message: platform_message.MessageChain,
):
"""发送主动消息
Args:
adapter (platform.adapter.MessageSourceAdapter): 消息平台适配器对象,调用 host.get_platform_adapters() 获取并取用其中某个
target_type (str): 目标类型,`person`或`group`
target_id (str): 目标ID
message (platform.types.MessageChain): 消息链
"""
await adapter.send_message(
target_type=target_type,
target_id=target_id,
message=message,
)
def require_ver(
self,
ge: str,
@@ -127,16 +159,16 @@ class APIHost:
Returns:
bool: 是否满足要求, False时为无法获取版本号True时为满足要求报错为不满足要求
"""
qchatgpt_version = ""
langbot_version = ""
try:
qchatgpt_version = self.ap.ver_mgr.get_current_version() # 从updater模块获取版本号
langbot_version = self.ap.ver_mgr.get_current_version() # 从updater模块获取版本号
except:
return False
if self.ap.ver_mgr.compare_version_str(qchatgpt_version, ge) < 0 or \
(self.ap.ver_mgr.compare_version_str(qchatgpt_version, le) > 0):
raise Exception("LangBot 版本不满足要求,某些功能(可能是由插件提供的)无法正常使用。(要求版本:{}-{},但当前版本:{}".format(ge, le, qchatgpt_version))
if self.ap.ver_mgr.compare_version_str(langbot_version, ge) < 0 or \
(self.ap.ver_mgr.compare_version_str(langbot_version, le) > 0):
raise Exception("LangBot 版本不满足要求,某些功能(可能是由插件提供的)无法正常使用。(要求版本:{}-{},但当前版本:{}".format(ge, le, langbot_version))
return True

View File

@@ -6,7 +6,7 @@ from . import entities, requester
from ...core import app
from . import token
from .requesters import chatcmpl, anthropicmsgs, moonshotchatcmpl, deepseekchatcmpl, ollamachat, giteeaichatcmpl
from .requesters import chatcmpl, anthropicmsgs, moonshotchatcmpl, deepseekchatcmpl, ollamachat, giteeaichatcmpl, xaichatcmpl, zhipuaichatcmpl
FETCH_MODEL_LIST_URL = "https://api.qchatgpt.rockchin.top/api/v2/fetch/model_list"

View File

@@ -0,0 +1,21 @@
from __future__ import annotations
import openai
from . import chatcmpl
from .. import requester
from ....core import app
@requester.requester_class("xai-chat-completions")
class XaiChatCompletions(chatcmpl.OpenAIChatCompletions):
"""xAI ChatCompletion API 请求器"""
client: openai.AsyncClient
requester_cfg: dict
def __init__(self, ap: app.Application):
self.ap = ap
self.requester_cfg = self.ap.provider_cfg.data['requester']['xai-chat-completions']

View File

@@ -0,0 +1,21 @@
from __future__ import annotations
import openai
from ....core import app
from . import chatcmpl
from .. import requester
@requester.requester_class("zhipuai-chat-completions")
class ZhipuAIChatCompletions(chatcmpl.OpenAIChatCompletions):
"""智谱AI ChatCompletion API 请求器"""
client: openai.AsyncClient
requester_cfg: dict
def __init__(self, ap: app.Application):
self.ap = ap
self.requester_cfg = self.ap.provider_cfg.data['requester']['zhipuai-chat-completions']

View File

@@ -5,6 +5,8 @@ import json
import uuid
import base64
import aiohttp
from .. import runner
from ...core import entities as core_entities
from .. import entities as llm_entities
@@ -97,7 +99,7 @@ class DifyServiceAPIRunner(runner.RequestRunner):
files=files,
timeout=self.ap.provider_cfg.data["dify-service-api"]["chat"]["timeout"],
):
self.ap.logger.debug("dify-chat-chunk: ", chunk)
self.ap.logger.debug("dify-chat-chunk: " + str(chunk))
if chunk['event'] == 'workflow_started':
mode = "workflow"
@@ -149,7 +151,8 @@ class DifyServiceAPIRunner(runner.RequestRunner):
files=files,
timeout=self.ap.provider_cfg.data["dify-service-api"]["chat"]["timeout"],
):
self.ap.logger.debug("dify-agent-chunk: ", chunk)
self.ap.logger.debug("dify-agent-chunk: " + str(chunk))
if chunk["event"] in ignored_events:
continue
if chunk["event"] == "agent_thought":
@@ -179,6 +182,21 @@ class DifyServiceAPIRunner(runner.RequestRunner):
],
)
yield msg
if chunk['event'] == 'message_file':
if chunk['type'] == 'image' and chunk['belongs_to'] == 'assistant':
base_url = self.dify_client.base_url
if base_url.endswith('/v1'):
base_url = base_url[:-3]
image_url = base_url + chunk['url']
yield llm_entities.Message(
role="assistant",
content=[llm_entities.ContentElement.from_image_url(image_url)],
)
query.session.using_conversation.uuid = chunk["conversation_id"]
@@ -215,7 +233,7 @@ class DifyServiceAPIRunner(runner.RequestRunner):
files=files,
timeout=self.ap.provider_cfg.data["dify-service-api"]["workflow"]["timeout"],
):
self.ap.logger.debug("dify-workflow-chunk: ", chunk)
self.ap.logger.debug("dify-workflow-chunk: " + str(chunk))
if chunk["event"] in ignored_events:
continue

View File

@@ -62,11 +62,11 @@ class AnnouncementManager:
async def fetch_saved(
self
) -> list[Announcement]:
if not os.path.exists("res/announcement_saved.json"):
with open("res/announcement_saved.json", "w", encoding="utf-8") as f:
if not os.path.exists("data/labels/announcement_saved.json"):
with open("data/labels/announcement_saved.json", "w", encoding="utf-8") as f:
f.write("[]")
with open("res/announcement_saved.json", "r", encoding="utf-8") as f:
with open("data/labels/announcement_saved.json", "r", encoding="utf-8") as f:
content = f.read()
if not content:
@@ -79,7 +79,7 @@ class AnnouncementManager:
content: list[Announcement]
):
with open("res/announcement_saved.json", "w", encoding="utf-8") as f:
with open("data/labels/announcement_saved.json", "w", encoding="utf-8") as f:
f.write(json.dumps([
item.to_dict() for item in content
], indent=4, ensure_ascii=False))

View File

@@ -1,4 +1,4 @@
semantic_version = "v3.4.1.5"
semantic_version = "v3.4.2.1"
debug_mode = False