From 5be17c55d28c21cb4bf0f1cee56a4d5154ab387d Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Thu, 27 Mar 2025 01:20:00 +0800 Subject: [PATCH] feat: crud of platform/bots --- pkg/api/http/controller/groups/pipelines.py | 6 +- .../controller/groups/platform/__init__.py | 0 .../controller/groups/platform/adapters.py | 25 +++++++ .../http/controller/groups/platform/bots.py | 38 ++++++++++ .../http/controller/groups/provider/models.py | 6 +- pkg/api/http/controller/main.py | 1 + pkg/api/http/service/bot.py | 70 +++++++++++++++++++ pkg/api/http/service/model.py | 7 +- pkg/api/http/service/pipeline.py | 4 +- pkg/core/app.py | 3 + pkg/core/stages/build_app.py | 4 ++ pkg/entity/persistence/bot.py | 17 +++++ pkg/persistence/mgr.py | 2 +- pkg/platform/manager.py | 12 ++++ 14 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 pkg/api/http/controller/groups/platform/__init__.py create mode 100644 pkg/api/http/controller/groups/platform/adapters.py create mode 100644 pkg/api/http/controller/groups/platform/bots.py create mode 100644 pkg/api/http/service/bot.py create mode 100644 pkg/entity/persistence/bot.py diff --git a/pkg/api/http/controller/groups/pipelines.py b/pkg/api/http/controller/groups/pipelines.py index deaf1ee6..400da593 100644 --- a/pkg/api/http/controller/groups/pipelines.py +++ b/pkg/api/http/controller/groups/pipelines.py @@ -20,9 +20,11 @@ class PipelinesRouterGroup(group.RouterGroup): elif quart.request.method == 'POST': json_data = await quart.request.json - await self.ap.pipeline_service.create_pipeline(json_data) + pipeline_uuid = await self.ap.pipeline_service.create_pipeline(json_data) - return self.success() + return self.success(data={ + 'uuid': pipeline_uuid + }) @self.route('/_/metadata', methods=['GET']) async def _() -> str: diff --git a/pkg/api/http/controller/groups/platform/__init__.py b/pkg/api/http/controller/groups/platform/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pkg/api/http/controller/groups/platform/adapters.py b/pkg/api/http/controller/groups/platform/adapters.py new file mode 100644 index 00000000..de5a1eae --- /dev/null +++ b/pkg/api/http/controller/groups/platform/adapters.py @@ -0,0 +1,25 @@ +import quart + +from ... import group + + +@group.group_class('adapters', '/api/v1/platform/adapters') +class AdaptersRouterGroup(group.RouterGroup): + + async def initialize(self) -> None: + @self.route('', methods=['GET']) + async def _() -> str: + return self.success(data={ + 'adapters': self.ap.platform_mgr.get_available_adapters_info() + }) + + @self.route('/', methods=['GET']) + async def _(adapter_name: str) -> str: + adapter_info = self.ap.platform_mgr.get_available_adapter_info_by_name(adapter_name) + + if adapter_info is None: + return self.http_status(404, -1, 'adapter not found') + + return self.success(data={ + 'adapter': adapter_info + }) diff --git a/pkg/api/http/controller/groups/platform/bots.py b/pkg/api/http/controller/groups/platform/bots.py new file mode 100644 index 00000000..fe20aa53 --- /dev/null +++ b/pkg/api/http/controller/groups/platform/bots.py @@ -0,0 +1,38 @@ +import quart + +from ... import group + + +@group.group_class('bots', '/api/v1/platform/bots') +class BotsRouterGroup(group.RouterGroup): + + async def initialize(self) -> None: + @self.route('', methods=['GET', 'POST']) + async def _() -> str: + if quart.request.method == 'GET': + return self.success(data={ + 'bots': await self.ap.bot_service.get_bots() + }) + elif quart.request.method == 'POST': + json_data = await quart.request.json + bot_uuid = await self.ap.bot_service.create_bot(json_data) + return self.success(data={ + 'uuid': bot_uuid + }) + + @self.route('/', methods=['GET', 'PUT', 'DELETE']) + async def _(bot_uuid: str) -> str: + if quart.request.method == 'GET': + bot = await self.ap.bot_service.get_bot(bot_uuid) + if bot is None: + return self.http_status(404, -1, 'bot not found') + return self.success(data={ + 'bot': bot + }) + elif quart.request.method == 'PUT': + json_data = await quart.request.json + await self.ap.bot_service.update_bot(bot_uuid, json_data) + return self.success() + elif quart.request.method == 'DELETE': + await self.ap.bot_service.delete_bot(bot_uuid) + return self.success() \ No newline at end of file diff --git a/pkg/api/http/controller/groups/provider/models.py b/pkg/api/http/controller/groups/provider/models.py index ab91ab6a..81e9078b 100644 --- a/pkg/api/http/controller/groups/provider/models.py +++ b/pkg/api/http/controller/groups/provider/models.py @@ -18,9 +18,11 @@ class LLMModelsRouterGroup(group.RouterGroup): elif quart.request.method == 'POST': json_data = await quart.request.json - await self.ap.model_service.create_llm_model(json_data) + model_uuid = await self.ap.model_service.create_llm_model(json_data) - return self.success() + return self.success(data={ + 'uuid': model_uuid + }) @self.route('/', methods=['GET', 'DELETE']) async def _(model_uuid: str) -> str: diff --git a/pkg/api/http/controller/main.py b/pkg/api/http/controller/main.py index 0f110c89..7e973b98 100644 --- a/pkg/api/http/controller/main.py +++ b/pkg/api/http/controller/main.py @@ -10,6 +10,7 @@ from ....core import app, entities as core_entities from .groups import logs, system, settings, plugins, stats, user, pipelines from .groups.provider import models, requesters +from .groups.platform import bots, adapters from . import group diff --git a/pkg/api/http/service/bot.py b/pkg/api/http/service/bot.py new file mode 100644 index 00000000..b8c0a46b --- /dev/null +++ b/pkg/api/http/service/bot.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +import uuid +import datetime +import sqlalchemy + +from ....core import app +from ....entity.persistence import bot as persistence_bot + + +class BotService: + """机器人服务""" + + ap: app.Application + + def __init__(self, ap: app.Application) -> None: + self.ap = ap + + async def get_bots(self) -> list[dict]: + """获取所有机器人""" + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_bot.Bot) + ) + + bots = result.all() + + return [ + self.ap.persistence_mgr.serialize_model(persistence_bot.Bot, bot) + for bot in bots + ] + + async def get_bot(self, bot_uuid: str) -> dict | None: + """获取机器人""" + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_bot.Bot).where(persistence_bot.Bot.uuid == bot_uuid) + ) + + bot = result.first() + + if bot is None: + return None + + return self.ap.persistence_mgr.serialize_model(persistence_bot.Bot, bot) + + async def create_bot(self, bot_data: dict) -> str: + """创建机器人""" + bot_data['uuid'] = str(uuid.uuid4()) + await self.ap.persistence_mgr.execute_async( + sqlalchemy.insert(persistence_bot.Bot).values(bot_data) + ) + # TODO: 加载机器人到机器人管理器 + return bot_data['uuid'] + + async def update_bot(self, bot_uuid: str, bot_data: dict) -> None: + """更新机器人""" + if 'uuid' in bot_data: + del bot_data['uuid'] + await self.ap.persistence_mgr.execute_async( + sqlalchemy.update(persistence_bot.Bot).values(bot_data).where(persistence_bot.Bot.uuid == bot_uuid) + ) + # TODO: 加载机器人到机器人管理器 + + async def delete_bot(self, bot_uuid: str) -> None: + """删除机器人""" + await self.ap.persistence_mgr.execute_async( + sqlalchemy.delete(persistence_bot.Bot).where(persistence_bot.Bot.uuid == bot_uuid) + ) + # TODO: 从机器人管理器中删除机器人 + + diff --git a/pkg/api/http/service/model.py b/pkg/api/http/service/model.py index 7ded6cd1..e96f014a 100644 --- a/pkg/api/http/service/model.py +++ b/pkg/api/http/service/model.py @@ -26,7 +26,7 @@ class ModelsService: for model in models ] - async def create_llm_model(self, model_data: dict) -> None: + async def create_llm_model(self, model_data: dict) -> str: model_data['uuid'] = str(uuid.uuid4()) @@ -37,6 +37,8 @@ class ModelsService: ) await self.ap.model_mgr.load_llm_model(model_data) + return model_data['uuid'] + async def get_llm_model(self, model_uuid: str) -> dict | None: result = await self.ap.persistence_mgr.execute_async( sqlalchemy.select(persistence_model.LLMModel).where(persistence_model.LLMModel.uuid == model_uuid) @@ -50,6 +52,9 @@ class ModelsService: return self.ap.persistence_mgr.serialize_model(persistence_model.LLMModel, model) async def update_llm_model(self, model_uuid: str, model_data: dict) -> None: + if 'uuid' in model_data: + del model_data['uuid'] + await self.ap.persistence_mgr.execute_async( sqlalchemy.update(persistence_model.LLMModel).where(persistence_model.LLMModel.uuid == model_uuid).values(**model_data) ) diff --git a/pkg/api/http/service/pipeline.py b/pkg/api/http/service/pipeline.py index 2e226fb1..22f9f4a9 100644 --- a/pkg/api/http/service/pipeline.py +++ b/pkg/api/http/service/pipeline.py @@ -46,7 +46,7 @@ class PipelineService: return self.ap.persistence_mgr.serialize_model(persistence_pipeline.LegacyPipeline, pipeline) - async def create_pipeline(self, pipeline_data: dict) -> None: + async def create_pipeline(self, pipeline_data: dict) -> str: pipeline_data['uuid'] = str(uuid.uuid4()) pipeline_data['for_version'] = self.ap.ver_mgr.get_current_version() pipeline_data['stages'] = stagemgr.stage_order.copy() @@ -58,6 +58,8 @@ class PipelineService: ) # TODO: 更新到pipeline manager + return pipeline_data['uuid'] + async def update_pipeline(self, pipeline_uuid: str, pipeline_data: dict) -> None: del pipeline_data['uuid'] del pipeline_data['for_version'] diff --git a/pkg/core/app.py b/pkg/core/app.py index 520d954f..f0884d81 100644 --- a/pkg/core/app.py +++ b/pkg/core/app.py @@ -27,6 +27,7 @@ from ..api.http.controller import main as http_controller from ..api.http.service import user as user_service from ..api.http.service import model as model_service from ..api.http.service import pipeline as pipeline_service +from ..api.http.service import bot as bot_service from ..discover import engine as discover_engine from ..utils import logcache, ip from . import taskmgr @@ -123,6 +124,8 @@ class Application: pipeline_service: pipeline_service.PipelineService = None + bot_service: bot_service.BotService = None + def __init__(self): pass diff --git a/pkg/core/stages/build_app.py b/pkg/core/stages/build_app.py index 5752d16d..3873d719 100644 --- a/pkg/core/stages/build_app.py +++ b/pkg/core/stages/build_app.py @@ -20,6 +20,7 @@ from ...api.http.controller import main as http_controller from ...api.http.service import user as user_service from ...api.http.service import model as model_service from ...api.http.service import pipeline as pipeline_service +from ...api.http.service import bot as bot_service from ...discover import engine as discover_engine from ...utils import logcache from .. import taskmgr @@ -131,5 +132,8 @@ class BuildAppStage(stage.BootingStage): pipeline_service_inst = pipeline_service.PipelineService(ap) ap.pipeline_service = pipeline_service_inst + bot_service_inst = bot_service.BotService(ap) + ap.bot_service = bot_service_inst + ctrl = controller.Controller(ap) ap.ctrl = ctrl diff --git a/pkg/entity/persistence/bot.py b/pkg/entity/persistence/bot.py new file mode 100644 index 00000000..355e1208 --- /dev/null +++ b/pkg/entity/persistence/bot.py @@ -0,0 +1,17 @@ +import sqlalchemy + +from .base import Base + + +class Bot(Base): + """机器人""" + __tablename__ = 'bots' + + uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True) + name = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) + description = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) + adapter = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) + adapter_config = sqlalchemy.Column(sqlalchemy.JSON, nullable=False) + enable = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False, default=False) + created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()) + updated_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now(), onupdate=sqlalchemy.func.now()) diff --git a/pkg/persistence/mgr.py b/pkg/persistence/mgr.py index 67b618e7..2892c52b 100644 --- a/pkg/persistence/mgr.py +++ b/pkg/persistence/mgr.py @@ -8,7 +8,7 @@ import sqlalchemy.ext.asyncio as sqlalchemy_asyncio import sqlalchemy from . import database -from ..entity.persistence import user, model, base +from ..entity.persistence import base, user, model, pipeline, bot from ..core import app from .databases import sqlite diff --git a/pkg/platform/manager.py b/pkg/platform/manager.py index acdf0990..d5ee7564 100644 --- a/pkg/platform/manager.py +++ b/pkg/platform/manager.py @@ -110,6 +110,18 @@ class PlatformManager: if len(self.adapters) == 0: self.ap.logger.warning('未运行平台适配器,请根据文档配置并启用平台适配器。') + def get_available_adapters_info(self) -> list[dict]: + return [ + component.to_plain_dict() + for component in self.message_platform_adapter_components + ] + + def get_available_adapter_info_by_name(self, name: str) -> dict | None: + for component in self.message_platform_adapter_components: + if component.metadata.name == name: + return component.to_plain_dict() + return None + async def write_back_config(self, adapter_name: str, adapter_inst: msadapter.MessagePlatformAdapter, config: dict): index = -2