Files

379 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""配置数据模型"""
from typing import ClassVar, Dict, List, Literal, Optional
from pydantic import BaseModel, Field, field_validator
class ProviderConfig(BaseModel):
"""LLM 提供商配置"""
api_key: str = ""
api_keys: List[str] = Field(default_factory=list, description="API 密钥列表,用于轮换")
api_base: Optional[str] = None
enabled: bool = True
model: Optional[str] = None
def get_effective_api_keys(self) -> List[str]:
"""返回去重后的有效 API Key 列表,保证至少包含 api_key。"""
seen: set[str] = set()
result: List[str] = []
for key in self.api_keys:
trimmed = (key or "").strip()
if trimmed and trimmed not in seen:
seen.add(trimmed)
result.append(trimmed)
primary = (self.api_key or "").strip()
if primary and primary not in seen:
result.insert(0, primary)
return result
class ModelConfig(BaseModel):
"""模型配置"""
MAX_TOKENS_LIMIT: ClassVar[int] = 2_000_000
provider: str = "zhipu"
model: str = "glm-5"
api_mode: Literal["chat_completions"] = Field(
default="chat_completions",
description="OpenAI API 模式,固定为 chat.completions",
)
temperature: float = Field(default=0.0, ge=0.0, le=2.0, description="温度参数,0 表示不发送")
max_tokens: int = Field(
default=0,
ge=0,
le=MAX_TOKENS_LIMIT,
description="最大输出 token 数,0 表示不发送",
)
max_iterations: int = Field(default=25, ge=1, le=150)
thinking_enabled: bool = Field(default=True, description="是否启用模型思考模式")
@field_validator("api_mode", mode="before")
@classmethod
def normalize_api_mode(cls, value):
return "chat_completions"
class WorkspaceConfig(BaseModel):
"""工作空间配置"""
path: str = ""
def __init__(self, **data):
"""初始化,设置默认工作空间路径"""
super().__init__(**data)
if not self.path:
self.path = self._get_default_workspace_path()
def _get_default_workspace_path(self) -> str:
"""获取默认工作空间路径"""
import os
import sys
from pathlib import Path
try:
# 获取程序目录
if getattr(sys, 'frozen', False):
# 打包后的可执行文件
app_dir = Path(sys.executable).parent
else:
# 开发环境
app_dir = Path(__file__).parent.parent.parent.parent
# 默认工作空间:程序目录/workspace
default_workspace = app_dir / "workspace"
default_workspace.mkdir(exist_ok=True)
# 创建临时目录
temp_dir = default_workspace / "temp"
temp_dir.mkdir(exist_ok=True)
return str(default_workspace.resolve())
except Exception:
# 备用方案:当前目录/workspace
fallback_workspace = Path.cwd() / "workspace"
fallback_workspace.mkdir(exist_ok=True)
(fallback_workspace / "temp").mkdir(exist_ok=True)
return str(fallback_workspace.resolve())
class HeartbeatConfig(BaseModel):
"""主动问候配置"""
enabled: bool = Field(default=False, description="是否启用主动问候")
channel: str = Field(default="", description="推送渠道(feishu/telegram/dingtalk/wecom/qq)")
account_id: str = Field(default="default", description="推送机器人账号 ID(多机器人渠道)")
chat_id: str = Field(default="", description="推送目标 ID(群组或用户)")
schedule: str = Field(default="0 * * * *", description="检查频率 cron 表达式")
idle_threshold_hours: int = Field(default=4, ge=1, le=24, description="用户空闲多少小时后触发")
quiet_start: int = Field(default=21, ge=0, le=23, description="免打扰开始时间(小时,北京时间)")
quiet_end: int = Field(default=8, ge=0, le=23, description="免打扰结束时间(小时,北京时间)")
max_greets_per_day: int = Field(default=2, ge=1, le=5, description="每天最多问候次数")
class PersonaConfig(BaseModel):
"""用户信息和AI人设配置"""
ai_name: str = Field(default="小C", description="AI的名字")
user_name: str = Field(default="主人", description="用户的称呼")
user_address: str = Field(default="", description="用户的常用地址(可选)")
output_language: str = Field(default="中文", description="AI默认输出语言")
personality: str = Field(default="grumpy", description="AI的性格类型")
custom_personality: str = Field(default="", description="自定义性格描述")
max_history_messages: int = Field(
default=0,
ge=-1,
le=500,
description="最大对话历史条数,0表示不限且关闭短期上下文总结,-1为旧版兼容的不限制",
)
enable_short_context_summary: bool = Field(
default=False,
description="是否启用短期上下文摘要缓存;默认关闭,关闭后即使限制历史条数,也只发送最近窗口原始消息",
)
heartbeat: HeartbeatConfig = Field(default_factory=HeartbeatConfig, description="主动问候配置")
class SecurityConfig(BaseModel):
"""安全配置"""
# 危险命令检测
dangerous_commands_blocked: bool = Field(default=True, description="是否阻止危险命令")
custom_deny_patterns: List[str] = Field(default_factory=list, description="自定义拒绝模式列表")
# 命令白名单
command_whitelist_enabled: bool = Field(default=False, description="是否启用命令白名单")
custom_allow_patterns: List[str] = Field(default_factory=list, description="自定义允许模式列表")
# 审计日志
audit_log_enabled: bool = Field(default=True, description="是否启用审计日志")
# 超时配置
command_timeout: int = Field(default=180, ge=10, le=1800, description="工具调用超时时间(秒)")
subagent_timeout: int = Field(default=1200, ge=60, le=3600, description="子代理超时时间(秒)")
# 输出限制
max_output_length: int = Field(default=10000, ge=100, le=1000000, description="最大输出长度(字符)")
# 工作空间限制
restrict_to_workspace: bool = Field(default=False, description="是否限制命令在工作空间内执行")
class ChannelAccountConfig(BaseModel):
"""支持多机器人实例的基础渠道配置。"""
enabled: bool = False
display_name: str = Field(default="", description="机器人名称")
account_id: str = Field(default="default", description="机器人账号 ID")
allow_from: List[str] = Field(default_factory=list)
routing_mode: str = Field(
default="ai",
description="默认路由模式:ai=通过 CountBot 主 AI,direct=直接转发给外部编程代理",
)
external_coding_profile: str = Field(
default="",
description="默认外部编程代理 profile 名称,如 codex、claude",
)
class TelegramAccountConfig(ChannelAccountConfig):
"""Telegram 机器人配置"""
token: str = ""
proxy: Optional[str] = None
class TelegramConfig(TelegramAccountConfig):
"""Telegram 渠道配置"""
accounts: Dict[str, TelegramAccountConfig] = Field(default_factory=dict)
class DiscordAccountConfig(ChannelAccountConfig):
"""Discord 机器人配置"""
token: str = ""
class DiscordConfig(DiscordAccountConfig):
"""Discord 渠道配置"""
accounts: Dict[str, DiscordAccountConfig] = Field(default_factory=dict)
class QQAccountConfig(ChannelAccountConfig):
"""QQ 机器人配置"""
app_id: str = ""
secret: str = ""
markdown_enabled: bool = True
group_markdown_enabled: bool = True
class QQConfig(QQAccountConfig):
"""QQ 渠道配置"""
accounts: Dict[str, QQAccountConfig] = Field(default_factory=dict)
class DingTalkAccountConfig(ChannelAccountConfig):
"""钉钉机器人配置"""
client_id: str = ""
client_secret: str = ""
class DingTalkConfig(DingTalkAccountConfig):
"""钉钉渠道配置"""
accounts: Dict[str, DingTalkAccountConfig] = Field(default_factory=dict)
class FeishuAccountConfig(ChannelAccountConfig):
"""飞书机器人配置"""
app_id: str = ""
app_secret: str = ""
class FeishuConfig(FeishuAccountConfig):
"""飞书渠道配置"""
accounts: Dict[str, FeishuAccountConfig] = Field(default_factory=dict)
class WeiboAccountConfig(ChannelAccountConfig):
"""微博机器人配置"""
app_id: str = ""
app_secret: str = ""
account_id: str = Field(default="default", description="账号 ID,用于多账号支持")
token_endpoint: str = Field(default="http://open-im.api.weibo.com/open/auth/ws_token")
ws_endpoint: str = Field(default="ws://open-im.api.weibo.com/ws/stream")
class WeiboConfig(WeiboAccountConfig):
"""微博渠道配置"""
accounts: Dict[str, WeiboAccountConfig] = Field(default_factory=dict)
class WeComAccountConfig(ChannelAccountConfig):
"""企业微信机器人配置"""
bot_id: str = ""
secret: str = ""
websocket_url: str = Field(default="wss://openws.work.weixin.qq.com", description="WebSocket 连接地址")
class WeComConfig(WeComAccountConfig):
"""企业微信渠道配置"""
accounts: Dict[str, WeComAccountConfig] = Field(default_factory=dict)
class WeChatAccountConfig(ChannelAccountConfig):
"""微信机器人配置"""
base_url: str = Field(default="https://ilinkai.weixin.qq.com", description="微信 iLink API 地址")
cdn_base_url: str = Field(default="https://novac2c.cdn.weixin.qq.com/c2c", description="微信 CDN 地址")
token: str = ""
login_bot_id: str = Field(default="", description="扫码登录返回的 bot 标识")
login_user_id: str = Field(default="", description="扫码登录返回的微信用户标识")
class WeChatConfig(WeChatAccountConfig):
"""微信渠道配置"""
accounts: Dict[str, WeChatAccountConfig] = Field(default_factory=dict)
class XiaozhiAccountConfig(ChannelAccountConfig):
"""小智AI 机器人配置(MCP Client 模式)"""
endpoint: str = Field(default="", description="小智AI MCP WebSocket 接入点,如 ws://192.168.1.x:8765")
enable_conversation: bool = Field(default=False, description="启用对话模式(通过 send_message 工具接收用户消息)")
class XiaozhiConfig(XiaozhiAccountConfig):
"""小智AI渠道配置(MCP Client 模式)"""
accounts: Dict[str, XiaozhiAccountConfig] = Field(default_factory=dict)
class McpServerConfig(BaseModel):
"""单个 MCP Server 连接配置"""
id: str = ""
name: str = ""
enabled: bool = True
transport: Optional[Literal["stdio", "streamable_http", "sse"]] = None
description: str = ""
include_tools: List[str] = Field(default_factory=lambda: ["*"])
exclude_tools: List[str] = Field(default_factory=list)
enable_resources: bool = False
enable_prompts: bool = False
command: str = ""
args: List[str] = Field(default_factory=list)
env: Dict[str, str] = Field(default_factory=dict)
url: str = ""
headers: Dict[str, str] = Field(default_factory=dict)
timeout: int = Field(default=30, ge=5, le=300)
connect_timeout: int = Field(default=10, ge=5, le=60)
class McpRegistryConfig(BaseModel):
"""MCP Server 注册表"""
version: int = 1
servers: List[McpServerConfig] = Field(default_factory=list)
class McpConfig(BaseModel):
"""MCP 总配置"""
enabled: bool = Field(default=False, description="是否启用 MCP 功能,默认关闭")
registry: McpRegistryConfig = Field(default_factory=McpRegistryConfig)
class ChannelsConfig(BaseModel):
"""渠道配置"""
telegram: TelegramConfig = Field(default_factory=TelegramConfig)
discord: DiscordConfig = Field(default_factory=DiscordConfig)
qq: QQConfig = Field(default_factory=QQConfig)
wechat: WeChatConfig = Field(default_factory=WeChatConfig)
dingtalk: DingTalkConfig = Field(default_factory=DingTalkConfig)
feishu: FeishuConfig = Field(default_factory=FeishuConfig)
weibo: WeiboConfig = Field(default_factory=WeiboConfig)
wecom: WeComConfig = Field(default_factory=WeComConfig)
xiaozhi: XiaozhiConfig = Field(default_factory=XiaozhiConfig)
class AppConfig(BaseModel):
"""应用配置"""
providers: Dict[str, ProviderConfig] = Field(default_factory=dict)
model: ModelConfig = Field(default_factory=ModelConfig)
workspace: WorkspaceConfig = Field(default_factory=WorkspaceConfig)
security: SecurityConfig = Field(default_factory=SecurityConfig)
channels: ChannelsConfig = Field(default_factory=ChannelsConfig)
persona: PersonaConfig = Field(default_factory=PersonaConfig)
mcp: McpConfig = Field(default_factory=McpConfig)
theme: str = "auto"
language: str = "auto"
font_size: str = "medium"
def __init__(self, **data):
"""初始化配置"""
super().__init__(**data)
from backend.modules.providers.registry import get_provider_ids, get_provider_metadata
for provider_id in get_provider_ids():
if provider_id not in self.providers:
metadata = get_provider_metadata(provider_id)
if provider_id == "zhipu":
self.providers[provider_id] = ProviderConfig(
api_key="",
api_base="https://open.bigmodel.cn/api/paas/v4",
enabled=True
)
else:
self.providers[provider_id] = ProviderConfig(
api_base=metadata.default_api_base if metadata else None,
enabled=True,
)