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.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 接口规范 | 待编写 |
|
||||
|
||||
@ -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
3
PRD.md
@ -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 接口规范 - 待编写
|
||||
- 数据字典 - 待编写
|
||||
|
||||
@ -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 接口规范 - 待编写
|
||||
* 数据字典 - 待编写
|
||||
* 测试计划 - 待编写
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user