Normalize canonical API formats

This commit is contained in:
fawney19
2026-04-29 10:20:41 +08:00
parent 02ad67fe33
commit 07a319259b
171 changed files with 2460 additions and 1962 deletions
@@ -1,3 +1,5 @@
use std::collections::BTreeSet;
pub(crate) fn normalize_provider_type_input(value: &str) -> Result<String, String> {
let normalized = value.trim().to_ascii_lowercase();
match normalized.as_str() {
@@ -10,6 +12,36 @@ pub(crate) fn normalize_provider_type_input(value: &str) -> Result<String, Strin
}
}
pub(crate) fn normalize_api_format_list(values: Vec<String>) -> Vec<String> {
let mut seen = BTreeSet::new();
let mut normalized = Vec::new();
for value in values {
let canonical = crate::ai_pipeline::normalize_api_format_alias(&value);
if seen.insert(canonical.clone()) {
normalized.push(canonical);
}
}
normalized
}
pub(crate) fn normalize_api_format_json_object_keys(
value: Option<serde_json::Value>,
field_name: &str,
) -> Result<Option<serde_json::Value>, String> {
let Some(value) = normalize_json_like_object(value, field_name)? else {
return Ok(None);
};
let serde_json::Value::Object(map) = value else {
return Ok(Some(value));
};
let mut normalized = serde_json::Map::new();
for (key, value) in map {
let canonical = crate::ai_pipeline::normalize_api_format_alias(&key);
normalized.insert(canonical, value);
}
Ok(Some(serde_json::Value::Object(normalized)))
}
pub(crate) fn normalize_auth_type(value: Option<&str>) -> Result<String, String> {
let auth_type = value.unwrap_or("api_key").trim().to_ascii_lowercase();
match auth_type.as_str() {
@@ -42,8 +74,8 @@ pub(crate) fn validate_vertex_api_formats(
}
let allowed = match auth_type {
"api_key" => &["gemini:chat"][..],
"service_account" | "vertex_ai" => &["claude:chat", "gemini:chat"][..],
"api_key" => &["gemini:generate_content"][..],
"service_account" | "vertex_ai" => &["claude:messages", "gemini:generate_content"][..],
_ => return Ok(()),
};
let invalid = api_formats
@@ -61,9 +93,26 @@ pub(crate) fn validate_vertex_api_formats(
))
}
fn normalize_json_like_object(
value: Option<serde_json::Value>,
field_name: &str,
) -> Result<Option<serde_json::Value>, String> {
let Some(value) = value else {
return Ok(None);
};
match value {
serde_json::Value::Null => Ok(None),
serde_json::Value::Object(map) => Ok(Some(serde_json::Value::Object(map))),
_ => Err(format!("{field_name} 必须是 JSON 对象")),
}
}
#[cfg(test)]
mod tests {
use super::{normalize_auth_type, normalize_pool_advanced_config};
use super::{
normalize_api_format_json_object_keys, normalize_api_format_list, normalize_auth_type,
normalize_pool_advanced_config, validate_vertex_api_formats,
};
use serde_json::json;
#[test]
@@ -93,4 +142,60 @@ mod tests {
"bearer"
);
}
#[test]
fn normalize_api_format_list_dedupes_canonical_formats() {
assert_eq!(
normalize_api_format_list(vec![
"claude:messages".to_string(),
"claude:messages".to_string(),
"gemini:generate_content".to_string(),
"openai:image".to_string(),
]),
vec![
"claude:messages".to_string(),
"gemini:generate_content".to_string(),
"openai:image".to_string(),
]
);
}
#[test]
fn normalize_api_format_json_object_keys_keeps_canonical_keys() {
assert_eq!(
normalize_api_format_json_object_keys(
Some(json!({
"claude:messages": 2,
"gemini:generate_content": 3,
"openai:video": 4
})),
"rate_multipliers",
)
.expect("object should normalize"),
Some(json!({
"claude:messages": 2,
"gemini:generate_content": 3,
"openai:video": 4
}))
);
}
#[test]
fn validate_vertex_api_formats_uses_canonical_message_formats() {
assert!(validate_vertex_api_formats(
"vertex_ai",
"service_account",
&[
"claude:messages".to_string(),
"gemini:generate_content".to_string()
],
)
.is_ok());
assert!(validate_vertex_api_formats(
"vertex_ai",
"service_account",
&["claude:chat".to_string()],
)
.is_err());
}
}