# AI 模型用户可见性控制 **日期**: 2026-02-10 **类型**: 功能增强 **影响范围**: AI 模型管理、前端 API ## 背景 当前 `ai_models` 表只有 `is_active` 字段控制模型是否可用,但存在以下问题: 1. **缺乏细粒度控制**:`is_active` 同时控制系统内部可用和用户界面可见 2. **内部模型暴露**:有些模型仅用于内部调用(如剧本解析的 Gemini 模型),不应展示给用户选择 3. **用户体验差**:用户看到大量不相关的模型,难以选择 ## 解决方案 添加 `is_visible` 字段,实现双层控制: - **is_active**: 控制系统内部是否可用(Factory 能否创建 Provider) - **is_visible**: 控制用户界面是否可见(前端 API 是否返回) ## 变更内容 ### 1. 数据库迁移 **文件**: `server/alembic/versions/20260210_1500_add_is_visible_to_ai_models.py` ```python def upgrade() -> None: # 添加 is_visible 字段(默认 true) op.add_column('ai_models', sa.Column( 'is_visible', sa.Boolean(), nullable=False, server_default='true', comment='是否对用户可见' )) # 创建部分索引(只索引可见且活跃的模型) op.create_index( 'idx_ai_models_visible', 'ai_models', ['is_visible'], unique=False, postgresql_where=sa.text('is_active = true AND is_visible = true') ) ``` ### 2. 模型定义更新 **文件**: `server/app/models/ai_model.py` ```python class AIModel(SQLModel, table=True): # ... 其他字段 ... # 状态 is_active: bool = Field( default=True, sa_column=Column( nullable=False, server_default='true', index=True, comment='是否启用' ) ) is_visible: bool = Field( default=True, sa_column=Column( nullable=False, server_default='true', comment='是否对用户可见' ) ) is_beta: bool = Field( default=False, sa_column=Column( nullable=False, server_default='false', comment='是否为测试版' ) ) ``` ### 3. Repository 更新 **文件**: `server/app/repositories/ai_model_repository.py` 新增 `get_visible_models()` 方法: ```python async def get_visible_models( self, model_type: Optional[int] = None ) -> List[AIModel]: """获取所有对用户可见的模型 Args: model_type: 模型类型(AIModelType 枚举值,可选) """ query = select(AIModel).where( and_( AIModel.is_active == True, AIModel.is_visible == True ) ) if model_type is not None: query = query.where(AIModel.model_type == model_type) query = query.order_by(AIModel.model_type, AIModel.cost_per_unit) result = await self.db.execute(query) return list(result.scalars().all()) ``` 保留 `get_active_models()` 方法供内部使用(仅查询 `is_active=true`)。 ### 4. Service 更新 **文件**: `server/app/services/ai_service.py` ```python async def get_available_models( self, model_type: Optional[int] = None ) -> List[Dict[str, Any]]: """获取可用的 AI 模型列表(仅返回对用户可见的模型)""" models = await self.model_repository.get_visible_models(model_type) # ... 序列化逻辑 ... ``` ### 5. 同步脚本更新 **文件**: `server/scripts/sync_models_from_api.py` 默认设置: ```python # 默认所有模型 is_active=False, is_visible=False is_active = False is_visible = False db_model = { 'model_name': model_id, 'display_name': display_name, 'description': api_model.get('desc', ''), 'provider': provider, 'model_type': model_type, 'cost_per_unit': cost_per_unit, 'unit_type': unit_type, 'credits_per_unit': credits_per_unit, 'is_active': is_active, 'is_visible': is_visible, # 新增 'is_beta': 'beta' in model_id.lower(), 'config': config, } ``` ### 6. 配置文件更新 **文件**: `server/scripts/models_override_config.json` **简化配置规则**: - 出现在配置文件中的模型自动设置 `is_active=true`(无需手动配置) - 只需配置 `is_visible` 字段控制用户可见性 ```json { "gemini-2.5-flash": { "is_visible": false, "display_name": "Gemini 2.5 Flash", "description": "Google Gemini 2.5 Flash 模型(内部使用)" }, "gpt-4o": { "is_visible": true, "display_name": "GPT-4o", "description": "OpenAI 多模态模型,支持文本和图像" } } ``` **配置语义**: - 配置文件中的模型 = 启用(`is_active=true`) - `is_visible=true` = 用户可见 - `is_visible=false` = 仅内部使用 - 未配置的模型 = 完全禁用(`is_active=false, is_visible=false`) ## 使用场景 ### 场景 1:内部调用模型 ```json { "gemini-2.5-flash": { "is_visible": false, "description": "用于剧本解析的内部模型" } } ``` - 出现在配置文件 → `is_active=true`(系统可用) - `is_visible=false` → 前端 API 不返回(用户不可见) - Factory 可以创建 Provider ### 场景 2:用户可选模型 ```json { "gpt-4o": { "is_visible": true, "description": "用户可选择的模型" } } ``` - 出现在配置文件 → `is_active=true`(系统可用) - `is_visible=true` → 前端 API 返回(用户可见) - Factory 可以创建 Provider ### 场景 3:禁用模型 不在配置文件中的模型: - `is_active=false`(系统不可用) - `is_visible=false`(用户不可见) - Factory 无法创建 Provider ## 迁移步骤 ### 1. 执行数据库迁移 ```bash docker exec jointo-server-app python scripts/db_migrate.py upgrade ``` ### 2. 更新配置文件 编辑 `server/scripts/models_override_config.json`: - 移除 `is_active` 字段(自动设置) - 为每个模型配置 `is_visible` 字段 ### 3. 重新同步模型 ```bash docker exec jointo-server-app python scripts/sync_models_from_api.py --force ``` ### 4. 重启应用 ```bash docker-compose -f server/docker-compose.yml restart jointo-server-app ``` ## 验证 ### 1. 检查数据库 ```sql -- 查看所有模型的可见性状态 SELECT model_name, is_active, is_visible, display_name FROM ai_models ORDER BY is_visible DESC, is_active DESC; -- 查看用户可见的模型 SELECT model_name, display_name FROM ai_models WHERE is_active = true AND is_visible = true; -- 查看内部使用的模型 SELECT model_name, display_name FROM ai_models WHERE is_active = true AND is_visible = false; ``` ### 2. 测试 API ```bash # 获取用户可见的模型列表 curl -X GET "http://localhost:8000/api/v1/ai/models" \ -H "Authorization: Bearer " # 应该只返回 is_visible=true 的模型 ``` ### 3. 测试内部调用 ```python # 内部代码仍然可以使用 is_visible=false 的模型 from app.services.ai_providers.factory import AIProviderFactory # 创建内部模型的 Provider(is_active=true, is_visible=false) provider = await AIProviderFactory.create_provider( db=db, model_name="gemini-2.5-flash" ) # 应该成功创建 ``` ## 影响范围 ### 前端 - **模型选择器**:只显示 `is_visible=true` 的模型 - **API 调用**:`GET /api/v1/ai/models` 只返回可见模型 ### 后端 - **内部调用**:不受影响,仍然可以使用 `is_visible=false` 的模型 - **Factory**:根据 `is_active` 判断是否可创建 Provider - **Repository**:新增 `get_visible_models()` 方法 ## 注意事项 1. **默认值**:新同步的模型默认 `is_visible=false`,需要手动配置 2. **兼容性**:现有模型默认 `is_visible=true`(迁移脚本设置) 3. **更新策略**:`--force` 更新时不会覆盖 `is_active` 和 `is_visible`,保留用户配置 ## 相关文档 - [AI 模型管理文档](../guides/ai-models-management.md) - [AI Provider Factory 重构](./2026-02-10-ai-provider-factory-database-driven.md) - [模型同步脚本使用说明](../../scripts/README.md) --- **作者**: Jointo 开发团队 **审核**: 待审核 **状态**: 已实施