第二层:配置系统

📌 核心文件

  • nanobot/config/schema.py - 配置数据模型
  • nanobot/config/loader.py - 配置加载器

概述

nanobot 使用基于 Pydantic 的配置系统,特点:

  • 单一 JSON 配置文件(~/.nanobot/config.json
  • 类型安全的配置模型
  • 自动验证配置正确性
  • 支持嵌套配置结构

配置模型定义

完整的配置结构

from pydantic import BaseModel, Field
from typing import Dict, Optional

class ProviderConfig(BaseModel):
    """单个提供商配置"""
    apiKey: str
    apiBase: Optional[str] = None

class AgentDefaults(BaseModel):
    """Agent 默认设置"""
    model: str = "anthropic/claude-opus-4-5"
    maxIterations: int = 20

class TelegramConfig(BaseModel):
    """Telegram 配置"""
    enabled: bool = False
    token: Optional[str] = None
    allowFrom: list[str] = Field(default_factory=list)

class WhatsAppConfig(BaseModel):
    """WhatsApp 配置"""
    enabled: bool = False
    allowFrom: list[str] = Field(default_factory=list)

class WebSearchConfig(BaseModel):
    """Web 搜索配置"""
    apiKey: Optional[str] = None

class Config(BaseModel):
    """根配置模型"""
    providers: Dict[str, ProviderConfig] = Field(default_factory=dict)
    agents: AgentDefaults = Field(default_factory=AgentDefaults)
    channels: ChannelsConfig = Field(default_factory=ChannelsConfig)
    tools: ToolsConfig = Field(default_factory=ToolsConfig)

配置示例

{
  "providers": {
    "openrouter": {
      "apiKey": "sk-or-v1-xxx",
      "apiBase": null
    },
    "groq": {
      "apiKey": "gsk_xxx"
    }
  },
  "agents": {
    "defaults": {
      "model": "anthropic/claude-opus-4-5",
      "maxIterations": 20
    }
  },
  "channels": {
    "telegram": {
      "enabled": true,
      "token": "123456:ABC...",
      "allowFrom": ["123456789"]
    },
    "whatsapp": {
      "enabled": false,
      "allowFrom": []
    }
  },
  "tools": {
    "web": {
      "search": {
        "apiKey": "BSA-xxx"
      }
    }
  }
}

配置加载器

class ConfigLoader:
    """配置加载器"""
    
    def __init__(self, config_path: Path | None = None):
        self.config_path = config_path or (Path.home() / ".nanobot" / "config.json")
    
    def load(self) -> Config:
        """加载并验证配置"""
        if not self.config_path.exists():
            raise FileNotFoundError(f"Config not found: {self.config_path}")
        
        # 读取 JSON
        data = json.loads(self.config_path.read_text())
        
        # Pydantic 自动验证
        try:
            config = Config(**data)
            return config
        except ValidationError as e:
            logger.error(f"Config validation failed: {e}")
            raise
    
    def save(self, config: Config):
        """保存配置"""
        self.config_path.write_text(
            config.model_dump_json(indent=2, exclude_none=True)
        )

配置验证

Pydantic 自动验证数据类型和约束:

# ❌ 错误的配置
{
  "agents": {
    "defaults": {
      "maxIterations": "twenty"  # 应该是整数
    }
  }
}

# 抛出 ValidationError:
# validation error for Config
# agents -> defaults -> maxIterations
#   value is not a valid integer

环境变量支持

虽然当前实现不直接支持环境变量,但可以轻松扩展:

class ProviderConfig(BaseModel):
    apiKey: str
    
    @field_validator('apiKey')
    def resolve_env_var(cls, v):
        """支持 ${ENV_VAR} 语法"""
        if v.startswith("${") and v.endswith("}"):
            env_var = v[2:-1]
            return os.getenv(env_var, "")
        return v

配置访问模式

# 加载配置
config = ConfigLoader().load()

# 访问配置
model = config.agents.defaults.model
api_key = config.providers.get("openrouter").apiKey
telegram_enabled = config.channels.telegram.enabled

# 修改配置
config.agents.defaults.model = "anthropic/claude-sonnet-4-5"
ConfigLoader().save(config)

小结

  • ✅ 基于 Pydantic 的类型安全配置
  • ✅ 单一 JSON 文件,简单直观
  • ✅ 自动验证,防止错误配置

下一步04-消息总线.md