refactor: externalize hardcoded model config and clean up robustness plan

- Add openai_model, tts_minimax_model, tts_elevenlabs_model, tts_edge_voice,
  antigravity_model to Settings; set image_model default to nano-banana-pro
- Update provider_router._get_default_config to read from settings
- Add nul to .gitignore (Windows artifact)
- Mark all robustness tasks as completed in dev-plan.md
This commit is contained in:
zhangtuo
2026-03-09 16:54:58 +08:00
parent c82d408ea1
commit 145be0e67b
4 changed files with 29 additions and 46 deletions

View File

@@ -7,60 +7,37 @@
### P0 - 关键问题修复 ### P0 - 关键问题修复
#### Task-1: 修复 Rate Limit 内存泄漏 #### Task-1: 修复 Rate Limit 内存泄漏
- **文件**: `backend/app/api/stories.py` - **文件**: `backend/app/api/stories.py`
- **问题**: `_request_log` 全局字典无清理机制,长期运行内存无限增长 - **方案**: 已迁移至 Redis 分布式限流,内存泄漏问题不再存在
- **方案**: 添加 TTL 自动清理机制,使用 `cachetools.TTLCache`
- **测试**: 验证过期条目自动清理
#### Task-2: 添加核心 API 测试 #### Task-2: 添加核心 API 测试
- **文件**: `backend/tests/` (新建) - **文件**: `backend/tests/`
- **范围**: - **范围**: test_auth, test_stories, test_profiles, test_universes, test_push_configs, test_reading_events, test_provider_router
- `test_auth.py`: OAuth 流程、session 验证
- `test_stories.py`: 故事 CRUD、rate limit
- **目标**: 核心路径覆盖率 ≥80%
### P1 - 稳定性提升 ### P1 - 稳定性提升
#### Task-3: 添加 API 重试机制 #### Task-3: 添加 API 重试机制
- **文件**: `backend/app/services/gemini.py`, `minimax.py`, `drawing.py` - **方案**: 所有适配器已使用 `tenacity` 指数退避重试 (gemini, openai, cqtai, antigravity, minimax, elevenlabs)
- **方案**: 使用 `tenacity` 库,指数退避重试
- **配置**: 最多 3 次重试,初始间隔 1s
#### Task-4: 添加结构化日志 #### Task-4: 添加结构化日志
- **文件**: `backend/app/core/logging.py` (新建), 各 service 文件 - **文件**: `backend/app/core/logging.py`
- **方案**: 使用 `structlog`JSON 格式输出 - **方案**: structlog JSON/Console 双模式,所有适配器和 provider_router 已集成
- **埋点**: API 调用、错误、性能指标
### P2 - 代码优化 ### P2 - 代码优化
#### Task-5: 重构 Provider Router #### Task-5: 重构 Provider Router
- **文件**: `backend/app/services/provider_router.py` - **文件**: `backend/app/services/provider_router.py`
- **问题**: 三个函数重复代码 - **方案**: 已实现统一 `_route_with_failover` 函数
- **方案**: 抽象通用 failover 函数
#### Task-6: 配置外部化 #### Task-6: 配置外部化
- **文件**: `backend/app/core/config.py`, `backend/app/services/gemini.py` - **文件**: `backend/app/core/config.py`, `backend/app/services/provider_router.py`
- **问题**: 模型名硬编码 - **方案**: 所有模型名已移至 Settings支持环境变量覆盖
- **方案**: 移至环境变量配置
#### Task-7: 修复脆弱的 URL 解析 #### Task-7: 修复脆弱的 URL 解析
- **文件**: `backend/app/services/drawing.py` - **状态**: `drawing.py` 已被适配器系统取代,不再存在
- **问题**: 字符串切片解析 URL 不可靠
- **方案**: 使用正则表达式
## 依赖关系 ## 新增依赖 (已添加)
```
Task-1 (独立)
Task-2 (独立,但需要 Task-1 完成后验证)
Task-3 (独立)
Task-4 (独立)
Task-5 (独立)
Task-6 (独立)
Task-7 (独立)
```
## 新增依赖
```toml ```toml
# pyproject.toml [project.dependencies] # pyproject.toml [project.dependencies]
cachetools>=5.0.0 # Task-1: TTL cache cachetools>=5.0.0 # Task-1: TTL cache
@@ -69,4 +46,4 @@ structlog>=24.0.0 # Task-4: 结构化日志
# [project.optional-dependencies.dev] # [project.optional-dependencies.dev]
pytest-cov>=4.0.0 # Task-2: 覆盖率报告 pytest-cov>=4.0.0 # Task-2: 覆盖率报告
httpx[http2] # Task-2: 测试 mock ```

1
.gitignore vendored
View File

@@ -32,6 +32,7 @@ htmlcov/
# 其他 # 其他
*.log *.log
.DS_Store .DS_Store
nul
# Vite # Vite
*.timestamp-*.mjs *.timestamp-*.mjs

View File

@@ -41,8 +41,13 @@ class Settings(BaseSettings):
# AI Model Configuration # AI Model Configuration
text_model: str = "gemini-2.0-flash" text_model: str = "gemini-2.0-flash"
openai_model: str = "gpt-4o-mini"
tts_model: str = "" tts_model: str = ""
image_model: str = "" image_model: str = "nano-banana-pro"
tts_minimax_model: str = "speech-2.6-turbo"
tts_elevenlabs_model: str = "eleven_multilingual_v2"
tts_edge_voice: str = "zh-CN-XiaoxiaoNeural"
antigravity_model: str = "gemini-3-pro-image"
# Provider routing (ordered lists) # Provider routing (ordered lists)
text_providers: list[str] = Field(default_factory=lambda: ["gemini"]) text_providers: list[str] = Field(default_factory=lambda: ["gemini"])

View File

@@ -98,7 +98,7 @@ def _get_default_config(adapter_name: str) -> AdapterConfig | None:
if adapter_name == "openai": if adapter_name == "openai":
return AdapterConfig( return AdapterConfig(
api_key=getattr(settings, "openai_api_key", ""), api_key=getattr(settings, "openai_api_key", ""),
model="gpt-4o-mini", # 这里可以从 settings 读取,看需求 model=settings.openai_model,
timeout_ms=60000, timeout_ms=60000,
) )
@@ -106,7 +106,7 @@ def _get_default_config(adapter_name: str) -> AdapterConfig | None:
if adapter_name in ("cqtai"): if adapter_name in ("cqtai"):
return AdapterConfig( return AdapterConfig(
api_key=getattr(settings, "cqtai_api_key", ""), api_key=getattr(settings, "cqtai_api_key", ""),
model="nano-banana-pro", # 默认使用 Pro model=settings.image_model or "nano-banana-pro",
timeout_ms=120000, timeout_ms=120000,
) )
if adapter_name == "image_primary": if adapter_name == "image_primary":
@@ -128,7 +128,7 @@ def _get_default_config(adapter_name: str) -> AdapterConfig | None:
# 我们这里暂时返回基础配置。 # 我们这里暂时返回基础配置。
return AdapterConfig( return AdapterConfig(
api_key=getattr(settings, "minimax_api_key", ""), api_key=getattr(settings, "minimax_api_key", ""),
model="speech-2.6-turbo", model=settings.tts_minimax_model,
timeout_ms=60000, timeout_ms=60000,
) )