Merge remote-tracking branch 'upstream/r-dev' into sync/pr-1564-upstream-20260331
# Conflicts: # src/chat/brain_chat/PFC/conversation.py # src/chat/brain_chat/PFC/pfc_KnowledgeFetcher.py # src/chat/knowledge/lpmm_ops.py
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Mapping, Sequence, TypeVar
|
||||
from typing import Any, Callable, Mapping, Sequence, TypeVar, cast
|
||||
|
||||
import asyncio
|
||||
import copy
|
||||
@@ -27,6 +27,7 @@ from .official_configs import (
|
||||
LPMMKnowledgeConfig,
|
||||
MaiSakaConfig,
|
||||
MaimMessageConfig,
|
||||
MCPConfig,
|
||||
PluginRuntimeConfig,
|
||||
MemoryConfig,
|
||||
MessageReceiveConfig,
|
||||
@@ -56,8 +57,8 @@ CONFIG_DIR: Path = PROJECT_ROOT / "config"
|
||||
BOT_CONFIG_PATH: Path = (CONFIG_DIR / "bot_config.toml").resolve().absolute()
|
||||
MODEL_CONFIG_PATH: Path = (CONFIG_DIR / "model_config.toml").resolve().absolute()
|
||||
MMC_VERSION: str = "1.0.0"
|
||||
CONFIG_VERSION: str = "8.1.4"
|
||||
MODEL_CONFIG_VERSION: str = "1.12.0"
|
||||
CONFIG_VERSION: str = "8.2.0"
|
||||
MODEL_CONFIG_VERSION: str = "1.13.1"
|
||||
|
||||
logger = get_logger("config")
|
||||
|
||||
@@ -134,6 +135,9 @@ class Config(ConfigBase):
|
||||
maisaka: MaiSakaConfig = Field(default_factory=MaiSakaConfig)
|
||||
"""MaiSaka对话系统配置类"""
|
||||
|
||||
mcp: MCPConfig = Field(default_factory=MCPConfig)
|
||||
"""MCP 配置类"""
|
||||
|
||||
plugin_runtime: PluginRuntimeConfig = Field(default_factory=PluginRuntimeConfig)
|
||||
"""插件运行时配置类"""
|
||||
|
||||
@@ -332,7 +336,12 @@ class ConfigManager:
|
||||
changed_scopes: 本次热重载命中的配置范围。
|
||||
"""
|
||||
|
||||
result = callback(changed_scopes) if self._callback_accepts_scopes(callback) else callback()
|
||||
if self._callback_accepts_scopes(callback):
|
||||
callback_with_scopes = cast(Callable[[Sequence[str]], object], callback)
|
||||
result = callback_with_scopes(changed_scopes)
|
||||
else:
|
||||
callback_without_scopes = cast(Callable[[], object], callback)
|
||||
result = callback_without_scopes()
|
||||
if asyncio.iscoroutine(result):
|
||||
await result
|
||||
|
||||
|
||||
@@ -1,7 +1,35 @@
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from .config_base import ConfigBase, Field
|
||||
from src.common.i18n import t
|
||||
from .config_base import ConfigBase, Field
|
||||
|
||||
|
||||
class OpenAICompatibleAuthType(str, Enum):
|
||||
"""OpenAI 兼容接口的鉴权方式。"""
|
||||
|
||||
BEARER = "bearer"
|
||||
HEADER = "header"
|
||||
QUERY = "query"
|
||||
NONE = "none"
|
||||
|
||||
|
||||
class ReasoningParseMode(str, Enum):
|
||||
"""推理内容解析策略。"""
|
||||
|
||||
AUTO = "auto"
|
||||
NATIVE = "native"
|
||||
THINK_TAG = "think_tag"
|
||||
NONE = "none"
|
||||
|
||||
|
||||
class ToolArgumentParseMode(str, Enum):
|
||||
"""工具调用参数的解析策略。"""
|
||||
|
||||
AUTO = "auto"
|
||||
STRICT = "strict"
|
||||
REPAIR = "repair"
|
||||
DOUBLE_DECODE = "double_decode"
|
||||
|
||||
|
||||
class APIProvider(ConfigBase):
|
||||
@@ -33,7 +61,7 @@ class APIProvider(ConfigBase):
|
||||
"x-icon": "key",
|
||||
},
|
||||
)
|
||||
"""API密钥"""
|
||||
"""API密钥。对于不需要鉴权的兼容端点,可将 `auth_type` 设为 `none`。"""
|
||||
|
||||
client_type: str = Field(
|
||||
default="openai",
|
||||
@@ -44,6 +72,105 @@ class APIProvider(ConfigBase):
|
||||
)
|
||||
"""客户端类型 (可选: openai/google, 默认为openai)"""
|
||||
|
||||
auth_type: str = Field(
|
||||
default=OpenAICompatibleAuthType.BEARER.value,
|
||||
json_schema_extra={
|
||||
"x-widget": "select",
|
||||
"x-icon": "shield",
|
||||
},
|
||||
)
|
||||
"""OpenAI 兼容接口的鉴权方式。可选值:`bearer`、`header`、`query`、`none`。"""
|
||||
|
||||
auth_header_name: str = Field(
|
||||
default="Authorization",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "header",
|
||||
},
|
||||
)
|
||||
"""当 `auth_type` 为 `header` 时使用的请求头名称。"""
|
||||
|
||||
auth_header_prefix: str = Field(
|
||||
default="Bearer",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "shield-check",
|
||||
},
|
||||
)
|
||||
"""当 `auth_type` 为 `header` 时使用的请求头前缀。留空表示直接发送原始密钥。"""
|
||||
|
||||
auth_query_name: str = Field(
|
||||
default="api_key",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "link",
|
||||
},
|
||||
)
|
||||
"""当 `auth_type` 为 `query` 时使用的查询参数名称。"""
|
||||
|
||||
default_headers: dict[str, str] = Field(
|
||||
default_factory=dict,
|
||||
json_schema_extra={
|
||||
"x-widget": "custom",
|
||||
"x-icon": "header",
|
||||
},
|
||||
)
|
||||
"""所有请求默认附带的 HTTP Header。"""
|
||||
|
||||
default_query: dict[str, str] = Field(
|
||||
default_factory=dict,
|
||||
json_schema_extra={
|
||||
"x-widget": "custom",
|
||||
"x-icon": "list-filter",
|
||||
},
|
||||
)
|
||||
"""所有请求默认附带的查询参数。"""
|
||||
|
||||
organization: str | None = Field(
|
||||
default=None,
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "building-2",
|
||||
},
|
||||
)
|
||||
"""OpenAI 官方接口可选的 `organization`。"""
|
||||
|
||||
project: str | None = Field(
|
||||
default=None,
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "folder-kanban",
|
||||
},
|
||||
)
|
||||
"""OpenAI 官方接口可选的 `project`。"""
|
||||
|
||||
model_list_endpoint: str = Field(
|
||||
default="/models",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "list",
|
||||
},
|
||||
)
|
||||
"""模型列表端点路径。适用于 OpenAI 兼容接口的探测与管理。"""
|
||||
|
||||
reasoning_parse_mode: str = Field(
|
||||
default=ReasoningParseMode.AUTO.value,
|
||||
json_schema_extra={
|
||||
"x-widget": "select",
|
||||
"x-icon": "brain",
|
||||
},
|
||||
)
|
||||
"""推理内容解析模式。可选值:`auto`、`native`、`think_tag`、`none`。"""
|
||||
|
||||
tool_argument_parse_mode: str = Field(
|
||||
default=ToolArgumentParseMode.AUTO.value,
|
||||
json_schema_extra={
|
||||
"x-widget": "select",
|
||||
"x-icon": "braces",
|
||||
},
|
||||
)
|
||||
"""工具参数解析模式。可选值:`auto`、`strict`、`repair`、`double_decode`。"""
|
||||
|
||||
max_retry: int = Field(
|
||||
default=2,
|
||||
ge=0,
|
||||
@@ -76,15 +203,26 @@ class APIProvider(ConfigBase):
|
||||
)
|
||||
"""重试间隔 (如果API调用失败, 重试的间隔时间, 单位: 秒)"""
|
||||
|
||||
def model_post_init(self, context: Any = None):
|
||||
"""确保api_key在repr中不被显示"""
|
||||
if not self.api_key:
|
||||
def model_post_init(self, context: Any = None) -> None:
|
||||
"""执行 API 提供商配置的后置校验。
|
||||
|
||||
Args:
|
||||
context: Pydantic 传入的上下文对象。
|
||||
|
||||
Raises:
|
||||
ValueError: 当配置项缺失或组合不合法时抛出。
|
||||
"""
|
||||
if self.auth_type != OpenAICompatibleAuthType.NONE and not self.api_key:
|
||||
raise ValueError(t("config.api_key_empty"))
|
||||
if not self.base_url and self.client_type != "gemini": # TODO: 允许gemini使用base_url
|
||||
raise ValueError(t("config.api_base_url_empty"))
|
||||
if not self.name:
|
||||
raise ValueError(t("config.api_provider_name_empty"))
|
||||
return super().model_post_init(context)
|
||||
if self.auth_type == OpenAICompatibleAuthType.HEADER and not self.auth_header_name.strip():
|
||||
raise ValueError("当 auth_type=header 时,auth_header_name 不能为空")
|
||||
if self.auth_type == OpenAICompatibleAuthType.QUERY and not self.auth_query_name.strip():
|
||||
raise ValueError("当 auth_type=query 时,auth_query_name 不能为空")
|
||||
super().model_post_init(context)
|
||||
|
||||
|
||||
class ModelInfo(ConfigBase):
|
||||
@@ -264,6 +402,15 @@ class ModelTaskConfig(ConfigBase):
|
||||
},
|
||||
)
|
||||
"""首要回复模型配置, 还用于表达器和表达方式学习"""
|
||||
|
||||
planner: TaskConfig = Field(
|
||||
default_factory=TaskConfig,
|
||||
json_schema_extra={
|
||||
"x-widget": "custom",
|
||||
"x-icon": "map",
|
||||
},
|
||||
)
|
||||
"""规划模型配置"""
|
||||
|
||||
vlm: TaskConfig = Field(
|
||||
default_factory=TaskConfig,
|
||||
@@ -283,24 +430,6 @@ class ModelTaskConfig(ConfigBase):
|
||||
)
|
||||
"""语音识别模型配置"""
|
||||
|
||||
tool_use: TaskConfig = Field(
|
||||
default_factory=TaskConfig,
|
||||
json_schema_extra={
|
||||
"x-widget": "custom",
|
||||
"x-icon": "tools",
|
||||
},
|
||||
)
|
||||
"""工具使用模型配置, 需要使用支持工具调用的模型"""
|
||||
|
||||
planner: TaskConfig = Field(
|
||||
default_factory=TaskConfig,
|
||||
json_schema_extra={
|
||||
"x-widget": "custom",
|
||||
"x-icon": "map",
|
||||
},
|
||||
)
|
||||
"""规划模型配置"""
|
||||
|
||||
embedding: TaskConfig = Field(
|
||||
default_factory=TaskConfig,
|
||||
json_schema_extra={
|
||||
@@ -308,22 +437,4 @@ class ModelTaskConfig(ConfigBase):
|
||||
"x-icon": "database",
|
||||
},
|
||||
)
|
||||
"""嵌入模型配置"""
|
||||
|
||||
lpmm_entity_extract: TaskConfig = Field(
|
||||
default_factory=TaskConfig,
|
||||
json_schema_extra={
|
||||
"x-widget": "custom",
|
||||
"x-icon": "filter",
|
||||
},
|
||||
)
|
||||
"""LPMM实体提取模型配置"""
|
||||
|
||||
lpmm_rdf_build: TaskConfig = Field(
|
||||
default_factory=TaskConfig,
|
||||
json_schema_extra={
|
||||
"x-widget": "custom",
|
||||
"x-icon": "network",
|
||||
},
|
||||
)
|
||||
"""LPMM RDF构建模型配置"""
|
||||
"""嵌入模型配置"""
|
||||
@@ -1,6 +1,8 @@
|
||||
from .config_base import ConfigBase, Field
|
||||
from typing import Literal, Optional
|
||||
|
||||
import re
|
||||
from typing import Optional, Literal
|
||||
|
||||
from .config_base import ConfigBase, Field
|
||||
|
||||
"""
|
||||
须知:
|
||||
@@ -234,17 +236,6 @@ class ChatConfig(ConfigBase):
|
||||
)
|
||||
"""上下文长度"""
|
||||
|
||||
planner_smooth: float = Field(
|
||||
default=3,
|
||||
ge=0,
|
||||
json_schema_extra={
|
||||
"x-widget": "slider",
|
||||
"x-icon": "gauge",
|
||||
"step": 0.5,
|
||||
},
|
||||
)
|
||||
"""规划器平滑,增大数值会减小planner负荷,略微降低反应速度,推荐1-5,0为关闭,必须大于等于0"""
|
||||
|
||||
think_mode: Literal["classic", "deep", "dynamic"] = Field(
|
||||
default="dynamic",
|
||||
json_schema_extra={
|
||||
@@ -677,21 +668,6 @@ class ExpressionConfig(ConfigBase):
|
||||
)
|
||||
"""是否在回复前尝试对上下文中的黑话进行解释(关闭可减少一次LLM调用,仅影响回复前的黑话匹配与解释,不影响黑话学习)"""
|
||||
|
||||
jargon_mode: Literal["context", "planner"] = Field(
|
||||
default="planner",
|
||||
json_schema_extra={
|
||||
"x-widget": "select",
|
||||
"x-icon": "settings",
|
||||
},
|
||||
)
|
||||
"""
|
||||
黑话解释来源模式
|
||||
|
||||
可选:
|
||||
- "context":使用上下文自动匹配黑话
|
||||
- "planner":仅使用Planner在reply动作中给出的unknown_words列表
|
||||
"""
|
||||
|
||||
|
||||
class ToolConfig(ConfigBase):
|
||||
"""工具配置类"""
|
||||
@@ -1528,33 +1504,6 @@ class MaiSakaConfig(ConfigBase):
|
||||
__ui_icon__ = "message-circle"
|
||||
__ui_parent__ = "experimental"
|
||||
|
||||
enable_emotion_module: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "heart",
|
||||
},
|
||||
)
|
||||
"""启用情绪感知模块"""
|
||||
|
||||
enable_cognition_module: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "brain",
|
||||
},
|
||||
)
|
||||
"""启用认知分析模块"""
|
||||
|
||||
enable_timing_module: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "clock",
|
||||
},
|
||||
)
|
||||
"""启用时间感知模块(含自我反思功能)"""
|
||||
|
||||
enable_knowledge_module: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
@@ -1564,42 +1513,6 @@ class MaiSakaConfig(ConfigBase):
|
||||
)
|
||||
"""启用知识库模块"""
|
||||
|
||||
enable_mcp: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "zap",
|
||||
},
|
||||
)
|
||||
"""启用 MCP (Model Context Protocol) 支持"""
|
||||
|
||||
enable_write_file: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "file-plus",
|
||||
},
|
||||
)
|
||||
"""启用文件写入工具"""
|
||||
|
||||
enable_read_file: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "file-text",
|
||||
},
|
||||
)
|
||||
"""启用文件读取工具"""
|
||||
|
||||
enable_list_files: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "list",
|
||||
},
|
||||
)
|
||||
"""启用文件列表工具"""
|
||||
|
||||
show_analyze_cognition_prompt: bool = Field(
|
||||
default=False,
|
||||
json_schema_extra={
|
||||
@@ -1609,15 +1522,6 @@ class MaiSakaConfig(ConfigBase):
|
||||
)
|
||||
"""是否在 CLI 中显示 analyze_cognition 的 Prompt"""
|
||||
|
||||
show_analyze_timing_prompt: bool = Field(
|
||||
default=False,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "terminal",
|
||||
},
|
||||
)
|
||||
"""是否在 CLI 中显示 analyze_timing 的 Prompt"""
|
||||
|
||||
show_thinking: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
@@ -1625,7 +1529,7 @@ class MaiSakaConfig(ConfigBase):
|
||||
"x-icon": "brain",
|
||||
},
|
||||
)
|
||||
"""鏄惁鍦?CLI 涓樉绀哄唴蹇冩€濊€冨拰瀹屾暣 Prompt"""
|
||||
"""是否显示MaiSaka思考过程"""
|
||||
|
||||
user_name: str = Field(
|
||||
default="用户",
|
||||
@@ -1634,7 +1538,465 @@ class MaiSakaConfig(ConfigBase):
|
||||
"x-icon": "user",
|
||||
},
|
||||
)
|
||||
"""MaiSaka 涓敤鎴风殑鏄剧ず鍚嶇О"""
|
||||
"""MaiSaka 使用的用户名称"""
|
||||
|
||||
direct_image_input: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "image",
|
||||
},
|
||||
)
|
||||
"""是否直接输入图片"""
|
||||
|
||||
merge_user_messages: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "merge",
|
||||
},
|
||||
)
|
||||
"""是否将新接收的用户发言合并为单个用户消息"""
|
||||
|
||||
max_internal_rounds: int = Field(
|
||||
default=6,
|
||||
ge=1,
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "repeat",
|
||||
},
|
||||
)
|
||||
"""每个入站消息的最大内部规划轮数"""
|
||||
|
||||
tool_filter_task_name: str = Field(
|
||||
default="utils",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "sparkles",
|
||||
},
|
||||
)
|
||||
"""工具筛选预判使用的模型任务名"""
|
||||
|
||||
tool_filter_threshold: int = Field(
|
||||
default=20,
|
||||
ge=1,
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "filter",
|
||||
},
|
||||
)
|
||||
"""当可用工具总数超过该阈值时,先进行一轮工具筛选"""
|
||||
|
||||
tool_filter_max_keep: int = Field(
|
||||
default=5,
|
||||
ge=1,
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "list-filter",
|
||||
},
|
||||
)
|
||||
"""工具筛选阶段最多保留的非内置工具数量"""
|
||||
|
||||
terminal_image_preview: bool = Field(
|
||||
default=False,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "image",
|
||||
},
|
||||
)
|
||||
"""是否渲染低分辨率终端预览图片"""
|
||||
|
||||
terminal_image_preview_width: int = Field(
|
||||
default=24,
|
||||
ge=8,
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "columns",
|
||||
},
|
||||
)
|
||||
"""Maisaka终端图片预览的字符宽度"""
|
||||
|
||||
|
||||
class MCPAuthorizationConfig(ConfigBase):
|
||||
"""MCP HTTP 认证配置。"""
|
||||
|
||||
mode: Literal["none", "bearer"] = Field(
|
||||
default="none",
|
||||
json_schema_extra={
|
||||
"x-widget": "select",
|
||||
"x-icon": "shield",
|
||||
},
|
||||
)
|
||||
"""认证模式,当前支持无认证和静态 Bearer Token"""
|
||||
|
||||
bearer_token: str = Field(
|
||||
default="",
|
||||
json_schema_extra={
|
||||
"x-widget": "password",
|
||||
"x-icon": "key",
|
||||
},
|
||||
)
|
||||
"""静态 Bearer Token,仅在 `mode=\"bearer\"` 时使用"""
|
||||
|
||||
def model_post_init(self, context: Optional[dict] = None) -> None:
|
||||
"""验证 MCP 认证配置。
|
||||
|
||||
Args:
|
||||
context: Pydantic 传入的上下文对象。
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
if self.mode == "bearer" and not self.bearer_token.strip():
|
||||
raise ValueError("MCP 使用 bearer 认证时必须填写 bearer_token")
|
||||
return super().model_post_init(context)
|
||||
|
||||
|
||||
class MCPRootItemConfig(ConfigBase):
|
||||
"""单个 MCP Root 配置。"""
|
||||
|
||||
enabled: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "power",
|
||||
},
|
||||
)
|
||||
"""是否启用当前 Root"""
|
||||
|
||||
uri: str = Field(
|
||||
default="",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "folder",
|
||||
},
|
||||
)
|
||||
"""Root URI,通常为 `file://` 路径 URI"""
|
||||
|
||||
name: str = Field(
|
||||
default="",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "tag",
|
||||
},
|
||||
)
|
||||
"""Root 的显示名称"""
|
||||
|
||||
def model_post_init(self, context: Optional[dict] = None) -> None:
|
||||
"""验证单个 Root 配置。
|
||||
|
||||
Args:
|
||||
context: Pydantic 传入的上下文对象。
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
if self.enabled and not self.uri.strip():
|
||||
raise ValueError("启用的 MCP Root 必须填写 uri")
|
||||
return super().model_post_init(context)
|
||||
|
||||
|
||||
class MCPRootsConfig(ConfigBase):
|
||||
"""MCP Roots 能力配置。"""
|
||||
|
||||
enable: bool = Field(
|
||||
default=False,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "folder-tree",
|
||||
},
|
||||
)
|
||||
"""是否向 MCP 服务器暴露 Roots 能力"""
|
||||
|
||||
items: list[MCPRootItemConfig] = Field(
|
||||
default_factory=lambda: [],
|
||||
json_schema_extra={
|
||||
"x-widget": "custom",
|
||||
"x-icon": "folder",
|
||||
},
|
||||
)
|
||||
"""Roots 列表"""
|
||||
|
||||
|
||||
class MCPSamplingConfig(ConfigBase):
|
||||
"""MCP Sampling 能力配置。"""
|
||||
|
||||
enable: bool = Field(
|
||||
default=False,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "brain",
|
||||
},
|
||||
)
|
||||
"""是否启用 Sampling 能力声明"""
|
||||
|
||||
task_name: str = Field(
|
||||
default="planner",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "sparkles",
|
||||
},
|
||||
)
|
||||
"""执行 Sampling 请求时使用的主程序模型任务名"""
|
||||
|
||||
include_context_support: bool = Field(
|
||||
default=False,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "layers",
|
||||
},
|
||||
)
|
||||
"""是否声明支持 `includeContext` 非 `none` 语义"""
|
||||
|
||||
tool_support: bool = Field(
|
||||
default=False,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "wrench",
|
||||
},
|
||||
)
|
||||
"""是否声明支持在 Sampling 中继续使用工具"""
|
||||
|
||||
|
||||
class MCPElicitationConfig(ConfigBase):
|
||||
"""MCP Elicitation 能力配置。"""
|
||||
|
||||
enable: bool = Field(
|
||||
default=False,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "message-circle-question",
|
||||
},
|
||||
)
|
||||
"""是否启用 Elicitation 能力声明"""
|
||||
|
||||
allow_form: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "form-input",
|
||||
},
|
||||
)
|
||||
"""是否允许表单模式 Elicitation"""
|
||||
|
||||
allow_url: bool = Field(
|
||||
default=False,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "link",
|
||||
},
|
||||
)
|
||||
"""是否允许 URL 模式 Elicitation"""
|
||||
|
||||
def model_post_init(self, context: Optional[dict] = None) -> None:
|
||||
"""验证 Elicitation 配置。
|
||||
|
||||
Args:
|
||||
context: Pydantic 传入的上下文对象。
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
if self.enable and not (self.allow_form or self.allow_url):
|
||||
raise ValueError("启用 MCP Elicitation 时至少需要允许一种模式")
|
||||
return super().model_post_init(context)
|
||||
|
||||
|
||||
class MCPClientConfig(ConfigBase):
|
||||
"""MCP 客户端宿主能力配置。"""
|
||||
|
||||
client_name: str = Field(
|
||||
default="MaiBot",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "bot",
|
||||
},
|
||||
)
|
||||
"""MCP 客户端实现名称"""
|
||||
|
||||
client_version: str = Field(
|
||||
default="1.0.0",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "info",
|
||||
},
|
||||
)
|
||||
"""MCP 客户端实现版本"""
|
||||
|
||||
roots: MCPRootsConfig = Field(default_factory=MCPRootsConfig)
|
||||
"""Roots 能力配置"""
|
||||
|
||||
sampling: MCPSamplingConfig = Field(default_factory=MCPSamplingConfig)
|
||||
"""Sampling 能力配置"""
|
||||
|
||||
elicitation: MCPElicitationConfig = Field(default_factory=MCPElicitationConfig)
|
||||
"""Elicitation 能力配置"""
|
||||
|
||||
|
||||
class MCPServerItemConfig(ConfigBase):
|
||||
"""单个 MCP 服务器配置。"""
|
||||
|
||||
name: str = Field(
|
||||
default="",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "tag",
|
||||
},
|
||||
)
|
||||
"""服务器名称,必须唯一"""
|
||||
|
||||
enabled: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "power",
|
||||
},
|
||||
)
|
||||
"""是否启用当前 MCP 服务器"""
|
||||
|
||||
transport: Literal["stdio", "streamable_http"] = Field(
|
||||
default="stdio",
|
||||
json_schema_extra={
|
||||
"x-widget": "select",
|
||||
"x-icon": "shuffle",
|
||||
},
|
||||
)
|
||||
"""传输方式,可选 `stdio` 或 `streamable_http`"""
|
||||
|
||||
command: str = Field(
|
||||
default="",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "terminal",
|
||||
},
|
||||
)
|
||||
"""stdio 模式下启动服务器的命令"""
|
||||
|
||||
args: list[str] = Field(
|
||||
default_factory=lambda: [],
|
||||
json_schema_extra={
|
||||
"x-widget": "custom",
|
||||
"x-icon": "list",
|
||||
},
|
||||
)
|
||||
"""stdio 模式下的命令参数列表"""
|
||||
|
||||
env: dict[str, str] = Field(
|
||||
default_factory=lambda: {},
|
||||
json_schema_extra={
|
||||
"x-widget": "custom",
|
||||
"x-icon": "variable",
|
||||
},
|
||||
)
|
||||
"""stdio 模式下附加的环境变量"""
|
||||
|
||||
url: str = Field(
|
||||
default="",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "link",
|
||||
},
|
||||
)
|
||||
"""`streamable_http` 模式下的 MCP 端点地址"""
|
||||
|
||||
headers: dict[str, str] = Field(
|
||||
default_factory=lambda: {},
|
||||
json_schema_extra={
|
||||
"x-widget": "custom",
|
||||
"x-icon": "file-json",
|
||||
},
|
||||
)
|
||||
"""HTTP 模式下附加的请求头"""
|
||||
|
||||
http_timeout_seconds: float = Field(
|
||||
default=30.0,
|
||||
gt=0,
|
||||
json_schema_extra={
|
||||
"x-widget": "number",
|
||||
"x-icon": "clock-3",
|
||||
},
|
||||
)
|
||||
"""HTTP 请求超时时间,单位秒"""
|
||||
|
||||
read_timeout_seconds: float = Field(
|
||||
default=300.0,
|
||||
gt=0,
|
||||
json_schema_extra={
|
||||
"x-widget": "number",
|
||||
"x-icon": "timer",
|
||||
},
|
||||
)
|
||||
"""会话读取超时时间,单位秒"""
|
||||
|
||||
authorization: MCPAuthorizationConfig = Field(default_factory=MCPAuthorizationConfig)
|
||||
"""HTTP 认证配置"""
|
||||
|
||||
def model_post_init(self, context: Optional[dict] = None) -> None:
|
||||
"""验证 MCP 服务器配置。
|
||||
|
||||
Args:
|
||||
context: Pydantic 传入的上下文对象。
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
if not self.name.strip():
|
||||
raise ValueError("MCPServerItemConfig.name 不能为空")
|
||||
|
||||
if self.transport == "stdio" and not self.command.strip():
|
||||
raise ValueError(f"MCP 服务器 {self.name} 使用 stdio 时必须填写 command")
|
||||
|
||||
if self.transport == "streamable_http" and not self.url.strip():
|
||||
raise ValueError(f"MCP 服务器 {self.name} 使用 streamable_http 时必须填写 url")
|
||||
|
||||
return super().model_post_init(context)
|
||||
|
||||
|
||||
class MCPConfig(ConfigBase):
|
||||
"""MCP 总配置。"""
|
||||
|
||||
__ui_parent__ = "maisaka"
|
||||
|
||||
enable: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "zap",
|
||||
},
|
||||
)
|
||||
"""是否启用 MCP(Model Context Protocol)"""
|
||||
|
||||
client: MCPClientConfig = Field(default_factory=MCPClientConfig)
|
||||
"""MCP 客户端宿主能力配置"""
|
||||
|
||||
servers: list[MCPServerItemConfig] = Field(
|
||||
default_factory=lambda: [],
|
||||
json_schema_extra={
|
||||
"x-widget": "custom",
|
||||
"x-icon": "server",
|
||||
},
|
||||
)
|
||||
"""_wrap_MCP 服务器配置列表"""
|
||||
|
||||
def model_post_init(self, context: Optional[dict] = None) -> None:
|
||||
"""验证 MCP 总配置。
|
||||
|
||||
Args:
|
||||
context: Pydantic 传入的上下文对象。
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
server_names = [server.name.strip() for server in self.servers if server.name.strip()]
|
||||
if len(server_names) != len(set(server_names)):
|
||||
raise ValueError("MCP 配置中的服务器名称不能重复")
|
||||
return super().model_post_init(context)
|
||||
|
||||
|
||||
class PluginRuntimeConfig(ConfigBase):
|
||||
"""插件运行时配置类"""
|
||||
|
||||
Reference in New Issue
Block a user