docs: 新增 AI 厂商动态配置架构设计

- 新增 AIProviderConfig.md:详细设计 AI 厂商动态配置系统
  - 数据库存储配置(而非环境变量)
  - 运行时动态加载,支持热更新
  - 多租户隔离,支持品牌方独立配置
  - API Key 加密存储
  - 故障转移机制

- 更新 DevelopmentPlan.md (V1.4):
  - 在 AI 模型选型章节添加动态配置说明
  - 添加 AIProviderConfig.md 到相关文档

- 更新 FeatureSummary.md (V1.3):
  - 新增系统管理模块 (F-47~F-50)
  - F-47: AI 厂商动态配置 (P0)
  - F-48: AI 厂商连通性测试 (P0)
  - F-49: 多租户 AI 配置隔离 (P1)
  - F-50: API Key 轮换管理 (P1)

- 更新 RequirementsDoc.md 和 PRD.md:
  - 在技术架构概述中添加 AI 配置管理说明

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-02-02 18:31:29 +08:00
parent f87ae48ad5
commit 83737090bf
5 changed files with 994 additions and 0 deletions

912
AIProviderConfig.md Normal file
View File

@ -0,0 +1,912 @@
# AIProviderConfig.md - AI 厂商动态配置架构设计
| 文档类型 | **Technical Design (技术设计文档)** |
| --- | --- |
| **项目名称** | SmartAudit (AI 营销内容合规审核平台) |
| **版本号** | V1.0 |
| **日期** | 2026-02-02 |
| **侧重** | AI 厂商动态配置、多租户隔离、运行时热更新 |
---
## 版本历史 (Version History)
| 版本 | 日期 | 作者 | 变更说明 |
| --- | --- | --- | --- |
| V1.0 | 2026-02-02 | Claude | 初稿AI 厂商动态配置架构设计 |
---
## 1. 设计背景与目标
### 1.1 问题陈述
传统方案将 AI 模型的 API Key 和 Base URL 写死在环境变量中,存在以下问题:
1. **灵活性差:** 切换 AI 厂商需要修改环境变量并重启服务
2. **多租户困难:** 无法支持不同品牌方使用不同的 AI 厂商
3. **安全隐患:** 环境变量容易泄露,难以细粒度管理
4. **运维成本高:** 密钥轮换需要重新部署
### 1.2 设计目标
实现**商业 SaaS 级别的 AI 厂商动态配置系统**
| 目标 | 描述 |
| --- | --- |
| **动态配置** | 管理员在后台配置 AI 厂商,无需修改代码或重启服务 |
| **多厂商支持** | 支持 DeepSeek、OpenAI、阿里云、OneAPI 中转等多种厂商 |
| **多租户隔离** | 不同品牌方可配置独立的 AI 厂商和配额 |
| **热更新** | 配置变更即时生效,无需重启服务 |
| **安全存储** | API Key 加密存储,支持密钥轮换 |
| **故障转移** | 主厂商不可用时自动切换到备用厂商 |
---
## 2. 系统架构
### 2.1 架构概览
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 管理后台 (Admin Portal) │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ AI 厂商配置页面:添加/编辑/删除/测试连通性 │ │
│ └──────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ API 层 (FastAPI) │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ POST /admin/ai-providers - 创建 AI 厂商配置 │ │
│ │ GET /admin/ai-providers - 获取厂商列表 │ │
│ │ PUT /admin/ai-providers/{id} - 更新配置 │ │
│ │ POST /admin/ai-providers/{id}/test - 测试连通性 │ │
│ └──────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ AI 客户端工厂 (AIClientFactory) │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ • 根据配置动态创建 AI 客户端实例 │ │
│ │ • 支持连接池和客户端复用 │ │
│ │ • 配置变更时自动刷新客户端 │ │
│ └──────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ DeepSeek │ │ OpenAI │ │ OneAPI │
│ Client │ │ Client │ │ (中转) │
└─────────────┘ └─────────────┘ └─────────────┘
```
### 2.2 核心组件
| 组件 | 职责 |
| --- | --- |
| **AIProviderConfig** | 数据模型,存储厂商配置 |
| **AIClientFactory** | 工厂类,根据配置创建客户端 |
| **AIClientRegistry** | 注册表,缓存和管理客户端实例 |
| **ConfigWatcher** | 监听配置变更,触发客户端刷新 |
| **SecretsManager** | 加密存储和解密 API Key |
---
## 3. 数据模型设计
### 3.1 AI 厂商配置表 (ai_provider_configs)
```sql
CREATE TABLE ai_provider_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- 基础信息
name VARCHAR(100) NOT NULL, -- 配置名称,如 "生产环境 DeepSeek"
provider_type VARCHAR(50) NOT NULL, -- 厂商类型deepseek/openai/oneapi/aliyun/...
description TEXT, -- 配置说明
-- 连接配置
base_url VARCHAR(500) NOT NULL, -- API Base URL
api_key_encrypted BYTEA NOT NULL, -- 加密后的 API Key
-- 模型配置
default_model VARCHAR(100), -- 默认模型,如 "deepseek-chat"
available_models JSONB DEFAULT '[]', -- 可用模型列表
-- 能力标签
capabilities JSONB DEFAULT '[]', -- 支持的能力:["chat", "vision", "embedding"]
-- 使用场景
use_cases JSONB DEFAULT '[]', -- 适用场景:["brief_parsing", "script_review", "video_audit"]
-- 租户隔离
tenant_id UUID, -- 所属租户品牌方NULL 表示全局配置
-- 优先级与状态
priority INT DEFAULT 100, -- 优先级,数字越小优先级越高
is_enabled BOOLEAN DEFAULT true, -- 是否启用
is_default BOOLEAN DEFAULT false, -- 是否为默认配置
-- 限流配置
rate_limit_rpm INT DEFAULT 60, -- 每分钟请求限制
rate_limit_tpm INT DEFAULT 100000, -- 每分钟 Token 限制
-- 故障转移
fallback_provider_id UUID, -- 备用厂商配置 ID
-- 扩展配置
extra_config JSONB DEFAULT '{}', -- 厂商特定配置
-- 元数据
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
created_by UUID,
-- 约束
CONSTRAINT uk_tenant_default UNIQUE (tenant_id, is_default)
WHERE is_default = true
);
-- 索引
CREATE INDEX idx_provider_tenant ON ai_provider_configs(tenant_id);
CREATE INDEX idx_provider_type ON ai_provider_configs(provider_type);
CREATE INDEX idx_provider_enabled ON ai_provider_configs(is_enabled);
CREATE INDEX idx_provider_use_cases ON ai_provider_configs USING GIN(use_cases);
```
### 3.2 厂商类型枚举
```python
from enum import Enum
class AIProviderType(str, Enum):
"""支持的 AI 厂商类型"""
# 国内厂商
DEEPSEEK = "deepseek" # DeepSeek
QWEN = "qwen" # 阿里云通义千问
DOUBAO = "doubao" # 字节豆包
ZHIPU = "zhipu" # 智谱 GLM
BAICHUAN = "baichuan" # 百川
MOONSHOT = "moonshot" # Moonshot (Kimi)
# 海外厂商(需注意合规)
OPENAI = "openai" # OpenAI
ANTHROPIC = "anthropic" # Anthropic Claude
# 中转服务
ONEAPI = "oneapi" # OneAPI 中转
OPENROUTER = "openrouter" # OpenRouter
# 本地部署
OLLAMA = "ollama" # Ollama 本地
VLLM = "vllm" # vLLM 部署
# ASR/OCR 专用
ALIYUN_ASR = "aliyun_asr" # 阿里云 ASR
ALIYUN_OCR = "aliyun_ocr" # 阿里云 OCR
PADDLEOCR = "paddleocr" # PaddleOCR 本地
WHISPER = "whisper" # OpenAI Whisper
class AICapability(str, Enum):
"""AI 能力标签"""
CHAT = "chat" # 对话/文本生成
VISION = "vision" # 图像理解
EMBEDDING = "embedding" # 向量嵌入
ASR = "asr" # 语音识别
OCR = "ocr" # 文字识别
TTS = "tts" # 语音合成
class AIUseCase(str, Enum):
"""AI 使用场景"""
BRIEF_PARSING = "brief_parsing" # Brief 解析
SCRIPT_REVIEW = "script_review" # 脚本预审
VIDEO_AUDIT = "video_audit" # 视频审核
CONTEXT_CLASSIFICATION = "context_classification" # 语境分类
SENTIMENT_ANALYSIS = "sentiment_analysis" # 情感分析
LOGO_DETECTION = "logo_detection" # Logo 检测
ASR_TRANSCRIPTION = "asr_transcription" # 语音转写
OCR_EXTRACTION = "ocr_extraction" # 文字提取
```
### 3.3 使用日志表 (ai_usage_logs)
```sql
CREATE TABLE ai_usage_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
provider_id UUID NOT NULL REFERENCES ai_provider_configs(id),
tenant_id UUID,
-- 请求信息
use_case VARCHAR(50) NOT NULL,
model VARCHAR(100),
-- 用量统计
prompt_tokens INT DEFAULT 0,
completion_tokens INT DEFAULT 0,
total_tokens INT DEFAULT 0,
-- 性能指标
latency_ms INT,
status VARCHAR(20), -- success/error/timeout
error_message TEXT,
-- 时间
created_at TIMESTAMPTZ DEFAULT NOW(),
-- 分区键
created_date DATE DEFAULT CURRENT_DATE
) PARTITION BY RANGE (created_date);
-- 按月分区
CREATE TABLE ai_usage_logs_2026_02 PARTITION OF ai_usage_logs
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
```
---
## 4. 核心代码设计
### 4.1 配置模型 (Pydantic)
```python
# app/models/ai_provider.py
from pydantic import BaseModel, Field, SecretStr
from typing import Optional, List
from uuid import UUID
from datetime import datetime
from enum import Enum
class AIProviderCreate(BaseModel):
"""创建 AI 厂商配置请求"""
name: str = Field(..., max_length=100)
provider_type: AIProviderType
description: Optional[str] = None
base_url: str
api_key: SecretStr # 接收时为明文,存储时加密
default_model: Optional[str] = None
available_models: List[str] = []
capabilities: List[AICapability] = []
use_cases: List[AIUseCase] = []
tenant_id: Optional[UUID] = None
priority: int = 100
is_enabled: bool = True
is_default: bool = False
rate_limit_rpm: int = 60
rate_limit_tpm: int = 100000
fallback_provider_id: Optional[UUID] = None
extra_config: dict = {}
class AIProviderResponse(BaseModel):
"""AI 厂商配置响应"""
id: UUID
name: str
provider_type: AIProviderType
description: Optional[str]
base_url: str
# 注意:不返回 api_key
default_model: Optional[str]
available_models: List[str]
capabilities: List[AICapability]
use_cases: List[AIUseCase]
tenant_id: Optional[UUID]
priority: int
is_enabled: bool
is_default: bool
rate_limit_rpm: int
rate_limit_tpm: int
fallback_provider_id: Optional[UUID]
extra_config: dict
created_at: datetime
updated_at: datetime
```
### 4.2 AI 客户端工厂
```python
# app/services/ai/client_factory.py
from abc import ABC, abstractmethod
from typing import Dict, Optional, Type
from functools import lru_cache
import asyncio
from openai import AsyncOpenAI
from app.models.ai_provider import AIProviderType
from app.services.secrets_manager import SecretsManager
class BaseAIClient(ABC):
"""AI 客户端基类"""
def __init__(self, config: dict):
self.config = config
self.base_url = config["base_url"]
self.api_key = config["api_key"]
self.default_model = config.get("default_model")
@abstractmethod
async def chat(self, messages: list, model: str = None, **kwargs) -> dict:
"""对话接口"""
pass
@abstractmethod
async def health_check(self) -> bool:
"""健康检查"""
pass
class OpenAICompatibleClient(BaseAIClient):
"""OpenAI 兼容客户端 (适用于 DeepSeek, OneAPI, Moonshot 等)"""
def __init__(self, config: dict):
super().__init__(config)
self.client = AsyncOpenAI(
api_key=self.api_key,
base_url=self.base_url,
)
async def chat(self, messages: list, model: str = None, **kwargs) -> dict:
model = model or self.default_model
response = await self.client.chat.completions.create(
model=model,
messages=messages,
**kwargs
)
return {
"content": response.choices[0].message.content,
"usage": {
"prompt_tokens": response.usage.prompt_tokens,
"completion_tokens": response.usage.completion_tokens,
"total_tokens": response.usage.total_tokens,
},
"model": response.model,
}
async def health_check(self) -> bool:
try:
await self.client.models.list()
return True
except Exception:
return False
class AIClientFactory:
"""AI 客户端工厂"""
# 厂商类型到客户端类的映射
_client_classes: Dict[AIProviderType, Type[BaseAIClient]] = {
AIProviderType.DEEPSEEK: OpenAICompatibleClient,
AIProviderType.OPENAI: OpenAICompatibleClient,
AIProviderType.ONEAPI: OpenAICompatibleClient,
AIProviderType.QWEN: OpenAICompatibleClient,
AIProviderType.MOONSHOT: OpenAICompatibleClient,
AIProviderType.ZHIPU: OpenAICompatibleClient,
# 可扩展更多厂商...
}
def __init__(self, secrets_manager: SecretsManager):
self.secrets_manager = secrets_manager
self._client_cache: Dict[str, BaseAIClient] = {}
self._cache_lock = asyncio.Lock()
async def get_client(self, provider_config: dict) -> BaseAIClient:
"""获取或创建 AI 客户端"""
cache_key = f"{provider_config['id']}:{provider_config['updated_at']}"
if cache_key in self._client_cache:
return self._client_cache[cache_key]
async with self._cache_lock:
# 双重检查
if cache_key in self._client_cache:
return self._client_cache[cache_key]
# 解密 API Key
api_key = await self.secrets_manager.decrypt(
provider_config["api_key_encrypted"]
)
config = {
**provider_config,
"api_key": api_key,
}
# 创建客户端
provider_type = AIProviderType(provider_config["provider_type"])
client_class = self._client_classes.get(provider_type)
if not client_class:
raise ValueError(f"Unsupported provider type: {provider_type}")
client = client_class(config)
# 缓存客户端
self._client_cache[cache_key] = client
# 清理旧缓存
self._cleanup_old_cache(provider_config['id'])
return client
def _cleanup_old_cache(self, provider_id: str):
"""清理同一 provider 的旧缓存"""
keys_to_remove = [
k for k in self._client_cache.keys()
if k.startswith(f"{provider_id}:")
]
# 保留最新的一个
for key in keys_to_remove[:-1]:
del self._client_cache[key]
def invalidate_cache(self, provider_id: str = None):
"""使缓存失效"""
if provider_id:
keys_to_remove = [
k for k in self._client_cache.keys()
if k.startswith(f"{provider_id}:")
]
for key in keys_to_remove:
del self._client_cache[key]
else:
self._client_cache.clear()
```
### 4.3 AI 服务路由器
```python
# app/services/ai/router.py
from typing import Optional, List
from uuid import UUID
from app.models.ai_provider import AIUseCase, AICapability
from app.repositories.ai_provider_repo import AIProviderRepository
from app.services.ai.client_factory import AIClientFactory, BaseAIClient
class AIServiceRouter:
"""AI 服务路由器 - 根据场景选择合适的 AI 厂商"""
def __init__(
self,
provider_repo: AIProviderRepository,
client_factory: AIClientFactory,
):
self.provider_repo = provider_repo
self.client_factory = client_factory
async def get_client_for_use_case(
self,
use_case: AIUseCase,
tenant_id: Optional[UUID] = None,
required_capabilities: List[AICapability] = None,
) -> BaseAIClient:
"""
根据使用场景获取合适的 AI 客户端
优先级:
1. 租户专属配置 (tenant_id 匹配)
2. 全局默认配置 (tenant_id = NULL)
3. 按 priority 排序
"""
# 查询符合条件的配置
configs = await self.provider_repo.find_by_use_case(
use_case=use_case,
tenant_id=tenant_id,
capabilities=required_capabilities,
enabled_only=True,
)
if not configs:
raise ValueError(
f"No AI provider configured for use case: {use_case}"
)
# 选择优先级最高的配置
selected_config = configs[0]
# 创建并返回客户端
client = await self.client_factory.get_client(selected_config)
# 健康检查,失败则尝试备用
if not await client.health_check():
if selected_config.get("fallback_provider_id"):
fallback_config = await self.provider_repo.get_by_id(
selected_config["fallback_provider_id"]
)
if fallback_config:
client = await self.client_factory.get_client(fallback_config)
return client
async def chat(
self,
messages: list,
use_case: AIUseCase,
tenant_id: Optional[UUID] = None,
model: str = None,
**kwargs
) -> dict:
"""统一的对话接口"""
client = await self.get_client_for_use_case(
use_case=use_case,
tenant_id=tenant_id,
required_capabilities=[AICapability.CHAT],
)
return await client.chat(messages, model=model, **kwargs)
```
### 4.4 管理后台 API
```python
# app/api/v1/endpoints/admin/ai_providers.py
from fastapi import APIRouter, Depends, HTTPException, status
from typing import List, Optional
from uuid import UUID
from app.models.ai_provider import (
AIProviderCreate,
AIProviderUpdate,
AIProviderResponse,
)
from app.services.ai_provider_service import AIProviderService
from app.api.deps import get_current_admin_user
router = APIRouter()
@router.post("", response_model=AIProviderResponse, status_code=status.HTTP_201_CREATED)
async def create_ai_provider(
request: AIProviderCreate,
service: AIProviderService = Depends(),
current_user = Depends(get_current_admin_user),
):
"""创建 AI 厂商配置(仅管理员)"""
return await service.create(request, created_by=current_user.id)
@router.get("", response_model=List[AIProviderResponse])
async def list_ai_providers(
tenant_id: Optional[UUID] = None,
provider_type: Optional[str] = None,
service: AIProviderService = Depends(),
current_user = Depends(get_current_admin_user),
):
"""获取 AI 厂商配置列表"""
return await service.list(tenant_id=tenant_id, provider_type=provider_type)
@router.get("/{provider_id}", response_model=AIProviderResponse)
async def get_ai_provider(
provider_id: UUID,
service: AIProviderService = Depends(),
current_user = Depends(get_current_admin_user),
):
"""获取单个 AI 厂商配置"""
provider = await service.get_by_id(provider_id)
if not provider:
raise HTTPException(status_code=404, detail="Provider not found")
return provider
@router.put("/{provider_id}", response_model=AIProviderResponse)
async def update_ai_provider(
provider_id: UUID,
request: AIProviderUpdate,
service: AIProviderService = Depends(),
current_user = Depends(get_current_admin_user),
):
"""更新 AI 厂商配置"""
provider = await service.update(provider_id, request)
if not provider:
raise HTTPException(status_code=404, detail="Provider not found")
return provider
@router.delete("/{provider_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_ai_provider(
provider_id: UUID,
service: AIProviderService = Depends(),
current_user = Depends(get_current_admin_user),
):
"""删除 AI 厂商配置"""
success = await service.delete(provider_id)
if not success:
raise HTTPException(status_code=404, detail="Provider not found")
@router.post("/{provider_id}/test")
async def test_ai_provider(
provider_id: UUID,
service: AIProviderService = Depends(),
current_user = Depends(get_current_admin_user),
):
"""测试 AI 厂商连通性"""
result = await service.test_connection(provider_id)
return {
"success": result.success,
"latency_ms": result.latency_ms,
"error": result.error,
}
@router.post("/{provider_id}/rotate-key", response_model=AIProviderResponse)
async def rotate_api_key(
provider_id: UUID,
new_api_key: str,
service: AIProviderService = Depends(),
current_user = Depends(get_current_admin_user),
):
"""轮换 API Key"""
return await service.rotate_api_key(provider_id, new_api_key)
```
---
## 5. 安全设计
### 5.1 API Key 加密存储
```python
# app/services/secrets_manager.py
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64
import os
class SecretsManager:
"""密钥管理器 - 负责加密/解密敏感信息"""
def __init__(self, master_key: str):
"""
初始化密钥管理器
Args:
master_key: 主密钥,从安全存储(如 Vault、KMS获取
"""
# 从主密钥派生加密密钥
salt = os.environ.get("ENCRYPTION_SALT", "smartaudit").encode()
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
)
key = base64.urlsafe_b64encode(kdf.derive(master_key.encode()))
self.fernet = Fernet(key)
async def encrypt(self, plaintext: str) -> bytes:
"""加密明文"""
return self.fernet.encrypt(plaintext.encode())
async def decrypt(self, ciphertext: bytes) -> str:
"""解密密文"""
return self.fernet.decrypt(ciphertext).decode()
```
### 5.2 权限控制
| 操作 | 系统管理员 | 品牌方管理员 | 代理商 | 达人 |
| --- | --- | --- | --- | --- |
| 创建全局配置 | ✅ | ❌ | ❌ | ❌ |
| 创建租户配置 | ✅ | ✅ (仅自己租户) | ❌ | ❌ |
| 查看配置列表 | ✅ (全部) | ✅ (仅自己租户) | ❌ | ❌ |
| 修改配置 | ✅ | ✅ (仅自己租户) | ❌ | ❌ |
| 删除配置 | ✅ | ✅ (仅自己租户) | ❌ | ❌ |
| 查看 API Key | ❌ | ❌ | ❌ | ❌ |
| 轮换 API Key | ✅ | ✅ (仅自己租户) | ❌ | ❌ |
---
## 6. 配置热更新
### 6.1 更新机制
```python
# app/services/ai/config_watcher.py
import asyncio
from datetime import datetime
from typing import Callable, List
from app.repositories.ai_provider_repo import AIProviderRepository
from app.services.ai.client_factory import AIClientFactory
class ConfigWatcher:
"""配置变更监听器"""
def __init__(
self,
provider_repo: AIProviderRepository,
client_factory: AIClientFactory,
poll_interval: int = 30, # 秒
):
self.provider_repo = provider_repo
self.client_factory = client_factory
self.poll_interval = poll_interval
self._last_check = datetime.min
self._running = False
self._callbacks: List[Callable] = []
def on_config_change(self, callback: Callable):
"""注册配置变更回调"""
self._callbacks.append(callback)
async def start(self):
"""启动监听"""
self._running = True
while self._running:
await self._check_for_changes()
await asyncio.sleep(self.poll_interval)
async def stop(self):
"""停止监听"""
self._running = False
async def _check_for_changes(self):
"""检查配置变更"""
changed_configs = await self.provider_repo.find_updated_since(
self._last_check
)
if changed_configs:
self._last_check = datetime.utcnow()
# 使相关缓存失效
for config in changed_configs:
self.client_factory.invalidate_cache(config["id"])
# 触发回调
for callback in self._callbacks:
await callback(changed_configs)
```
### 6.2 应用启动集成
```python
# app/main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from app.services.ai.config_watcher import ConfigWatcher
@asynccontextmanager
async def lifespan(app: FastAPI):
# 启动时
config_watcher = ConfigWatcher(
provider_repo=app.state.provider_repo,
client_factory=app.state.client_factory,
)
asyncio.create_task(config_watcher.start())
yield
# 关闭时
await config_watcher.stop()
app = FastAPI(lifespan=lifespan)
```
---
## 7. 使用示例
### 7.1 在业务代码中使用
```python
# app/services/brief_parser.py
from app.services.ai.router import AIServiceRouter
from app.models.ai_provider import AIUseCase
class BriefParserService:
"""Brief 解析服务"""
def __init__(self, ai_router: AIServiceRouter):
self.ai_router = ai_router
async def parse_brief(self, content: str, tenant_id: UUID = None) -> dict:
"""解析 Brief 文档"""
messages = [
{"role": "system", "content": "你是一个专业的 Brief 解析助手..."},
{"role": "user", "content": f"请解析以下 Brief 内容:\n{content}"},
]
# 自动选择合适的 AI 厂商
result = await self.ai_router.chat(
messages=messages,
use_case=AIUseCase.BRIEF_PARSING,
tenant_id=tenant_id,
)
return self._parse_response(result["content"])
```
### 7.2 管理员配置流程
```
1. 管理员登录后台
2. 进入「系统设置 → AI 厂商管理」
3. 点击「添加厂商」
4. 填写配置:
- 名称:生产环境 DeepSeek
- 厂商类型DeepSeek
- Base URLhttps://api.deepseek.com/v1
- API Keysk-xxx
- 默认模型deepseek-chat
- 适用场景Brief 解析、脚本预审
- 优先级10
5. 点击「测试连通性」
6. 保存配置
7. 配置立即生效,无需重启服务
```
---
## 8. 监控与告警
### 8.1 监控指标
| 指标 | 说明 | 告警阈值 |
| --- | --- | --- |
| `ai_request_total` | AI 请求总数 | - |
| `ai_request_latency_p99` | P99 延迟 | > 10s |
| `ai_request_error_rate` | 错误率 | > 5% |
| `ai_token_usage_total` | Token 使用量 | 接近配额 80% |
| `ai_provider_health` | 厂商健康状态 | 连续失败 > 3 次 |
### 8.2 告警规则
```yaml
# prometheus/alerts/ai_provider.yml
groups:
- name: ai_provider
rules:
- alert: AIProviderHighErrorRate
expr: rate(ai_request_errors_total[5m]) / rate(ai_request_total[5m]) > 0.05
for: 2m
labels:
severity: warning
annotations:
summary: "AI 厂商 {{ $labels.provider }} 错误率过高"
- alert: AIProviderDown
expr: ai_provider_health == 0
for: 1m
labels:
severity: critical
annotations:
summary: "AI 厂商 {{ $labels.provider }} 不可用"
```
---
## 9. 相关文档
| 文档 | 说明 |
| --- | --- |
| DevelopmentPlan.md | 开发计划(已更新 AI 配置章节) |
| RequirementsDoc.md | 需求文档 |
| FeatureSummary.md | 功能清单 |
| API 接口规范 | 待编写 |

View File

@ -27,6 +27,7 @@
| V1.2 | 2026-02-03 | Claude | Reviewer 修正Logo检测改向量检索、Brief解析增VLM、弹性GPU、H5防锁屏、排期调整 |
| V1.2.1 | 2026-02-03 | Claude | 补充多模态时间戳对齐流程图 (Gemini 建议) |
| V1.3 | 2026-02-02 | Claude | **确立 TDD 为项目核心开发规范**,关联 tdd_plan.md |
| V1.4 | 2026-02-02 | Claude | **新增 AI 厂商动态配置架构**,支持数据库配置、运行时热更新、多租户隔离 |
---
@ -111,6 +112,24 @@ graph TD
| **版面分析 (Layout)** | **PaddleOCR Layout / LayoutLMv3** | Brief PDF 版面分析,提取图文混排结构 |
| **竞品 Logo 检测** | **Grounding DINO + Vector DB** | ⭐ V1.2 修正:改为向量检索方案,见下方说明 |
> ⭐ **V1.3 重要更新 - AI 厂商动态配置:**
>
> 本系统采用**商业 SaaS 级别的 AI 厂商动态配置架构**,详见 [AIProviderConfig.md](./AIProviderConfig.md)。
>
> **核心特性:**
> - **数据库存储配置:** AI 厂商的 API Key、Base URL 等配置存储在数据库中,而非环境变量
> - **运行时动态加载:** 管理员可在后台配置 AI 厂商,系统运行时动态读取配置初始化客户端
> - **多租户隔离:** 不同品牌方可配置独立的 AI 厂商和配额
> - **热更新:** 配置变更即时生效,无需重启服务
> - **故障转移:** 主厂商不可用时自动切换到备用厂商
> - **API Key 加密:** 使用 Fernet 对称加密存储敏感信息
>
> **支持的厂商类型:**
> - 国内厂商DeepSeek、通义千问、豆包、智谱、百川、Moonshot
> - 海外厂商OpenAI、Anthropic需注意合规
> - 中转服务OneAPI、OpenRouter
> - 本地部署Ollama、vLLM
> ⚠️ **V1.2 重要修正 - Logo 检测架构变更:**
>
> **废弃方案:** ~~YOLOv8 Fine-tuning~~
@ -494,5 +513,6 @@ sequenceDiagram
| User_Role_Interfaces.md | 界面规范 |
| tasks.md | 开发任务清单 |
| **featuredoc/tdd_plan.md** | **TDD 实施计划(核心规范)** |
| **AIProviderConfig.md** | **AI 厂商动态配置架构设计V1.3 新增)** |
| 数据字典 | 待编写 |
| API 接口规范 | 待编写 |

View File

@ -17,6 +17,7 @@
| V1.0 | 2026-02-02 | Claude | 基于 RD/PRD/UI 文档整合产出功能清单 |
| V1.1 | 2026-02-02 | Claude | 根据 Gemini 修订意见调整补充验收标准、Out of Scope、核心痛点细化 |
| V1.2 | 2026-02-02 | Claude | 根据 Gemini 关键改进意见:优先级调整、功能拆分、新增功能、移动端适配 |
| V1.3 | 2026-02-02 | Claude | **新增 AI 厂商动态配置功能模块 (F-47~F-50)**,支持数据库配置、多租户隔离 |
**Gemini 修订意见采纳情况:**
@ -686,6 +687,57 @@ V1 版本指出 3 个违规点:✅ 已修复 2 个 | ❌ 未修复 1 个
| --- | --- | --- | --- | --- |
| F-46 | 负样本清洗与回流 | P2 | - | 系统 |
---
### 3.11 系统管理 - AI 厂商配置 (V1.4 新增)
| 功能编号 | 功能名称 | 优先级 | 用户故事 | 使用角色 |
| --- | --- | --- | --- | --- |
| F-47 | AI 厂商动态配置 | P0 | - | 系统管理员 |
| F-48 | AI 厂商连通性测试 | P0 | - | 系统管理员 |
| F-49 | 多租户 AI 配置隔离 | P1 | - | 系统管理员/品牌方 |
| F-50 | API Key 轮换管理 | P1 | - | 系统管理员 |
#### F-47 AI 厂商动态配置 ⭐ P0
**功能描述:** 系统管理员可在后台配置多个 AI 厂商DeepSeek、OpenAI、通义千问、OneAPI 中转等),配置存储在数据库中,运行时动态加载,无需修改代码或重启服务。
**核心功能:**
- 支持添加、编辑、删除 AI 厂商配置
- 配置 Base URL、API Key加密存储、默认模型
- 为不同使用场景Brief 解析、脚本预审、视频审核)指定不同厂商
- 配置优先级和备用厂商(故障转移)
**为什么是 P0** 这是 AI 服务的基础设施,所有 AI 功能都依赖此配置。
**界面映射:** 系统管理后台 → AI 厂商管理
**技术文档:** 详见 [AIProviderConfig.md](./AIProviderConfig.md)
---
#### F-48 AI 厂商连通性测试
**功能描述:** 配置 AI 厂商后,可测试连通性,验证 API Key 是否有效。
**界面映射:** 系统管理后台 → AI 厂商管理 → [测试连通性]
---
#### F-49 多租户 AI 配置隔离
**功能描述:** 不同品牌方可配置独立的 AI 厂商,实现租户级别的配置隔离和配额管理。
**界面映射:** 品牌方后台 → 系统设置 → AI 配置
---
#### F-50 API Key 轮换管理
**功能描述:** 支持定期轮换 API Key无需重启服务即可生效。
**界面映射:** 系统管理后台 → AI 厂商管理 → [轮换密钥]
#### F-46 负样本清洗与回流 (Feedback Loop)
**功能描述:** 系统自动收集"人工驳回 AI 判定"的案例,清洗为微调数据集,用于后续模型优化。
@ -730,6 +782,8 @@ V1 版本指出 3 个违规点:✅ 已修复 2 个 | ❌ 未修复 1 个
| F-19 | 风险列表展示 | 审核台 | |
| F-20 | 确认/驳回操作 | 审核台 | |
| F-33 | 核心指标卡片 | 数据看板 | |
| F-47 | AI 厂商动态配置 | 系统管理 | ⭐ V1.3 新增AI 基础设施 |
| F-48 | AI 厂商连通性测试 | 系统管理 | ⭐ V1.3 新增 |
### 4.2 V1.1 (P1) - 首版后快速迭代
@ -747,6 +801,8 @@ V1 版本指出 3 个违规点:✅ 已修复 2 个 | ❌ 未修复 1 个
| F-34~36 | 趋势图表与预警 | 数据看板 | |
| F-38~40 | 审计日志与证据导出 | 审计 | |
| F-43 | 舆情阈值设置 | 舆情 | |
| F-49 | 多租户 AI 配置隔离 | 系统管理 | ⭐ V1.3 新增 |
| F-50 | API Key 轮换管理 | 系统管理 | ⭐ V1.3 新增 |
> ⚠️ **注意:** F-09 (语境理解) 和 F-17 (进度展示) 已提升至 P0
@ -846,6 +902,7 @@ V1 版本指出 3 个违规点:✅ 已修复 2 个 | ❌ 未修复 1 个
| RequirementsDoc.md | 业务需求文档(用户故事、成功指标) |
| PRD.md | 产品需求文档(功能需求、技术架构) |
| User_Role_Interfaces.md | 用户角色与界面规范 |
| **AIProviderConfig.md** | **AI 厂商动态配置架构设计V1.3 新增)** |
| 技术设计文档 (TDD) | 待编写 |
| API 接口规范 | 待编写 |
| 数据字典 | 待编写 |

3
PRD.md
View File

@ -18,6 +18,7 @@
| V0.2 | 2026-01-30 | ClaudeCode | 根据 RD 审阅修订:补充技术架构、术语定义、用户故事引用、品牌方工作流 |
| V0.3 | 2026-01-30 | Codex | 合规一致性修订:补充一致性定义、软性风控提示边界与特例记录规范 |
| V0.4 | 2026-01-30 | Claude | 审阅调整:补充产品愿景与量化目标、假设与约束章节、细化背景数据 |
| V1.0 | 2026-02-02 | Claude | 新增 AI 厂商动态配置架构引用 |
---
@ -351,6 +352,7 @@
- **ASR/OCR**:支持普通话及主流方言的语音识别,支持复杂背景字幕识别
- **计算机视觉**Logo 检测、物体识别、场景分类
- **消息队列**:异步处理视频审核任务,支持优先级调度
- **AI 厂商动态配置**:支持在数据库中配置多个 AI 厂商DeepSeek/OpenAI/OneAPI 等),运行时动态加载,支持多租户隔离和故障转移(详见 AIProviderConfig.md
---
@ -378,6 +380,7 @@
## 16. 相关文档 (References)
- RequirementsDoc.md - 业务需求文档
- **AIProviderConfig.md - AI 厂商动态配置架构设计**
- 技术设计文档 (TDD) - 待编写
- API 接口规范 - 待编写
- 数据字典 - 待编写

View File

@ -187,6 +187,7 @@
* **ASR/OCR** 支持普通话及主流方言的语音识别,支持复杂背景字幕识别
* **计算机视觉:** Logo 检测、物体识别、场景分类
* **消息队列:** 异步处理视频审核任务,支持优先级调度
* **AI 厂商动态配置:** 支持在数据库中配置多个 AI 厂商DeepSeek/OpenAI/OneAPI 等),运行时动态加载,支持多租户隔离和故障转移(详见 AIProviderConfig.md
---
@ -241,6 +242,7 @@
### 11.1 相关文档
* 技术设计文档 (TDD) - 待编写
* **AIProviderConfig.md - AI 厂商动态配置架构设计**
* API 接口规范 - 待编写
* 数据字典 - 待编写
* 测试计划 - 待编写