Unverified Commit 7043707e authored by Graham King's avatar Graham King Committed by GitHub
Browse files

feat(frontend): Well-known prefix for validation errors (#4984)


Signed-off-by: default avatarGraham King <grahamk@nvidia.com>
parent 54dcf3ad
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
use std::{ use std::{
collections::HashSet, collections::HashSet,
fmt::Display,
sync::Arc, sync::Arc,
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
...@@ -59,6 +60,8 @@ pub const DYNAMO_REQUEST_ID_HEADER: &str = "x-dynamo-request-id"; ...@@ -59,6 +60,8 @@ pub const DYNAMO_REQUEST_ID_HEADER: &str = "x-dynamo-request-id";
/// Dynamo Annotation for the request ID /// Dynamo Annotation for the request ID
pub const ANNOTATION_REQUEST_ID: &str = "request_id"; pub const ANNOTATION_REQUEST_ID: &str = "request_id";
const VALIDATION_PREFIX: &str = "Validation: ";
// Default axum max body limit without configuring is 2MB: https://docs.rs/axum/latest/axum/extract/struct.DefaultBodyLimit.html // Default axum max body limit without configuring is 2MB: https://docs.rs/axum/latest/axum/extract/struct.DefaultBodyLimit.html
/// Default body limit in bytes (45MB) to support 500k+ token payloads. /// Default body limit in bytes (45MB) to support 500k+ token payloads.
/// Can be configured at compile time using the DYN_FRONTEND_BODY_LIMIT_MB environment variable /// Can be configured at compile time using the DYN_FRONTEND_BODY_LIMIT_MB environment variable
...@@ -138,7 +141,7 @@ impl ErrorMessage { ...@@ -138,7 +141,7 @@ impl ErrorMessage {
/// Not Implemented Error /// Not Implemented Error
/// Return this error when the client requests a feature that is not yet implemented. /// Return this error when the client requests a feature that is not yet implemented.
/// This should be used for features that are planned but not available. /// This should be used for features that are planned but not available.
pub fn not_implemented_error(msg: &str) -> ErrorResponse { pub fn not_implemented_error<T: Display>(msg: T) -> ErrorResponse {
tracing::error!("Not Implemented error: {msg}"); tracing::error!("Not Implemented error: {msg}");
let code = StatusCode::NOT_IMPLEMENTED; let code = StatusCode::NOT_IMPLEMENTED;
let error_type = map_error_code_to_error_type(code); let error_type = map_error_code_to_error_type(code);
...@@ -1028,13 +1031,15 @@ pub fn validate_chat_completion_unsupported_fields( ...@@ -1028,13 +1031,15 @@ pub fn validate_chat_completion_unsupported_fields(
if inner.function_call.is_some() { if inner.function_call.is_some() {
return Err(ErrorMessage::not_implemented_error( return Err(ErrorMessage::not_implemented_error(
"`function_call` is deprecated. Please migrate to use `tool_choice` instead.", VALIDATION_PREFIX.to_string()
+ "`function_call` is deprecated. Please migrate to use `tool_choice` instead.",
)); ));
} }
if inner.functions.is_some() { if inner.functions.is_some() {
return Err(ErrorMessage::not_implemented_error( return Err(ErrorMessage::not_implemented_error(
"`functions` is deprecated. Please migrate to use `tools` instead.", VALIDATION_PREFIX.to_string()
+ "`functions` is deprecated. Please migrate to use `tools` instead.",
)); ));
} }
...@@ -1050,8 +1055,8 @@ pub fn validate_chat_completion_required_fields( ...@@ -1050,8 +1055,8 @@ pub fn validate_chat_completion_required_fields(
if inner.messages.is_empty() { if inner.messages.is_empty() {
return Err(ErrorMessage::from_http_error(HttpError { return Err(ErrorMessage::from_http_error(HttpError {
code: 400, code: 400,
message: "The 'messages' field cannot be empty. At least one message is required." message: VALIDATION_PREFIX.to_string()
.to_string(), + "The 'messages' field cannot be empty. At least one message is required.",
})); }));
} }
...@@ -1068,7 +1073,7 @@ pub fn validate_chat_completion_fields_generic( ...@@ -1068,7 +1073,7 @@ pub fn validate_chat_completion_fields_generic(
request.validate().map_err(|e| { request.validate().map_err(|e| {
ErrorMessage::from_http_error(HttpError { ErrorMessage::from_http_error(HttpError {
code: 400, code: 400,
message: e.to_string(), message: VALIDATION_PREFIX.to_string() + &e.to_string(),
}) })
}) })
} }
...@@ -1083,7 +1088,7 @@ pub fn validate_completion_fields_generic( ...@@ -1083,7 +1088,7 @@ pub fn validate_completion_fields_generic(
request.validate().map_err(|e| { request.validate().map_err(|e| {
ErrorMessage::from_http_error(HttpError { ErrorMessage::from_http_error(HttpError {
code: 400, code: 400,
message: e.to_string(), message: VALIDATION_PREFIX.to_string() + &e.to_string(),
}) })
}) })
} }
...@@ -1169,16 +1174,18 @@ async fn responses( ...@@ -1169,16 +1174,18 @@ async fn responses(
let request_id = request.id().to_string(); let request_id = request.id().to_string();
let (request, context) = request.into_parts(); let (request, context) = request.into_parts();
let mut request: NvCreateChatCompletionRequest = request.try_into().map_err(|e| { let mut request: NvCreateChatCompletionRequest =
request.try_into().map_err(|e: anyhow::Error| {
tracing::error!( tracing::error!(
request_id, request_id,
"Failed to convert NvCreateResponse to NvCreateChatCompletionRequest: {:?}", error = %e,
e "Failed to convert NvCreateResponse to NvCreateChatCompletionRequest",
); );
ErrorMessage::not_implemented_error(&format!( ErrorMessage::not_implemented_error(
"Only Input::Text(_) is currently supported: {}", VALIDATION_PREFIX.to_string()
e + "Only Input::Text(_) is currently supported: "
)) + &e.to_string(),
)
})?; })?;
let request = context.map(|mut _req| { let request = context.map(|mut _req| {
...@@ -1259,7 +1266,8 @@ pub fn validate_response_input_is_text_only( ...@@ -1259,7 +1266,8 @@ pub fn validate_response_input_is_text_only(
match &request.inner.input { match &request.inner.input {
dynamo_async_openai::types::responses::Input::Text(_) => None, dynamo_async_openai::types::responses::Input::Text(_) => None,
_ => Some(ErrorMessage::not_implemented_error( _ => Some(ErrorMessage::not_implemented_error(
"Only `Input::Text` is supported. Structured, multimedia, or custom input types are not yet implemented.", VALIDATION_PREFIX.to_string()
+ "Only `Input::Text` is supported. Structured, multimedia, or custom input types are not yet implemented.",
)), )),
} }
} }
...@@ -1273,77 +1281,77 @@ pub fn validate_response_unsupported_fields( ...@@ -1273,77 +1281,77 @@ pub fn validate_response_unsupported_fields(
if inner.background == Some(true) { if inner.background == Some(true) {
return Some(ErrorMessage::not_implemented_error( return Some(ErrorMessage::not_implemented_error(
"`background: true` is not supported.", VALIDATION_PREFIX.to_string() + "`background: true` is not supported.",
)); ));
} }
if inner.include.is_some() { if inner.include.is_some() {
return Some(ErrorMessage::not_implemented_error( return Some(ErrorMessage::not_implemented_error(
"`include` is not supported.", VALIDATION_PREFIX.to_string() + "`include` is not supported.",
)); ));
} }
if inner.instructions.is_some() { if inner.instructions.is_some() {
return Some(ErrorMessage::not_implemented_error( return Some(ErrorMessage::not_implemented_error(
"`instructions` is not supported.", VALIDATION_PREFIX.to_string() + "`instructions` is not supported.",
)); ));
} }
if inner.max_tool_calls.is_some() { if inner.max_tool_calls.is_some() {
return Some(ErrorMessage::not_implemented_error( return Some(ErrorMessage::not_implemented_error(
"`max_tool_calls` is not supported.", VALIDATION_PREFIX.to_string() + "`max_tool_calls` is not supported.",
)); ));
} }
if inner.previous_response_id.is_some() { if inner.previous_response_id.is_some() {
return Some(ErrorMessage::not_implemented_error( return Some(ErrorMessage::not_implemented_error(
"`previous_response_id` is not supported.", VALIDATION_PREFIX.to_string() + "`previous_response_id` is not supported.",
)); ));
} }
if inner.prompt.is_some() { if inner.prompt.is_some() {
return Some(ErrorMessage::not_implemented_error( return Some(ErrorMessage::not_implemented_error(
"`prompt` is not supported.", VALIDATION_PREFIX.to_string() + "`prompt` is not supported.",
)); ));
} }
if inner.reasoning.is_some() { if inner.reasoning.is_some() {
return Some(ErrorMessage::not_implemented_error( return Some(ErrorMessage::not_implemented_error(
"`reasoning` is not supported.", VALIDATION_PREFIX.to_string() + "`reasoning` is not supported.",
)); ));
} }
if inner.service_tier.is_some() { if inner.service_tier.is_some() {
return Some(ErrorMessage::not_implemented_error( return Some(ErrorMessage::not_implemented_error(
"`service_tier` is not supported.", VALIDATION_PREFIX.to_string() + "`service_tier` is not supported.",
)); ));
} }
if inner.store == Some(true) { if inner.store == Some(true) {
return Some(ErrorMessage::not_implemented_error( return Some(ErrorMessage::not_implemented_error(
"`store: true` is not supported.", VALIDATION_PREFIX.to_string() + "`store: true` is not supported.",
)); ));
} }
if inner.stream == Some(true) { if inner.stream == Some(true) {
return Some(ErrorMessage::not_implemented_error( return Some(ErrorMessage::not_implemented_error(
"`stream: true` is not supported.", VALIDATION_PREFIX.to_string() + "`stream: true` is not supported.",
)); ));
} }
if inner.text.is_some() { if inner.text.is_some() {
return Some(ErrorMessage::not_implemented_error( return Some(ErrorMessage::not_implemented_error(
"`text` is not supported.", VALIDATION_PREFIX.to_string() + "`text` is not supported.",
)); ));
} }
if inner.tool_choice.is_some() { if inner.tool_choice.is_some() {
return Some(ErrorMessage::not_implemented_error( return Some(ErrorMessage::not_implemented_error(
"`tool_choice` is not supported.", VALIDATION_PREFIX.to_string() + "`tool_choice` is not supported.",
)); ));
} }
if inner.tools.is_some() { if inner.tools.is_some() {
return Some(ErrorMessage::not_implemented_error( return Some(ErrorMessage::not_implemented_error(
"`tools` is not supported.", VALIDATION_PREFIX.to_string() + "`tools` is not supported.",
)); ));
} }
if inner.truncation.is_some() { if inner.truncation.is_some() {
return Some(ErrorMessage::not_implemented_error( return Some(ErrorMessage::not_implemented_error(
"`truncation` is not supported.", VALIDATION_PREFIX.to_string() + "`truncation` is not supported.",
)); ));
} }
if inner.user.is_some() { if inner.user.is_some() {
return Some(ErrorMessage::not_implemented_error( return Some(ErrorMessage::not_implemented_error(
"`user` is not supported.", VALIDATION_PREFIX.to_string() + "`user` is not supported.",
)); ));
} }
...@@ -1735,7 +1743,9 @@ mod tests { ...@@ -1735,7 +1743,9 @@ mod tests {
assert_eq!(error_response.0, StatusCode::BAD_REQUEST); assert_eq!(error_response.0, StatusCode::BAD_REQUEST);
assert_eq!( assert_eq!(
error_response.1.message, error_response.1.message,
"The 'messages' field cannot be empty. At least one message is required." format!(
"{VALIDATION_PREFIX}The 'messages' field cannot be empty. At least one message is required."
)
); );
} }
} }
...@@ -1802,7 +1812,7 @@ mod tests { ...@@ -1802,7 +1812,7 @@ mod tests {
assert_eq!(error_response.0, StatusCode::BAD_REQUEST); assert_eq!(error_response.0, StatusCode::BAD_REQUEST);
assert_eq!( assert_eq!(
error_response.1.message, error_response.1.message,
"Frequency penalty must be between -2 and 2, got -3" format!("{VALIDATION_PREFIX}Frequency penalty must be between -2 and 2, got -3")
); );
} }
...@@ -1825,7 +1835,7 @@ mod tests { ...@@ -1825,7 +1835,7 @@ mod tests {
assert_eq!(error_response.0, StatusCode::BAD_REQUEST); assert_eq!(error_response.0, StatusCode::BAD_REQUEST);
assert_eq!( assert_eq!(
error_response.1.message, error_response.1.message,
"Presence penalty must be between -2 and 2, got -3" format!("{VALIDATION_PREFIX}Presence penalty must be between -2 and 2, got -3")
); );
} }
...@@ -1848,7 +1858,7 @@ mod tests { ...@@ -1848,7 +1858,7 @@ mod tests {
assert_eq!(error_response.0, StatusCode::BAD_REQUEST); assert_eq!(error_response.0, StatusCode::BAD_REQUEST);
assert_eq!( assert_eq!(
error_response.1.message, error_response.1.message,
"Temperature must be between 0 and 2, got -3" format!("{VALIDATION_PREFIX}Temperature must be between 0 and 2, got -3")
); );
} }
...@@ -1871,7 +1881,7 @@ mod tests { ...@@ -1871,7 +1881,7 @@ mod tests {
assert_eq!(error_response.0, StatusCode::BAD_REQUEST); assert_eq!(error_response.0, StatusCode::BAD_REQUEST);
assert_eq!( assert_eq!(
error_response.1.message, error_response.1.message,
"Top_p must be between 0 and 1, got -3" format!("{VALIDATION_PREFIX}Top_p must be between 0 and 1, got -3")
); );
} }
...@@ -1896,7 +1906,7 @@ mod tests { ...@@ -1896,7 +1906,7 @@ mod tests {
assert_eq!(error_response.0, StatusCode::BAD_REQUEST); assert_eq!(error_response.0, StatusCode::BAD_REQUEST);
assert_eq!( assert_eq!(
error_response.1.message, error_response.1.message,
"Repetition penalty must be between 0 and 2, got -3" format!("{VALIDATION_PREFIX}Repetition penalty must be between 0 and 2, got -3")
); );
} }
...@@ -1919,7 +1929,7 @@ mod tests { ...@@ -1919,7 +1929,7 @@ mod tests {
assert_eq!(error_response.0, StatusCode::BAD_REQUEST); assert_eq!(error_response.0, StatusCode::BAD_REQUEST);
assert_eq!( assert_eq!(
error_response.1.message, error_response.1.message,
"Logprobs must be between 0 and 5, got 6" format!("{VALIDATION_PREFIX}Logprobs must be between 0 and 5, got 6")
); );
} }
} }
...@@ -1980,7 +1990,7 @@ mod tests { ...@@ -1980,7 +1990,7 @@ mod tests {
assert_eq!(error_response.0, StatusCode::BAD_REQUEST); assert_eq!(error_response.0, StatusCode::BAD_REQUEST);
assert_eq!( assert_eq!(
error_response.1.message, error_response.1.message,
"Frequency penalty must be between -2 and 2, got -3" format!("{VALIDATION_PREFIX}Frequency penalty must be between -2 and 2, got -3")
); );
} }
...@@ -2008,7 +2018,7 @@ mod tests { ...@@ -2008,7 +2018,7 @@ mod tests {
assert_eq!(error_response.0, StatusCode::BAD_REQUEST); assert_eq!(error_response.0, StatusCode::BAD_REQUEST);
assert_eq!( assert_eq!(
error_response.1.message, error_response.1.message,
"Presence penalty must be between -2 and 2, got -3" format!("{VALIDATION_PREFIX}Presence penalty must be between -2 and 2, got -3")
); );
} }
...@@ -2036,7 +2046,7 @@ mod tests { ...@@ -2036,7 +2046,7 @@ mod tests {
assert_eq!(error_response.0, StatusCode::BAD_REQUEST); assert_eq!(error_response.0, StatusCode::BAD_REQUEST);
assert_eq!( assert_eq!(
error_response.1.message, error_response.1.message,
"Temperature must be between 0 and 2, got -3" format!("{VALIDATION_PREFIX}Temperature must be between 0 and 2, got -3")
); );
} }
...@@ -2064,7 +2074,7 @@ mod tests { ...@@ -2064,7 +2074,7 @@ mod tests {
assert_eq!(error_response.0, StatusCode::BAD_REQUEST); assert_eq!(error_response.0, StatusCode::BAD_REQUEST);
assert_eq!( assert_eq!(
error_response.1.message, error_response.1.message,
"Top_p must be between 0 and 1, got -3" format!("{VALIDATION_PREFIX}Top_p must be between 0 and 1, got -3")
); );
} }
...@@ -2094,7 +2104,7 @@ mod tests { ...@@ -2094,7 +2104,7 @@ mod tests {
assert_eq!(error_response.0, StatusCode::BAD_REQUEST); assert_eq!(error_response.0, StatusCode::BAD_REQUEST);
assert_eq!( assert_eq!(
error_response.1.message, error_response.1.message,
"Repetition penalty must be between 0 and 2, got -3" format!("{VALIDATION_PREFIX}Repetition penalty must be between 0 and 2, got -3")
); );
} }
...@@ -2122,7 +2132,7 @@ mod tests { ...@@ -2122,7 +2132,7 @@ mod tests {
assert_eq!(error_response.0, StatusCode::BAD_REQUEST); assert_eq!(error_response.0, StatusCode::BAD_REQUEST);
assert_eq!( assert_eq!(
error_response.1.message, error_response.1.message,
"Top_logprobs must be between 0 and 20, got 25" format!("{VALIDATION_PREFIX}Top_logprobs must be between 0 and 20, got 25")
); );
} }
} }
......
...@@ -505,7 +505,7 @@ impl HttpServiceConfigBuilder { ...@@ -505,7 +505,7 @@ impl HttpServiceConfigBuilder {
Ok(next.run(req).await) Ok(next.run(req).await)
} else { } else {
tracing::debug!("{} endpoints are disabled", endpoint_type.as_str()); tracing::debug!("{} endpoints are disabled", endpoint_type.as_str());
Err(axum::http::StatusCode::SERVICE_UNAVAILABLE) Err(axum::http::StatusCode::NOT_FOUND)
} }
} }
}, },
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment