Skip to content

Latest commit

 

History

History
260 lines (197 loc) · 14.2 KB

File metadata and controls

260 lines (197 loc) · 14.2 KB

stocklens/config/ — 配置管理

职责

统一管理所有环境变量(130+),提供 Config 单例。包含 LLM 通道解析、字段注册表、配置校验。

文件清单

文件 职责 行数
settings.py Config 单例 dataclass(160 属性 + 30 方法);_load_from_env 调用 StockLensSettings 解析 env,自身只做编排(调 loader.X(...) ~1160
loader.py 2026-05 G8 新增_load_from_env 的 9 个步骤抽出的可组合纯函数(proxy / 自选股 / 多 key / 模型推断 / fallback / Router 列表 / SearXNG 白名单 / WeChat 字节预算) 368
raw_settings.py ★ pydantic-settings StockLensSettings — 158 个 env var 的类型化定义(types/defaults/范围/枚举),单一类型解析事实源 ~430
env_validator.py 轻量启动校验 CoreEnvSettings(64 个核心字段,仅警告不阻断启动)— 用于无需写入 Config 的纯启动期变量 ~220
settings_helpers.py LLM/News/Agent 工具函数 — 唯一源_get_litellm_provider_uses_direct_env_providerparse_env_boolresolve_* 等 17 个函数 + 常量) 317
registry.py 字段注册表 API(get_field_definition / get_category_definitions / build_schema_response)+ 从 fields/ 导入 _FIELD_DEFINITIONS 239
fields/ 配置字段子包 — 按业务域拆分 107 个字段定义(详见下方子表)
llm.py LLM 多通道解析器,将 LLM_CHANNELS 转为 LiteLLM Router 配置 217
manager.py .env 文件读写 214
validation.py 启动校验(结构化 issues) 244
types.py 配置相关 TypedDict / pydantic.dataclass(10 个 sub-config,构造时类型校验) 227
__init__.py 导出 Configget_config() 6

fields/ 子包

category 维度拆分原 1554 行 registry_fields.py,由 fields/__init__.py 重新合并为 _FIELD_DEFINITIONS,对外完全兼容。

文件 category 字段数 说明
base_fields.py base 1 自选股列表
ai_model_fields.py ai_model 25 LiteLLM / 多通道 / Legacy 单 Key / Agent 模型
data_source_fields.py data_source 15 Tushare / yfinance / akshare / Tavily / SerpAPI / Bocha …
notification_fields.py notification 32 10 大渠道:微信/飞书/Telegram/Email/Discord/钉钉/PushPlus/ServerChan/Astrbot/Custom Webhook
system_fields.py system 12 日志、调度、Web UI、HTTP、市场回顾、交易日检查
agent_fields.py agent 17 架构、协调器、技能、模型
backtest_fields.py backtest 5 回测窗口、止损、止盈、强制重算

核心类

Config(settings.py)

单例模式,通过 Config() 获取全局唯一实例。

关键属性(按分类)

股票列表

  • stock_list: List[str] — 自选股代码列表
  • stock_groups: Dict[int, List[str]] — 分组股票
  • email_groups: Dict[int, List[str]] — 分组邮件

LLM 配置

  • litellm_model: str — 主模型(provider/model 格式)
  • litellm_fallback_models: List[str] — 备用模型列表
  • litellm_config: Optional[str] — YAML 配置路径
  • llm_channels: List[str] — 通道名列表
  • llm_temperature: float — 采样温度
  • gemini_api_key: Optional[str] — Gemini Key
  • gemini_api_keys: List[str] — Gemini 多 Key
  • anthropic_api_key: Optional[str] — Anthropic Key
  • openai_api_key: Optional[str] — OpenAI Key
  • openai_base_url: Optional[str] — OpenAI 兼容端点
  • deepseek_api_keys: List[str] — DeepSeek 多 Key
  • agent_litellm_model: Optional[str] — Agent 专用模型

搜索引擎

  • tavily_api_keys: List[str]
  • serpapi_api_keys: List[str]
  • bocha_api_keys: List[str]
  • minimax_api_keys: List[str]
  • brave_api_keys: List[str]
  • searxng_base_urls: List[str]

数据源

  • tushare_token: Optional[str]
  • twelvedata_api_key: Optional[str]
  • realtime_source_priority: List[str] — 实时行情源优先级

通知渠道(每个渠道对应一组属性,详见 .env.example

Agent 模式

  • agent_mode: bool — 启用 Agent
  • agent_arch: str — 架构: single/multi
  • agent_orchestrator_mode: str — 流水线: quick/standard/full/strategy
  • agent_max_steps: int — 最大步数
  • agent_skills: List[str] — 激活策略
  • agent_risk_override: bool — 风控一票否决

报告配置

  • report_type: str — simple/full/brief
  • single_stock_notify: bool — 单股立即推送
  • report_integrity_enabled: bool — 完整性校验

关键方法

  • Config() -> Config — 获取单例
  • get(key: str, default=None) -> Any — 读取配置项
  • set(key: str, value: Any) -> None — 运行时修改配置
  • reload() -> None — 重新加载 .env 文件
  • to_dict() -> Dict — 导出所有配置(脱敏)
  • validate() -> List[str] — 校验配置,返回警告列表

Sub-config 视图(B1,2026-05 推荐用法)

Config 的 161 个顶层字段按 domain 拆成 10 个 pydantic.dataclass sub-config(见 types.py),可通过 lazy property 访问。这些 sub-config 是顶层字段的只读快照,构造时做类型校验。新代码请优先用 sub-config 路径:

Sub-config Property 覆盖字段 典型字段示例
LLMConfig config.llm LLM 通道 / Key / 模型 / 重试 litellm_model, gemini_api_keys, openai_base_url
SearchConfig config.search 搜索源 + 新闻策略 tavily_api_keys, news_max_age_days, news_strategy_profile
AgentConfig config.agent Agent 模式 / Orchestrator / Tools / Memory agent_mode, agent_arch, agent_max_steps, agent_risk_override
NotificationConfig config.notification 10 渠道 + Bot tokens wechat_webhook_url, feishu_app_id, pushover_api_token
ReportConfig config.report 报告类型 / 完整性 / 单股推送 report_type, single_stock_notify, report_integrity_enabled
DataSourceConfig config.datasource 数据源 token / 限流 / 实时优先级 / fundamental 超时 tushare_token, tickflow_api_key, realtime_source_priority, akshare_sleep_min/max, fundamental_stage_timeout_seconds
BacktestConfig config.backtest 回测窗口 + 评估配置 backtest_eval_window_days, backtest_min_age_days
PortfolioConfig config.portfolio 投资组合风险 / 货币 portfolio_risk_lookback_days
BotConfig config.bot Bot 平台 + 限流 bot_rate_limit_requests, dingtalk_app_secret
SystemConfig config.system 日志 / 路径 / 端口 / Auth webui_port, admin_auth_enabled, log_level

用法对比

# 旧写法(仍可用,向后兼容)
priority = config.realtime_source_priority

# 新写法(推荐 — 类型校验 + 后续重构无忧)
priority = config.datasource.realtime_source_priority

为什么这是正确的方向Config 161 字段平铺早晚需要拆。直接迁到 sub-config 让上帝对象的 transition 路径平滑:旧字段保留兼容性,新代码用 sub-config 后下一轮重构可以删除顶层 alias。

字段注册表(registry.py + registry_fields.py)

registry_fields.py 是纯数据 — 130+ 个字段的 _FIELD_DEFINITIONS 列表;registry.py 是 API 函数(get_field / get_fields_by_category / validate_value)。这种拆分让注册表逻辑和数据可以独立演化。

字段元数据结构

{
    "key": "STOCK_LIST",           # 环境变量名
    "type": "str",                 # 类型: str/int/float/bool/list
    "default": "AAPL,AMZN,GOOGL", # 默认值
    "required": False,             # 是否必填
    "category": "基础配置",         # 分组
    "description": "自选股列表",    # 中文描述
    "validation": {"min": 1},      # 校验规则
    "sensitive": False,            # 是否敏感(脱敏显示)
}

关键方法

  • get_field(key: str) -> FieldMeta — 获取字段元数据
  • get_fields_by_category(category: str) -> List[FieldMeta] — 按分组获取
  • get_all_fields() -> List[FieldMeta] — 获取全部字段
  • validate_value(key: str, value: Any) -> Tuple[bool, str] — 校验单个值

LLMChannelParser(llm.py)

解析 LLM_CHANNELS 环境变量,生成 LiteLLM Router 配置。

解析流程

LLM_CHANNELS=deepseek,gemini
  ↓
读取 LLM_DEEPSEEK_BASE_URL, LLM_DEEPSEEK_API_KEY, LLM_DEEPSEEK_MODELS, ...
读取 LLM_GEMINI_API_KEY, LLM_GEMINI_MODELS, ...
  ↓
生成 LiteLLM Router model_list:
[
  {"model_name": "openai/deepseek-chat", "litellm_params": {...}},
  {"model_name": "gemini/gemini-3-flash", "litellm_params": {...}},
]

关键方法

  • parse_channels() -> List[Dict] — 解析所有通道,返回 LiteLLM deployment 列表
  • _parse_single_channel(name: str) -> List[Dict] — 解析单个通道
  • _detect_protocol(name: str, base_url: str) -> str — 自动检测协议

ConfigManager(manager.py)

.env 文件的读写管理器。

关键方法

  • load_env(path: str = None) -> None — 加载 .env 文件
  • save_env(key: str, value: str) -> None — 写入单个配置到 .env
  • save_env_batch(updates: Dict[str, str]) -> None — 批量写入
  • delete_env(key: str) -> None — 删除配置项

ConfigValidator(validation.py)

启动时配置校验。

关键方法

  • validate_all() -> List[str] — 校验所有配置,返回警告/错误列表
  • validate_llm_config() -> List[str] — 校验 LLM 配置完整性
  • check_deprecated() -> List[str] — 检查已废弃的配置项

依赖关系

  • 被依赖:几乎所有模块都依赖 Config
  • 依赖:python-dotenv(.env 加载)

配置项

本模块管理所有 130+ 配置项,完整列表见 .env.example

注意事项

  • Config 是单例,首次调用时初始化,后续调用返回同一实例
  • LLM 通道解析优先级:LiteLLM YAML > 多通道环境变量 > Legacy 单 Key
  • GEMINI_API_KEYS(复数)优先于 GEMINI_API_KEY(单数),其他 provider 同理
  • sensitive=True 的字段在 to_dict() 时会脱敏显示(***
  • OPENAI_VISION_MODEL 已移除(2026-05),env 中仍可设置但值会自动合并到 VISION_MODEL
  • 新增字段:直接编辑 fields/<category>_fields.py 中的 FIELDS 字典,registry.py 无需修改
  • 兼容性 shim 清理(2026-05)helpers.pyregistry_fields.py 已删除;llm.py / validation.py 直接从 settings_helpers.py 导入;registry.py 直接从 fields/ 导入
  • 配置分层:107 个字段已按 tier 标签分为 required(2) / tuning(80) / experimental(25):
    • required —— 核心功能必备(STOCK_LIST + LITELLM_MODEL),不配则系统不可用
    • tuning —— 日常调优(多通道 LLM key、数据源 priority、推送渠道、超时等)
    • experimental —— 实验性 / 高级(Agent 模式、LiteLLM YAML、罕见渠道、回测高级参数) 调用 get_field_tier(key) 取分层、get_fields_by_tier("required") 取所有必填字段、 get_tier_summary() 取统计。新用户配置时优先看 required 即可
  • _get_litellm_provider / _uses_direct_env_provider 等 LLM 工具的唯一源settings_helpers.pyhelpers.py / settings.py 仅 re-export,不要重复定义
  • STOCKLENS_*_WORKERS 系列变量由 stocklens/utils/concurrency.py 单独读取,不进入 Config 单例
  • 跨模块 LLM 通道校验已抽到 stocklens/services/system_config_validators.pySystemConfigService 通过模块级 import 调用
  • 2026-05 修复 settings_helpers.py 循环引用注解get_effective_agent_primary_model(config: "Config") / get_effective_agent_models_to_try(config: "Config") 的 forward-reference 字符串 Config 在静态检查阶段无法解析(ruff F821)。修法:在 if TYPE_CHECKING: from stocklens.config.settings import Config —— 不引入运行时循环依赖。

G7 + G8 — Config 收尾(2026-05)

G8 — _load_from_env 拆解为 loader.py

历史:Config._load_from_env 是个 ~380 行的 classmethod,里面用「Step 1 / Step 2 / ... / Step 9」注释标段。新增 env var 时这个方法持续膨胀,单元测试只能 end-to-end(构造完整 StockLensSettings 再跑)。

修复:把 9 步业务逻辑各自抽成 stocklens/config/loader.py 的 9 个纯函数:

Step 函数 输入 输出
2 apply_proxy_side_effects http_proxy, https_proxy None(修改 os.environ
3 resolve_stock_list stock_list_raw, split_csv List[str]
4 resolve_provider_keys raw, split_csv {"gemini":[...], "anthropic":[...], "openai":[...], "deepseek":[...]}
5 infer_litellm_model raw, provider_keys str(model 全名)
6 resolve_fallback_models raw, litellm_model, split_csv List[str]
7 build_router_model_list raw, provider_keys, 4 个 callable hooks (model_list, channels, source)
7' auto_infer_from_channels litellm_model, fallback_list, channels (updated_model, updated_fallback)
8 filter_searxng_urls raw_urls, split_csv List[str](过滤无效 + 警告)
9 compute_wechat_max_bytes msg_type, override int

Config._load_from_env 现在只剩 ~70 行编排:调 loader.X(...) → 把结果填进 cls(...) dataclass。

settings.py 1290 → 1161 行 (-10%);loader.py 是 368 行集中的可组合纯函数。

G7 — 子配置漂移守卫

tests/unit/test_config_subconfig.py::TestSubConfigFieldRoundtrip 参数化覆盖全部 10 个子配置(llm / search / agent / notification / report / datasource / backtest / portfolio / bot / system),断言每个声明字段都能从扁平 Config round-trip 到子配置。任何未来的子配置 schema 漂移(漏写字段、改名)都会被这个测试抓住。

测试统计:G 阶段总共新增 test_config_loader.py(37) + 扩展 test_config_subconfig.py(+10),合计 47 个 config 相关测试。