mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
feat: 添加提供商格式转换优先级保持配置
- 新增 Provider.keep_priority_on_conversion 字段,控制格式转换时是否保持优先级 - 新增全局配置 KEEP_PRIORITY_ON_CONVERSION,可全局启用优先级保持 - 调度器根据配置决定是否降级需要格式转换的候选 - 前端 Provider 表单添加"保持优先级"开关 - 调整 HTTP 读写超时默认值为 3600 秒
This commit is contained in:
@@ -0,0 +1,58 @@
|
|||||||
|
"""add_keep_priority_on_conversion_to_providers
|
||||||
|
|
||||||
|
Revision ID: 364680d1bc99
|
||||||
|
Revises: f7c8d9e0a1b2
|
||||||
|
Create Date: 2026-01-28 12:00:00+00:00
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
1. providers 表: 添加 keep_priority_on_conversion 字段
|
||||||
|
- 格式转换时是否保持提供商原优先级
|
||||||
|
- 默认 False:需要格式转换时,候选会被降级到不需要转换的候选之后
|
||||||
|
- 设为 True:即使需要格式转换,也保持原优先级排名
|
||||||
|
"""
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "364680d1bc99"
|
||||||
|
down_revision = "f7c8d9e0a1b2"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def table_exists(table_name: str) -> bool:
|
||||||
|
bind = op.get_bind()
|
||||||
|
inspector = inspect(bind)
|
||||||
|
return table_name in inspector.get_table_names()
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
# === providers 表: 添加格式转换优先级保持配置 ===
|
||||||
|
if table_exists("providers"):
|
||||||
|
if not column_exists("providers", "keep_priority_on_conversion"):
|
||||||
|
op.add_column(
|
||||||
|
"providers",
|
||||||
|
sa.Column(
|
||||||
|
"keep_priority_on_conversion",
|
||||||
|
sa.Boolean(),
|
||||||
|
nullable=False,
|
||||||
|
server_default="false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# === providers 表: 移除格式转换优先级保持配置 ===
|
||||||
|
if table_exists("providers"):
|
||||||
|
if column_exists("providers", "keep_priority_on_conversion"):
|
||||||
|
op.drop_column("providers", "keep_priority_on_conversion")
|
||||||
@@ -309,6 +309,7 @@ export interface ProviderWithEndpointsSummary {
|
|||||||
description?: string
|
description?: string
|
||||||
website?: string
|
website?: string
|
||||||
provider_priority: number
|
provider_priority: number
|
||||||
|
keep_priority_on_conversion: boolean // 格式转换时是否保持优先级
|
||||||
billing_type?: 'monthly_quota' | 'pay_as_you_go' | 'free_tier'
|
billing_type?: 'monthly_quota' | 'pay_as_you_go' | 'free_tier'
|
||||||
monthly_quota_usd?: number
|
monthly_quota_usd?: number
|
||||||
monthly_used_usd?: number
|
monthly_used_usd?: number
|
||||||
|
|||||||
@@ -171,6 +171,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 格式转换配置 -->
|
||||||
|
<div class="space-y-3">
|
||||||
|
<h3 class="text-sm font-medium border-b pb-2">
|
||||||
|
格式转换
|
||||||
|
</h3>
|
||||||
|
<div class="flex items-center justify-between p-3 border rounded-lg bg-muted/50">
|
||||||
|
<div class="space-y-0.5">
|
||||||
|
<span class="text-sm font-medium">保持优先级</span>
|
||||||
|
<p class="text-xs text-muted-foreground">
|
||||||
|
跨格式请求时保持原优先级排名,不降级到格式匹配的提供商之后
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
:model-value="form.keep_priority_on_conversion"
|
||||||
|
@update:model-value="(v: boolean) => form.keep_priority_on_conversion = v"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 代理配置 -->
|
<!-- 代理配置 -->
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
@@ -295,6 +314,7 @@ const form = ref({
|
|||||||
quota_last_reset_at: '', // 周期开始时间
|
quota_last_reset_at: '', // 周期开始时间
|
||||||
quota_expires_at: '',
|
quota_expires_at: '',
|
||||||
provider_priority: 999,
|
provider_priority: 999,
|
||||||
|
keep_priority_on_conversion: false, // 格式转换时是否保持优先级
|
||||||
// 状态配置
|
// 状态配置
|
||||||
is_active: true,
|
is_active: true,
|
||||||
rate_limit: undefined as number | undefined,
|
rate_limit: undefined as number | undefined,
|
||||||
@@ -323,6 +343,7 @@ function resetForm() {
|
|||||||
quota_last_reset_at: '',
|
quota_last_reset_at: '',
|
||||||
quota_expires_at: '',
|
quota_expires_at: '',
|
||||||
provider_priority: 999,
|
provider_priority: 999,
|
||||||
|
keep_priority_on_conversion: false,
|
||||||
is_active: true,
|
is_active: true,
|
||||||
rate_limit: undefined,
|
rate_limit: undefined,
|
||||||
concurrent_limit: undefined,
|
concurrent_limit: undefined,
|
||||||
@@ -356,6 +377,7 @@ function loadProviderData() {
|
|||||||
quota_expires_at: props.provider.quota_expires_at ?
|
quota_expires_at: props.provider.quota_expires_at ?
|
||||||
new Date(props.provider.quota_expires_at).toISOString().slice(0, 16) : '',
|
new Date(props.provider.quota_expires_at).toISOString().slice(0, 16) : '',
|
||||||
provider_priority: props.provider.provider_priority || 999,
|
provider_priority: props.provider.provider_priority || 999,
|
||||||
|
keep_priority_on_conversion: props.provider.keep_priority_on_conversion ?? false,
|
||||||
is_active: props.provider.is_active,
|
is_active: props.provider.is_active,
|
||||||
rate_limit: undefined,
|
rate_limit: undefined,
|
||||||
concurrent_limit: undefined,
|
concurrent_limit: undefined,
|
||||||
@@ -416,6 +438,7 @@ const handleSubmit = async () => {
|
|||||||
quota_last_reset_at: form.value.quota_last_reset_at || undefined,
|
quota_last_reset_at: form.value.quota_last_reset_at || undefined,
|
||||||
quota_expires_at: form.value.quota_expires_at || undefined,
|
quota_expires_at: form.value.quota_expires_at || undefined,
|
||||||
provider_priority: form.value.provider_priority,
|
provider_priority: form.value.provider_priority,
|
||||||
|
keep_priority_on_conversion: form.value.keep_priority_on_conversion,
|
||||||
is_active: form.value.is_active,
|
is_active: form.value.is_active,
|
||||||
// 请求配置
|
// 请求配置
|
||||||
max_retries: form.value.max_retries ?? undefined,
|
max_retries: form.value.max_retries ?? undefined,
|
||||||
|
|||||||
@@ -301,6 +301,7 @@ def _build_provider_summary(db: Session, provider: Provider) -> ProviderWithEndp
|
|||||||
description=provider.description,
|
description=provider.description,
|
||||||
website=provider.website,
|
website=provider.website,
|
||||||
provider_priority=provider.provider_priority,
|
provider_priority=provider.provider_priority,
|
||||||
|
keep_priority_on_conversion=provider.keep_priority_on_conversion,
|
||||||
is_active=provider.is_active,
|
is_active=provider.is_active,
|
||||||
billing_type=provider.billing_type.value if provider.billing_type else None,
|
billing_type=provider.billing_type.value if provider.billing_type else None,
|
||||||
monthly_quota_usd=provider.monthly_quota_usd,
|
monthly_quota_usd=provider.monthly_quota_usd,
|
||||||
|
|||||||
@@ -141,8 +141,8 @@ class Config:
|
|||||||
|
|
||||||
# HTTP 请求超时配置(秒)
|
# HTTP 请求超时配置(秒)
|
||||||
self.http_connect_timeout = float(os.getenv("HTTP_CONNECT_TIMEOUT", "10.0"))
|
self.http_connect_timeout = float(os.getenv("HTTP_CONNECT_TIMEOUT", "10.0"))
|
||||||
self.http_read_timeout = float(os.getenv("HTTP_READ_TIMEOUT", "60.0"))
|
self.http_read_timeout = float(os.getenv("HTTP_READ_TIMEOUT", "3600.0"))
|
||||||
self.http_write_timeout = float(os.getenv("HTTP_WRITE_TIMEOUT", "60.0"))
|
self.http_write_timeout = float(os.getenv("HTTP_WRITE_TIMEOUT", "3600.0"))
|
||||||
self.http_pool_timeout = float(os.getenv("HTTP_POOL_TIMEOUT", "10.0"))
|
self.http_pool_timeout = float(os.getenv("HTTP_POOL_TIMEOUT", "10.0"))
|
||||||
# HTTP_REQUEST_TIMEOUT: 非流式请求整体超时(秒),默认 300 秒
|
# HTTP_REQUEST_TIMEOUT: 非流式请求整体超时(秒),默认 300 秒
|
||||||
self.http_request_timeout = float(os.getenv("HTTP_REQUEST_TIMEOUT", "300.0"))
|
self.http_request_timeout = float(os.getenv("HTTP_REQUEST_TIMEOUT", "300.0"))
|
||||||
@@ -154,6 +154,14 @@ class Config:
|
|||||||
"FORMAT_CONVERSION_ENABLED", "true"
|
"FORMAT_CONVERSION_ENABLED", "true"
|
||||||
).lower() == "true"
|
).lower() == "true"
|
||||||
|
|
||||||
|
# KEEP_PRIORITY_ON_CONVERSION: 格式转换时是否保持提供商原优先级,默认关闭
|
||||||
|
# - false(默认): 需要格式转换的候选整体降级到不需要转换的候选之后
|
||||||
|
# - true: 所有提供商保持原优先级,不因格式转换降级
|
||||||
|
# 注意:即使全局关闭,单个提供商也可以通过 keep_priority_on_conversion 字段保持自己的优先级
|
||||||
|
self.keep_priority_on_conversion = os.getenv(
|
||||||
|
"KEEP_PRIORITY_ON_CONVERSION", "false"
|
||||||
|
).lower() == "true"
|
||||||
|
|
||||||
# HTTP 连接池配置
|
# HTTP 连接池配置
|
||||||
# HTTP_MAX_CONNECTIONS: 最大连接数,影响并发能力
|
# HTTP_MAX_CONNECTIONS: 最大连接数,影响并发能力
|
||||||
# - 每个连接占用一个 socket,过多会耗尽系统资源
|
# - 每个连接占用一个 socket,过多会耗尽系统资源
|
||||||
|
|||||||
@@ -645,6 +645,12 @@ class Provider(Base):
|
|||||||
# 101+: 备用(高成本或限制严格的)
|
# 101+: 备用(高成本或限制严格的)
|
||||||
provider_priority = Column(Integer, default=100)
|
provider_priority = Column(Integer, default=100)
|
||||||
|
|
||||||
|
# 格式转换时是否保持优先级(默认 False)
|
||||||
|
# - False: 需要格式转换时,该提供商的候选会被降级到不需要转换的候选之后
|
||||||
|
# - True: 即使需要格式转换,也保持原优先级排名
|
||||||
|
# 注意:如果全局配置 KEEP_PRIORITY_ON_CONVERSION=true,此字段被忽略(所有提供商都保持优先级)
|
||||||
|
keep_priority_on_conversion = Column(Boolean, default=False, nullable=False)
|
||||||
|
|
||||||
# 状态
|
# 状态
|
||||||
is_active = Column(Boolean, default=True, nullable=False)
|
is_active = Column(Boolean, default=True, nullable=False)
|
||||||
|
|
||||||
|
|||||||
@@ -613,6 +613,10 @@ class ProviderUpdateRequest(BaseModel):
|
|||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
website: Optional[str] = Field(None, max_length=500, description="主站网站")
|
website: Optional[str] = Field(None, max_length=500, description="主站网站")
|
||||||
provider_priority: Optional[int] = Field(None, description="提供商优先级(数字越小越优先)")
|
provider_priority: Optional[int] = Field(None, description="提供商优先级(数字越小越优先)")
|
||||||
|
keep_priority_on_conversion: Optional[bool] = Field(
|
||||||
|
None,
|
||||||
|
description="格式转换时是否保持优先级(True=保持原优先级,False=需要转换时降级)",
|
||||||
|
)
|
||||||
is_active: Optional[bool] = None
|
is_active: Optional[bool] = None
|
||||||
billing_type: Optional[str] = Field(
|
billing_type: Optional[str] = Field(
|
||||||
None, description="计费类型:monthly_quota/pay_as_you_go/free_tier"
|
None, description="计费类型:monthly_quota/pay_as_you_go/free_tier"
|
||||||
@@ -637,6 +641,10 @@ class ProviderWithEndpointsSummary(BaseModel):
|
|||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
website: Optional[str] = None
|
website: Optional[str] = None
|
||||||
provider_priority: int = Field(default=100, description="提供商优先级(数字越小越优先)")
|
provider_priority: int = Field(default=100, description="提供商优先级(数字越小越优先)")
|
||||||
|
keep_priority_on_conversion: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="格式转换时是否保持优先级(True=保持原优先级,False=需要转换时降级)",
|
||||||
|
)
|
||||||
is_active: bool
|
is_active: bool
|
||||||
|
|
||||||
# 计费相关字段
|
# 计费相关字段
|
||||||
|
|||||||
130
src/services/cache/aware_scheduler.py
vendored
130
src/services/cache/aware_scheduler.py
vendored
@@ -1162,9 +1162,22 @@ class CacheAwareScheduler:
|
|||||||
candidate.is_cached = False
|
candidate.is_cached = False
|
||||||
return candidates
|
return candidates
|
||||||
|
|
||||||
# 按是否匹配缓存亲和性分类候选
|
# 判断候选是否应该被降级(用于分组)
|
||||||
cached_candidates: List[ProviderCandidate] = []
|
from src.config.settings import config
|
||||||
other_candidates: List[ProviderCandidate] = []
|
global_keep_priority = config.keep_priority_on_conversion
|
||||||
|
|
||||||
|
def should_demote(c: ProviderCandidate) -> bool:
|
||||||
|
"""判断候选是否应该被降级"""
|
||||||
|
if global_keep_priority:
|
||||||
|
return False # 全局开启时,所有候选都不降级
|
||||||
|
if not c.needs_conversion:
|
||||||
|
return False # exact 候选不降级
|
||||||
|
if getattr(c.provider, "keep_priority_on_conversion", False):
|
||||||
|
return False # 提供商配置了保持优先级
|
||||||
|
return True # 需要降级
|
||||||
|
|
||||||
|
# 按是否匹配缓存亲和性分类候选,同时记录是否降级
|
||||||
|
matched_candidate: Optional[ProviderCandidate] = None
|
||||||
matched = False
|
matched = False
|
||||||
|
|
||||||
for candidate in candidates:
|
for candidate in candidates:
|
||||||
@@ -1178,7 +1191,7 @@ class CacheAwareScheduler:
|
|||||||
and key.id == affinity.key_id
|
and key.id == affinity.key_id
|
||||||
):
|
):
|
||||||
candidate.is_cached = True
|
candidate.is_cached = True
|
||||||
cached_candidates.append(candidate)
|
matched_candidate = candidate
|
||||||
matched = True
|
matched = True
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"检测到缓存亲和性: affinity_key={affinity_key[:8]}..., "
|
f"检测到缓存亲和性: affinity_key={affinity_key[:8]}..., "
|
||||||
@@ -1189,18 +1202,57 @@ class CacheAwareScheduler:
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
candidate.is_cached = False
|
candidate.is_cached = False
|
||||||
other_candidates.append(candidate)
|
|
||||||
|
|
||||||
if not matched:
|
if not matched:
|
||||||
logger.debug(f"API格式 {api_format_str} 的缓存亲和性存在但组合不可用")
|
logger.debug(f"API格式 {api_format_str} 的缓存亲和性存在但组合不可用")
|
||||||
|
return candidates
|
||||||
|
|
||||||
# 重新排序:缓存候选优先
|
# 缓存亲和性命中且该候选可用(未被跳过)时,无条件优先使用
|
||||||
if cached_candidates:
|
# 理由:1) 它之前成功过;2) 它有 prompt cache 优势
|
||||||
result = cached_candidates + other_candidates
|
# 只有当缓存亲和性的候选被跳过(健康度太低/熔断)时,才按 exact 优先排序
|
||||||
logger.debug(f"{len(cached_candidates)} 个缓存组合已提升至优先级")
|
assert matched_candidate is not None # guaranteed by matched=True
|
||||||
|
|
||||||
|
if not matched_candidate.is_skipped:
|
||||||
|
# 缓存命中且健康,无条件提升到最前面
|
||||||
|
other_candidates = [c for c in candidates if c is not matched_candidate]
|
||||||
|
result = [matched_candidate] + other_candidates
|
||||||
|
logger.debug(
|
||||||
|
f"缓存亲和性命中且健康,无条件优先使用 "
|
||||||
|
f"(needs_conversion={matched_candidate.needs_conversion})"
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
return candidates
|
# 缓存命中但被跳过(不健康),按 exact 优先排序
|
||||||
|
# 缓存候选在其所属类别内提升到最前面
|
||||||
|
logger.debug(
|
||||||
|
f"缓存亲和性命中但不健康 (skip_reason={matched_candidate.skip_reason}),"
|
||||||
|
f"按 exact 优先排序"
|
||||||
|
)
|
||||||
|
matched_should_demote = should_demote(matched_candidate)
|
||||||
|
|
||||||
|
# 分组:非降级类 和 降级类
|
||||||
|
keep_priority_candidates: List[ProviderCandidate] = []
|
||||||
|
demote_candidates: List[ProviderCandidate] = []
|
||||||
|
|
||||||
|
for c in candidates:
|
||||||
|
if c is matched_candidate:
|
||||||
|
continue # 先跳过缓存命中的候选
|
||||||
|
if should_demote(c):
|
||||||
|
demote_candidates.append(c)
|
||||||
|
else:
|
||||||
|
keep_priority_candidates.append(c)
|
||||||
|
|
||||||
|
# 将缓存命中的候选插入到其所属类别的最前面
|
||||||
|
if matched_should_demote:
|
||||||
|
# 缓存命中的是降级类,插入到降级类最前面
|
||||||
|
demote_candidates.insert(0, matched_candidate)
|
||||||
|
else:
|
||||||
|
# 缓存命中的是非降级类,插入到非降级类最前面
|
||||||
|
keep_priority_candidates.insert(0, matched_candidate)
|
||||||
|
|
||||||
|
result = keep_priority_candidates + demote_candidates
|
||||||
|
logger.debug(f"缓存组合已提升至其类别内优先级 (demote={matched_should_demote})")
|
||||||
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"检查缓存亲和性失败: {e},继续使用默认排序")
|
logger.warning(f"检查缓存亲和性失败: {e},继续使用默认排序")
|
||||||
@@ -1249,31 +1301,59 @@ class CacheAwareScheduler:
|
|||||||
"""
|
"""
|
||||||
根据优先级模式对候选列表排序(数字越小越优先)
|
根据优先级模式对候选列表排序(数字越小越优先)
|
||||||
|
|
||||||
排序规则:
|
排序规则(受 KEEP_PRIORITY_ON_CONVERSION 配置影响):
|
||||||
1. exact 候选(needs_conversion=False)优先于 convertible 候选
|
1. 如果全局配置 keep_priority_on_conversion=True,所有候选保持原优先级
|
||||||
2. 在同一类型内,按优先级模式排序:
|
2. 否则,按 needs_conversion 和 provider.keep_priority_on_conversion 分组:
|
||||||
- provider: 提供商优先模式,按 Provider.provider_priority -> Key.internal_priority 排序
|
- 保持优先级的候选(exact 或 provider.keep_priority_on_conversion=True)按原优先级排序
|
||||||
- global_key: 全局 Key 优先模式,按 Key.global_priority_by_format 排序
|
- 需要降级的候选(convertible 且 provider.keep_priority_on_conversion=False)整体排在后面
|
||||||
|
3. 在同一组内,按优先级模式排序:
|
||||||
|
- provider: 按 Provider.provider_priority -> Key.internal_priority 排序
|
||||||
|
- global_key: 按 Key.global_priority_by_format 排序
|
||||||
"""
|
"""
|
||||||
if not candidates:
|
if not candidates:
|
||||||
return candidates
|
return candidates
|
||||||
|
|
||||||
# 按 needs_conversion 分组:exact 优先
|
from src.config.settings import config
|
||||||
exact_candidates = [c for c in candidates if not c.needs_conversion]
|
|
||||||
convertible_candidates = [c for c in candidates if c.needs_conversion]
|
# 全局配置:如果开启,所有候选保持原优先级
|
||||||
|
global_keep_priority = config.keep_priority_on_conversion
|
||||||
|
|
||||||
|
if global_keep_priority:
|
||||||
|
# 全局开启:不分组,直接按优先级模式排序
|
||||||
|
if self.priority_mode == self.PRIORITY_MODE_GLOBAL_KEY:
|
||||||
|
return self._sort_by_global_priority_with_hash(candidates, affinity_key, api_format)
|
||||||
|
# 提供商优先模式:保持构建时的顺序(已按 provider_priority 排序)
|
||||||
|
return candidates
|
||||||
|
|
||||||
|
# 全局未开启:按是否需要降级分组
|
||||||
|
# - 不需要降级:exact 候选 或 provider.keep_priority_on_conversion=True 的 convertible 候选
|
||||||
|
# - 需要降级:convertible 且 provider.keep_priority_on_conversion=False
|
||||||
|
keep_priority_candidates: List[ProviderCandidate] = []
|
||||||
|
demote_candidates: List[ProviderCandidate] = []
|
||||||
|
|
||||||
|
for c in candidates:
|
||||||
|
if not c.needs_conversion:
|
||||||
|
# exact 候选:不需要降级
|
||||||
|
keep_priority_candidates.append(c)
|
||||||
|
elif getattr(c.provider, "keep_priority_on_conversion", False):
|
||||||
|
# convertible 但提供商配置了保持优先级
|
||||||
|
keep_priority_candidates.append(c)
|
||||||
|
else:
|
||||||
|
# convertible 且未配置保持优先级:降级
|
||||||
|
demote_candidates.append(c)
|
||||||
|
|
||||||
if self.priority_mode == self.PRIORITY_MODE_GLOBAL_KEY:
|
if self.priority_mode == self.PRIORITY_MODE_GLOBAL_KEY:
|
||||||
# 全局 Key 优先模式:分别对两组排序后合并
|
# 全局 Key 优先模式:分别对两组排序后合并
|
||||||
sorted_exact = self._sort_by_global_priority_with_hash(
|
sorted_keep = self._sort_by_global_priority_with_hash(
|
||||||
exact_candidates, affinity_key, api_format
|
keep_priority_candidates, affinity_key, api_format
|
||||||
)
|
)
|
||||||
sorted_convertible = self._sort_by_global_priority_with_hash(
|
sorted_demote = self._sort_by_global_priority_with_hash(
|
||||||
convertible_candidates, affinity_key, api_format
|
demote_candidates, affinity_key, api_format
|
||||||
)
|
)
|
||||||
return sorted_exact + sorted_convertible
|
return sorted_keep + sorted_demote
|
||||||
|
|
||||||
# 提供商优先模式:exact 在前,convertible 在后(各组内部顺序已由构建时保证)
|
# 提供商优先模式:保持优先级的在前,降级的在后(各组内部顺序已由构建时保证)
|
||||||
return exact_candidates + convertible_candidates
|
return keep_priority_candidates + demote_candidates
|
||||||
|
|
||||||
def _sort_by_global_priority_with_hash(
|
def _sort_by_global_priority_with_hash(
|
||||||
self,
|
self,
|
||||||
|
|||||||
Reference in New Issue
Block a user