Allow request body model editing

This commit is contained in:
fawney19
2026-05-06 20:35:49 +08:00
parent 4bd49d0d7a
commit 7b11d43466
3 changed files with 79 additions and 12 deletions

View File

@@ -247,7 +247,9 @@ fn provider_query_extract_request_headers(payload: &Value) -> HeaderMap {
fn provider_query_build_test_request_body(payload: &Value, model: &str) -> Value { fn provider_query_build_test_request_body(payload: &Value, model: &str) -> Value {
if let Some(mut body) = provider_query_extract_request_body(payload) { if let Some(mut body) = provider_query_extract_request_body(payload) {
if let Some(object) = body.as_object_mut() { if let Some(object) = body.as_object_mut() {
object.insert("model".to_string(), Value::String(model.to_string())); object
.entry("model".to_string())
.or_insert_with(|| Value::String(model.to_string()));
} }
return body; return body;
} }
@@ -265,6 +267,15 @@ fn provider_query_build_test_request_body(payload: &Value, model: &str) -> Value
}) })
} }
fn provider_query_request_body_model<'a>(request_body: &'a Value, fallback: &'a str) -> &'a str {
request_body
.get("model")
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or(fallback)
}
fn provider_query_select_kiro_endpoint<'a>( fn provider_query_select_kiro_endpoint<'a>(
endpoints: &'a [StoredProviderCatalogEndpoint], endpoints: &'a [StoredProviderCatalogEndpoint],
endpoint_id: Option<&str>, endpoint_id: Option<&str>,
@@ -781,9 +792,11 @@ async fn provider_query_execute_kiro_test_candidate(
}; };
let request_body = provider_query_build_test_request_body(payload, &candidate.effective_model); let request_body = provider_query_build_test_request_body(payload, &candidate.effective_model);
let request_model =
provider_query_request_body_model(&request_body, &candidate.effective_model);
let provider_request_body = match build_kiro_provider_request_body( let provider_request_body = match build_kiro_provider_request_body(
&request_body, &request_body,
&candidate.effective_model, request_model,
&kiro_auth.auth_config, &kiro_auth.auth_config,
transport.endpoint.body_rules.as_ref(), transport.endpoint.body_rules.as_ref(),
) { ) {
@@ -797,7 +810,7 @@ async fn provider_query_execute_kiro_test_candidate(
latency_ms: None, latency_ms: None,
request_url: String::new(), request_url: String::new(),
request_headers: BTreeMap::new(), request_headers: BTreeMap::new(),
request_body, request_body: request_body.clone(),
response_headers: BTreeMap::new(), response_headers: BTreeMap::new(),
response_body: None, response_body: None,
}); });
@@ -845,7 +858,7 @@ async fn provider_query_execute_kiro_test_candidate(
stream: true, stream: true,
client_api_format: candidate.endpoint.api_format.clone(), client_api_format: candidate.endpoint.api_format.clone(),
provider_api_format: candidate.endpoint.api_format.clone(), provider_api_format: candidate.endpoint.api_format.clone(),
model_name: Some(candidate.effective_model.clone()), model_name: Some(request_model.to_string()),
proxy: state proxy: state
.resolve_transport_proxy_snapshot_with_tunnel_affinity(&transport) .resolve_transport_proxy_snapshot_with_tunnel_affinity(&transport)
.await, .await,
@@ -862,7 +875,7 @@ async fn provider_query_execute_kiro_test_candidate(
trace_id, trace_id,
requested_model, requested_model,
candidate.endpoint.api_format.as_str(), candidate.endpoint.api_format.as_str(),
candidate.effective_model.as_str(), request_model,
&request_body, &request_body,
&result, &result,
) )
@@ -942,6 +955,8 @@ async fn provider_query_execute_standard_test_candidate(
if let Some(object) = request_body.as_object_mut() { if let Some(object) = request_body.as_object_mut() {
object.insert("stream".to_string(), Value::Bool(false)); object.insert("stream".to_string(), Value::Bool(false));
} }
let request_model =
provider_query_request_body_model(&request_body, &candidate.effective_model);
let provider_api_format = candidate.endpoint.api_format.as_str(); let provider_api_format = candidate.endpoint.api_format.as_str();
let normalized_provider_api_format = let normalized_provider_api_format =
@@ -951,7 +966,7 @@ async fn provider_query_execute_standard_test_candidate(
let Some(mut provider_request_body) = let Some(mut provider_request_body) =
crate::ai_serving::build_local_openai_chat_request_body( crate::ai_serving::build_local_openai_chat_request_body(
&request_body, &request_body,
&candidate.effective_model, request_model,
false, false,
) )
else { else {
@@ -976,7 +991,7 @@ async fn provider_query_execute_standard_test_candidate(
let Some(mut provider_request_body) = let Some(mut provider_request_body) =
crate::ai_serving::build_cross_format_openai_chat_request_body( crate::ai_serving::build_cross_format_openai_chat_request_body(
&request_body, &request_body,
&candidate.effective_model, request_model,
normalized_provider_api_format.as_str(), normalized_provider_api_format.as_str(),
false, false,
) )
@@ -1002,7 +1017,7 @@ async fn provider_query_execute_standard_test_candidate(
let Some(mut provider_request_body) = let Some(mut provider_request_body) =
crate::ai_serving::build_cross_format_openai_chat_request_body( crate::ai_serving::build_cross_format_openai_chat_request_body(
&request_body, &request_body,
&candidate.effective_model, request_model,
normalized_provider_api_format.as_str(), normalized_provider_api_format.as_str(),
false, false,
) )
@@ -1096,7 +1111,7 @@ async fn provider_query_execute_standard_test_candidate(
&transport, &transport,
crate::provider_transport::TransportRequestUrlParams { crate::provider_transport::TransportRequestUrlParams {
provider_api_format, provider_api_format,
mapped_model: Some(candidate.effective_model.as_str()), mapped_model: Some(request_model),
upstream_is_stream: false, upstream_is_stream: false,
request_query: parts.uri.query(), request_query: parts.uri.query(),
kiro_api_region: None, kiro_api_region: None,
@@ -1210,7 +1225,7 @@ async fn provider_query_execute_standard_test_candidate(
stream: false, stream: false,
client_api_format: "openai:chat".to_string(), client_api_format: "openai:chat".to_string(),
provider_api_format: candidate.endpoint.api_format.clone(), provider_api_format: candidate.endpoint.api_format.clone(),
model_name: Some(candidate.effective_model.clone()), model_name: Some(request_model.to_string()),
proxy: state proxy: state
.resolve_transport_proxy_snapshot_with_tunnel_affinity(&transport) .resolve_transport_proxy_snapshot_with_tunnel_affinity(&transport)
.await, .await,
@@ -1935,3 +1950,56 @@ pub(crate) fn build_admin_provider_query_test_model_failover_response(
})) }))
.into_response() .into_response()
} }
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn provider_query_test_request_body_preserves_custom_model() {
let payload = json!({
"request_body": {
"model": "custom-upstream-model",
"messages": []
}
});
let body = provider_query_build_test_request_body(&payload, "fallback-model");
assert_eq!(body["model"], json!("custom-upstream-model"));
}
#[test]
fn provider_query_test_request_body_defaults_missing_model() {
let payload = json!({
"request_body": {
"messages": []
}
});
let body = provider_query_build_test_request_body(&payload, "fallback-model");
assert_eq!(body["model"], json!("fallback-model"));
}
#[test]
fn provider_query_request_body_model_uses_non_empty_string_only() {
let custom = json!({ "model": " custom-model " });
let blank = json!({ "model": " " });
let non_string = json!({ "model": 123 });
assert_eq!(
provider_query_request_body_model(&custom, "fallback-model"),
"custom-model"
);
assert_eq!(
provider_query_request_body_model(&blank, "fallback-model"),
"fallback-model"
);
assert_eq!(
provider_query_request_body_model(&non_string, "fallback-model"),
"fallback-model"
);
}
}

View File

@@ -1422,7 +1422,6 @@ const RESPONSE_HEADER_RULES_CAMEL_CONFIG_KEY = 'responseHeaderRules'
// 系统保留的 body 字段名(不允许用户设置) // 系统保留的 body 字段名(不允许用户设置)
const RESERVED_BODY_FIELDS = new Set([ const RESERVED_BODY_FIELDS = new Set([
'model',
'stream', 'stream',
]) ])

View File

@@ -148,7 +148,7 @@
{{ requestBodyError }} {{ requestBodyError }}
</div> </div>
<div class="rounded-md border border-border/60 bg-muted/20 px-3 py-2 text-[11px] text-muted-foreground"> <div class="rounded-md border border-border/60 bg-muted/20 px-3 py-2 text-[11px] text-muted-foreground">
会强制使用当前测试模型这里编辑的是测试基础请求体实际发送时会按端点格式转换并应用规则 请求体中的 model 会按当前编辑内容发送未填写时使用当前测试模型实际发送时会按端点格式转换并应用规则
</div> </div>
</div> </div>
</div> </div>