feat(plugin_system): 引入插件HTTP端点系统
引入了全新的 `BaseRouterComponent` 组件类型,允许插件开发者通过继承并实现 `register_endpoints` 方法来创建 FastAPI 路由。 - 插件系统现在可以自动发现并注册这些路由组件,并将它们挂载到主 FastAPI 应用的 `/plugins/<plugin_name>` 前缀下。 - 新增了全局配置 `[plugin_http_system]`,提供了总开关、API 速率限制和 API 密钥认证 (`X-API-Key`) 等功能,以确保端点的安全性和稳定性。 - 更新了 `hello_world_plugin` 插件,增加了一个简单的 `/greet` 端点作为实现示例。
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import random
|
import random
|
||||||
from typing import Any, ClassVar
|
from typing import Any, ClassVar
|
||||||
|
|
||||||
|
from mmc.src.plugin_system.base.base_http_component import BaseRouterComponent
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
|
|
||||||
# 修正导入路径,让Pylance不再抱怨
|
# 修正导入路径,让Pylance不再抱怨
|
||||||
@@ -24,6 +25,7 @@ from src.plugin_system.base.component_types import InjectionRule, InjectionType
|
|||||||
|
|
||||||
logger = get_logger("hello_world_plugin")
|
logger = get_logger("hello_world_plugin")
|
||||||
|
|
||||||
|
|
||||||
class StartupMessageHandler(BaseEventHandler):
|
class StartupMessageHandler(BaseEventHandler):
|
||||||
"""启动时打印消息的事件处理器。"""
|
"""启动时打印消息的事件处理器。"""
|
||||||
|
|
||||||
@@ -198,12 +200,25 @@ class WeatherPrompt(BasePrompt):
|
|||||||
return "当前天气:晴朗,温度25°C。"
|
return "当前天气:晴朗,温度25°C。"
|
||||||
|
|
||||||
|
|
||||||
|
class HelloWorldRouter(BaseRouterComponent):
|
||||||
|
"""一个简单的HTTP端点示例。"""
|
||||||
|
|
||||||
|
component_name = "hello_world_router"
|
||||||
|
component_description = "提供一个简单的 /greet HTTP GET 端点。"
|
||||||
|
|
||||||
|
def register_endpoints(self) -> None:
|
||||||
|
@self.router.get("/greet", summary="返回一个问候消息")
|
||||||
|
def greet():
|
||||||
|
"""这个端点返回一个固定的问候语。"""
|
||||||
|
return {"message": "Hello from your new API endpoint!"}
|
||||||
|
|
||||||
|
|
||||||
@register_plugin
|
@register_plugin
|
||||||
class HelloWorldPlugin(BasePlugin):
|
class HelloWorldPlugin(BasePlugin):
|
||||||
"""一个包含四大核心组件和高级配置功能的入门示例插件。"""
|
"""一个包含四大核心组件和高级配置功能的入门示例插件。"""
|
||||||
|
|
||||||
plugin_name = "hello_world_plugin"
|
plugin_name = "hello_world_plugin"
|
||||||
enable_plugin = False
|
enable_plugin = True
|
||||||
dependencies: ClassVar = []
|
dependencies: ClassVar = []
|
||||||
python_dependencies: ClassVar = []
|
python_dependencies: ClassVar = []
|
||||||
config_file_name = "config.toml"
|
config_file_name = "config.toml"
|
||||||
@@ -225,7 +240,7 @@ class HelloWorldPlugin(BasePlugin):
|
|||||||
|
|
||||||
def get_plugin_components(self) -> list[tuple[ComponentInfo, type]]:
|
def get_plugin_components(self) -> list[tuple[ComponentInfo, type]]:
|
||||||
"""根据配置文件动态注册插件的功能组件。"""
|
"""根据配置文件动态注册插件的功能组件。"""
|
||||||
components: ClassVar[list[tuple[ComponentInfo, type]] ] = []
|
components: list[tuple[ComponentInfo, type]] = []
|
||||||
|
|
||||||
components.append((StartupMessageHandler.get_handler_info(), StartupMessageHandler))
|
components.append((StartupMessageHandler.get_handler_info(), StartupMessageHandler))
|
||||||
components.append((GetSystemInfoTool.get_tool_info(), GetSystemInfoTool))
|
components.append((GetSystemInfoTool.get_tool_info(), GetSystemInfoTool))
|
||||||
@@ -239,4 +254,7 @@ class HelloWorldPlugin(BasePlugin):
|
|||||||
# 注册新的Prompt组件
|
# 注册新的Prompt组件
|
||||||
components.append((WeatherPrompt.get_prompt_info(), WeatherPrompt))
|
components.append((WeatherPrompt.get_prompt_info(), WeatherPrompt))
|
||||||
|
|
||||||
|
# 注册新的Router组件
|
||||||
|
components.append((HelloWorldRouter.get_router_info(), HelloWorldRouter))
|
||||||
|
|
||||||
return components
|
return components
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ from pathlib import Path
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
from fastapi import APIRouter, HTTPException, Query, Request
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
||||||
from fastapi.responses import HTMLResponse, JSONResponse
|
from fastapi.responses import HTMLResponse, JSONResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
|
from src.common.security import get_api_key
|
||||||
|
|
||||||
# 调整项目根目录的计算方式
|
# 调整项目根目录的计算方式
|
||||||
project_root = Path(__file__).parent.parent.parent
|
project_root = Path(__file__).parent.parent.parent
|
||||||
data_dir = project_root / "data" / "memory_graph"
|
data_dir = project_root / "data" / "memory_graph"
|
||||||
@@ -23,7 +25,7 @@ graph_data_cache = None
|
|||||||
current_data_file = None
|
current_data_file = None
|
||||||
|
|
||||||
# FastAPI 路由
|
# FastAPI 路由
|
||||||
router = APIRouter()
|
router = APIRouter(dependencies=[Depends(get_api_key)])
|
||||||
|
|
||||||
# Jinja2 模板引擎
|
# Jinja2 模板引擎
|
||||||
templates = Jinja2Templates(directory=str(Path(__file__).parent / "templates"))
|
templates = Jinja2Templates(directory=str(Path(__file__).parent / "templates"))
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import time
|
import time
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
|
||||||
from src.chat.message_receive.chat_stream import get_chat_manager
|
from src.chat.message_receive.chat_stream import get_chat_manager
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
|
from src.common.security import get_api_key
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
from src.plugin_system.apis import message_api, person_api
|
from src.plugin_system.apis import message_api, person_api
|
||||||
|
|
||||||
logger = get_logger("HTTP消息API")
|
logger = get_logger("HTTP消息API")
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter(dependencies=[Depends(get_api_key)])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/messages/recent")
|
@router.get("/messages/recent")
|
||||||
@@ -161,5 +162,3 @@ async def get_message_stats_by_chat(
|
|||||||
# 统一异常处理
|
# 统一异常处理
|
||||||
logger.error(f"获取消息统计时发生错误: {e}")
|
logger.error(f"获取消息统计时发生错误: {e}")
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
|
||||||
from src.chat.utils.statistic import (
|
from src.chat.utils.statistic import (
|
||||||
StatisticOutputTask,
|
StatisticOutputTask,
|
||||||
)
|
)
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
|
from src.common.security import get_api_key
|
||||||
|
|
||||||
logger = get_logger("LLM统计API")
|
logger = get_logger("LLM统计API")
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter(dependencies=[Depends(get_api_key)])
|
||||||
|
|
||||||
# 定义统计数据的键,以减少魔法字符串
|
# 定义统计数据的键,以减少魔法字符串
|
||||||
TOTAL_REQ_CNT = "total_requests"
|
TOTAL_REQ_CNT = "total_requests"
|
||||||
|
|||||||
32
src/common/security.py
Normal file
32
src/common/security.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from fastapi import Depends, HTTPException, Security
|
||||||
|
from fastapi.security.api_key import APIKeyHeader
|
||||||
|
from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
|
||||||
|
|
||||||
|
from src.common.logger import get_logger
|
||||||
|
from src.config.config import global_config as bot_config
|
||||||
|
|
||||||
|
logger = get_logger("security")
|
||||||
|
|
||||||
|
API_KEY_HEADER = "X-API-Key"
|
||||||
|
api_key_header_auth = APIKeyHeader(name=API_KEY_HEADER, auto_error=True)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_api_key(api_key: str = Security(api_key_header_auth)) -> str:
|
||||||
|
"""
|
||||||
|
FastAPI 依赖项,用于验证API密钥。
|
||||||
|
从请求头中提取 X-API-Key 并验证它是否存在于配置的有效密钥列表中。
|
||||||
|
"""
|
||||||
|
valid_keys = bot_config.plugin_http_system.plugin_api_valid_keys
|
||||||
|
if not valid_keys:
|
||||||
|
logger.warning("API密钥认证已启用,但未配置任何有效的API密钥。所有请求都将被拒绝。")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="服务未正确配置API密钥",
|
||||||
|
)
|
||||||
|
if api_key not in valid_keys:
|
||||||
|
logger.warning(f"无效的API密钥: {api_key}")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTP_403_FORBIDDEN,
|
||||||
|
detail="无效的API密钥",
|
||||||
|
)
|
||||||
|
return api_key
|
||||||
@@ -1,32 +1,60 @@
|
|||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
from fastapi import APIRouter, FastAPI
|
from fastapi import APIRouter, FastAPI, Request, Response
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from rich.traceback import install
|
from rich.traceback import install
|
||||||
from uvicorn import Config
|
from uvicorn import Config
|
||||||
from uvicorn import Server as UvicornServer
|
from uvicorn import Server as UvicornServer
|
||||||
|
|
||||||
|
from slowapi import Limiter, _rate_limit_exceeded_handler
|
||||||
|
from slowapi.errors import RateLimitExceeded
|
||||||
|
from slowapi.middleware import SlowAPIMiddleware
|
||||||
|
from slowapi.util import get_remote_address
|
||||||
|
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
|
from src.config.config import global_config as bot_config
|
||||||
|
|
||||||
install(extra_lines=3)
|
install(extra_lines=3)
|
||||||
|
|
||||||
logger = get_logger("Server")
|
logger = get_logger("Server")
|
||||||
|
|
||||||
|
|
||||||
|
def rate_limit_exceeded_handler(request: Request, exc: Exception) -> Response:
|
||||||
|
"""自定义速率限制超出处理器以解决类型提示问题"""
|
||||||
|
# 由于此处理器专门用于 RateLimitExceeded,我们可以安全地断言异常类型。
|
||||||
|
# 这满足了类型检查器的要求,并确保了运行时安全。
|
||||||
|
assert isinstance(exc, RateLimitExceeded)
|
||||||
|
return _rate_limit_exceeded_handler(request, exc)
|
||||||
|
|
||||||
|
|
||||||
class Server:
|
class Server:
|
||||||
def __init__(self, host: str | None = None, port: int | None = None, app_name: str = "MaiMCore"):
|
def __init__(self, host: str | None = None, port: int | None = None, app_name: str = "MoFox-Bot"):
|
||||||
|
# 根据配置初始化速率限制器
|
||||||
|
limiter = Limiter(
|
||||||
|
key_func=get_remote_address,
|
||||||
|
default_limits=[bot_config.plugin_http_system.plugin_api_rate_limit_default],
|
||||||
|
)
|
||||||
|
|
||||||
self.app = FastAPI(title=app_name)
|
self.app = FastAPI(title=app_name)
|
||||||
self.host: str = "127.0.0.1"
|
self.host: str = "127.0.0.1"
|
||||||
self.port: int = 8080
|
self.port: int = 8080
|
||||||
self._server: UvicornServer | None = None
|
self._server: UvicornServer | None = None
|
||||||
self.set_address(host, port)
|
self.set_address(host, port)
|
||||||
|
|
||||||
|
# 设置速率限制
|
||||||
|
self.app.state.limiter = limiter
|
||||||
|
self.app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler)
|
||||||
|
|
||||||
|
# 根据配置决定是否添加中间件
|
||||||
|
if bot_config.plugin_http_system.plugin_api_rate_limit_enable:
|
||||||
|
logger.info(f"已为插件API启用全局速率限制: {bot_config.plugin_http_system.plugin_api_rate_limit_default}")
|
||||||
|
self.app.add_middleware(SlowAPIMiddleware)
|
||||||
|
|
||||||
# 配置 CORS
|
# 配置 CORS
|
||||||
origins = [
|
origins = [
|
||||||
"http://localhost:3000", # 允许的前端源
|
"http://localhost:3000", # 允许的前端源
|
||||||
"http://127.0.0.1:3000",
|
"http://127.0.0.1:3000",
|
||||||
"http://127.0.0.1:3000",
|
|
||||||
# 在生产环境中,您应该添加实际的前端域名
|
# 在生产环境中,您应该添加实际的前端域名
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ from src.config.official_configs import (
|
|||||||
PermissionConfig,
|
PermissionConfig,
|
||||||
PersonalityConfig,
|
PersonalityConfig,
|
||||||
PlanningSystemConfig,
|
PlanningSystemConfig,
|
||||||
|
PluginHttpSystemConfig,
|
||||||
ProactiveThinkingConfig,
|
ProactiveThinkingConfig,
|
||||||
ReactionConfig,
|
ReactionConfig,
|
||||||
ResponsePostProcessConfig,
|
ResponsePostProcessConfig,
|
||||||
@@ -414,6 +415,9 @@ class Config(ValidatedConfigBase):
|
|||||||
proactive_thinking: ProactiveThinkingConfig = Field(
|
proactive_thinking: ProactiveThinkingConfig = Field(
|
||||||
default_factory=lambda: ProactiveThinkingConfig(), description="主动思考配置"
|
default_factory=lambda: ProactiveThinkingConfig(), description="主动思考配置"
|
||||||
)
|
)
|
||||||
|
plugin_http_system: PluginHttpSystemConfig = Field(
|
||||||
|
default_factory=lambda: PluginHttpSystemConfig(), description="插件HTTP端点系统配置"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class APIAdapterConfig(ValidatedConfigBase):
|
class APIAdapterConfig(ValidatedConfigBase):
|
||||||
|
|||||||
@@ -736,6 +736,23 @@ class CommandConfig(ValidatedConfigBase):
|
|||||||
command_prefixes: list[str] = Field(default_factory=lambda: ["/", "!", ".", "#"], description="支持的命令前缀列表")
|
command_prefixes: list[str] = Field(default_factory=lambda: ["/", "!", ".", "#"], description="支持的命令前缀列表")
|
||||||
|
|
||||||
|
|
||||||
|
class PluginHttpSystemConfig(ValidatedConfigBase):
|
||||||
|
"""插件http系统相关配置"""
|
||||||
|
|
||||||
|
enable_plugin_http_endpoints: bool = Field(
|
||||||
|
default=True, description="总开关,是否允许插件创建HTTP端点"
|
||||||
|
)
|
||||||
|
plugin_api_rate_limit_enable: bool = Field(
|
||||||
|
default=True, description="是否为插件API启用全局速率限制"
|
||||||
|
)
|
||||||
|
plugin_api_rate_limit_default: str = Field(
|
||||||
|
default="100/minute", description="插件API的默认速率限制策略"
|
||||||
|
)
|
||||||
|
plugin_api_valid_keys: list[str] = Field(
|
||||||
|
default_factory=list, description="有效的API密钥列表,用于插件认证"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MasterPromptConfig(ValidatedConfigBase):
|
class MasterPromptConfig(ValidatedConfigBase):
|
||||||
"""主人身份提示词配置"""
|
"""主人身份提示词配置"""
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ from .base import (
|
|||||||
PluginInfo,
|
PluginInfo,
|
||||||
# 新增的增强命令系统
|
# 新增的增强命令系统
|
||||||
PlusCommand,
|
PlusCommand,
|
||||||
|
BaseRouterComponent,
|
||||||
PythonDependency,
|
PythonDependency,
|
||||||
ToolInfo,
|
ToolInfo,
|
||||||
ToolParamType,
|
ToolParamType,
|
||||||
@@ -56,7 +57,7 @@ from .utils.dependency_manager import configure_dependency_manager, get_dependen
|
|||||||
|
|
||||||
__version__ = "2.0.0"
|
__version__ = "2.0.0"
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [ # noqa: RUF022
|
||||||
"ActionActivationType",
|
"ActionActivationType",
|
||||||
"ActionInfo",
|
"ActionInfo",
|
||||||
"BaseAction",
|
"BaseAction",
|
||||||
@@ -82,6 +83,7 @@ __all__ = [
|
|||||||
"PluginInfo",
|
"PluginInfo",
|
||||||
# 增强命令系统
|
# 增强命令系统
|
||||||
"PlusCommand",
|
"PlusCommand",
|
||||||
|
"BaseRouterComponent"
|
||||||
"PythonDependency",
|
"PythonDependency",
|
||||||
"ToolInfo",
|
"ToolInfo",
|
||||||
"ToolParamType",
|
"ToolParamType",
|
||||||
@@ -114,4 +116,4 @@ __all__ = [
|
|||||||
# "ManifestGenerator",
|
# "ManifestGenerator",
|
||||||
# "validate_plugin_manifest",
|
# "validate_plugin_manifest",
|
||||||
# "generate_plugin_manifest",
|
# "generate_plugin_manifest",
|
||||||
]
|
] # type: ignore
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
from .base_action import BaseAction
|
from .base_action import BaseAction
|
||||||
from .base_command import BaseCommand
|
from .base_command import BaseCommand
|
||||||
from .base_events_handler import BaseEventHandler
|
from .base_events_handler import BaseEventHandler
|
||||||
|
from .base_http_component import BaseRouterComponent
|
||||||
from .base_plugin import BasePlugin
|
from .base_plugin import BasePlugin
|
||||||
from .base_prompt import BasePrompt
|
from .base_prompt import BasePrompt
|
||||||
from .base_tool import BaseTool
|
from .base_tool import BaseTool
|
||||||
@@ -55,7 +56,7 @@ __all__ = [
|
|||||||
"PluginMetadata",
|
"PluginMetadata",
|
||||||
# 增强命令系统
|
# 增强命令系统
|
||||||
"PlusCommand",
|
"PlusCommand",
|
||||||
"PlusCommandAdapter",
|
"BaseRouterComponent"
|
||||||
"PlusCommandInfo",
|
"PlusCommandInfo",
|
||||||
"PythonDependency",
|
"PythonDependency",
|
||||||
"ToolInfo",
|
"ToolInfo",
|
||||||
|
|||||||
37
src/plugin_system/base/base_http_component.py
Normal file
37
src/plugin_system/base/base_http_component.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from fastapi import APIRouter
|
||||||
|
from .component_types import ComponentType, RouterInfo
|
||||||
|
|
||||||
|
class BaseRouterComponent(ABC):
|
||||||
|
"""
|
||||||
|
用于暴露HTTP端点的组件基类。
|
||||||
|
插件开发者应继承此类,并实现 register_endpoints 方法来定义API路由。
|
||||||
|
"""
|
||||||
|
# 组件元数据,由插件管理器读取
|
||||||
|
component_name: str
|
||||||
|
component_description: str
|
||||||
|
component_version: str = "1.0.0"
|
||||||
|
|
||||||
|
# 每个组件实例都会管理自己的APIRouter
|
||||||
|
router: APIRouter
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.router = APIRouter()
|
||||||
|
self.register_endpoints()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def register_endpoints(self) -> None:
|
||||||
|
"""
|
||||||
|
【开发者必须实现】
|
||||||
|
在此方法中定义所有HTTP端点。
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_router_info(cls) -> "RouterInfo":
|
||||||
|
"""从类属性生成RouterInfo"""
|
||||||
|
return RouterInfo(
|
||||||
|
name=cls.component_name,
|
||||||
|
description=getattr(cls, "component_description", "路由组件"),
|
||||||
|
component_type=ComponentType.ROUTER,
|
||||||
|
)
|
||||||
@@ -53,6 +53,7 @@ class ComponentType(Enum):
|
|||||||
CHATTER = "chatter" # 聊天处理器组件
|
CHATTER = "chatter" # 聊天处理器组件
|
||||||
INTEREST_CALCULATOR = "interest_calculator" # 兴趣度计算组件
|
INTEREST_CALCULATOR = "interest_calculator" # 兴趣度计算组件
|
||||||
PROMPT = "prompt" # Prompt组件
|
PROMPT = "prompt" # Prompt组件
|
||||||
|
ROUTER = "router" # 路由组件
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.value
|
return self.value
|
||||||
@@ -146,6 +147,7 @@ class PermissionNodeField:
|
|||||||
node_name: str # 节点名称 (例如 "manage" 或 "view")
|
node_name: str # 节点名称 (例如 "manage" 或 "view")
|
||||||
description: str # 权限描述
|
description: str # 权限描述
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ComponentInfo:
|
class ComponentInfo:
|
||||||
"""组件信息"""
|
"""组件信息"""
|
||||||
@@ -442,3 +444,13 @@ class MaiMessages:
|
|||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if self.message_segments is None:
|
if self.message_segments is None:
|
||||||
self.message_segments = []
|
self.message_segments = []
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RouterInfo(ComponentInfo):
|
||||||
|
"""路由组件信息"""
|
||||||
|
|
||||||
|
auth_required: bool = False
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
super().__post_init__()
|
||||||
|
self.component_type = ComponentType.ROUTER
|
||||||
|
|||||||
@@ -5,11 +5,15 @@ from pathlib import Path
|
|||||||
from re import Pattern
|
from re import Pattern
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
|
from fastapi import Depends
|
||||||
|
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
|
from src.config.config import global_config as bot_config
|
||||||
from src.plugin_system.base.base_action import BaseAction
|
from src.plugin_system.base.base_action import BaseAction
|
||||||
from src.plugin_system.base.base_chatter import BaseChatter
|
from src.plugin_system.base.base_chatter import BaseChatter
|
||||||
from src.plugin_system.base.base_command import BaseCommand
|
from src.plugin_system.base.base_command import BaseCommand
|
||||||
from src.plugin_system.base.base_events_handler import BaseEventHandler
|
from src.plugin_system.base.base_events_handler import BaseEventHandler
|
||||||
|
from src.plugin_system.base.base_http_component import BaseRouterComponent
|
||||||
from src.plugin_system.base.base_interest_calculator import BaseInterestCalculator
|
from src.plugin_system.base.base_interest_calculator import BaseInterestCalculator
|
||||||
from src.plugin_system.base.base_prompt import BasePrompt
|
from src.plugin_system.base.base_prompt import BasePrompt
|
||||||
from src.plugin_system.base.base_tool import BaseTool
|
from src.plugin_system.base.base_tool import BaseTool
|
||||||
@@ -24,6 +28,7 @@ from src.plugin_system.base.component_types import (
|
|||||||
PluginInfo,
|
PluginInfo,
|
||||||
PlusCommandInfo,
|
PlusCommandInfo,
|
||||||
PromptInfo,
|
PromptInfo,
|
||||||
|
RouterInfo,
|
||||||
ToolInfo,
|
ToolInfo,
|
||||||
)
|
)
|
||||||
from src.plugin_system.base.plus_command import PlusCommand, create_legacy_command_adapter
|
from src.plugin_system.base.plus_command import PlusCommand, create_legacy_command_adapter
|
||||||
@@ -40,6 +45,7 @@ ComponentClassType = (
|
|||||||
| type[BaseChatter]
|
| type[BaseChatter]
|
||||||
| type[BaseInterestCalculator]
|
| type[BaseInterestCalculator]
|
||||||
| type[BasePrompt]
|
| type[BasePrompt]
|
||||||
|
| type[BaseRouterComponent]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -194,6 +200,10 @@ class ComponentRegistry:
|
|||||||
assert isinstance(component_info, PromptInfo)
|
assert isinstance(component_info, PromptInfo)
|
||||||
assert issubclass(component_class, BasePrompt)
|
assert issubclass(component_class, BasePrompt)
|
||||||
ret = self._register_prompt_component(component_info, component_class)
|
ret = self._register_prompt_component(component_info, component_class)
|
||||||
|
case ComponentType.ROUTER:
|
||||||
|
assert isinstance(component_info, RouterInfo)
|
||||||
|
assert issubclass(component_class, BaseRouterComponent)
|
||||||
|
ret = self._register_router_component(component_info, component_class)
|
||||||
case _:
|
case _:
|
||||||
logger.warning(f"未知组件类型: {component_type}")
|
logger.warning(f"未知组件类型: {component_type}")
|
||||||
ret = False
|
ret = False
|
||||||
@@ -373,6 +383,48 @@ class ComponentRegistry:
|
|||||||
logger.debug(f"已注册Prompt组件: {prompt_name}")
|
logger.debug(f"已注册Prompt组件: {prompt_name}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _register_router_component(self, router_info: RouterInfo, router_class: type[BaseRouterComponent]) -> bool:
|
||||||
|
"""注册Router组件并将其端点挂载到主服务器"""
|
||||||
|
# 1. 检查总开关是否开启
|
||||||
|
if not bot_config.plugin_http_system.enable_plugin_http_endpoints:
|
||||||
|
logger.info("插件HTTP端点功能已禁用,跳过路由注册")
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
from src.common.security import get_api_key
|
||||||
|
from src.common.server import get_global_server
|
||||||
|
|
||||||
|
router_name = router_info.name
|
||||||
|
plugin_name = router_info.plugin_name
|
||||||
|
|
||||||
|
# 2. 实例化组件以触发其 __init__ 和 register_endpoints
|
||||||
|
component_instance = router_class()
|
||||||
|
|
||||||
|
# 3. 获取配置好的 APIRouter
|
||||||
|
plugin_router = component_instance.router
|
||||||
|
|
||||||
|
# 4. 获取全局服务器实例
|
||||||
|
server = get_global_server()
|
||||||
|
|
||||||
|
# 5. 生成唯一的URL前缀
|
||||||
|
prefix = f"/plugins/{plugin_name}"
|
||||||
|
|
||||||
|
# 6. 根据需要应用安全依赖项
|
||||||
|
dependencies = []
|
||||||
|
if router_info.auth_required:
|
||||||
|
dependencies.append(Depends(get_api_key))
|
||||||
|
|
||||||
|
# 7. 注册路由,并使用插件名作为API文档的分组标签
|
||||||
|
server.app.include_router(
|
||||||
|
plugin_router, prefix=prefix, tags=[plugin_name], dependencies=dependencies
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(f"成功将插件 '{plugin_name}' 的路由组件 '{router_name}' 挂载到: {prefix}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"注册路由组件 '{router_info.name}' 时出错: {e}", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
# === 组件移除相关 ===
|
# === 组件移除相关 ===
|
||||||
|
|
||||||
async def remove_component(self, component_name: str, component_type: ComponentType, plugin_name: str) -> bool:
|
async def remove_component(self, component_name: str, component_type: ComponentType, plugin_name: str) -> bool:
|
||||||
@@ -616,6 +668,7 @@ class ComponentRegistry:
|
|||||||
| BaseChatter
|
| BaseChatter
|
||||||
| BaseInterestCalculator
|
| BaseInterestCalculator
|
||||||
| BasePrompt
|
| BasePrompt
|
||||||
|
| BaseRouterComponent
|
||||||
]
|
]
|
||||||
| None
|
| None
|
||||||
):
|
):
|
||||||
@@ -643,6 +696,8 @@ class ComponentRegistry:
|
|||||||
| type[PlusCommand]
|
| type[PlusCommand]
|
||||||
| type[BaseChatter]
|
| type[BaseChatter]
|
||||||
| type[BaseInterestCalculator]
|
| type[BaseInterestCalculator]
|
||||||
|
| type[BasePrompt]
|
||||||
|
| type[BaseRouterComponent]
|
||||||
| None,
|
| None,
|
||||||
self._components_classes.get(namespaced_name),
|
self._components_classes.get(namespaced_name),
|
||||||
)
|
)
|
||||||
@@ -867,6 +922,7 @@ class ComponentRegistry:
|
|||||||
plus_command_components: int = 0
|
plus_command_components: int = 0
|
||||||
chatter_components: int = 0
|
chatter_components: int = 0
|
||||||
prompt_components: int = 0
|
prompt_components: int = 0
|
||||||
|
router_components: int = 0
|
||||||
for component in self._components.values():
|
for component in self._components.values():
|
||||||
if component.component_type == ComponentType.ACTION:
|
if component.component_type == ComponentType.ACTION:
|
||||||
action_components += 1
|
action_components += 1
|
||||||
@@ -882,6 +938,8 @@ class ComponentRegistry:
|
|||||||
chatter_components += 1
|
chatter_components += 1
|
||||||
elif component.component_type == ComponentType.PROMPT:
|
elif component.component_type == ComponentType.PROMPT:
|
||||||
prompt_components += 1
|
prompt_components += 1
|
||||||
|
elif component.component_type == ComponentType.ROUTER:
|
||||||
|
router_components += 1
|
||||||
return {
|
return {
|
||||||
"action_components": action_components,
|
"action_components": action_components,
|
||||||
"command_components": command_components,
|
"command_components": command_components,
|
||||||
@@ -891,6 +949,7 @@ class ComponentRegistry:
|
|||||||
"plus_command_components": plus_command_components,
|
"plus_command_components": plus_command_components,
|
||||||
"chatter_components": chatter_components,
|
"chatter_components": chatter_components,
|
||||||
"prompt_components": prompt_components,
|
"prompt_components": prompt_components,
|
||||||
|
"router_components": router_components,
|
||||||
"total_components": len(self._components),
|
"total_components": len(self._components),
|
||||||
"total_plugins": len(self._plugins),
|
"total_plugins": len(self._plugins),
|
||||||
"components_by_type": {
|
"components_by_type": {
|
||||||
|
|||||||
@@ -405,13 +405,14 @@ class PluginManager:
|
|||||||
plus_command_count = stats.get("plus_command_components", 0)
|
plus_command_count = stats.get("plus_command_components", 0)
|
||||||
chatter_count = stats.get("chatter_components", 0)
|
chatter_count = stats.get("chatter_components", 0)
|
||||||
prompt_count = stats.get("prompt_components", 0)
|
prompt_count = stats.get("prompt_components", 0)
|
||||||
|
router_count = stats.get("router_components", 0)
|
||||||
total_components = stats.get("total_components", 0)
|
total_components = stats.get("total_components", 0)
|
||||||
|
|
||||||
# 📋 显示插件加载总览
|
# 📋 显示插件加载总览
|
||||||
if total_registered > 0:
|
if total_registered > 0:
|
||||||
logger.info("🎉 插件系统加载完成!")
|
logger.info("🎉 插件系统加载完成!")
|
||||||
logger.info(
|
logger.info(
|
||||||
f"📊 总览: {total_registered}个插件, {total_components}个组件 (Action: {action_count}, Command: {command_count}, Tool: {tool_count}, PlusCommand: {plus_command_count}, EventHandler: {event_handler_count}, Chatter: {chatter_count}, Prompt: {prompt_count})"
|
f"📊 总览: {total_registered}个插件, {total_components}个组件 (Action: {action_count}, Command: {command_count}, Tool: {tool_count}, PlusCommand: {plus_command_count}, EventHandler: {event_handler_count}, Chatter: {chatter_count}, Prompt: {prompt_count}, Router: {router_count})"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 显示详细的插件列表
|
# 显示详细的插件列表
|
||||||
@@ -452,6 +453,9 @@ class PluginManager:
|
|||||||
prompt_components = [
|
prompt_components = [
|
||||||
c for c in plugin_info.components if c.component_type == ComponentType.PROMPT
|
c for c in plugin_info.components if c.component_type == ComponentType.PROMPT
|
||||||
]
|
]
|
||||||
|
router_components = [
|
||||||
|
c for c in plugin_info.components if c.component_type == ComponentType.ROUTER
|
||||||
|
]
|
||||||
|
|
||||||
if action_components:
|
if action_components:
|
||||||
action_details = [format_component(c) for c in action_components]
|
action_details = [format_component(c) for c in action_components]
|
||||||
@@ -478,6 +482,9 @@ class PluginManager:
|
|||||||
if prompt_components:
|
if prompt_components:
|
||||||
prompt_details = [format_component(c) for c in prompt_components]
|
prompt_details = [format_component(c) for c in prompt_components]
|
||||||
logger.info(f" 📝 Prompt组件: {', '.join(prompt_details)}")
|
logger.info(f" 📝 Prompt组件: {', '.join(prompt_details)}")
|
||||||
|
if router_components:
|
||||||
|
router_details = [format_component(c) for c in router_components]
|
||||||
|
logger.info(f" 🌐 Router组件: {', '.join(router_details)}")
|
||||||
|
|
||||||
# 权限节点信息
|
# 权限节点信息
|
||||||
if plugin_instance := self.loaded_plugins.get(plugin_name):
|
if plugin_instance := self.loaded_plugins.get(plugin_name):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[inner]
|
[inner]
|
||||||
version = "7.7.1"
|
version = "7.7.3"
|
||||||
|
|
||||||
#----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读----
|
#----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读----
|
||||||
#如果你想要修改配置文件,请递增version的值
|
#如果你想要修改配置文件,请递增version的值
|
||||||
@@ -59,6 +59,26 @@ cache_max_item_size_mb = 5 # 单个缓存条目最大大小(MB),超过此
|
|||||||
# 示例:[["qq", "123456"], ["telegram", "user789"]]
|
# 示例:[["qq", "123456"], ["telegram", "user789"]]
|
||||||
master_users = []# ["qq", "123456789"], # 示例:QQ平台的Master用户
|
master_users = []# ["qq", "123456789"], # 示例:QQ平台的Master用户
|
||||||
|
|
||||||
|
# ==================== 插件HTTP端点系统配置 ====================
|
||||||
|
[plugin_http_system]
|
||||||
|
# 总开关,用于启用或禁用所有插件的HTTP端点功能
|
||||||
|
enable_plugin_http_endpoints = true
|
||||||
|
|
||||||
|
# ==================== 安全相关配置 ====================
|
||||||
|
[security]
|
||||||
|
# --- 插件API速率限制 ---
|
||||||
|
# 是否为插件暴露的API启用全局速率限制
|
||||||
|
plugin_api_rate_limit_enable = true
|
||||||
|
# 默认的速率限制策略 (格式: "次数/时间单位")
|
||||||
|
# 可用单位: second, minute, hour, day
|
||||||
|
plugin_api_rate_limit_default = "100/minute"
|
||||||
|
|
||||||
|
# --- 插件API密钥认证 ---
|
||||||
|
# 用于访问需要认证的插件API的有效密钥列表
|
||||||
|
# 如果列表为空,则所有需要认证的API都将无法访问
|
||||||
|
# 例如: ["your-secret-key-1", "your-secret-key-2"]
|
||||||
|
plugin_api_valid_keys = []
|
||||||
|
|
||||||
[permission.master_prompt] # 主人身份提示词配置
|
[permission.master_prompt] # 主人身份提示词配置
|
||||||
enable = false # 是否启用主人/非主人提示注入
|
enable = false # 是否启用主人/非主人提示注入
|
||||||
master_hint = "你正在与自己的主人交流,注意展现亲切与尊重。" # 主人提示词
|
master_hint = "你正在与自己的主人交流,注意展现亲切与尊重。" # 主人提示词
|
||||||
|
|||||||
Reference in New Issue
Block a user