# 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 接口规范 | 待编写 |