feat: 实现跨 API 格式自动转换功能

- 新增端点级 format_acceptance_config 配置,控制是否接受跨格式请求
- 重构 EndpointFormDialog 为卡片式布局,支持内联编辑和格式转换开关
- StreamProcessor 实现流式响应的跨格式转换,支持 OpenAI/Claude/Gemini 互转
- CacheAwareScheduler 按端点格式筛选候选,同格式优先于跨格式
- 健康度/熔断按 Provider 端点格式分桶,而非客户端请求格式
- 新增 format_conversion_total 和 format_conversion_duration_seconds 指标
- 新增全局配置 format_conversion_enabled 控制总开关
- Input 组件新增 size="sm" 尺寸选项
This commit is contained in:
fawney19
2026-01-22 01:48:56 +08:00
parent 99388bfa33
commit cc5db20c58
25 changed files with 1818 additions and 547 deletions

View File

@@ -278,7 +278,9 @@ class Usage(Base):
request_id = Column(String(100), unique=True, index=True, nullable=False)
provider_name = Column(String(100), nullable=False) # Provider 名称(非外键)
model = Column(String(100), nullable=False)
target_model = Column(String(100), nullable=True, comment="映射后的目标模型名(若无映射则为空)")
target_model = Column(
String(100), nullable=True, comment="映射后的目标模型名(若无映射则为空)"
)
# Provider 侧追踪信息(记录最终成功的 Provider/Endpoint/Key
provider_id = Column(String(36), ForeignKey("providers.id", ondelete="SET NULL"), nullable=True)
@@ -465,7 +467,9 @@ class LDAPConfig(Base):
user_search_filter = Column(
String(500), default="(uid={username})", nullable=False
) # 用户搜索过滤器
username_attr = Column(String(50), default="uid", nullable=False) # 用户名属性 (uid/sAMAccountName)
username_attr = Column(
String(50), default="uid", nullable=False
) # 用户名属性 (uid/sAMAccountName)
email_attr = Column(String(50), default="mail", nullable=False) # 邮箱属性
display_name_attr = Column(String(50), default="cn", nullable=False) # 显示名称属性
is_enabled = Column(Boolean, default=False, nullable=False) # 是否启用 LDAP 认证
@@ -706,6 +710,14 @@ class ProviderEndpoint(Base):
# 额外配置
config = Column(JSON, nullable=True) # 端点特定配置(不推荐使用,优先使用专用字段)
# 格式转换配置
format_acceptance_config = Column(
JSON,
nullable=True,
default=None,
comment="格式接受策略配置(跨格式转换开关/白黑名单等)",
)
# 代理配置
proxy = Column(JSONB, nullable=True) # 代理配置: {url, username, password}
@@ -1041,8 +1053,7 @@ class Model(Base):
# 获取所有最高优先级的映射
top_priority_mappings = [
mapping for mapping in sorted_mappings
if mapping["priority"] == highest_priority
mapping for mapping in sorted_mappings if mapping["priority"] == highest_priority
]
# 如果有多个相同优先级的映射,通过哈希分散选择
@@ -1119,9 +1130,7 @@ class ProviderAPIKey(Base):
# 示例: {"cache_1h": true, "context_1m": true}
# 自适应 RPM 调整(仅当 rpm_limit = NULL 时生效)
learned_rpm_limit = Column(
Integer, nullable=True
) # 学习到的 RPM 限制(自适应模式下的有效值)
learned_rpm_limit = Column(Integer, nullable=True) # 学习到的 RPM 限制(自适应模式下的有效值)
concurrent_429_count = Column(Integer, default=0, nullable=False) # 因并发导致的429次数
rpm_429_count = Column(Integer, default=0, nullable=False) # 因RPM导致的429次数
last_429_at = Column(DateTime(timezone=True), nullable=True) # 最后429时间
@@ -1132,9 +1141,7 @@ class ProviderAPIKey(Base):
utilization_samples = Column(
JSON, nullable=True
) # 利用率采样窗口 [{"ts": timestamp, "util": 0.8}, ...]
last_probe_increase_at = Column(
DateTime(timezone=True), nullable=True
) # 上次探测性扩容时间
last_probe_increase_at = Column(DateTime(timezone=True), nullable=True) # 上次探测性扩容时间
# 健康度追踪(按 API 格式存储)
# 结构: {"CLAUDE": {"health_score": 1.0, "consecutive_failures": 0, "last_failure_at": null, "request_results_window": []}, ...}
@@ -1568,7 +1575,9 @@ class RequestCandidate(Base):
)
# 状态信息
status = Column(String(20), nullable=False) # 'pending', 'streaming', 'success', 'failed', 'cancelled', 'skipped'
status = Column(
String(20), nullable=False
) # 'pending', 'streaming', 'success', 'failed', 'cancelled', 'skipped'
skip_reason = Column(Text, nullable=True) # 跳过/失败原因
is_cached = Column(Boolean, default=False) # 是否为缓存亲和性候选

View File

@@ -45,6 +45,12 @@ class ProviderEndpointCreate(BaseModel):
# 代理配置
proxy: Optional[ProxyConfig] = Field(default=None, description="代理配置")
# 格式转换配置
format_acceptance_config: Optional[Dict[str, Any]] = Field(
default=None,
description="格式接受策略配置(跨格式转换开关/白黑名单等)",
)
@field_validator("api_format")
@classmethod
def validate_api_format(cls, v: str) -> str:
@@ -85,6 +91,12 @@ class ProviderEndpointUpdate(BaseModel):
config: Optional[Dict[str, Any]] = Field(default=None, description="额外配置")
proxy: Optional[ProxyConfig] = Field(default=None, description="代理配置")
# 格式转换配置
format_acceptance_config: Optional[Dict[str, Any]] = Field(
default=None,
description="格式接受策略配置(跨格式转换开关/白黑名单等)",
)
@field_validator("base_url")
@classmethod
def validate_base_url(cls, v: Optional[str]) -> Optional[str]:
@@ -126,6 +138,12 @@ class ProviderEndpointResponse(BaseModel):
# 代理配置(响应中密码已脱敏)
proxy: Optional[Dict[str, Any]] = Field(default=None, description="代理配置(密码已脱敏)")
# 格式转换配置
format_acceptance_config: Optional[Dict[str, Any]] = Field(
default=None,
description="格式接受策略配置(跨格式转换开关/白黑名单等)",
)
# 统计(从 Keys 聚合)
total_keys: int = Field(default=0, description="总 Key 数量")
active_keys: int = Field(default=0, description="活跃 Key 数量")