You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

8.1 KiB

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

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

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() 方法:

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

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

默认设置:

# 默认所有模型 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 字段控制用户可见性
{
    "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:内部调用模型

{
    "gemini-2.5-flash": {
        "is_visible": false,
        "description": "用于剧本解析的内部模型"
    }
}
  • 出现在配置文件 → is_active=true(系统可用)
  • is_visible=false → 前端 API 不返回(用户不可见)
  • Factory 可以创建 Provider

场景 2:用户可选模型

{
    "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. 执行数据库迁移

docker exec jointo-server-app python scripts/db_migrate.py upgrade

2. 更新配置文件

编辑 server/scripts/models_override_config.json

  • 移除 is_active 字段(自动设置)
  • 为每个模型配置 is_visible 字段

3. 重新同步模型

docker exec jointo-server-app python scripts/sync_models_from_api.py --force

4. 重启应用

docker-compose -f server/docker-compose.yml restart jointo-server-app

验证

1. 检查数据库

-- 查看所有模型的可见性状态
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

# 获取用户可见的模型列表
curl -X GET "http://localhost:8000/api/v1/ai/models" \
  -H "Authorization: Bearer <token>"

# 应该只返回 is_visible=true 的模型

3. 测试内部调用

# 内部代码仍然可以使用 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_activeis_visible,保留用户配置

相关文档


作者: Jointo 开发团队
审核: 待审核
状态: 已实施