mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
feat: 候选排序引入 API 格式偏好, 追踪页面展示完整格式转换信息
- 候选排序优先同 kind (chat/cli) 再同 family, 替代原有固定顺序 - 不再隐藏 format_conversion_disabled 候选, 保留完整追踪链路 - DecisionTrace 新增 provider/endpoint/key 格式转换相关字段 - 前端追踪面板新增 Key 支持端点和转换策略展示
This commit is contained in:
@@ -291,13 +291,18 @@ pub fn build_admin_monitoring_trace_request_candidate_payload(
|
||||
"provider_website": item.provider_website,
|
||||
"provider_priority": item.provider_priority,
|
||||
"provider_keep_priority_on_conversion": item.provider_keep_priority_on_conversion,
|
||||
"provider_enable_format_conversion": item.provider_enable_format_conversion,
|
||||
"endpoint_id": candidate.endpoint_id,
|
||||
"endpoint_name": item.endpoint_api_format,
|
||||
"endpoint_api_family": item.endpoint_api_family,
|
||||
"endpoint_kind": item.endpoint_kind,
|
||||
"endpoint_format_acceptance_config": item.endpoint_format_acceptance_config,
|
||||
"key_id": candidate.key_id,
|
||||
"key_name": item.provider_key_name,
|
||||
"key_account_label": serde_json::Value::Null,
|
||||
"key_preview": serde_json::Value::Null,
|
||||
"key_auth_type": item.provider_key_auth_type,
|
||||
"key_api_formats": item.provider_key_api_formats,
|
||||
"key_internal_priority": item.provider_key_internal_priority,
|
||||
"key_global_priority_by_format": item.provider_key_global_priority_by_format,
|
||||
"key_oauth_plan_type": serde_json::Value::Null,
|
||||
|
||||
@@ -69,12 +69,13 @@ pub use crate::conversion::response::{
|
||||
};
|
||||
pub use crate::conversion::{
|
||||
build_core_error_body_for_client_format, is_core_error_finalize_kind,
|
||||
request_candidate_api_formats, request_conversion_direct_auth,
|
||||
request_conversion_enabled_for_transport, request_conversion_kind,
|
||||
request_conversion_requires_enable_flag, request_conversion_transport_supported,
|
||||
request_conversion_transport_unsupported_reason, request_pair_allowed_for_transport,
|
||||
sync_chat_response_conversion_kind, sync_cli_response_conversion_kind, LocalCoreSyncErrorKind,
|
||||
RequestConversionKind, SyncChatResponseConversionKind, SyncCliResponseConversionKind,
|
||||
request_candidate_api_format_preference, request_candidate_api_formats,
|
||||
request_conversion_direct_auth, request_conversion_enabled_for_transport,
|
||||
request_conversion_kind, request_conversion_requires_enable_flag,
|
||||
request_conversion_transport_supported, request_conversion_transport_unsupported_reason,
|
||||
request_pair_allowed_for_transport, sync_chat_response_conversion_kind,
|
||||
sync_cli_response_conversion_kind, LocalCoreSyncErrorKind, RequestConversionKind,
|
||||
SyncChatResponseConversionKind, SyncCliResponseConversionKind,
|
||||
};
|
||||
pub use crate::finalize::common::{
|
||||
build_generated_tool_call_id, build_local_success_background_report,
|
||||
|
||||
@@ -9,10 +9,11 @@ pub use error::{
|
||||
is_core_error_finalize_kind, LocalCoreSyncErrorKind,
|
||||
};
|
||||
pub use registry::{
|
||||
request_candidate_api_formats, request_conversion_direct_auth,
|
||||
request_conversion_enabled_for_transport, request_conversion_kind,
|
||||
request_conversion_requires_enable_flag, request_conversion_transport_supported,
|
||||
request_conversion_transport_unsupported_reason, request_pair_allowed_for_transport,
|
||||
sync_chat_response_conversion_kind, sync_cli_response_conversion_kind, RequestConversionKind,
|
||||
SyncChatResponseConversionKind, SyncCliResponseConversionKind,
|
||||
request_candidate_api_format_preference, request_candidate_api_formats,
|
||||
request_conversion_direct_auth, request_conversion_enabled_for_transport,
|
||||
request_conversion_kind, request_conversion_requires_enable_flag,
|
||||
request_conversion_transport_supported, request_conversion_transport_unsupported_reason,
|
||||
request_pair_allowed_for_transport, sync_chat_response_conversion_kind,
|
||||
sync_cli_response_conversion_kind, RequestConversionKind, SyncChatResponseConversionKind,
|
||||
SyncCliResponseConversionKind,
|
||||
};
|
||||
|
||||
@@ -40,18 +40,57 @@ const NON_COMPACT_STANDARD_CANDIDATE_API_FORMATS: &[&str] = &[
|
||||
"gemini:chat",
|
||||
"gemini:cli",
|
||||
];
|
||||
const STANDARD_API_FAMILY_ORDER: &[&str] = &["openai", "claude", "gemini"];
|
||||
|
||||
pub fn request_candidate_api_format_preference(
|
||||
client_api_format: &str,
|
||||
provider_api_format: &str,
|
||||
) -> Option<(u8, u8)> {
|
||||
let client_api_format = client_api_format.trim().to_ascii_lowercase();
|
||||
let provider_api_format = provider_api_format.trim().to_ascii_lowercase();
|
||||
|
||||
if client_api_format == "openai:compact" {
|
||||
return (provider_api_format == "openai:compact").then_some((0, 0));
|
||||
}
|
||||
|
||||
let (client_family, client_kind) =
|
||||
parse_non_compact_standard_api_format(client_api_format.as_str())?;
|
||||
let (provider_family, provider_kind) =
|
||||
parse_non_compact_standard_api_format(provider_api_format.as_str())?;
|
||||
let preference_bucket = if client_family == provider_family && client_kind == provider_kind {
|
||||
0
|
||||
} else if client_kind == provider_kind {
|
||||
1
|
||||
} else if client_family == provider_family {
|
||||
2
|
||||
} else {
|
||||
3
|
||||
};
|
||||
|
||||
Some((
|
||||
preference_bucket,
|
||||
standard_api_family_priority(provider_family),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn request_candidate_api_formats(
|
||||
client_api_format: &str,
|
||||
_require_streaming: bool,
|
||||
) -> Vec<&'static str> {
|
||||
let client_api_format = client_api_format.trim().to_ascii_lowercase();
|
||||
match client_api_format.as_str() {
|
||||
"openai:chat" | "openai:cli" | "claude:chat" | "claude:cli" | "gemini:chat"
|
||||
| "gemini:cli" => NON_COMPACT_STANDARD_CANDIDATE_API_FORMATS.to_vec(),
|
||||
"openai:compact" => vec!["openai:compact"],
|
||||
_ => Vec::new(),
|
||||
if client_api_format == "openai:compact" {
|
||||
return vec!["openai:compact"];
|
||||
}
|
||||
if parse_non_compact_standard_api_format(client_api_format.as_str()).is_none() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut candidate_api_formats = NON_COMPACT_STANDARD_CANDIDATE_API_FORMATS.to_vec();
|
||||
candidate_api_formats.sort_by_key(|provider_api_format| {
|
||||
request_candidate_api_format_preference(client_api_format.as_str(), provider_api_format)
|
||||
.unwrap_or((u8::MAX, u8::MAX))
|
||||
});
|
||||
candidate_api_formats
|
||||
}
|
||||
|
||||
pub fn request_conversion_kind(
|
||||
@@ -259,6 +298,21 @@ fn is_standard_api_format(api_format: &str) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_non_compact_standard_api_format(api_format: &str) -> Option<(&str, &str)> {
|
||||
let (family, kind) = api_format.split_once(':')?;
|
||||
if !STANDARD_API_FAMILY_ORDER.contains(&family) || !matches!(kind, "chat" | "cli") {
|
||||
return None;
|
||||
}
|
||||
Some((family, kind))
|
||||
}
|
||||
|
||||
fn standard_api_family_priority(family: &str) -> u8 {
|
||||
STANDARD_API_FAMILY_ORDER
|
||||
.iter()
|
||||
.position(|candidate| *candidate == family)
|
||||
.unwrap_or(STANDARD_API_FAMILY_ORDER.len()) as u8
|
||||
}
|
||||
|
||||
fn api_data_format_id(api_format: &str) -> Option<&'static str> {
|
||||
match api_format {
|
||||
"claude:chat" | "claude:cli" => Some("claude"),
|
||||
@@ -315,12 +369,12 @@ fn json_format_list_contains(value: &serde_json::Value, api_format: &str) -> boo
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
request_candidate_api_formats, request_conversion_direct_auth,
|
||||
request_conversion_enabled_for_transport, request_conversion_kind,
|
||||
request_conversion_requires_enable_flag, request_conversion_transport_supported,
|
||||
request_pair_allowed_for_transport, sync_chat_response_conversion_kind,
|
||||
sync_cli_response_conversion_kind, RequestConversionKind, SyncChatResponseConversionKind,
|
||||
SyncCliResponseConversionKind,
|
||||
request_candidate_api_format_preference, request_candidate_api_formats,
|
||||
request_conversion_direct_auth, request_conversion_enabled_for_transport,
|
||||
request_conversion_kind, request_conversion_requires_enable_flag,
|
||||
request_conversion_transport_supported, request_pair_allowed_for_transport,
|
||||
sync_chat_response_conversion_kind, sync_cli_response_conversion_kind,
|
||||
RequestConversionKind, SyncChatResponseConversionKind, SyncCliResponseConversionKind,
|
||||
};
|
||||
use aether_provider_transport::snapshot::{
|
||||
GatewayProviderTransportEndpoint, GatewayProviderTransportKey,
|
||||
@@ -406,33 +460,33 @@ mod tests {
|
||||
request_candidate_api_formats("openai:chat", false),
|
||||
vec![
|
||||
"openai:chat",
|
||||
"openai:cli",
|
||||
"claude:chat",
|
||||
"claude:cli",
|
||||
"gemini:chat",
|
||||
"openai:cli",
|
||||
"claude:cli",
|
||||
"gemini:cli",
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_formats("openai:cli", false),
|
||||
vec![
|
||||
"openai:chat",
|
||||
"openai:cli",
|
||||
"claude:chat",
|
||||
"claude:cli",
|
||||
"gemini:chat",
|
||||
"gemini:cli",
|
||||
"openai:chat",
|
||||
"claude:chat",
|
||||
"gemini:chat",
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_formats("claude:cli", false),
|
||||
vec![
|
||||
"openai:chat",
|
||||
"openai:cli",
|
||||
"claude:chat",
|
||||
"claude:cli",
|
||||
"gemini:chat",
|
||||
"openai:cli",
|
||||
"gemini:cli",
|
||||
"claude:chat",
|
||||
"openai:chat",
|
||||
"gemini:chat",
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -441,6 +495,22 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_candidate_registry_prefers_same_kind_before_same_family_fallbacks() {
|
||||
assert_eq!(
|
||||
request_candidate_api_format_preference("claude:cli", "openai:cli"),
|
||||
Some((1, 0))
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_format_preference("claude:cli", "claude:chat"),
|
||||
Some((2, 1))
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_format_preference("claude:cli", "openai:chat"),
|
||||
Some((3, 0))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_conversion_enable_flag_only_applies_to_real_data_format_conversions() {
|
||||
assert!(!request_conversion_requires_enable_flag(
|
||||
|
||||
@@ -305,11 +305,14 @@ pub struct DecisionTraceCandidate {
|
||||
pub provider_type: Option<String>,
|
||||
pub provider_priority: Option<i32>,
|
||||
pub provider_keep_priority_on_conversion: Option<bool>,
|
||||
pub provider_enable_format_conversion: Option<bool>,
|
||||
pub endpoint_api_format: Option<String>,
|
||||
pub endpoint_api_family: Option<String>,
|
||||
pub endpoint_kind: Option<String>,
|
||||
pub endpoint_format_acceptance_config: Option<serde_json::Value>,
|
||||
pub provider_key_name: Option<String>,
|
||||
pub provider_key_auth_type: Option<String>,
|
||||
pub provider_key_api_formats: Option<serde_json::Value>,
|
||||
pub provider_key_internal_priority: Option<i32>,
|
||||
pub provider_key_global_priority_by_format: Option<serde_json::Value>,
|
||||
pub provider_key_capabilities: Option<serde_json::Value>,
|
||||
@@ -384,13 +387,17 @@ fn enrich_decision_trace_candidate(
|
||||
provider_type: provider.map(|item| item.provider_type.clone()),
|
||||
provider_priority: provider.map(|item| item.provider_priority),
|
||||
provider_keep_priority_on_conversion: provider.map(|item| item.keep_priority_on_conversion),
|
||||
provider_enable_format_conversion: provider.map(|item| item.enable_format_conversion),
|
||||
endpoint_api_format: endpoint.map(|item| item.api_format.clone()),
|
||||
endpoint_api_family: endpoint.and_then(|item| item.api_family.clone()),
|
||||
endpoint_kind: endpoint.and_then(|item| item.endpoint_kind.clone()),
|
||||
endpoint_format_acceptance_config: endpoint
|
||||
.and_then(|item| item.format_acceptance_config.clone()),
|
||||
provider_key_name: provider_key
|
||||
.map(|item| item.name.clone())
|
||||
.or_else(|| candidate.api_key_name.clone()),
|
||||
provider_key_auth_type: provider_key.map(|item| item.auth_type.clone()),
|
||||
provider_key_api_formats: provider_key.and_then(|item| item.api_formats.clone()),
|
||||
provider_key_internal_priority: provider_key.map(|item| item.internal_priority),
|
||||
provider_key_global_priority_by_format: provider_key
|
||||
.and_then(|item| item.global_priority_by_format.clone()),
|
||||
|
||||
@@ -259,11 +259,14 @@ mod tests {
|
||||
provider_type: Some("custom".to_string()),
|
||||
provider_priority: Some(0),
|
||||
provider_keep_priority_on_conversion: Some(false),
|
||||
provider_enable_format_conversion: Some(false),
|
||||
endpoint_api_format: Some("openai:chat".to_string()),
|
||||
endpoint_api_family: Some("openai".to_string()),
|
||||
endpoint_kind: Some("chat".to_string()),
|
||||
endpoint_format_acceptance_config: None,
|
||||
provider_key_name: Some("prod".to_string()),
|
||||
provider_key_auth_type: Some("api_key".to_string()),
|
||||
provider_key_api_formats: None,
|
||||
provider_key_internal_priority: Some(10),
|
||||
provider_key_global_priority_by_format: None,
|
||||
provider_key_capabilities: None,
|
||||
|
||||
Reference in New Issue
Block a user