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:
parent
f87ae48ad5
commit
83737090bf
912
AIProviderConfig.md
Normal file
912
AIProviderConfig.md
Normal 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 URL:https://api.deepseek.com/v1
|
||||||
|
- API Key:sk-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 接口规范 | 待编写 |
|
||||||
@ -27,6 +27,7 @@
|
|||||||
| V1.2 | 2026-02-03 | Claude | Reviewer 修正:Logo检测改向量检索、Brief解析增VLM、弹性GPU、H5防锁屏、排期调整 |
|
| V1.2 | 2026-02-03 | Claude | Reviewer 修正:Logo检测改向量检索、Brief解析增VLM、弹性GPU、H5防锁屏、排期调整 |
|
||||||
| V1.2.1 | 2026-02-03 | Claude | 补充多模态时间戳对齐流程图 (Gemini 建议) |
|
| V1.2.1 | 2026-02-03 | Claude | 补充多模态时间戳对齐流程图 (Gemini 建议) |
|
||||||
| V1.3 | 2026-02-02 | Claude | **确立 TDD 为项目核心开发规范**,关联 tdd_plan.md |
|
| 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 版面分析,提取图文混排结构 |
|
| **版面分析 (Layout)** | **PaddleOCR Layout / LayoutLMv3** | Brief PDF 版面分析,提取图文混排结构 |
|
||||||
| **竞品 Logo 检测** | **Grounding DINO + Vector DB** | ⭐ V1.2 修正:改为向量检索方案,见下方说明 |
|
| **竞品 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 检测架构变更:**
|
> ⚠️ **V1.2 重要修正 - Logo 检测架构变更:**
|
||||||
>
|
>
|
||||||
> **废弃方案:** ~~YOLOv8 Fine-tuning~~
|
> **废弃方案:** ~~YOLOv8 Fine-tuning~~
|
||||||
@ -494,5 +513,6 @@ sequenceDiagram
|
|||||||
| User_Role_Interfaces.md | 界面规范 |
|
| User_Role_Interfaces.md | 界面规范 |
|
||||||
| tasks.md | 开发任务清单 |
|
| tasks.md | 开发任务清单 |
|
||||||
| **featuredoc/tdd_plan.md** | **TDD 实施计划(核心规范)** |
|
| **featuredoc/tdd_plan.md** | **TDD 实施计划(核心规范)** |
|
||||||
|
| **AIProviderConfig.md** | **AI 厂商动态配置架构设计(V1.3 新增)** |
|
||||||
| 数据字典 | 待编写 |
|
| 数据字典 | 待编写 |
|
||||||
| API 接口规范 | 待编写 |
|
| API 接口规范 | 待编写 |
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
| V1.0 | 2026-02-02 | Claude | 基于 RD/PRD/UI 文档整合产出功能清单 |
|
| V1.0 | 2026-02-02 | Claude | 基于 RD/PRD/UI 文档整合产出功能清单 |
|
||||||
| V1.1 | 2026-02-02 | Claude | 根据 Gemini 修订意见调整:补充验收标准、Out of Scope、核心痛点细化 |
|
| V1.1 | 2026-02-02 | Claude | 根据 Gemini 修订意见调整:补充验收标准、Out of Scope、核心痛点细化 |
|
||||||
| V1.2 | 2026-02-02 | Claude | 根据 Gemini 关键改进意见:优先级调整、功能拆分、新增功能、移动端适配 |
|
| V1.2 | 2026-02-02 | Claude | 根据 Gemini 关键改进意见:优先级调整、功能拆分、新增功能、移动端适配 |
|
||||||
|
| V1.3 | 2026-02-02 | Claude | **新增 AI 厂商动态配置功能模块 (F-47~F-50)**,支持数据库配置、多租户隔离 |
|
||||||
|
|
||||||
**Gemini 修订意见采纳情况:**
|
**Gemini 修订意见采纳情况:**
|
||||||
|
|
||||||
@ -686,6 +687,57 @@ V1 版本指出 3 个违规点:✅ 已修复 2 个 | ❌ 未修复 1 个
|
|||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| F-46 | 负样本清洗与回流 | P2 | - | 系统 |
|
| 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)
|
#### F-46 负样本清洗与回流 (Feedback Loop)
|
||||||
|
|
||||||
**功能描述:** 系统自动收集"人工驳回 AI 判定"的案例,清洗为微调数据集,用于后续模型优化。
|
**功能描述:** 系统自动收集"人工驳回 AI 判定"的案例,清洗为微调数据集,用于后续模型优化。
|
||||||
@ -730,6 +782,8 @@ V1 版本指出 3 个违规点:✅ 已修复 2 个 | ❌ 未修复 1 个
|
|||||||
| F-19 | 风险列表展示 | 审核台 | |
|
| F-19 | 风险列表展示 | 审核台 | |
|
||||||
| F-20 | 确认/驳回操作 | 审核台 | |
|
| F-20 | 确认/驳回操作 | 审核台 | |
|
||||||
| F-33 | 核心指标卡片 | 数据看板 | |
|
| F-33 | 核心指标卡片 | 数据看板 | |
|
||||||
|
| F-47 | AI 厂商动态配置 | 系统管理 | ⭐ V1.3 新增,AI 基础设施 |
|
||||||
|
| F-48 | AI 厂商连通性测试 | 系统管理 | ⭐ V1.3 新增 |
|
||||||
|
|
||||||
### 4.2 V1.1 (P1) - 首版后快速迭代
|
### 4.2 V1.1 (P1) - 首版后快速迭代
|
||||||
|
|
||||||
@ -747,6 +801,8 @@ V1 版本指出 3 个违规点:✅ 已修复 2 个 | ❌ 未修复 1 个
|
|||||||
| F-34~36 | 趋势图表与预警 | 数据看板 | |
|
| F-34~36 | 趋势图表与预警 | 数据看板 | |
|
||||||
| F-38~40 | 审计日志与证据导出 | 审计 | |
|
| F-38~40 | 审计日志与证据导出 | 审计 | |
|
||||||
| F-43 | 舆情阈值设置 | 舆情 | |
|
| F-43 | 舆情阈值设置 | 舆情 | |
|
||||||
|
| F-49 | 多租户 AI 配置隔离 | 系统管理 | ⭐ V1.3 新增 |
|
||||||
|
| F-50 | API Key 轮换管理 | 系统管理 | ⭐ V1.3 新增 |
|
||||||
|
|
||||||
> ⚠️ **注意:** F-09 (语境理解) 和 F-17 (进度展示) 已提升至 P0
|
> ⚠️ **注意:** F-09 (语境理解) 和 F-17 (进度展示) 已提升至 P0
|
||||||
|
|
||||||
@ -846,6 +902,7 @@ V1 版本指出 3 个违规点:✅ 已修复 2 个 | ❌ 未修复 1 个
|
|||||||
| RequirementsDoc.md | 业务需求文档(用户故事、成功指标) |
|
| RequirementsDoc.md | 业务需求文档(用户故事、成功指标) |
|
||||||
| PRD.md | 产品需求文档(功能需求、技术架构) |
|
| PRD.md | 产品需求文档(功能需求、技术架构) |
|
||||||
| User_Role_Interfaces.md | 用户角色与界面规范 |
|
| User_Role_Interfaces.md | 用户角色与界面规范 |
|
||||||
|
| **AIProviderConfig.md** | **AI 厂商动态配置架构设计(V1.3 新增)** |
|
||||||
| 技术设计文档 (TDD) | 待编写 |
|
| 技术设计文档 (TDD) | 待编写 |
|
||||||
| API 接口规范 | 待编写 |
|
| API 接口规范 | 待编写 |
|
||||||
| 数据字典 | 待编写 |
|
| 数据字典 | 待编写 |
|
||||||
|
|||||||
3
PRD.md
3
PRD.md
@ -18,6 +18,7 @@
|
|||||||
| V0.2 | 2026-01-30 | ClaudeCode | 根据 RD 审阅修订:补充技术架构、术语定义、用户故事引用、品牌方工作流 |
|
| V0.2 | 2026-01-30 | ClaudeCode | 根据 RD 审阅修订:补充技术架构、术语定义、用户故事引用、品牌方工作流 |
|
||||||
| V0.3 | 2026-01-30 | Codex | 合规一致性修订:补充一致性定义、软性风控提示边界与特例记录规范 |
|
| V0.3 | 2026-01-30 | Codex | 合规一致性修订:补充一致性定义、软性风控提示边界与特例记录规范 |
|
||||||
| V0.4 | 2026-01-30 | Claude | 审阅调整:补充产品愿景与量化目标、假设与约束章节、细化背景数据 |
|
| V0.4 | 2026-01-30 | Claude | 审阅调整:补充产品愿景与量化目标、假设与约束章节、细化背景数据 |
|
||||||
|
| V1.0 | 2026-02-02 | Claude | 新增 AI 厂商动态配置架构引用 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -351,6 +352,7 @@
|
|||||||
- **ASR/OCR**:支持普通话及主流方言的语音识别,支持复杂背景字幕识别
|
- **ASR/OCR**:支持普通话及主流方言的语音识别,支持复杂背景字幕识别
|
||||||
- **计算机视觉**:Logo 检测、物体识别、场景分类
|
- **计算机视觉**:Logo 检测、物体识别、场景分类
|
||||||
- **消息队列**:异步处理视频审核任务,支持优先级调度
|
- **消息队列**:异步处理视频审核任务,支持优先级调度
|
||||||
|
- **AI 厂商动态配置**:支持在数据库中配置多个 AI 厂商(DeepSeek/OpenAI/OneAPI 等),运行时动态加载,支持多租户隔离和故障转移(详见 AIProviderConfig.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -378,6 +380,7 @@
|
|||||||
## 16. 相关文档 (References)
|
## 16. 相关文档 (References)
|
||||||
|
|
||||||
- RequirementsDoc.md - 业务需求文档
|
- RequirementsDoc.md - 业务需求文档
|
||||||
|
- **AIProviderConfig.md - AI 厂商动态配置架构设计**
|
||||||
- 技术设计文档 (TDD) - 待编写
|
- 技术设计文档 (TDD) - 待编写
|
||||||
- API 接口规范 - 待编写
|
- API 接口规范 - 待编写
|
||||||
- 数据字典 - 待编写
|
- 数据字典 - 待编写
|
||||||
|
|||||||
@ -187,6 +187,7 @@
|
|||||||
* **ASR/OCR:** 支持普通话及主流方言的语音识别,支持复杂背景字幕识别
|
* **ASR/OCR:** 支持普通话及主流方言的语音识别,支持复杂背景字幕识别
|
||||||
* **计算机视觉:** Logo 检测、物体识别、场景分类
|
* **计算机视觉:** Logo 检测、物体识别、场景分类
|
||||||
* **消息队列:** 异步处理视频审核任务,支持优先级调度
|
* **消息队列:** 异步处理视频审核任务,支持优先级调度
|
||||||
|
* **AI 厂商动态配置:** 支持在数据库中配置多个 AI 厂商(DeepSeek/OpenAI/OneAPI 等),运行时动态加载,支持多租户隔离和故障转移(详见 AIProviderConfig.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -241,6 +242,7 @@
|
|||||||
### 11.1 相关文档
|
### 11.1 相关文档
|
||||||
|
|
||||||
* 技术设计文档 (TDD) - 待编写
|
* 技术设计文档 (TDD) - 待编写
|
||||||
|
* **AIProviderConfig.md - AI 厂商动态配置架构设计**
|
||||||
* API 接口规范 - 待编写
|
* API 接口规范 - 待编写
|
||||||
* 数据字典 - 待编写
|
* 数据字典 - 待编写
|
||||||
* 测试计划 - 待编写
|
* 测试计划 - 待编写
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user