mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-09 19:22:26 +08:00
refactor: 清理数据库字段命名歧义
- users 表:重命名 allowed_endpoints 为 allowed_api_formats(修正历史命名错误) - api_keys 表:删除 allowed_endpoints 字段(未使用的功能) - providers 表:删除 rate_limit 字段(与 rpm_limit 重复) - usage 表:重命名 provider 为 provider_name(避免与 provider_id 外键混淆) 同步更新前后端所有相关代码
This commit is contained in:
@@ -0,0 +1,73 @@
|
|||||||
|
"""cleanup ambiguous database fields
|
||||||
|
|
||||||
|
Revision ID: 02a45b66b7c4
|
||||||
|
Revises: ad55f1d008b7
|
||||||
|
Create Date: 2026-01-07 11:20:12.684426+00:00
|
||||||
|
|
||||||
|
变更内容:
|
||||||
|
1. users 表:重命名 allowed_endpoints 为 allowed_api_formats(修正历史命名错误)
|
||||||
|
2. api_keys 表:删除 allowed_endpoints 字段(未使用的功能)
|
||||||
|
3. providers 表:删除 rate_limit 字段(与 rpm_limit 功能重复,且未使用)
|
||||||
|
4. usage 表:重命名 provider 为 provider_name(避免与 provider_id 外键混淆)
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '02a45b66b7c4'
|
||||||
|
down_revision = 'ad55f1d008b7'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def _column_exists(table_name: str, column_name: str) -> bool:
|
||||||
|
"""检查列是否存在"""
|
||||||
|
bind = op.get_bind()
|
||||||
|
inspector = inspect(bind)
|
||||||
|
columns = [col['name'] for col in inspector.get_columns(table_name)]
|
||||||
|
return column_name in columns
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""
|
||||||
|
1. users.allowed_endpoints -> allowed_api_formats(重命名)
|
||||||
|
2. api_keys.allowed_endpoints 删除
|
||||||
|
3. providers.rate_limit 删除(与 rpm_limit 重复)
|
||||||
|
4. usage.provider -> provider_name(重命名)
|
||||||
|
"""
|
||||||
|
# 1. users 表:重命名 allowed_endpoints 为 allowed_api_formats
|
||||||
|
if _column_exists('users', 'allowed_endpoints'):
|
||||||
|
op.alter_column('users', 'allowed_endpoints', new_column_name='allowed_api_formats')
|
||||||
|
|
||||||
|
# 2. api_keys 表:删除 allowed_endpoints 字段
|
||||||
|
if _column_exists('api_keys', 'allowed_endpoints'):
|
||||||
|
op.drop_column('api_keys', 'allowed_endpoints')
|
||||||
|
|
||||||
|
# 3. providers 表:删除 rate_limit 字段(与 rpm_limit 功能重复)
|
||||||
|
if _column_exists('providers', 'rate_limit'):
|
||||||
|
op.drop_column('providers', 'rate_limit')
|
||||||
|
|
||||||
|
# 4. usage 表:重命名 provider 为 provider_name
|
||||||
|
if _column_exists('usage', 'provider'):
|
||||||
|
op.alter_column('usage', 'provider', new_column_name='provider_name')
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""回滚:恢复原字段"""
|
||||||
|
# 4. usage 表:将 provider_name 改回 provider
|
||||||
|
if _column_exists('usage', 'provider_name'):
|
||||||
|
op.alter_column('usage', 'provider_name', new_column_name='provider')
|
||||||
|
|
||||||
|
# 3. providers 表:恢复 rate_limit 字段
|
||||||
|
if not _column_exists('providers', 'rate_limit'):
|
||||||
|
op.add_column('providers', sa.Column('rate_limit', sa.Integer(), nullable=True))
|
||||||
|
|
||||||
|
# 2. api_keys 表:恢复 allowed_endpoints 字段
|
||||||
|
if not _column_exists('api_keys', 'allowed_endpoints'):
|
||||||
|
op.add_column('api_keys', sa.Column('allowed_endpoints', sa.JSON(), nullable=True))
|
||||||
|
|
||||||
|
# 1. users 表:将 allowed_api_formats 改回 allowed_endpoints
|
||||||
|
if _column_exists('users', 'allowed_api_formats'):
|
||||||
|
op.alter_column('users', 'allowed_api_formats', new_column_name='allowed_endpoints')
|
||||||
@@ -22,7 +22,7 @@ export interface UserExport {
|
|||||||
password_hash: string
|
password_hash: string
|
||||||
role: string
|
role: string
|
||||||
allowed_providers?: string[] | null
|
allowed_providers?: string[] | null
|
||||||
allowed_endpoints?: string[] | null
|
allowed_api_formats?: string[] | null
|
||||||
allowed_models?: string[] | null
|
allowed_models?: string[] | null
|
||||||
model_capability_settings?: any
|
model_capability_settings?: any
|
||||||
quota_usd?: number | null
|
quota_usd?: number | null
|
||||||
@@ -40,7 +40,6 @@ export interface UserApiKeyExport {
|
|||||||
balance_used_usd?: number
|
balance_used_usd?: number
|
||||||
current_balance_usd?: number | null
|
current_balance_usd?: number | null
|
||||||
allowed_providers?: string[] | null
|
allowed_providers?: string[] | null
|
||||||
allowed_endpoints?: string[] | null
|
|
||||||
allowed_api_formats?: string[] | null
|
allowed_api_formats?: string[] | null
|
||||||
allowed_models?: string[] | null
|
allowed_models?: string[] | null
|
||||||
rate_limit?: number | null // null = 无限制
|
rate_limit?: number | null // null = 无限制
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export interface User {
|
|||||||
used_usd?: number
|
used_usd?: number
|
||||||
total_usd?: number
|
total_usd?: number
|
||||||
allowed_providers?: string[] | null // 允许使用的提供商 ID 列表
|
allowed_providers?: string[] | null // 允许使用的提供商 ID 列表
|
||||||
allowed_endpoints?: string[] | null // 允许使用的端点 ID 列表
|
allowed_api_formats?: string[] | null // 允许使用的 API 格式列表
|
||||||
allowed_models?: string[] | null // 允许使用的模型名称列表
|
allowed_models?: string[] | null // 允许使用的模型名称列表
|
||||||
created_at: string
|
created_at: string
|
||||||
last_login_at?: string
|
last_login_at?: string
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export interface User {
|
|||||||
used_usd: number
|
used_usd: number
|
||||||
total_usd: number
|
total_usd: number
|
||||||
allowed_providers: string[] | null // 允许使用的提供商 ID 列表
|
allowed_providers: string[] | null // 允许使用的提供商 ID 列表
|
||||||
allowed_endpoints: string[] | null // 允许使用的端点 ID 列表
|
allowed_api_formats: string[] | null // 允许使用的 API 格式列表
|
||||||
allowed_models: string[] | null // 允许使用的模型名称列表
|
allowed_models: string[] | null // 允许使用的模型名称列表
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
@@ -23,7 +23,7 @@ export interface CreateUserRequest {
|
|||||||
role?: 'admin' | 'user'
|
role?: 'admin' | 'user'
|
||||||
quota_usd?: number | null
|
quota_usd?: number | null
|
||||||
allowed_providers?: string[] | null
|
allowed_providers?: string[] | null
|
||||||
allowed_endpoints?: string[] | null
|
allowed_api_formats?: string[] | null
|
||||||
allowed_models?: string[] | null
|
allowed_models?: string[] | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ export interface UpdateUserRequest {
|
|||||||
quota_usd?: number | null
|
quota_usd?: number | null
|
||||||
password?: string
|
password?: string
|
||||||
allowed_providers?: string[] | null
|
allowed_providers?: string[] | null
|
||||||
allowed_endpoints?: string[] | null
|
allowed_api_formats?: string[] | null
|
||||||
allowed_models?: string[] | null
|
allowed_models?: string[] | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -273,8 +273,8 @@
|
|||||||
class="w-full h-10 px-3 border rounded-lg bg-background text-left flex items-center justify-between hover:bg-muted/50 transition-colors"
|
class="w-full h-10 px-3 border rounded-lg bg-background text-left flex items-center justify-between hover:bg-muted/50 transition-colors"
|
||||||
@click="endpointDropdownOpen = !endpointDropdownOpen"
|
@click="endpointDropdownOpen = !endpointDropdownOpen"
|
||||||
>
|
>
|
||||||
<span :class="form.allowed_endpoints.length ? 'text-foreground' : 'text-muted-foreground'">
|
<span :class="form.allowed_api_formats.length ? 'text-foreground' : 'text-muted-foreground'">
|
||||||
{{ form.allowed_endpoints.length ? `已选择 ${form.allowed_endpoints.length} 个` : '全部可用' }}
|
{{ form.allowed_api_formats.length ? `已选择 ${form.allowed_api_formats.length} 个` : '全部可用' }}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
class="h-4 w-4 text-muted-foreground transition-transform"
|
class="h-4 w-4 text-muted-foreground transition-transform"
|
||||||
@@ -294,14 +294,14 @@
|
|||||||
v-for="format in apiFormats"
|
v-for="format in apiFormats"
|
||||||
:key="format.value"
|
:key="format.value"
|
||||||
class="flex items-center gap-2 px-3 py-2 hover:bg-muted/50 cursor-pointer"
|
class="flex items-center gap-2 px-3 py-2 hover:bg-muted/50 cursor-pointer"
|
||||||
@click="toggleSelection('allowed_endpoints', format.value)"
|
@click="toggleSelection('allowed_api_formats', format.value)"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:checked="form.allowed_endpoints.includes(format.value)"
|
:checked="form.allowed_api_formats.includes(format.value)"
|
||||||
class="h-4 w-4 rounded border-gray-300 cursor-pointer"
|
class="h-4 w-4 rounded border-gray-300 cursor-pointer"
|
||||||
@click.stop
|
@click.stop
|
||||||
@change="toggleSelection('allowed_endpoints', format.value)"
|
@change="toggleSelection('allowed_api_formats', format.value)"
|
||||||
>
|
>
|
||||||
<span class="text-sm">{{ format.label }}</span>
|
<span class="text-sm">{{ format.label }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -374,7 +374,7 @@ export interface UserFormData {
|
|||||||
role: 'admin' | 'user'
|
role: 'admin' | 'user'
|
||||||
is_active?: boolean
|
is_active?: boolean
|
||||||
allowed_providers?: string[] | null
|
allowed_providers?: string[] | null
|
||||||
allowed_endpoints?: string[] | null
|
allowed_api_formats?: string[] | null
|
||||||
allowed_models?: string[] | null
|
allowed_models?: string[] | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,7 +414,7 @@ const form = ref({
|
|||||||
unlimited: false,
|
unlimited: false,
|
||||||
is_active: true,
|
is_active: true,
|
||||||
allowed_providers: [] as string[],
|
allowed_providers: [] as string[],
|
||||||
allowed_endpoints: [] as string[],
|
allowed_api_formats: [] as string[],
|
||||||
allowed_models: [] as string[]
|
allowed_models: [] as string[]
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -435,7 +435,7 @@ function resetForm() {
|
|||||||
unlimited: false,
|
unlimited: false,
|
||||||
is_active: true,
|
is_active: true,
|
||||||
allowed_providers: [],
|
allowed_providers: [],
|
||||||
allowed_endpoints: [],
|
allowed_api_formats: [],
|
||||||
allowed_models: []
|
allowed_models: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -454,7 +454,7 @@ function loadUserData() {
|
|||||||
unlimited: props.user.quota_usd == null,
|
unlimited: props.user.quota_usd == null,
|
||||||
is_active: props.user.is_active ?? true,
|
is_active: props.user.is_active ?? true,
|
||||||
allowed_providers: props.user.allowed_providers || [],
|
allowed_providers: props.user.allowed_providers || [],
|
||||||
allowed_endpoints: props.user.allowed_endpoints || [],
|
allowed_api_formats: props.user.allowed_api_formats || [],
|
||||||
allowed_models: props.user.allowed_models || []
|
allowed_models: props.user.allowed_models || []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -495,7 +495,7 @@ async function loadAccessControlOptions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 切换选择
|
// 切换选择
|
||||||
function toggleSelection(field: 'allowed_providers' | 'allowed_endpoints' | 'allowed_models', value: string) {
|
function toggleSelection(field: 'allowed_providers' | 'allowed_api_formats' | 'allowed_models', value: string) {
|
||||||
const arr = form.value[field]
|
const arr = form.value[field]
|
||||||
const index = arr.indexOf(value)
|
const index = arr.indexOf(value)
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
@@ -520,7 +520,7 @@ async function handleSubmit() {
|
|||||||
quota_usd: form.value.unlimited ? null : form.value.quota,
|
quota_usd: form.value.unlimited ? null : form.value.quota,
|
||||||
role: form.value.role,
|
role: form.value.role,
|
||||||
allowed_providers: form.value.allowed_providers.length > 0 ? form.value.allowed_providers : null,
|
allowed_providers: form.value.allowed_providers.length > 0 ? form.value.allowed_providers : null,
|
||||||
allowed_endpoints: form.value.allowed_endpoints.length > 0 ? form.value.allowed_endpoints : null,
|
allowed_api_formats: form.value.allowed_api_formats.length > 0 ? form.value.allowed_api_formats : null,
|
||||||
allowed_models: form.value.allowed_models.length > 0 ? form.value.allowed_models : null
|
allowed_models: form.value.allowed_models.length > 0 ? form.value.allowed_models : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const MOCK_ADMIN_USER: User = {
|
|||||||
used_usd: 156.78,
|
used_usd: 156.78,
|
||||||
total_usd: 1234.56,
|
total_usd: 1234.56,
|
||||||
allowed_providers: null,
|
allowed_providers: null,
|
||||||
allowed_endpoints: null,
|
allowed_api_formats: null,
|
||||||
allowed_models: null,
|
allowed_models: null,
|
||||||
created_at: '2024-01-01T00:00:00Z',
|
created_at: '2024-01-01T00:00:00Z',
|
||||||
last_login_at: new Date().toISOString()
|
last_login_at: new Date().toISOString()
|
||||||
@@ -38,7 +38,7 @@ export const MOCK_NORMAL_USER: User = {
|
|||||||
used_usd: 45.32,
|
used_usd: 45.32,
|
||||||
total_usd: 245.32,
|
total_usd: 245.32,
|
||||||
allowed_providers: null,
|
allowed_providers: null,
|
||||||
allowed_endpoints: null,
|
allowed_api_formats: null,
|
||||||
allowed_models: null,
|
allowed_models: null,
|
||||||
created_at: '2024-06-01T00:00:00Z',
|
created_at: '2024-06-01T00:00:00Z',
|
||||||
last_login_at: new Date().toISOString()
|
last_login_at: new Date().toISOString()
|
||||||
@@ -274,7 +274,7 @@ export const MOCK_ALL_USERS: AdminUser[] = [
|
|||||||
used_usd: 156.78,
|
used_usd: 156.78,
|
||||||
total_usd: 1234.56,
|
total_usd: 1234.56,
|
||||||
allowed_providers: null,
|
allowed_providers: null,
|
||||||
allowed_endpoints: null,
|
allowed_api_formats: null,
|
||||||
allowed_models: null,
|
allowed_models: null,
|
||||||
created_at: '2024-01-01T00:00:00Z'
|
created_at: '2024-01-01T00:00:00Z'
|
||||||
},
|
},
|
||||||
@@ -288,7 +288,7 @@ export const MOCK_ALL_USERS: AdminUser[] = [
|
|||||||
used_usd: 45.32,
|
used_usd: 45.32,
|
||||||
total_usd: 245.32,
|
total_usd: 245.32,
|
||||||
allowed_providers: null,
|
allowed_providers: null,
|
||||||
allowed_endpoints: null,
|
allowed_api_formats: null,
|
||||||
allowed_models: null,
|
allowed_models: null,
|
||||||
created_at: '2024-06-01T00:00:00Z'
|
created_at: '2024-06-01T00:00:00Z'
|
||||||
},
|
},
|
||||||
@@ -302,7 +302,7 @@ export const MOCK_ALL_USERS: AdminUser[] = [
|
|||||||
used_usd: 23.45,
|
used_usd: 23.45,
|
||||||
total_usd: 123.45,
|
total_usd: 123.45,
|
||||||
allowed_providers: null,
|
allowed_providers: null,
|
||||||
allowed_endpoints: null,
|
allowed_api_formats: null,
|
||||||
allowed_models: null,
|
allowed_models: null,
|
||||||
created_at: '2024-03-15T00:00:00Z'
|
created_at: '2024-03-15T00:00:00Z'
|
||||||
},
|
},
|
||||||
@@ -316,7 +316,7 @@ export const MOCK_ALL_USERS: AdminUser[] = [
|
|||||||
used_usd: 89.12,
|
used_usd: 89.12,
|
||||||
total_usd: 589.12,
|
total_usd: 589.12,
|
||||||
allowed_providers: null,
|
allowed_providers: null,
|
||||||
allowed_endpoints: null,
|
allowed_api_formats: null,
|
||||||
allowed_models: null,
|
allowed_models: null,
|
||||||
created_at: '2024-02-20T00:00:00Z'
|
created_at: '2024-02-20T00:00:00Z'
|
||||||
},
|
},
|
||||||
@@ -330,7 +330,7 @@ export const MOCK_ALL_USERS: AdminUser[] = [
|
|||||||
used_usd: 30.00,
|
used_usd: 30.00,
|
||||||
total_usd: 30.00,
|
total_usd: 30.00,
|
||||||
allowed_providers: null,
|
allowed_providers: null,
|
||||||
allowed_endpoints: null,
|
allowed_api_formats: null,
|
||||||
allowed_models: null,
|
allowed_models: null,
|
||||||
created_at: '2024-04-10T00:00:00Z'
|
created_at: '2024-04-10T00:00:00Z'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -690,7 +690,7 @@ const mockHandlers: Record<string, (config: AxiosRequestConfig) => Promise<Axios
|
|||||||
used_usd: 0,
|
used_usd: 0,
|
||||||
total_usd: 0,
|
total_usd: 0,
|
||||||
allowed_providers: null,
|
allowed_providers: null,
|
||||||
allowed_endpoints: null,
|
allowed_api_formats: null,
|
||||||
allowed_models: null,
|
allowed_models: null,
|
||||||
created_at: new Date().toISOString()
|
created_at: new Date().toISOString()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -907,7 +907,7 @@ function editUser(user: any) {
|
|||||||
role: user.role,
|
role: user.role,
|
||||||
is_active: user.is_active,
|
is_active: user.is_active,
|
||||||
allowed_providers: user.allowed_providers || [],
|
allowed_providers: user.allowed_providers || [],
|
||||||
allowed_endpoints: user.allowed_endpoints || [],
|
allowed_api_formats: user.allowed_api_formats || [],
|
||||||
allowed_models: user.allowed_models || []
|
allowed_models: user.allowed_models || []
|
||||||
}
|
}
|
||||||
showUserFormDialog.value = true
|
showUserFormDialog.value = true
|
||||||
@@ -929,7 +929,7 @@ async function handleUserFormSubmit(data: UserFormData & { password?: string })
|
|||||||
quota_usd: data.quota_usd,
|
quota_usd: data.quota_usd,
|
||||||
role: data.role,
|
role: data.role,
|
||||||
allowed_providers: data.allowed_providers,
|
allowed_providers: data.allowed_providers,
|
||||||
allowed_endpoints: data.allowed_endpoints,
|
allowed_api_formats: data.allowed_api_formats,
|
||||||
allowed_models: data.allowed_models
|
allowed_models: data.allowed_models
|
||||||
}
|
}
|
||||||
if (data.password) {
|
if (data.password) {
|
||||||
@@ -946,7 +946,7 @@ async function handleUserFormSubmit(data: UserFormData & { password?: string })
|
|||||||
quota_usd: data.quota_usd,
|
quota_usd: data.quota_usd,
|
||||||
role: data.role,
|
role: data.role,
|
||||||
allowed_providers: data.allowed_providers,
|
allowed_providers: data.allowed_providers,
|
||||||
allowed_endpoints: data.allowed_endpoints,
|
allowed_api_formats: data.allowed_api_formats,
|
||||||
allowed_models: data.allowed_models
|
allowed_models: data.allowed_models
|
||||||
})
|
})
|
||||||
// 如果创建时指定为禁用,则更新状态
|
// 如果创建时指定为禁用,则更新状态
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ async def create_provider(request: Request, db: Session = Depends(get_db)):
|
|||||||
- `rpm_limit`: 每分钟请求数限制(可选)
|
- `rpm_limit`: 每分钟请求数限制(可选)
|
||||||
- `provider_priority`: 提供商优先级(数字越小优先级越高,默认 100)
|
- `provider_priority`: 提供商优先级(数字越小优先级越高,默认 100)
|
||||||
- `is_active`: 是否启用(默认 true)
|
- `is_active`: 是否启用(默认 true)
|
||||||
- `rate_limit`: 速率限制配置(可选)
|
|
||||||
- `concurrent_limit`: 并发限制(可选)
|
- `concurrent_limit`: 并发限制(可选)
|
||||||
- `config`: 额外配置信息(JSON,可选)
|
- `config`: 额外配置信息(JSON,可选)
|
||||||
|
|
||||||
@@ -110,7 +109,6 @@ async def update_provider(provider_id: str, request: Request, db: Session = Depe
|
|||||||
- `rpm_limit`: 每分钟请求数限制
|
- `rpm_limit`: 每分钟请求数限制
|
||||||
- `provider_priority`: 提供商优先级
|
- `provider_priority`: 提供商优先级
|
||||||
- `is_active`: 是否启用
|
- `is_active`: 是否启用
|
||||||
- `rate_limit`: 速率限制配置
|
|
||||||
- `concurrent_limit`: 并发限制
|
- `concurrent_limit`: 并发限制
|
||||||
- `config`: 额外配置信息(JSON)
|
- `config`: 额外配置信息(JSON)
|
||||||
|
|
||||||
@@ -228,7 +226,6 @@ class AdminCreateProviderAdapter(AdminApiAdapter):
|
|||||||
rpm_limit=validated_data.rpm_limit,
|
rpm_limit=validated_data.rpm_limit,
|
||||||
provider_priority=validated_data.provider_priority,
|
provider_priority=validated_data.provider_priority,
|
||||||
is_active=validated_data.is_active,
|
is_active=validated_data.is_active,
|
||||||
rate_limit=validated_data.rate_limit,
|
|
||||||
concurrent_limit=validated_data.concurrent_limit,
|
concurrent_limit=validated_data.concurrent_limit,
|
||||||
config=validated_data.config,
|
config=validated_data.config,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -684,7 +684,6 @@ class AdminExportConfigAdapter(AdminApiAdapter):
|
|||||||
"rpm_limit": provider.rpm_limit,
|
"rpm_limit": provider.rpm_limit,
|
||||||
"provider_priority": provider.provider_priority,
|
"provider_priority": provider.provider_priority,
|
||||||
"is_active": provider.is_active,
|
"is_active": provider.is_active,
|
||||||
"rate_limit": provider.rate_limit,
|
|
||||||
"concurrent_limit": provider.concurrent_limit,
|
"concurrent_limit": provider.concurrent_limit,
|
||||||
"config": provider.config,
|
"config": provider.config,
|
||||||
"endpoints": endpoints_data,
|
"endpoints": endpoints_data,
|
||||||
@@ -831,7 +830,6 @@ class AdminImportConfigAdapter(AdminApiAdapter):
|
|||||||
"provider_priority", 100
|
"provider_priority", 100
|
||||||
)
|
)
|
||||||
existing_provider.is_active = prov_data.get("is_active", True)
|
existing_provider.is_active = prov_data.get("is_active", True)
|
||||||
existing_provider.rate_limit = prov_data.get("rate_limit")
|
|
||||||
existing_provider.concurrent_limit = prov_data.get(
|
existing_provider.concurrent_limit = prov_data.get(
|
||||||
"concurrent_limit"
|
"concurrent_limit"
|
||||||
)
|
)
|
||||||
@@ -856,7 +854,6 @@ class AdminImportConfigAdapter(AdminApiAdapter):
|
|||||||
rpm_limit=prov_data.get("rpm_limit"),
|
rpm_limit=prov_data.get("rpm_limit"),
|
||||||
provider_priority=prov_data.get("provider_priority", 100),
|
provider_priority=prov_data.get("provider_priority", 100),
|
||||||
is_active=prov_data.get("is_active", True),
|
is_active=prov_data.get("is_active", True),
|
||||||
rate_limit=prov_data.get("rate_limit"),
|
|
||||||
concurrent_limit=prov_data.get("concurrent_limit"),
|
concurrent_limit=prov_data.get("concurrent_limit"),
|
||||||
config=prov_data.get("config"),
|
config=prov_data.get("config"),
|
||||||
)
|
)
|
||||||
@@ -1109,7 +1106,6 @@ class AdminExportUsersAdapter(AdminApiAdapter):
|
|||||||
"balance_used_usd": key.balance_used_usd,
|
"balance_used_usd": key.balance_used_usd,
|
||||||
"current_balance_usd": key.current_balance_usd,
|
"current_balance_usd": key.current_balance_usd,
|
||||||
"allowed_providers": key.allowed_providers,
|
"allowed_providers": key.allowed_providers,
|
||||||
"allowed_endpoints": key.allowed_endpoints,
|
|
||||||
"allowed_api_formats": key.allowed_api_formats,
|
"allowed_api_formats": key.allowed_api_formats,
|
||||||
"allowed_models": key.allowed_models,
|
"allowed_models": key.allowed_models,
|
||||||
"rate_limit": key.rate_limit,
|
"rate_limit": key.rate_limit,
|
||||||
@@ -1146,7 +1142,7 @@ class AdminExportUsersAdapter(AdminApiAdapter):
|
|||||||
"password_hash": user.password_hash,
|
"password_hash": user.password_hash,
|
||||||
"role": user.role.value if user.role else "user",
|
"role": user.role.value if user.role else "user",
|
||||||
"allowed_providers": user.allowed_providers,
|
"allowed_providers": user.allowed_providers,
|
||||||
"allowed_endpoints": user.allowed_endpoints,
|
"allowed_api_formats": user.allowed_api_formats,
|
||||||
"allowed_models": user.allowed_models,
|
"allowed_models": user.allowed_models,
|
||||||
"model_capability_settings": user.model_capability_settings,
|
"model_capability_settings": user.model_capability_settings,
|
||||||
"quota_usd": user.quota_usd,
|
"quota_usd": user.quota_usd,
|
||||||
@@ -1238,7 +1234,6 @@ class AdminImportUsersAdapter(AdminApiAdapter):
|
|||||||
balance_used_usd=key_data.get("balance_used_usd", 0.0),
|
balance_used_usd=key_data.get("balance_used_usd", 0.0),
|
||||||
current_balance_usd=key_data.get("current_balance_usd"),
|
current_balance_usd=key_data.get("current_balance_usd"),
|
||||||
allowed_providers=key_data.get("allowed_providers"),
|
allowed_providers=key_data.get("allowed_providers"),
|
||||||
allowed_endpoints=key_data.get("allowed_endpoints"),
|
|
||||||
allowed_api_formats=key_data.get("allowed_api_formats"),
|
allowed_api_formats=key_data.get("allowed_api_formats"),
|
||||||
allowed_models=key_data.get("allowed_models"),
|
allowed_models=key_data.get("allowed_models"),
|
||||||
rate_limit=key_data.get("rate_limit"),
|
rate_limit=key_data.get("rate_limit"),
|
||||||
@@ -1282,7 +1277,7 @@ class AdminImportUsersAdapter(AdminApiAdapter):
|
|||||||
if user_data.get("role"):
|
if user_data.get("role"):
|
||||||
existing_user.role = UserRole(user_data["role"])
|
existing_user.role = UserRole(user_data["role"])
|
||||||
existing_user.allowed_providers = user_data.get("allowed_providers")
|
existing_user.allowed_providers = user_data.get("allowed_providers")
|
||||||
existing_user.allowed_endpoints = user_data.get("allowed_endpoints")
|
existing_user.allowed_api_formats = user_data.get("allowed_api_formats")
|
||||||
existing_user.allowed_models = user_data.get("allowed_models")
|
existing_user.allowed_models = user_data.get("allowed_models")
|
||||||
existing_user.model_capability_settings = user_data.get(
|
existing_user.model_capability_settings = user_data.get(
|
||||||
"model_capability_settings"
|
"model_capability_settings"
|
||||||
@@ -1306,7 +1301,7 @@ class AdminImportUsersAdapter(AdminApiAdapter):
|
|||||||
password_hash=user_data.get("password_hash", ""),
|
password_hash=user_data.get("password_hash", ""),
|
||||||
role=role,
|
role=role,
|
||||||
allowed_providers=user_data.get("allowed_providers"),
|
allowed_providers=user_data.get("allowed_providers"),
|
||||||
allowed_endpoints=user_data.get("allowed_endpoints"),
|
allowed_api_formats=user_data.get("allowed_api_formats"),
|
||||||
allowed_models=user_data.get("allowed_models"),
|
allowed_models=user_data.get("allowed_models"),
|
||||||
model_capability_settings=user_data.get("model_capability_settings"),
|
model_capability_settings=user_data.get("model_capability_settings"),
|
||||||
quota_usd=user_data.get("quota_usd"),
|
quota_usd=user_data.get("quota_usd"),
|
||||||
|
|||||||
@@ -353,8 +353,8 @@ class AdminUsageByModelAdapter(AdminApiAdapter):
|
|||||||
)
|
)
|
||||||
# 过滤掉 pending/streaming 状态的请求(尚未完成的请求不应计入统计)
|
# 过滤掉 pending/streaming 状态的请求(尚未完成的请求不应计入统计)
|
||||||
query = query.filter(Usage.status.notin_(["pending", "streaming"]))
|
query = query.filter(Usage.status.notin_(["pending", "streaming"]))
|
||||||
# 过滤掉 unknown/pending provider(请求未到达任何提供商)
|
# 过滤掉 unknown/pending provider_name(请求未到达任何提供商)
|
||||||
query = query.filter(Usage.provider.notin_(["unknown", "pending"]))
|
query = query.filter(Usage.provider_name.notin_(["unknown", "pending"]))
|
||||||
|
|
||||||
if self.start_date:
|
if self.start_date:
|
||||||
query = query.filter(Usage.created_at >= self.start_date)
|
query = query.filter(Usage.created_at >= self.start_date)
|
||||||
@@ -565,8 +565,8 @@ class AdminUsageByApiFormatAdapter(AdminApiAdapter):
|
|||||||
)
|
)
|
||||||
# 过滤掉 pending/streaming 状态的请求
|
# 过滤掉 pending/streaming 状态的请求
|
||||||
query = query.filter(Usage.status.notin_(["pending", "streaming"]))
|
query = query.filter(Usage.status.notin_(["pending", "streaming"]))
|
||||||
# 过滤掉 unknown/pending provider
|
# 过滤掉 unknown/pending provider_name
|
||||||
query = query.filter(Usage.provider.notin_(["unknown", "pending"]))
|
query = query.filter(Usage.provider_name.notin_(["unknown", "pending"]))
|
||||||
# 只统计有 api_format 的记录
|
# 只统计有 api_format 的记录
|
||||||
query = query.filter(Usage.api_format.isnot(None))
|
query = query.filter(Usage.api_format.isnot(None))
|
||||||
|
|
||||||
@@ -765,8 +765,8 @@ class AdminUsageRecordsAdapter(AdminApiAdapter):
|
|||||||
float(usage.rate_multiplier) if usage.rate_multiplier is not None else 1.0
|
float(usage.rate_multiplier) if usage.rate_multiplier is not None else 1.0
|
||||||
)
|
)
|
||||||
|
|
||||||
# 提供商名称优先级:关联的 Provider 表 > usage.provider 字段
|
# 提供商名称优先级:关联的 Provider 表 > usage.provider_name 字段
|
||||||
provider_name = usage.provider
|
provider_name = usage.provider_name
|
||||||
if usage.provider_id and str(usage.provider_id) in provider_map:
|
if usage.provider_id and str(usage.provider_id) in provider_map:
|
||||||
provider_name = provider_map[str(usage.provider_id)]
|
provider_name = provider_map[str(usage.provider_id)]
|
||||||
|
|
||||||
@@ -881,7 +881,7 @@ class AdminUsageDetailAdapter(AdminApiAdapter):
|
|||||||
"name": api_key.name if api_key else None,
|
"name": api_key.name if api_key else None,
|
||||||
"display": api_key.get_display_key() if api_key else None,
|
"display": api_key.get_display_key() if api_key else None,
|
||||||
},
|
},
|
||||||
"provider": usage_record.provider,
|
"provider": usage_record.provider_name,
|
||||||
"api_format": usage_record.api_format,
|
"api_format": usage_record.api_format,
|
||||||
"model": usage_record.model,
|
"model": usage_record.model,
|
||||||
"target_model": usage_record.target_model,
|
"target_model": usage_record.target_model,
|
||||||
@@ -934,7 +934,7 @@ class AdminUsageDetailAdapter(AdminApiAdapter):
|
|||||||
# 尝试获取模型的阶梯配置(带来源信息)
|
# 尝试获取模型的阶梯配置(带来源信息)
|
||||||
cost_service = ModelCostService(db)
|
cost_service = ModelCostService(db)
|
||||||
pricing_result = await cost_service.get_tiered_pricing_with_source_async(
|
pricing_result = await cost_service.get_tiered_pricing_with_source_async(
|
||||||
usage_record.provider, usage_record.model
|
usage_record.provider_name, usage_record.model
|
||||||
)
|
)
|
||||||
|
|
||||||
if not pricing_result:
|
if not pricing_result:
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ class AdminCreateUserAdapter(AdminApiAdapter):
|
|||||||
"username": user.username,
|
"username": user.username,
|
||||||
"role": user.role.value,
|
"role": user.role.value,
|
||||||
"allowed_providers": user.allowed_providers,
|
"allowed_providers": user.allowed_providers,
|
||||||
"allowed_endpoints": user.allowed_endpoints,
|
"allowed_api_formats": user.allowed_api_formats,
|
||||||
"allowed_models": user.allowed_models,
|
"allowed_models": user.allowed_models,
|
||||||
"quota_usd": user.quota_usd,
|
"quota_usd": user.quota_usd,
|
||||||
"used_usd": user.used_usd,
|
"used_usd": user.used_usd,
|
||||||
@@ -274,7 +274,7 @@ class AdminListUsersAdapter(AdminApiAdapter):
|
|||||||
"username": u.username,
|
"username": u.username,
|
||||||
"role": u.role.value,
|
"role": u.role.value,
|
||||||
"allowed_providers": u.allowed_providers,
|
"allowed_providers": u.allowed_providers,
|
||||||
"allowed_endpoints": u.allowed_endpoints,
|
"allowed_api_formats": u.allowed_api_formats,
|
||||||
"allowed_models": u.allowed_models,
|
"allowed_models": u.allowed_models,
|
||||||
"quota_usd": u.quota_usd,
|
"quota_usd": u.quota_usd,
|
||||||
"used_usd": u.used_usd,
|
"used_usd": u.used_usd,
|
||||||
@@ -309,7 +309,7 @@ class AdminGetUserAdapter(AdminApiAdapter):
|
|||||||
"username": user.username,
|
"username": user.username,
|
||||||
"role": user.role.value,
|
"role": user.role.value,
|
||||||
"allowed_providers": user.allowed_providers,
|
"allowed_providers": user.allowed_providers,
|
||||||
"allowed_endpoints": user.allowed_endpoints,
|
"allowed_api_formats": user.allowed_api_formats,
|
||||||
"allowed_models": user.allowed_models,
|
"allowed_models": user.allowed_models,
|
||||||
"quota_usd": user.quota_usd,
|
"quota_usd": user.quota_usd,
|
||||||
"used_usd": user.used_usd,
|
"used_usd": user.used_usd,
|
||||||
@@ -375,7 +375,7 @@ class AdminUpdateUserAdapter(AdminApiAdapter):
|
|||||||
"username": user.username,
|
"username": user.username,
|
||||||
"role": user.role.value,
|
"role": user.role.value,
|
||||||
"allowed_providers": user.allowed_providers,
|
"allowed_providers": user.allowed_providers,
|
||||||
"allowed_endpoints": user.allowed_endpoints,
|
"allowed_api_formats": user.allowed_api_formats,
|
||||||
"allowed_models": user.allowed_models,
|
"allowed_models": user.allowed_models,
|
||||||
"quota_usd": user.quota_usd,
|
"quota_usd": user.quota_usd,
|
||||||
"used_usd": user.used_usd,
|
"used_usd": user.used_usd,
|
||||||
|
|||||||
@@ -528,7 +528,7 @@ class AuthCurrentUserAdapter(AuthenticatedApiAdapter):
|
|||||||
"used_usd": user.used_usd,
|
"used_usd": user.used_usd,
|
||||||
"total_usd": user.total_usd,
|
"total_usd": user.total_usd,
|
||||||
"allowed_providers": user.allowed_providers,
|
"allowed_providers": user.allowed_providers,
|
||||||
"allowed_endpoints": user.allowed_endpoints,
|
"allowed_api_formats": user.allowed_api_formats,
|
||||||
"allowed_models": user.allowed_models,
|
"allowed_models": user.allowed_models,
|
||||||
"created_at": user.created_at.isoformat(),
|
"created_at": user.created_at.isoformat(),
|
||||||
"last_login_at": user.last_login_at.isoformat() if user.last_login_at else None,
|
"last_login_at": user.last_login_at.isoformat() if user.last_login_at else None,
|
||||||
|
|||||||
@@ -143,12 +143,13 @@ class AccessRestrictions:
|
|||||||
allowed_api_formats = api_key.allowed_api_formats
|
allowed_api_formats = api_key.allowed_api_formats
|
||||||
|
|
||||||
# 如果 API Key 没有限制,检查 User 的限制
|
# 如果 API Key 没有限制,检查 User 的限制
|
||||||
# 注意: User 没有 allowed_api_formats 字段
|
|
||||||
if user:
|
if user:
|
||||||
if allowed_providers is None and user.allowed_providers is not None:
|
if allowed_providers is None and user.allowed_providers is not None:
|
||||||
allowed_providers = user.allowed_providers
|
allowed_providers = user.allowed_providers
|
||||||
if allowed_models is None and user.allowed_models is not None:
|
if allowed_models is None and user.allowed_models is not None:
|
||||||
allowed_models = user.allowed_models
|
allowed_models = user.allowed_models
|
||||||
|
if allowed_api_formats is None and user.allowed_api_formats is not None:
|
||||||
|
allowed_api_formats = user.allowed_api_formats
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
allowed_providers=allowed_providers,
|
allowed_providers=allowed_providers,
|
||||||
|
|||||||
@@ -766,7 +766,7 @@ class DashboardProviderStatusAdapter(DashboardAdapter):
|
|||||||
for provider in providers:
|
for provider in providers:
|
||||||
count = (
|
count = (
|
||||||
db.query(func.count(Usage.id))
|
db.query(func.count(Usage.id))
|
||||||
.filter(and_(Usage.provider == provider.name, Usage.created_at >= since))
|
.filter(and_(Usage.provider_name == provider.name, Usage.created_at >= since))
|
||||||
.scalar()
|
.scalar()
|
||||||
)
|
)
|
||||||
entries.append(
|
entries.append(
|
||||||
@@ -854,7 +854,7 @@ class DashboardDailyStatsAdapter(DashboardAdapter):
|
|||||||
.scalar() or 0
|
.scalar() or 0
|
||||||
)
|
)
|
||||||
today_unique_providers = (
|
today_unique_providers = (
|
||||||
db.query(func.count(func.distinct(Usage.provider)))
|
db.query(func.count(func.distinct(Usage.provider_name)))
|
||||||
.filter(Usage.created_at >= today)
|
.filter(Usage.created_at >= today)
|
||||||
.scalar() or 0
|
.scalar() or 0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -126,7 +126,9 @@ def _filter_formats_by_restrictions(
|
|||||||
"""
|
"""
|
||||||
if restrictions.allowed_api_formats is None:
|
if restrictions.allowed_api_formats is None:
|
||||||
return formats, None
|
return formats, None
|
||||||
filtered = [f for f in formats if f in restrictions.allowed_api_formats]
|
# 统一转为大写比较,兼容数据库中存储的大小写
|
||||||
|
allowed_upper = {f.upper() for f in restrictions.allowed_api_formats}
|
||||||
|
filtered = [f for f in formats if f.upper() in allowed_upper]
|
||||||
if not filtered:
|
if not filtered:
|
||||||
logger.info(f"[Models] API Key 不允许访问格式 {api_format}")
|
logger.info(f"[Models] API Key 不允许访问格式 {api_format}")
|
||||||
return [], _build_empty_list_response(api_format)
|
return [], _build_empty_list_response(api_format)
|
||||||
|
|||||||
@@ -847,7 +847,7 @@ class GetUsageAdapter(AuthenticatedApiAdapter):
|
|||||||
"records": [
|
"records": [
|
||||||
{
|
{
|
||||||
"id": r.id,
|
"id": r.id,
|
||||||
"provider": r.provider,
|
"provider": r.provider_name,
|
||||||
"model": r.model,
|
"model": r.model,
|
||||||
"target_model": r.target_model, # 映射后的目标模型名
|
"target_model": r.target_model, # 映射后的目标模型名
|
||||||
"api_format": r.api_format,
|
"api_format": r.api_format,
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ class CreateProviderRequest(BaseModel):
|
|||||||
rpm_limit: Optional[int] = Field(None, ge=0, description="RPM 限制")
|
rpm_limit: Optional[int] = Field(None, ge=0, description="RPM 限制")
|
||||||
provider_priority: Optional[int] = Field(100, ge=0, le=1000, description="提供商优先级(数字越小越优先)")
|
provider_priority: Optional[int] = Field(100, ge=0, le=1000, description="提供商优先级(数字越小越优先)")
|
||||||
is_active: Optional[bool] = Field(True, description="是否启用")
|
is_active: Optional[bool] = Field(True, description="是否启用")
|
||||||
rate_limit: Optional[int] = Field(None, ge=0, description="速率限制")
|
|
||||||
concurrent_limit: Optional[int] = Field(None, ge=0, description="并发限制")
|
concurrent_limit: Optional[int] = Field(None, ge=0, description="并发限制")
|
||||||
config: Optional[Dict[str, Any]] = Field(None, description="其他配置")
|
config: Optional[Dict[str, Any]] = Field(None, description="其他配置")
|
||||||
|
|
||||||
@@ -174,7 +173,6 @@ class UpdateProviderRequest(BaseModel):
|
|||||||
rpm_limit: Optional[int] = Field(None, ge=0)
|
rpm_limit: Optional[int] = Field(None, ge=0)
|
||||||
provider_priority: Optional[int] = Field(None, ge=0, le=1000)
|
provider_priority: Optional[int] = Field(None, ge=0, le=1000)
|
||||||
is_active: Optional[bool] = None
|
is_active: Optional[bool] = None
|
||||||
rate_limit: Optional[int] = Field(None, ge=0)
|
|
||||||
concurrent_limit: Optional[int] = Field(None, ge=0)
|
concurrent_limit: Optional[int] = Field(None, ge=0)
|
||||||
config: Optional[Dict[str, Any]] = None
|
config: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
@@ -322,7 +320,7 @@ class UpdateUserRequest(BaseModel):
|
|||||||
is_active: Optional[bool] = None
|
is_active: Optional[bool] = None
|
||||||
role: Optional[str] = None
|
role: Optional[str] = None
|
||||||
allowed_providers: Optional[List[str]] = Field(None, description="允许使用的提供商 ID 列表")
|
allowed_providers: Optional[List[str]] = Field(None, description="允许使用的提供商 ID 列表")
|
||||||
allowed_endpoints: Optional[List[str]] = Field(None, description="允许使用的端点 ID 列表")
|
allowed_api_formats: Optional[List[str]] = Field(None, description="允许使用的 API 格式列表")
|
||||||
allowed_models: Optional[List[str]] = Field(None, description="允许使用的模型名称列表")
|
allowed_models: Optional[List[str]] = Field(None, description="允许使用的模型名称列表")
|
||||||
|
|
||||||
@field_validator("username")
|
@field_validator("username")
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ class UpdateUserRequest(BaseModel):
|
|||||||
password: Optional[str] = None
|
password: Optional[str] = None
|
||||||
role: Optional[UserRole] = None
|
role: Optional[UserRole] = None
|
||||||
allowed_providers: Optional[List[str]] = None # 允许使用的提供商 ID 列表
|
allowed_providers: Optional[List[str]] = None # 允许使用的提供商 ID 列表
|
||||||
allowed_endpoints: Optional[List[str]] = None # 允许使用的端点 ID 列表
|
allowed_api_formats: Optional[List[str]] = None # 允许使用的 API 格式列表
|
||||||
allowed_models: Optional[List[str]] = None # 允许使用的模型名称列表
|
allowed_models: Optional[List[str]] = None # 允许使用的模型名称列表
|
||||||
quota_usd: Optional[float] = None
|
quota_usd: Optional[float] = None
|
||||||
is_active: Optional[bool] = None
|
is_active: Optional[bool] = None
|
||||||
@@ -316,7 +316,6 @@ class CreateApiKeyRequest(BaseModel):
|
|||||||
|
|
||||||
name: Optional[str] = None
|
name: Optional[str] = None
|
||||||
allowed_providers: Optional[List[str]] = None # 允许使用的提供商 ID 列表
|
allowed_providers: Optional[List[str]] = None # 允许使用的提供商 ID 列表
|
||||||
allowed_endpoints: Optional[List[str]] = None # 允许使用的端点 ID 列表
|
|
||||||
allowed_api_formats: Optional[List[str]] = None # 允许使用的 API 格式列表
|
allowed_api_formats: Optional[List[str]] = None # 允许使用的 API 格式列表
|
||||||
allowed_models: Optional[List[str]] = None # 允许使用的模型名称列表
|
allowed_models: Optional[List[str]] = None # 允许使用的模型名称列表
|
||||||
rate_limit: Optional[int] = None # None = 无限制
|
rate_limit: Optional[int] = None # None = 无限制
|
||||||
@@ -339,7 +338,7 @@ class UserResponse(BaseModel):
|
|||||||
username: str
|
username: str
|
||||||
role: UserRole
|
role: UserRole
|
||||||
allowed_providers: Optional[List[str]] = None # 允许使用的提供商 ID 列表
|
allowed_providers: Optional[List[str]] = None # 允许使用的提供商 ID 列表
|
||||||
allowed_endpoints: Optional[List[str]] = None # 允许使用的端点 ID 列表
|
allowed_api_formats: Optional[List[str]] = None # 允许使用的 API 格式列表
|
||||||
allowed_models: Optional[List[str]] = None # 允许使用的模型名称列表
|
allowed_models: Optional[List[str]] = None # 允许使用的模型名称列表
|
||||||
quota_usd: float
|
quota_usd: float
|
||||||
used_usd: float
|
used_usd: float
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class User(Base):
|
|||||||
|
|
||||||
# 访问限制(NULL 表示不限制,允许访问所有资源)
|
# 访问限制(NULL 表示不限制,允许访问所有资源)
|
||||||
allowed_providers = Column(JSON, nullable=True) # 允许使用的提供商 ID 列表
|
allowed_providers = Column(JSON, nullable=True) # 允许使用的提供商 ID 列表
|
||||||
allowed_endpoints = Column(JSON, nullable=True) # 允许使用的端点 ID 列表
|
allowed_api_formats = Column(JSON, nullable=True) # 允许使用的 API 格式列表
|
||||||
allowed_models = Column(JSON, nullable=True) # 允许使用的模型名称列表
|
allowed_models = Column(JSON, nullable=True) # 允许使用的模型名称列表
|
||||||
|
|
||||||
# Key 能力配置
|
# Key 能力配置
|
||||||
@@ -165,7 +165,6 @@ class ApiKey(Base):
|
|||||||
|
|
||||||
# 访问限制(NULL 表示不限制,允许访问所有资源)
|
# 访问限制(NULL 表示不限制,允许访问所有资源)
|
||||||
allowed_providers = Column(JSON, nullable=True) # 允许使用的提供商 ID 列表
|
allowed_providers = Column(JSON, nullable=True) # 允许使用的提供商 ID 列表
|
||||||
allowed_endpoints = Column(JSON, nullable=True) # 允许使用的端点 ID 列表
|
|
||||||
allowed_api_formats = Column(JSON, nullable=True) # 允许使用的 API 格式列表
|
allowed_api_formats = Column(JSON, nullable=True) # 允许使用的 API 格式列表
|
||||||
allowed_models = Column(JSON, nullable=True) # 允许使用的模型名称列表
|
allowed_models = Column(JSON, nullable=True) # 允许使用的模型名称列表
|
||||||
rate_limit = Column(Integer, default=None, nullable=True) # 每分钟请求限制,None = 无限制
|
rate_limit = Column(Integer, default=None, nullable=True) # 每分钟请求限制,None = 无限制
|
||||||
@@ -272,7 +271,7 @@ class Usage(Base):
|
|||||||
|
|
||||||
# 请求信息
|
# 请求信息
|
||||||
request_id = Column(String(100), unique=True, index=True, nullable=False)
|
request_id = Column(String(100), unique=True, index=True, nullable=False)
|
||||||
provider = Column(String(100), nullable=False)
|
provider_name = Column(String(100), nullable=False) # Provider 名称(非外键)
|
||||||
model = Column(String(100), nullable=False)
|
model = Column(String(100), nullable=False)
|
||||||
target_model = Column(String(100), nullable=True, comment="映射后的目标模型名(若无映射则为空)")
|
target_model = Column(String(100), nullable=True, comment="映射后的目标模型名(若无映射则为空)")
|
||||||
|
|
||||||
@@ -554,7 +553,6 @@ class Provider(Base):
|
|||||||
is_active = Column(Boolean, default=True, nullable=False)
|
is_active = Column(Boolean, default=True, nullable=False)
|
||||||
|
|
||||||
# 限制
|
# 限制
|
||||||
rate_limit = Column(Integer, nullable=True) # 每分钟请求限制
|
|
||||||
concurrent_limit = Column(Integer, nullable=True) # 并发请求限制
|
concurrent_limit = Column(Integer, nullable=True) # 并发请求限制
|
||||||
|
|
||||||
# 配置
|
# 配置
|
||||||
|
|||||||
34
src/services/cache/aware_scheduler.py
vendored
34
src/services/cache/aware_scheduler.py
vendored
@@ -486,11 +486,10 @@ class CacheAwareScheduler:
|
|||||||
user_api_key: 用户 API Key 对象(可能包含 user relationship)
|
user_api_key: 用户 API Key 对象(可能包含 user relationship)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
包含 allowed_providers, allowed_endpoints, allowed_models 的字典
|
包含 allowed_providers, allowed_models, allowed_api_formats 的字典
|
||||||
"""
|
"""
|
||||||
result = {
|
result = {
|
||||||
"allowed_providers": None,
|
"allowed_providers": None,
|
||||||
"allowed_endpoints": None,
|
|
||||||
"allowed_models": None,
|
"allowed_models": None,
|
||||||
"allowed_api_formats": None,
|
"allowed_api_formats": None,
|
||||||
}
|
}
|
||||||
@@ -534,20 +533,16 @@ class CacheAwareScheduler:
|
|||||||
user_api_key.allowed_providers, user.allowed_providers if user else None
|
user_api_key.allowed_providers, user.allowed_providers if user else None
|
||||||
)
|
)
|
||||||
|
|
||||||
# 合并 allowed_endpoints
|
|
||||||
result["allowed_endpoints"] = merge_restrictions(
|
|
||||||
user_api_key.allowed_endpoints if hasattr(user_api_key, "allowed_endpoints") else None,
|
|
||||||
user.allowed_endpoints if user else None,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 合并 allowed_models
|
# 合并 allowed_models
|
||||||
result["allowed_models"] = merge_restrictions(
|
result["allowed_models"] = merge_restrictions(
|
||||||
user_api_key.allowed_models, user.allowed_models if user else None
|
user_api_key.allowed_models, user.allowed_models if user else None
|
||||||
)
|
)
|
||||||
|
|
||||||
# API 格式仅从 ApiKey 获取(User 不设置此限制)
|
# 合并 allowed_api_formats
|
||||||
if user_api_key.allowed_api_formats:
|
result["allowed_api_formats"] = merge_restrictions(
|
||||||
result["allowed_api_formats"] = set(user_api_key.allowed_api_formats)
|
user_api_key.allowed_api_formats,
|
||||||
|
user.allowed_api_formats if user else None
|
||||||
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -607,12 +602,13 @@ class CacheAwareScheduler:
|
|||||||
restrictions = self._get_effective_restrictions(user_api_key)
|
restrictions = self._get_effective_restrictions(user_api_key)
|
||||||
allowed_api_formats = restrictions["allowed_api_formats"]
|
allowed_api_formats = restrictions["allowed_api_formats"]
|
||||||
allowed_providers = restrictions["allowed_providers"]
|
allowed_providers = restrictions["allowed_providers"]
|
||||||
allowed_endpoints = restrictions["allowed_endpoints"]
|
|
||||||
allowed_models = restrictions["allowed_models"]
|
allowed_models = restrictions["allowed_models"]
|
||||||
|
|
||||||
# 0.1 检查 API 格式是否被允许
|
# 0.1 检查 API 格式是否被允许
|
||||||
if allowed_api_formats is not None:
|
if allowed_api_formats is not None:
|
||||||
if target_format.value not in allowed_api_formats:
|
# 统一转为大写比较,兼容数据库中存储的大小写
|
||||||
|
allowed_upper = {f.upper() for f in allowed_api_formats}
|
||||||
|
if target_format.value.upper() not in allowed_upper:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"API Key {user_api_key.id[:8] if user_api_key else 'N/A'}... 不允许使用 API 格式 {target_format.value}, "
|
f"API Key {user_api_key.id[:8] if user_api_key else 'N/A'}... 不允许使用 API 格式 {target_format.value}, "
|
||||||
f"允许的格式: {allowed_api_formats}"
|
f"允许的格式: {allowed_api_formats}"
|
||||||
@@ -659,7 +655,7 @@ class CacheAwareScheduler:
|
|||||||
if not providers:
|
if not providers:
|
||||||
return [], global_model_id
|
return [], global_model_id
|
||||||
|
|
||||||
# 2. 构建候选列表(传入 allowed_endpoints、is_stream 和 capability_requirements 用于过滤)
|
# 2. 构建候选列表(传入 is_stream 和 capability_requirements 用于过滤)
|
||||||
candidates = await self._build_candidates(
|
candidates = await self._build_candidates(
|
||||||
db=db,
|
db=db,
|
||||||
providers=providers,
|
providers=providers,
|
||||||
@@ -668,7 +664,6 @@ class CacheAwareScheduler:
|
|||||||
resolved_model_name=resolved_model_name,
|
resolved_model_name=resolved_model_name,
|
||||||
affinity_key=affinity_key,
|
affinity_key=affinity_key,
|
||||||
max_candidates=max_candidates,
|
max_candidates=max_candidates,
|
||||||
allowed_endpoints=allowed_endpoints,
|
|
||||||
is_stream=is_stream,
|
is_stream=is_stream,
|
||||||
capability_requirements=capability_requirements,
|
capability_requirements=capability_requirements,
|
||||||
)
|
)
|
||||||
@@ -905,7 +900,6 @@ class CacheAwareScheduler:
|
|||||||
affinity_key: Optional[str],
|
affinity_key: Optional[str],
|
||||||
resolved_model_name: Optional[str] = None,
|
resolved_model_name: Optional[str] = None,
|
||||||
max_candidates: Optional[int] = None,
|
max_candidates: Optional[int] = None,
|
||||||
allowed_endpoints: Optional[set] = None,
|
|
||||||
is_stream: bool = False,
|
is_stream: bool = False,
|
||||||
capability_requirements: Optional[Dict[str, bool]] = None,
|
capability_requirements: Optional[Dict[str, bool]] = None,
|
||||||
) -> List[ProviderCandidate]:
|
) -> List[ProviderCandidate]:
|
||||||
@@ -920,7 +914,6 @@ class CacheAwareScheduler:
|
|||||||
affinity_key: 亲和性标识符(通常为API Key ID)
|
affinity_key: 亲和性标识符(通常为API Key ID)
|
||||||
resolved_model_name: 解析后的 GlobalModel.name(用于 Key.allowed_models 校验)
|
resolved_model_name: 解析后的 GlobalModel.name(用于 Key.allowed_models 校验)
|
||||||
max_candidates: 最大候选数
|
max_candidates: 最大候选数
|
||||||
allowed_endpoints: 允许的 Endpoint ID 集合(None 表示不限制)
|
|
||||||
is_stream: 是否是流式请求,如果为 True 则过滤不支持流式的 Provider
|
is_stream: 是否是流式请求,如果为 True 则过滤不支持流式的 Provider
|
||||||
capability_requirements: 能力需求(可选)
|
capability_requirements: 能力需求(可选)
|
||||||
|
|
||||||
@@ -949,13 +942,6 @@ class CacheAwareScheduler:
|
|||||||
if not endpoint.is_active or endpoint_format_str != target_format.value:
|
if not endpoint.is_active or endpoint_format_str != target_format.value:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 检查 Endpoint 是否在允许列表中
|
|
||||||
if allowed_endpoints is not None and endpoint.id not in allowed_endpoints:
|
|
||||||
logger.debug(
|
|
||||||
f"Endpoint {endpoint.id[:8]}... 不在用户/API Key 的允许列表中,跳过"
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 获取活跃的 Key 并按 internal_priority + 负载均衡排序
|
# 获取活跃的 Key 并按 internal_priority + 负载均衡排序
|
||||||
active_keys = [key for key in endpoint.api_keys if key.is_active]
|
active_keys = [key for key in endpoint.api_keys if key.is_active]
|
||||||
# 检查是否所有 Key 都是 TTL=0(轮换模式)
|
# 检查是否所有 Key 都是 TTL=0(轮换模式)
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ class StatsAggregatorService:
|
|||||||
or 0
|
or 0
|
||||||
)
|
)
|
||||||
unique_providers = (
|
unique_providers = (
|
||||||
db.query(func.count(func.distinct(Usage.provider)))
|
db.query(func.count(func.distinct(Usage.provider_name)))
|
||||||
.filter(and_(Usage.created_at >= day_start, Usage.created_at < day_end))
|
.filter(and_(Usage.created_at >= day_start, Usage.created_at < day_end))
|
||||||
.scalar()
|
.scalar()
|
||||||
or 0
|
or 0
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ class UsageService:
|
|||||||
"user_id": user.id if user else None,
|
"user_id": user.id if user else None,
|
||||||
"api_key_id": api_key.id if api_key else None,
|
"api_key_id": api_key.id if api_key else None,
|
||||||
"request_id": request_id,
|
"request_id": request_id,
|
||||||
"provider": provider,
|
"provider_name": provider,
|
||||||
"model": model,
|
"model": model,
|
||||||
"target_model": target_model,
|
"target_model": target_model,
|
||||||
"provider_id": provider_id,
|
"provider_id": provider_id,
|
||||||
@@ -479,7 +479,7 @@ class UsageService:
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""更新已存在的 Usage 记录(内部方法)"""
|
"""更新已存在的 Usage 记录(内部方法)"""
|
||||||
# 更新关键字段
|
# 更新关键字段
|
||||||
existing_usage.provider = usage_params["provider"]
|
existing_usage.provider_name = usage_params["provider_name"]
|
||||||
existing_usage.status = usage_params["status"]
|
existing_usage.status = usage_params["status"]
|
||||||
existing_usage.status_code = usage_params["status_code"]
|
existing_usage.status_code = usage_params["status_code"]
|
||||||
existing_usage.error_message = usage_params["error_message"]
|
existing_usage.error_message = usage_params["error_message"]
|
||||||
@@ -1092,7 +1092,7 @@ class UsageService:
|
|||||||
# 汇总查询
|
# 汇总查询
|
||||||
summary = db.query(
|
summary = db.query(
|
||||||
date_func.label("period"),
|
date_func.label("period"),
|
||||||
Usage.provider,
|
Usage.provider_name,
|
||||||
Usage.model,
|
Usage.model,
|
||||||
func.count(Usage.id).label("requests"),
|
func.count(Usage.id).label("requests"),
|
||||||
func.sum(Usage.input_tokens).label("input_tokens"),
|
func.sum(Usage.input_tokens).label("input_tokens"),
|
||||||
@@ -1111,12 +1111,12 @@ class UsageService:
|
|||||||
if end_date:
|
if end_date:
|
||||||
summary = summary.filter(Usage.created_at <= end_date)
|
summary = summary.filter(Usage.created_at <= end_date)
|
||||||
|
|
||||||
summary = summary.group_by(date_func, Usage.provider, Usage.model).all()
|
summary = summary.group_by(date_func, Usage.provider_name, Usage.model).all()
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"period": row.period,
|
"period": row.period,
|
||||||
"provider": row.provider,
|
"provider": row.provider_name,
|
||||||
"model": row.model,
|
"model": row.model,
|
||||||
"requests": row.requests,
|
"requests": row.requests,
|
||||||
"input_tokens": row.input_tokens,
|
"input_tokens": row.input_tokens,
|
||||||
@@ -1445,7 +1445,7 @@ class UsageService:
|
|||||||
user_id=user.id if user else None,
|
user_id=user.id if user else None,
|
||||||
api_key_id=api_key.id if api_key else None,
|
api_key_id=api_key.id if api_key else None,
|
||||||
request_id=request_id,
|
request_id=request_id,
|
||||||
provider="pending", # 尚未确定 provider
|
provider_name="pending", # 尚未确定 provider
|
||||||
model=model,
|
model=model,
|
||||||
input_tokens=0,
|
input_tokens=0,
|
||||||
output_tokens=0,
|
output_tokens=0,
|
||||||
@@ -1508,12 +1508,12 @@ class UsageService:
|
|||||||
if error_message:
|
if error_message:
|
||||||
usage.error_message = error_message
|
usage.error_message = error_message
|
||||||
if provider:
|
if provider:
|
||||||
usage.provider = provider
|
usage.provider_name = provider
|
||||||
elif status == "streaming" and usage.provider == "pending":
|
elif status == "streaming" and usage.provider_name == "pending":
|
||||||
# 状态变为 streaming 但 provider 仍为 pending,记录警告
|
# 状态变为 streaming 但 provider_name 仍为 pending,记录警告
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"状态更新为 streaming 但 provider 为空: request_id={request_id}, "
|
f"状态更新为 streaming 但 provider_name 为空: request_id={request_id}, "
|
||||||
f"当前 provider={usage.provider}"
|
f"当前 provider_name={usage.provider_name}"
|
||||||
)
|
)
|
||||||
if target_model:
|
if target_model:
|
||||||
usage.target_model = target_model
|
usage.target_model = target_model
|
||||||
@@ -1679,7 +1679,7 @@ class UsageService:
|
|||||||
from src.models.database import ProviderAPIKey
|
from src.models.database import ProviderAPIKey
|
||||||
|
|
||||||
query = query.add_columns(
|
query = query.add_columns(
|
||||||
Usage.provider,
|
Usage.provider_name,
|
||||||
ProviderAPIKey.name.label("api_key_name"),
|
ProviderAPIKey.name.label("api_key_name"),
|
||||||
).outerjoin(ProviderAPIKey, Usage.provider_api_key_id == ProviderAPIKey.id)
|
).outerjoin(ProviderAPIKey, Usage.provider_api_key_id == ProviderAPIKey.id)
|
||||||
|
|
||||||
@@ -1731,7 +1731,7 @@ class UsageService:
|
|||||||
"first_byte_time_ms": r.first_byte_time_ms, # 首字时间 (TTFB)
|
"first_byte_time_ms": r.first_byte_time_ms, # 首字时间 (TTFB)
|
||||||
}
|
}
|
||||||
if include_admin_fields:
|
if include_admin_fields:
|
||||||
item["provider"] = r.provider
|
item["provider"] = r.provider_name
|
||||||
item["api_key_name"] = r.api_key_name
|
item["api_key_name"] = r.api_key_name
|
||||||
result.append(item)
|
result.append(item)
|
||||||
|
|
||||||
|
|||||||
@@ -182,12 +182,12 @@ class UserService:
|
|||||||
"role",
|
"role",
|
||||||
# 访问限制字段
|
# 访问限制字段
|
||||||
"allowed_providers",
|
"allowed_providers",
|
||||||
"allowed_endpoints",
|
"allowed_api_formats",
|
||||||
"allowed_models",
|
"allowed_models",
|
||||||
]
|
]
|
||||||
|
|
||||||
# 允许设置为 None 的字段(表示无限制)
|
# 允许设置为 None 的字段(表示无限制)
|
||||||
nullable_fields = ["quota_usd", "allowed_providers", "allowed_endpoints", "allowed_models"]
|
nullable_fields = ["quota_usd", "allowed_providers", "allowed_api_formats", "allowed_models"]
|
||||||
|
|
||||||
for field, value in kwargs.items():
|
for field, value in kwargs.items():
|
||||||
if field not in updatable_fields:
|
if field not in updatable_fields:
|
||||||
|
|||||||
Reference in New Issue
Block a user