fix(gateway): normalize Gemini Vertex embedding transport

This commit is contained in:
MMEXA
2026-05-18 15:39:29 +00:00
parent 66f154a251
commit b480f3aaff
24 changed files with 1046 additions and 107 deletions

View File

@@ -2304,7 +2304,7 @@ async fn provider_query_execute_standard_test_candidate(
}
"openai:embedding" | "gemini:embedding" | "jina:embedding" | "doubao:embedding"
| "openai:rerank" | "jina:rerank" => {
let Some(provider_request_body) =
let Some(mut provider_request_body) =
crate::ai_serving::build_standard_request_body_with_model_directives_and_request_headers(
&request_body,
client_api_format,
@@ -2324,6 +2324,18 @@ async fn provider_query_execute_standard_test_candidate(
format!("Provider request body could not be built for {provider_api_format}"),
));
};
if let Err(err) = crate::provider_transport::apply_transport_request_body_semantics(
&mut provider_request_body,
&transport,
normalized_provider_api_format.as_str(),
) {
return Ok(provider_query_skipped_execution_outcome(
provider_request_body,
format!(
"Provider request body is not compatible with transport semantics: {err}"
),
));
}
provider_request_body
}
_ => {
@@ -2404,7 +2416,7 @@ async fn provider_query_execute_standard_test_candidate(
*synthetic_request.headers_mut() = incoming_request_headers;
let (parts, _) = synthetic_request.into_parts();
let request_url = crate::provider_transport::build_transport_request_url(
let request_url = crate::provider_transport::build_transport_request_url_for_request_body(
&transport,
crate::provider_transport::TransportRequestUrlParams {
provider_api_format,
@@ -2413,6 +2425,7 @@ async fn provider_query_execute_standard_test_candidate(
request_query: parts.uri.query(),
kiro_api_region: None,
},
Some(&provider_request_body),
);
let Some(request_url) = request_url else {
return Ok(provider_query_skipped_execution_outcome(

View File

@@ -62,10 +62,10 @@ pub(super) fn provider_query_standard_test_unsupported_reason(
api_format,
)
}
"gemini:generate_content"
if crate::provider_transport::is_vertex_api_key_transport_context(transport) =>
"gemini:generate_content" | "gemini:embedding"
if crate::provider_transport::is_vertex_transport_context(transport) =>
{
aether_provider_transport::vertex::local_vertex_api_key_gemini_transport_unsupported_reason_with_network(
aether_provider_transport::vertex::local_vertex_gemini_transport_unsupported_reason_with_network(
transport,
)
}

View File

@@ -7,11 +7,29 @@ pub(super) fn provider_query_test_attempt_payload(
candidate: &ProviderQueryTestCandidate,
execution: &ProviderQueryExecutionOutcome,
) -> Value {
let endpoint_route = provider_query_endpoint_route_payload(candidate, execution);
let endpoint_product = endpoint_route
.get("product")
.cloned()
.unwrap_or(Value::Null);
let endpoint_variant = endpoint_route
.get("variant")
.cloned()
.unwrap_or(Value::Null);
let endpoint_action = endpoint_route.get("action").cloned().unwrap_or(Value::Null);
let endpoint_batch_strategy = endpoint_route
.get("batch_strategy")
.cloned()
.unwrap_or(Value::Null);
json!({
"candidate_index": candidate_index,
"retry_index": 0,
"endpoint_api_format": candidate.endpoint.api_format,
"endpoint_base_url": candidate.endpoint.base_url,
"endpoint_product": endpoint_product,
"endpoint_variant": endpoint_variant,
"endpoint_action": endpoint_action,
"endpoint_batch_strategy": endpoint_batch_strategy,
"key_name": provider_query_key_display_name(&candidate.key),
"key_id": candidate.key.id,
"auth_type": candidate.key.auth_type,
@@ -29,6 +47,129 @@ pub(super) fn provider_query_test_attempt_payload(
})
}
fn provider_query_endpoint_route_payload(
candidate: &ProviderQueryTestCandidate,
execution: &ProviderQueryExecutionOutcome,
) -> Value {
let api_format = aether_ai_formats::normalize_api_format_alias(&candidate.endpoint.api_format);
let request_url = execution.request_url.to_ascii_lowercase();
let base_url = candidate.endpoint.base_url.to_ascii_lowercase();
let is_vertex = request_url.contains("aiplatform.googleapis.com")
|| base_url.contains("aiplatform.googleapis.com");
let is_gemini_api = request_url.contains("generativelanguage.googleapis.com")
|| base_url.contains("generativelanguage.googleapis.com");
let is_openai_compat =
request_url.contains("/endpoints/openapi") || request_url.contains("/openai/");
let is_batch = execution
.request_body
.get("requests")
.and_then(Value::as_array)
.is_some_and(|items| !items.is_empty());
let vertex_instance_count = execution
.request_body
.get("instances")
.and_then(Value::as_array)
.map(Vec::len)
.unwrap_or(0);
let (product, variant, action, batch_strategy) = match api_format.as_str() {
"gemini:embedding" if is_vertex => (
"Vertex AI",
"vertex_native",
"predict",
if vertex_instance_count > 1 {
"predict_instances"
} else {
"single_instance"
},
),
"gemini:embedding" if is_gemini_api => (
"Gemini API",
"gemini_native",
if is_batch {
"batchEmbedContents"
} else {
"embedContent"
},
if is_batch {
"native_batch"
} else {
"single_native"
},
),
"gemini:embedding" => (
"Gemini native",
"gemini_native",
if is_batch {
"batchEmbedContents"
} else {
"embedContent"
},
if is_batch {
"native_batch"
} else {
"single_native"
},
),
"gemini:generate_content" if is_vertex => {
("Vertex AI", "vertex_native", "generateContent", "")
}
"gemini:generate_content" if is_gemini_api => {
("Gemini API", "gemini_native", "generateContent", "")
}
"gemini:generate_content" => ("Gemini native", "gemini_native", "generateContent", ""),
"openai:embedding" if is_vertex && is_openai_compat => (
"Vertex AI OpenAI-compatible",
"openai_compatible",
"embeddings",
"openai_batch",
),
"openai:embedding" if is_gemini_api && is_openai_compat => (
"Gemini API OpenAI-compatible",
"openai_compatible",
"embeddings",
"openai_batch",
),
"openai:embedding" => (
"OpenAI-compatible",
"openai_compatible",
"embeddings",
"openai_batch",
),
"openai:chat" if is_vertex && is_openai_compat => (
"Vertex AI OpenAI-compatible",
"openai_compatible",
"chat/completions",
"",
),
"openai:chat" if is_gemini_api && is_openai_compat => (
"Gemini API OpenAI-compatible",
"openai_compatible",
"chat/completions",
"",
),
"openai:chat" => (
"OpenAI-compatible",
"openai_compatible",
"chat/completions",
"",
),
_ => (
"Provider endpoint",
"provider_native",
"provider_request",
"",
),
};
json!({
"product": product,
"variant": variant,
"action": action,
"batch_strategy": batch_strategy,
})
}
pub(super) fn provider_query_candidate_summary_payload(
total_candidates: usize,
total_attempts: usize,