Add code field and unify error responses for router (#15028)

This commit is contained in:
fzyzcjy
2025-12-13 13:21:34 +08:00
committed by GitHub
parent 05325db349
commit 9d3b411c56
26 changed files with 354 additions and 155 deletions
+103 -35
View File
@@ -5,88 +5,156 @@ use axum::{
}; };
use serde_json::json; use serde_json::json;
pub fn internal_error(message: impl Into<String>) -> Response { pub fn internal_error(code: impl Into<String>, message: impl Into<String>) -> Response {
create_error(StatusCode::INTERNAL_SERVER_ERROR, "internal_error", message) create_error(StatusCode::INTERNAL_SERVER_ERROR, code, message)
} }
pub fn bad_request(message: impl Into<String>) -> Response { pub fn bad_request(code: impl Into<String>, message: impl Into<String>) -> Response {
create_error(StatusCode::BAD_REQUEST, "invalid_request_error", message) create_error(StatusCode::BAD_REQUEST, code, message)
} }
pub fn not_found(message: impl Into<String>) -> Response { pub fn not_found(code: impl Into<String>, message: impl Into<String>) -> Response {
create_error(StatusCode::NOT_FOUND, "invalid_request_error", message) create_error(StatusCode::NOT_FOUND, code, message)
} }
pub fn service_unavailable(message: impl Into<String>) -> Response { pub fn service_unavailable(code: impl Into<String>, message: impl Into<String>) -> Response {
create_error( create_error(StatusCode::SERVICE_UNAVAILABLE, code, message)
StatusCode::SERVICE_UNAVAILABLE,
"service_unavailable",
message,
)
} }
pub fn failed_dependency(message: impl Into<String>) -> Response { pub fn failed_dependency(code: impl Into<String>, message: impl Into<String>) -> Response {
create_error( create_error(StatusCode::FAILED_DEPENDENCY, code, message)
StatusCode::FAILED_DEPENDENCY,
"external_connector_error",
message,
)
} }
pub fn not_implemented(message: impl Into<String>) -> Response { pub fn not_implemented(code: impl Into<String>, message: impl Into<String>) -> Response {
create_error( create_error(StatusCode::NOT_IMPLEMENTED, code, message)
StatusCode::NOT_IMPLEMENTED,
"not_implemented_error",
message,
)
} }
fn create_error(status_code: StatusCode, error_type: &str, message: impl Into<String>) -> Response { fn create_error(
let msg = message.into(); status: StatusCode,
code: impl Into<String>,
message: impl Into<String>,
) -> Response {
( (
status_code, status,
Json(json!({ Json(json!({
"error": { "error": {
"message": msg, "message": message.into(),
"type": error_type, "type": status_code_to_str(status),
"code": status_code.as_u16() "code": code.into(),
} }
})), })),
) )
.into_response() .into_response()
} }
fn status_code_to_str(status_code: StatusCode) -> &'static str {
match status_code {
// 1xx
StatusCode::CONTINUE => "continue",
StatusCode::SWITCHING_PROTOCOLS => "switching_protocols",
StatusCode::PROCESSING => "processing",
StatusCode::EARLY_HINTS => "early_hints",
// 2xx
StatusCode::OK => "ok",
StatusCode::CREATED => "created",
StatusCode::ACCEPTED => "accepted",
StatusCode::NON_AUTHORITATIVE_INFORMATION => "non_authoritative_information",
StatusCode::NO_CONTENT => "no_content",
StatusCode::RESET_CONTENT => "reset_content",
StatusCode::PARTIAL_CONTENT => "partial_content",
StatusCode::MULTI_STATUS => "multi_status",
StatusCode::ALREADY_REPORTED => "already_reported",
StatusCode::IM_USED => "im_used",
// 3xx
StatusCode::MULTIPLE_CHOICES => "multiple_choices",
StatusCode::MOVED_PERMANENTLY => "moved_permanently",
StatusCode::FOUND => "found",
StatusCode::SEE_OTHER => "see_other",
StatusCode::NOT_MODIFIED => "not_modified",
StatusCode::USE_PROXY => "use_proxy",
StatusCode::TEMPORARY_REDIRECT => "temporary_redirect",
StatusCode::PERMANENT_REDIRECT => "permanent_redirect",
// 4xx
StatusCode::BAD_REQUEST => "bad_request",
StatusCode::UNAUTHORIZED => "unauthorized",
StatusCode::PAYMENT_REQUIRED => "payment_required",
StatusCode::FORBIDDEN => "forbidden",
StatusCode::NOT_FOUND => "not_found",
StatusCode::METHOD_NOT_ALLOWED => "method_not_allowed",
StatusCode::NOT_ACCEPTABLE => "not_acceptable",
StatusCode::PROXY_AUTHENTICATION_REQUIRED => "proxy_authentication_required",
StatusCode::REQUEST_TIMEOUT => "request_timeout",
StatusCode::CONFLICT => "conflict",
StatusCode::GONE => "gone",
StatusCode::LENGTH_REQUIRED => "length_required",
StatusCode::PRECONDITION_FAILED => "precondition_failed",
StatusCode::PAYLOAD_TOO_LARGE => "payload_too_large",
StatusCode::URI_TOO_LONG => "uri_too_long",
StatusCode::UNSUPPORTED_MEDIA_TYPE => "unsupported_media_type",
StatusCode::RANGE_NOT_SATISFIABLE => "range_not_satisfiable",
StatusCode::EXPECTATION_FAILED => "expectation_failed",
StatusCode::IM_A_TEAPOT => "im_a_teapot",
StatusCode::MISDIRECTED_REQUEST => "misdirected_request",
StatusCode::UNPROCESSABLE_ENTITY => "unprocessable_entity",
StatusCode::LOCKED => "locked",
StatusCode::FAILED_DEPENDENCY => "failed_dependency",
StatusCode::UPGRADE_REQUIRED => "upgrade_required",
StatusCode::PRECONDITION_REQUIRED => "precondition_required",
StatusCode::TOO_MANY_REQUESTS => "too_many_requests",
StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE => "request_header_fields_too_large",
StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS => "unavailable_for_legal_reasons",
// 5xx
StatusCode::INTERNAL_SERVER_ERROR => "internal_server_error",
StatusCode::NOT_IMPLEMENTED => "not_implemented",
StatusCode::BAD_GATEWAY => "bad_gateway",
StatusCode::SERVICE_UNAVAILABLE => "service_unavailable",
StatusCode::GATEWAY_TIMEOUT => "gateway_timeout",
StatusCode::HTTP_VERSION_NOT_SUPPORTED => "http_version_not_supported",
StatusCode::VARIANT_ALSO_NEGOTIATES => "variant_also_negotiates",
StatusCode::INSUFFICIENT_STORAGE => "insufficient_storage",
StatusCode::LOOP_DETECTED => "loop_detected",
StatusCode::NOT_EXTENDED => "not_extended",
StatusCode::NETWORK_AUTHENTICATION_REQUIRED => "network_authentication_required",
_ => "unknown_status_code",
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_internal_error_string() { fn test_internal_error_string() {
let response = internal_error("Test error"); let response = internal_error("test_error", "Test error");
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
} }
#[test] #[test]
fn test_internal_error_format() { fn test_internal_error_format() {
let response = internal_error(format!("Error: {}", 42)); let response = internal_error("test_error", format!("Error: {}", 42));
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
} }
#[test] #[test]
fn test_bad_request() { fn test_bad_request() {
let response = bad_request("Invalid input"); let response = bad_request("invalid_input", "Invalid input");
assert_eq!(response.status(), StatusCode::BAD_REQUEST); assert_eq!(response.status(), StatusCode::BAD_REQUEST);
} }
#[test] #[test]
fn test_not_found() { fn test_not_found() {
let response = not_found("Resource not found"); let response = not_found("resource_not_found", "Resource not found");
assert_eq!(response.status(), StatusCode::NOT_FOUND); assert_eq!(response.status(), StatusCode::NOT_FOUND);
} }
#[test] #[test]
fn test_service_unavailable() { fn test_service_unavailable() {
let response = service_unavailable("No workers"); let response = service_unavailable("no_workers", "No workers");
assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE); assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
} }
} }
@@ -58,7 +58,10 @@ pub async fn collect_responses(
}; };
if all_responses.is_empty() { if all_responses.is_empty() {
return Err(error::internal_error("No responses from server")); return Err(error::internal_error(
"no_responses_from_server",
"No responses from server",
));
} }
Ok(all_responses) Ok(all_responses)
@@ -49,6 +49,7 @@ pub async fn ensure_mcp_connection(
"Failed to connect to MCP server" "Failed to connect to MCP server"
); );
return Err(error::failed_dependency( return Err(error::failed_dependency(
"connect_mcp_server_failed",
"Failed to connect to MCP server. Check server_url and authorization.", "Failed to connect to MCP server. Check server_url and authorization.",
)); ));
} }
@@ -24,7 +24,10 @@ impl PipelineStage for ClientAcquisitionStage {
function = "ClientAcquisitionStage::execute", function = "ClientAcquisitionStage::execute",
"Worker selection stage not completed" "Worker selection stage not completed"
); );
error::internal_error("Worker selection not completed") error::internal_error(
"worker_selection_not_completed",
"Worker selection not completed",
)
})?; })?;
let clients = match workers { let clients = match workers {
@@ -43,6 +46,7 @@ impl PipelineStage for ClientAcquisitionStage {
"vLLM backend does not support dual (PD disaggregated) mode" "vLLM backend does not support dual (PD disaggregated) mode"
); );
return Err(error::bad_request( return Err(error::bad_request(
"vllm_pd_mode_not_supported",
"vLLM backend does not support prefill/decode disaggregated mode. \ "vLLM backend does not support prefill/decode disaggregated mode. \
Please use runtime_type: sglang for PD mode, or use a regular (non-PD) worker configuration." Please use runtime_type: sglang for PD mode, or use a regular (non-PD) worker configuration."
)); ));
@@ -23,7 +23,7 @@ impl PipelineStage for DispatchMetadataStage {
function = "DispatchMetadataStage::execute", function = "DispatchMetadataStage::execute",
"Proto request not built" "Proto request not built"
); );
error::internal_error("Proto request not built") error::internal_error("proto_request_not_built", "Proto request not built")
})?; })?;
let request_id = proto_request.request_id().to_string(); let request_id = proto_request.request_id().to_string();
@@ -42,7 +42,7 @@ impl PipelineStage for RequestExecutionStage {
function = "RequestExecutionStage::execute", function = "RequestExecutionStage::execute",
"Proto request not built" "Proto request not built"
); );
error::internal_error("Proto request not built") error::internal_error("proto_request_not_built", "Proto request not built")
})?; })?;
let clients = ctx.state.clients.as_mut().ok_or_else(|| { let clients = ctx.state.clients.as_mut().ok_or_else(|| {
@@ -50,7 +50,10 @@ impl PipelineStage for RequestExecutionStage {
function = "RequestExecutionStage::execute", function = "RequestExecutionStage::execute",
"Client acquisition not completed" "Client acquisition not completed"
); );
error::internal_error("Client acquisition not completed") error::internal_error(
"client_acquisition_not_completed",
"Client acquisition not completed",
)
})?; })?;
// Extract dispatch metadata for tracing span // Extract dispatch metadata for tracing span
@@ -108,7 +111,10 @@ impl RequestExecutionStage {
function = "execute_single", function = "execute_single",
"Expected single client but got dual" "Expected single client but got dual"
); );
error::internal_error("Expected single client but got dual") error::internal_error(
"expected_single_client_got_dual",
"Expected single client but got dual",
)
})?; })?;
let stream = client.generate(proto_request).await.map_err(|e| { let stream = client.generate(proto_request).await.map_err(|e| {
@@ -117,7 +123,10 @@ impl RequestExecutionStage {
error = %e, error = %e,
"Failed to start generation" "Failed to start generation"
); );
error::internal_error(format!("Failed to start generation: {}", e)) error::internal_error(
"start_generation_failed",
format!("Failed to start generation: {}", e),
)
})?; })?;
Ok(ExecutionResult::Single { stream }) Ok(ExecutionResult::Single { stream })
@@ -133,7 +142,10 @@ impl RequestExecutionStage {
function = "execute_dual_dispatch", function = "execute_dual_dispatch",
"Expected dual clients but got single" "Expected dual clients but got single"
); );
error::internal_error("Expected dual clients but got single") error::internal_error(
"expected_dual_clients_got_single",
"Expected dual clients but got single",
)
})?; })?;
let prefill_request = proto_request.clone_inner(); let prefill_request = proto_request.clone_inner();
@@ -153,10 +165,10 @@ impl RequestExecutionStage {
error = %e, error = %e,
"Prefill worker failed to start" "Prefill worker failed to start"
); );
return Err(error::internal_error(format!( return Err(error::internal_error(
"Prefill worker failed to start: {}", "prefill_worker_failed_to_start",
e format!("Prefill worker failed to start: {}", e),
))); ));
} }
}; };
@@ -169,10 +181,10 @@ impl RequestExecutionStage {
error = %e, error = %e,
"Decode worker failed to start" "Decode worker failed to start"
); );
return Err(error::internal_error(format!( return Err(error::internal_error(
"Decode worker failed to start: {}", "decode_worker_failed_to_start",
e format!("Decode worker failed to start: {}", e),
))); ));
} }
}; };
@@ -52,7 +52,10 @@ impl PipelineStage for WorkerSelectionStage {
function = "WorkerSelectionStage::execute", function = "WorkerSelectionStage::execute",
"Preparation stage not completed" "Preparation stage not completed"
); );
error::internal_error("Preparation stage not completed") error::internal_error(
"preparation_stage_not_completed",
"Preparation stage not completed",
)
})?; })?;
// For Harmony, use selection_text produced during Harmony encoding // For Harmony, use selection_text produced during Harmony encoding
@@ -74,10 +77,10 @@ impl PipelineStage for WorkerSelectionStage {
model_id = ?ctx.input.model_id, model_id = ?ctx.input.model_id,
"No available workers for model" "No available workers for model"
); );
return Err(error::service_unavailable(format!( return Err(error::service_unavailable(
"No available workers for model: {:?}", "no_available_workers",
ctx.input.model_id format!("No available workers for model: {:?}", ctx.input.model_id),
))); ));
} }
} }
} }
@@ -91,10 +94,13 @@ impl PipelineStage for WorkerSelectionStage {
model_id = ?ctx.input.model_id, model_id = ?ctx.input.model_id,
"No available PD worker pairs for model" "No available PD worker pairs for model"
); );
return Err(error::service_unavailable(format!( return Err(error::service_unavailable(
"No available PD worker pairs for model: {:?}", "no_available_pd_worker_pairs",
ctx.input.model_id format!(
))); "No available PD worker pairs for model: {:?}",
ctx.input.model_id
),
));
} }
} }
} }
@@ -47,7 +47,10 @@ impl HarmonyResponseProcessor {
// Collect all completed responses (one per choice) // Collect all completed responses (one per choice)
let all_responses = response_collection::collect_responses(execution_result, false).await?; let all_responses = response_collection::collect_responses(execution_result, false).await?;
if all_responses.is_empty() { if all_responses.is_empty() {
return Err(error::internal_error("No responses from server")); return Err(error::internal_error(
"no_responses_from_server",
"No responses from server",
));
} }
// Build choices by parsing output with HarmonyParserAdapter // Build choices by parsing output with HarmonyParserAdapter
@@ -72,7 +75,10 @@ impl HarmonyResponseProcessor {
error = %e, error = %e,
"Failed to create Harmony parser" "Failed to create Harmony parser"
); );
error::internal_error(format!("Failed to create Harmony parser: {}", e)) error::internal_error(
"create_harmony_parser_failed",
format!("Failed to create Harmony parser: {}", e),
)
})?; })?;
// Parse Harmony channels with finish_reason and matched_stop // Parse Harmony channels with finish_reason and matched_stop
@@ -88,7 +94,10 @@ impl HarmonyResponseProcessor {
error = %e, error = %e,
"Harmony parsing failed on complete response" "Harmony parsing failed on complete response"
); );
error::internal_error(format!("Harmony parsing failed: {}", e)) error::internal_error(
"harmony_parsing_failed",
format!("Harmony parsing failed: {}", e),
)
})?; })?;
// Build response message (assistant) // Build response message (assistant)
@@ -187,13 +196,16 @@ impl HarmonyResponseProcessor {
// Collect all completed responses // Collect all completed responses
let all_responses = response_collection::collect_responses(execution_result, false).await?; let all_responses = response_collection::collect_responses(execution_result, false).await?;
if all_responses.is_empty() { if all_responses.is_empty() {
return Err(error::internal_error("No responses from server")); return Err(error::internal_error(
"no_responses_from_server",
"No responses from server",
));
} }
// For Responses API, we only process the first response (n=1) // For Responses API, we only process the first response (n=1)
let complete = all_responses let complete = all_responses
.first() .first()
.ok_or_else(|| error::internal_error("No complete response"))?; .ok_or_else(|| error::internal_error("no_complete_response", "No complete response"))?;
// Parse Harmony channels // Parse Harmony channels
let mut parser = HarmonyParserAdapter::new().map_err(|e| { let mut parser = HarmonyParserAdapter::new().map_err(|e| {
@@ -202,7 +214,10 @@ impl HarmonyResponseProcessor {
error = %e, error = %e,
"Failed to create Harmony parser" "Failed to create Harmony parser"
); );
error::internal_error(format!("Failed to create Harmony parser: {}", e)) error::internal_error(
"create_harmony_parser_failed",
format!("Failed to create Harmony parser: {}", e),
)
})?; })?;
// Convert matched_stop from proto to JSON // Convert matched_stop from proto to JSON
@@ -227,7 +242,10 @@ impl HarmonyResponseProcessor {
error = %e, error = %e,
"Harmony parsing failed on complete response" "Harmony parsing failed on complete response"
); );
error::internal_error(format!("Harmony parsing failed: {}", e)) error::internal_error(
"harmony_parsing_failed",
format!("Harmony parsing failed: {}", e),
)
})?; })?;
// VALIDATION: Check if model incorrectly generated Tool role messages // VALIDATION: Check if model incorrectly generated Tool role messages
@@ -332,10 +332,10 @@ async fn execute_with_mcp_loop(
max_iterations = MAX_TOOL_ITERATIONS, max_iterations = MAX_TOOL_ITERATIONS,
"Maximum tool iterations exceeded" "Maximum tool iterations exceeded"
); );
return Err(error::internal_error(format!( return Err(error::internal_error(
"Maximum tool iterations ({}) exceeded", "tool_iterations_exceeded",
MAX_TOOL_ITERATIONS format!("Maximum tool iterations ({}) exceeded", MAX_TOOL_ITERATIONS),
))); ));
} }
debug!( debug!(
@@ -1176,10 +1176,13 @@ async fn execute_mcp_tools(
error = %e, error = %e,
"Failed to parse tool arguments JSON" "Failed to parse tool arguments JSON"
); );
error::internal_error(format!( error::internal_error(
"Invalid tool arguments JSON for tool '{}': {}", "invalid_tool_args",
tool_call.function.name, e format!(
)) "Invalid tool arguments JSON for tool '{}': {}",
tool_call.function.name, e
),
)
})?; })?;
// Execute tool via MCP manager // Execute tool via MCP manager
@@ -1544,10 +1547,13 @@ async fn load_previous_messages(
error = %e, error = %e,
"Failed to load previous response chain from storage" "Failed to load previous response chain from storage"
); );
error::internal_error(format!( error::internal_error(
"Failed to load previous response chain for {}: {}", "load_previous_response_chain_failed",
prev_id_str, e format!(
)) "Failed to load previous response chain for {}: {}",
prev_id_str, e
),
)
})?; })?;
// Build conversation history from stored responses // Build conversation history from stored responses
@@ -65,6 +65,7 @@ impl PipelineStage for HarmonyPreparationStage {
"Unsupported request type for Harmony pipeline" "Unsupported request type for Harmony pipeline"
); );
return Err(error::bad_request( return Err(error::bad_request(
"harmony_request_type_invalid",
"Only Chat and Responses requests supported in Harmony pipeline".to_string(), "Only Chat and Responses requests supported in Harmony pipeline".to_string(),
)); ));
} }
@@ -91,6 +92,7 @@ impl HarmonyPreparationStage {
"logprobs requested but not supported for Harmony models" "logprobs requested but not supported for Harmony models"
); );
return Err(error::bad_request( return Err(error::bad_request(
"harmony_logprobs_not_supported",
"logprobs are not supported for Harmony models".to_string(), "logprobs are not supported for Harmony models".to_string(),
)); ));
} }
@@ -112,7 +114,10 @@ impl HarmonyPreparationStage {
error = %e, error = %e,
"Harmony build failed for chat request" "Harmony build failed for chat request"
); );
error::bad_request(format!("Harmony build failed: {}", e)) error::bad_request(
"harmony_build_failed",
format!("Harmony build failed: {}", e),
)
})?; })?;
// Step 4: Store results // Step 4: Store results
@@ -175,6 +180,7 @@ impl HarmonyPreparationStage {
"Conflicting constraints: both tool_choice and text format specified" "Conflicting constraints: both tool_choice and text format specified"
); );
return Err(error::bad_request( return Err(error::bad_request(
"conflicting_constraints",
"Cannot use both tool_choice (required/function) and text format (json_object/json_schema) simultaneously".to_string(), "Cannot use both tool_choice (required/function) and text format (json_object/json_schema) simultaneously".to_string(),
)); ));
} }
@@ -188,7 +194,10 @@ impl HarmonyPreparationStage {
error = %e, error = %e,
"Harmony build failed for responses request" "Harmony build failed for responses request"
); );
error::bad_request(format!("Harmony build failed: {}", e)) error::bad_request(
"harmony_build_failed",
format!("Harmony build failed: {}", e),
)
})?; })?;
// Step 4: Store results with constraint // Step 4: Store results with constraint
@@ -230,7 +239,7 @@ impl HarmonyPreparationStage {
error = %e, error = %e,
"Failed to build text format structural tag for JsonObject" "Failed to build text format structural tag for JsonObject"
); );
Box::new(error::internal_error(e)) Box::new(error::internal_error("build_text_format_tag_failed", e))
})?; })?;
Ok(Some(("structural_tag".to_string(), tag))) Ok(Some(("structural_tag".to_string(), tag)))
} }
@@ -241,7 +250,7 @@ impl HarmonyPreparationStage {
error = %e, error = %e,
"Failed to build text format structural tag for JsonSchema" "Failed to build text format structural tag for JsonSchema"
); );
Box::new(error::internal_error(e)) Box::new(error::internal_error("build_text_format_tag_failed", e))
})?; })?;
Ok(Some(("structural_tag".to_string(), tag))) Ok(Some(("structural_tag".to_string(), tag)))
} }
@@ -310,10 +319,10 @@ impl HarmonyPreparationStage {
tool_name = %tool_name, tool_name = %tool_name,
"Specified tool not found in tools list" "Specified tool not found in tools list"
); );
return Err(Box::new(error::bad_request(format!( return Err(Box::new(error::bad_request(
"Tool '{}' not found in tools list", "tool_not_found",
tool_name format!("Tool '{}' not found in tools list", tool_name),
)))); )));
} }
_ => {} _ => {}
} }
@@ -362,10 +371,10 @@ impl HarmonyPreparationStage {
error = %e, error = %e,
"Failed to serialize structural tag" "Failed to serialize structural tag"
); );
Box::new(error::internal_error(format!( Box::new(error::internal_error(
"Failed to serialize structural tag: {}", "serialize_structural_tag_failed",
e format!("Failed to serialize structural tag: {}", e),
))) ))
}) })
} }
} }
@@ -38,7 +38,7 @@ impl PipelineStage for HarmonyRequestBuildingStage {
function = "HarmonyRequestBuildingStage::execute", function = "HarmonyRequestBuildingStage::execute",
"Preparation stage not completed" "Preparation stage not completed"
); );
error::internal_error("Preparation not completed") error::internal_error("preparation_not_completed", "Preparation not completed")
})?; })?;
// Get clients // Get clients
@@ -47,7 +47,10 @@ impl PipelineStage for HarmonyRequestBuildingStage {
function = "HarmonyRequestBuildingStage::execute", function = "HarmonyRequestBuildingStage::execute",
"Client acquisition stage not completed" "Client acquisition stage not completed"
); );
error::internal_error("Client acquisition not completed") error::internal_error(
"client_acquisition_not_completed",
"Client acquisition not completed",
)
})?; })?;
let builder_client = match clients { let builder_client = match clients {
ClientSelection::Single { client } => client, ClientSelection::Single { client } => client,
@@ -57,6 +60,7 @@ impl PipelineStage for HarmonyRequestBuildingStage {
// Harmony model support not yet implemented for vLLM // Harmony model support not yet implemented for vLLM
if builder_client.is_vllm() { if builder_client.is_vllm() {
return Err(error::not_implemented( return Err(error::not_implemented(
"harmony_vllm_not_supported",
"Harmony model support is not yet implemented for vLLM backend. \ "Harmony model support is not yet implemented for vLLM backend. \
Please use runtime_type: sglang for Harmony models.", Please use runtime_type: sglang for Harmony models.",
)); ));
@@ -72,6 +76,7 @@ impl PipelineStage for HarmonyRequestBuildingStage {
"Generate request type not supported for Harmony models" "Generate request type not supported for Harmony models"
); );
return Err(error::bad_request( return Err(error::bad_request(
"harmony_generate_not_supported",
"Generate requests are not supported with Harmony models".to_string(), "Generate requests are not supported with Harmony models".to_string(),
)); ));
} }
@@ -102,7 +107,10 @@ impl PipelineStage for HarmonyRequestBuildingStage {
error = %e, error = %e,
"Failed to build generate request from chat" "Failed to build generate request from chat"
); );
error::bad_request(format!("Invalid request parameters: {}", e)) error::bad_request(
"invalid_request_parameters",
format!("Invalid request parameters: {}", e),
)
})? })?
} }
RequestType::Responses(request) => sglang_client RequestType::Responses(request) => sglang_client
@@ -120,7 +128,10 @@ impl PipelineStage for HarmonyRequestBuildingStage {
error = %e, error = %e,
"Failed to build generate request from responses" "Failed to build generate request from responses"
); );
error::bad_request(format!("Invalid request parameters: {}", e)) error::bad_request(
"invalid_request_parameters",
format!("Invalid request parameters: {}", e),
)
})?, })?,
_ => unreachable!(), _ => unreachable!(),
}; };
@@ -56,7 +56,7 @@ impl PipelineStage for HarmonyResponseProcessingStage {
request_type = "Chat", request_type = "Chat",
"No execution result available" "No execution result available"
); );
error::internal_error("No execution result") error::internal_error("no_execution_result", "No execution result")
})?; })?;
let dispatch = ctx.state.dispatch.as_ref().cloned().ok_or_else(|| { let dispatch = ctx.state.dispatch.as_ref().cloned().ok_or_else(|| {
@@ -65,7 +65,7 @@ impl PipelineStage for HarmonyResponseProcessingStage {
request_type = "Chat", request_type = "Chat",
"Dispatch metadata not set" "Dispatch metadata not set"
); );
error::internal_error("Dispatch metadata not set") error::internal_error("dispatch_metadata_not_set", "Dispatch metadata not set")
})?; })?;
// For streaming, delegate to streaming processor and return SSE response // For streaming, delegate to streaming processor and return SSE response
@@ -107,7 +107,7 @@ impl PipelineStage for HarmonyResponseProcessingStage {
request_type = "Responses", request_type = "Responses",
"No execution result available" "No execution result available"
); );
error::internal_error("No execution result") error::internal_error("no_execution_result", "No execution result")
})?; })?;
let dispatch = ctx.state.dispatch.as_ref().cloned().ok_or_else(|| { let dispatch = ctx.state.dispatch.as_ref().cloned().ok_or_else(|| {
@@ -116,7 +116,7 @@ impl PipelineStage for HarmonyResponseProcessingStage {
request_type = "Responses", request_type = "Responses",
"Dispatch metadata not set" "Dispatch metadata not set"
); );
error::internal_error("Dispatch metadata not set") error::internal_error("dispatch_metadata_not_set", "Dispatch metadata not set")
})?; })?;
let responses_request = ctx.responses_request_arc(); let responses_request = ctx.responses_request_arc();
@@ -134,6 +134,7 @@ impl PipelineStage for HarmonyResponseProcessingStage {
"Generate request type not supported in Harmony pipeline" "Generate request type not supported in Harmony pipeline"
); );
Err(error::internal_error( Err(error::internal_error(
"generate_requests_not_supported_in_harmony",
"Generate requests not supported in Harmony pipeline", "Generate requests not supported in Harmony pipeline",
)) ))
} }
+21 -8
View File
@@ -224,14 +224,14 @@ impl RequestPipeline {
function = "execute_chat", function = "execute_chat",
"Wrong response type: expected Chat, got Generate" "Wrong response type: expected Chat, got Generate"
); );
error::internal_error("Internal error: wrong response type") error::internal_error("wrong_response_type", "Internal error: wrong response type")
} }
None => { None => {
error!( error!(
function = "execute_chat", function = "execute_chat",
"No response produced by pipeline" "No response produced by pipeline"
); );
error::internal_error("No response produced") error::internal_error("no_response_produced", "No response produced")
} }
} }
} }
@@ -275,14 +275,14 @@ impl RequestPipeline {
function = "execute_generate", function = "execute_generate",
"Wrong response type: expected Generate, got Chat" "Wrong response type: expected Generate, got Chat"
); );
error::internal_error("Internal error: wrong response type") error::internal_error("wrong_response_type", "Internal error: wrong response type")
} }
None => { None => {
error!( error!(
function = "execute_generate", function = "execute_generate",
"No response produced by pipeline" "No response produced by pipeline"
); );
error::internal_error("No response produced") error::internal_error("no_response_produced", "No response produced")
} }
} }
} }
@@ -311,6 +311,7 @@ impl RequestPipeline {
"Streaming attempted in responses context" "Streaming attempted in responses context"
); );
return Err(error::bad_request( return Err(error::bad_request(
"streaming_not_supported",
"Streaming is not supported in this context".to_string(), "Streaming is not supported in this context".to_string(),
)); ));
} }
@@ -337,14 +338,20 @@ impl RequestPipeline {
function = "execute_chat_for_responses", function = "execute_chat_for_responses",
"Wrong response type: expected Chat, got Generate" "Wrong response type: expected Chat, got Generate"
); );
Err(error::internal_error("Internal error: wrong response type")) Err(error::internal_error(
"wrong_response_type",
"Internal error: wrong response type",
))
} }
None => { None => {
error!( error!(
function = "execute_chat_for_responses", function = "execute_chat_for_responses",
"No response produced by pipeline" "No response produced by pipeline"
); );
Err(error::internal_error("No response produced")) Err(error::internal_error(
"no_response_produced",
"No response produced",
))
} }
} }
} }
@@ -415,7 +422,10 @@ impl RequestPipeline {
function = "execute_harmony_responses", function = "execute_harmony_responses",
"No ResponsesIterationResult produced by pipeline" "No ResponsesIterationResult produced by pipeline"
); );
error::internal_error("No ResponsesIterationResult produced by pipeline") error::internal_error(
"no_responses_iteration_result",
"No ResponsesIterationResult produced by pipeline",
)
}) })
} }
@@ -465,7 +475,10 @@ impl RequestPipeline {
function = "execute_harmony_responses_streaming", function = "execute_harmony_responses_streaming",
"No ExecutionResult produced by pipeline" "No ExecutionResult produced by pipeline"
); );
error::internal_error("No ExecutionResult produced by pipeline") error::internal_error(
"no_execution_result_produced",
"No ExecutionResult produced by pipeline",
)
}) })
} }
} }
@@ -278,10 +278,10 @@ impl ResponseProcessor {
{ {
Ok(choice) => choices.push(choice), Ok(choice) => choices.push(choice),
Err(e) => { Err(e) => {
return Err(error::internal_error(format!( return Err(error::internal_error(
"Failed to process choice {}: {}", "process_choice_failed",
index, e format!("Failed to process choice {}: {}", index, e),
))); ));
} }
} }
} }
@@ -380,10 +380,10 @@ impl ResponseProcessor {
let outputs = match stop_decoder.process_tokens(complete.output_ids()) { let outputs = match stop_decoder.process_tokens(complete.output_ids()) {
Ok(outputs) => outputs, Ok(outputs) => outputs,
Err(e) => { Err(e) => {
return Err(error::internal_error(format!( return Err(error::internal_error(
"Failed to process tokens: {}", "process_tokens_failed",
e format!("Failed to process tokens: {}", e),
))) ))
} }
}; };
@@ -607,7 +607,10 @@ async fn execute_without_mcp(
error = %e, error = %e,
"Failed to convert ResponsesRequest to ChatCompletionRequest" "Failed to convert ResponsesRequest to ChatCompletionRequest"
); );
error::bad_request(format!("Failed to convert request: {}", e)) error::bad_request(
"convert_request_failed",
format!("Failed to convert request: {}", e),
)
})?; })?;
// Execute chat pipeline (errors already have proper HTTP status codes) // Execute chat pipeline (errors already have proper HTTP status codes)
@@ -628,7 +631,10 @@ async fn execute_without_mcp(
error = %e, error = %e,
"Failed to convert ChatCompletionResponse to ResponsesResponse" "Failed to convert ChatCompletionResponse to ResponsesResponse"
); );
error::internal_error(format!("Failed to convert to responses format: {}", e)) error::internal_error(
"convert_to_responses_format_failed",
format!("Failed to convert to responses format: {}", e),
)
}) })
} }
@@ -713,14 +719,20 @@ async fn load_conversation_history(
error = %e, error = %e,
"Failed to check conversation existence in storage" "Failed to check conversation existence in storage"
); );
error::internal_error(format!("Failed to check conversation: {}", e)) error::internal_error(
"check_conversation_failed",
format!("Failed to check conversation: {}", e),
)
})?; })?;
if conversation.is_none() { if conversation.is_none() {
return Err(error::not_found(format!( return Err(error::not_found(
"Conversation '{}' not found. Please create the conversation first using the conversations API.", "conversation_not_found",
conv_id_str format!(
))); "Conversation '{}' not found. Please create the conversation first using the conversations API.",
conv_id_str
)
));
} }
// Load conversation history // Load conversation history
@@ -257,7 +257,10 @@ pub(super) async fn execute_tool_loop(
error = %e, error = %e,
"Failed to convert ResponsesRequest to ChatCompletionRequest in tool loop" "Failed to convert ResponsesRequest to ChatCompletionRequest in tool loop"
); );
error::bad_request(format!("Failed to convert request: {}", e)) error::bad_request(
"convert_request_failed",
format!("Failed to convert request: {}", e),
)
})?; })?;
// Prepare tools and tool_choice for this iteration // Prepare tools and tool_choice for this iteration
@@ -315,7 +318,10 @@ pub(super) async fn execute_tool_loop(
context = "function_tool_calls", context = "function_tool_calls",
"Failed to convert ChatCompletionResponse to ResponsesResponse" "Failed to convert ChatCompletionResponse to ResponsesResponse"
); );
error::internal_error(format!("Failed to convert to responses format: {}", e)) error::internal_error(
"convert_to_responses_format_failed",
format!("Failed to convert to responses format: {}", e),
)
})?; })?;
// Return response with function tool calls to caller // Return response with function tool calls to caller
@@ -352,7 +358,10 @@ pub(super) async fn execute_tool_loop(
context = "max_tool_calls_limit", context = "max_tool_calls_limit",
"Failed to convert ChatCompletionResponse to ResponsesResponse" "Failed to convert ChatCompletionResponse to ResponsesResponse"
); );
error::internal_error(format!("Failed to convert to responses format: {}", e)) error::internal_error(
"convert_to_responses_format_failed",
format!("Failed to convert to responses format: {}", e),
)
})?; })?;
// Mark as completed but with incomplete details // Mark as completed but with incomplete details
@@ -481,7 +490,10 @@ pub(super) async fn execute_tool_loop(
context = "final_response", context = "final_response",
"Failed to convert ChatCompletionResponse to ResponsesResponse" "Failed to convert ChatCompletionResponse to ResponsesResponse"
); );
error::internal_error(format!("Failed to convert to responses format: {}", e)) error::internal_error(
"convert_to_responses_format_failed",
format!("Failed to convert to responses format: {}", e),
)
})?; })?;
// Inject MCP metadata into output // Inject MCP metadata into output
@@ -54,7 +54,7 @@ impl ChatPreparationStage {
Ok(msgs) => msgs, Ok(msgs) => msgs,
Err(e) => { Err(e) => {
error!(function = "ChatPreparationStage::execute", error = %e, "Failed to process chat messages"); error!(function = "ChatPreparationStage::execute", error = %e, "Failed to process chat messages");
return Err(error::bad_request(e)); return Err(error::bad_request("process_messages_failed", e));
} }
}; };
@@ -63,7 +63,10 @@ impl ChatPreparationStage {
Ok(encoding) => encoding, Ok(encoding) => encoding,
Err(e) => { Err(e) => {
error!(function = "ChatPreparationStage::execute", error = %e, "Tokenization failed"); error!(function = "ChatPreparationStage::execute", error = %e, "Tokenization failed");
return Err(error::internal_error(format!("Tokenization failed: {}", e))); return Err(error::internal_error(
"tokenization_failed",
format!("Tokenization failed: {}", e),
));
} }
}; };
@@ -74,7 +77,7 @@ impl ChatPreparationStage {
utils::generate_tool_constraints(tools, &request.tool_choice, &request.model) utils::generate_tool_constraints(tools, &request.tool_choice, &request.model)
.map_err(|e| { .map_err(|e| {
error!(function = "ChatPreparationStage::execute", error = %e, "Invalid tool configuration"); error!(function = "ChatPreparationStage::execute", error = %e, "Invalid tool configuration");
error::bad_request(format!("Invalid tool configuration: {}", e)) error::bad_request("invalid_tool_configuration", format!("Invalid tool configuration: {}", e))
})? })?
} else { } else {
None None
@@ -36,7 +36,7 @@ impl PipelineStage for ChatRequestBuildingStage {
function = "ChatRequestBuildingStage::execute", function = "ChatRequestBuildingStage::execute",
"Preparation not completed" "Preparation not completed"
); );
error::internal_error("Preparation not completed") error::internal_error("preparation_not_completed", "Preparation not completed")
})?; })?;
let clients = ctx.state.clients.as_ref().ok_or_else(|| { let clients = ctx.state.clients.as_ref().ok_or_else(|| {
@@ -44,7 +44,10 @@ impl PipelineStage for ChatRequestBuildingStage {
function = "ChatRequestBuildingStage::execute", function = "ChatRequestBuildingStage::execute",
"Client acquisition not completed" "Client acquisition not completed"
); );
error::internal_error("Client acquisition not completed") error::internal_error(
"client_acquisition_not_completed",
"Client acquisition not completed",
)
})?; })?;
let chat_request = ctx.chat_request_arc(); let chat_request = ctx.chat_request_arc();
@@ -77,7 +80,7 @@ impl PipelineStage for ChatRequestBuildingStage {
) )
.map_err(|e| { .map_err(|e| {
error!(function = "ChatRequestBuildingStage::execute", error = %e, "Failed to build SGLang generate request"); error!(function = "ChatRequestBuildingStage::execute", error = %e, "Failed to build SGLang generate request");
error::bad_request(format!("Invalid request parameters: {}", e)) error::bad_request("invalid_request_parameters", format!("Invalid request parameters: {}", e))
})?; })?;
ProtoGenerateRequest::Sglang(Box::new(req)) ProtoGenerateRequest::Sglang(Box::new(req))
} }
@@ -92,7 +95,7 @@ impl PipelineStage for ChatRequestBuildingStage {
) )
.map_err(|e| { .map_err(|e| {
error!(function = "ChatRequestBuildingStage::execute", error = %e, "Failed to build vLLM generate request"); error!(function = "ChatRequestBuildingStage::execute", error = %e, "Failed to build vLLM generate request");
error::bad_request(format!("Invalid request parameters: {}", e)) error::bad_request("invalid_request_parameters", format!("Invalid request parameters: {}", e))
})?; })?;
ProtoGenerateRequest::Vllm(Box::new(req)) ProtoGenerateRequest::Vllm(Box::new(req))
} }
@@ -62,7 +62,7 @@ impl ChatResponseProcessingStage {
function = "ChatResponseProcessingStage::execute", function = "ChatResponseProcessingStage::execute",
"No execution result" "No execution result"
); );
error::internal_error("No execution result") error::internal_error("no_execution_result", "No execution result")
})?; })?;
// Get dispatch metadata (needed by both streaming and non-streaming) // Get dispatch metadata (needed by both streaming and non-streaming)
@@ -75,7 +75,7 @@ impl ChatResponseProcessingStage {
function = "ChatResponseProcessingStage::execute", function = "ChatResponseProcessingStage::execute",
"Dispatch metadata not set" "Dispatch metadata not set"
); );
error::internal_error("Dispatch metadata not set") error::internal_error("dispatch_metadata_not_set", "Dispatch metadata not set")
})? })?
.clone(); .clone();
@@ -100,7 +100,10 @@ impl ChatResponseProcessingStage {
function = "ChatResponseProcessingStage::execute", function = "ChatResponseProcessingStage::execute",
"Stop decoder not initialized" "Stop decoder not initialized"
); );
error::internal_error("Stop decoder not initialized") error::internal_error(
"stop_decoder_not_initialized",
"Stop decoder not initialized",
)
})?; })?;
let response = self let response = self
@@ -49,7 +49,7 @@ impl GeneratePreparationStage {
Ok(res) => res, Ok(res) => res,
Err(msg) => { Err(msg) => {
error!(function = "GeneratePreparationStage::execute", error = %msg, "Failed to resolve generate input"); error!(function = "GeneratePreparationStage::execute", error = %msg, "Failed to resolve generate input");
return Err(error::bad_request(msg)); return Err(error::bad_request("resolve_input_failed", msg));
} }
}; };
@@ -36,7 +36,7 @@ impl PipelineStage for GenerateRequestBuildingStage {
function = "GenerateRequestBuildingStage::execute", function = "GenerateRequestBuildingStage::execute",
"Preparation not completed" "Preparation not completed"
); );
error::internal_error("Preparation not completed") error::internal_error("preparation_not_completed", "Preparation not completed")
})?; })?;
let clients = ctx.state.clients.as_ref().ok_or_else(|| { let clients = ctx.state.clients.as_ref().ok_or_else(|| {
@@ -44,7 +44,10 @@ impl PipelineStage for GenerateRequestBuildingStage {
function = "GenerateRequestBuildingStage::execute", function = "GenerateRequestBuildingStage::execute",
"Client acquisition not completed" "Client acquisition not completed"
); );
error::internal_error("Client acquisition not completed") error::internal_error(
"client_acquisition_not_completed",
"Client acquisition not completed",
)
})?; })?;
let generate_request = ctx.generate_request_arc(); let generate_request = ctx.generate_request_arc();
@@ -73,7 +76,7 @@ impl PipelineStage for GenerateRequestBuildingStage {
) )
.map_err(|e| { .map_err(|e| {
error!(function = "GenerateRequestBuildingStage::execute", error = %e, "Failed to build SGLang generate request"); error!(function = "GenerateRequestBuildingStage::execute", error = %e, "Failed to build SGLang generate request");
error::bad_request(e) error::bad_request("build_request_failed", e)
})?; })?;
ProtoGenerateRequest::Sglang(Box::new(req)) ProtoGenerateRequest::Sglang(Box::new(req))
} }
@@ -87,7 +90,7 @@ impl PipelineStage for GenerateRequestBuildingStage {
) )
.map_err(|e| { .map_err(|e| {
error!(function = "GenerateRequestBuildingStage::execute", error = %e, "Failed to build vLLM generate request"); error!(function = "GenerateRequestBuildingStage::execute", error = %e, "Failed to build vLLM generate request");
error::bad_request(e) error::bad_request("build_request_failed", e)
})?; })?;
ProtoGenerateRequest::Vllm(Box::new(req)) ProtoGenerateRequest::Vllm(Box::new(req))
} }
@@ -60,7 +60,7 @@ impl GenerateResponseProcessingStage {
function = "GenerateResponseProcessingStage::execute", function = "GenerateResponseProcessingStage::execute",
"No execution result" "No execution result"
); );
error::internal_error("No execution result") error::internal_error("no_execution_result", "No execution result")
})?; })?;
// Get dispatch metadata (needed by both streaming and non-streaming) // Get dispatch metadata (needed by both streaming and non-streaming)
@@ -73,7 +73,7 @@ impl GenerateResponseProcessingStage {
function = "GenerateResponseProcessingStage::execute", function = "GenerateResponseProcessingStage::execute",
"Dispatch metadata not set" "Dispatch metadata not set"
); );
error::internal_error("Dispatch metadata not set") error::internal_error("dispatch_metadata_not_set", "Dispatch metadata not set")
})? })?
.clone(); .clone();
@@ -97,7 +97,10 @@ impl GenerateResponseProcessingStage {
function = "GenerateResponseProcessingStage::execute", function = "GenerateResponseProcessingStage::execute",
"Stop decoder not initialized" "Stop decoder not initialized"
); );
error::internal_error("Stop decoder not initialized") error::internal_error(
"stop_decoder_not_initialized",
"Stop decoder not initialized",
)
})?; })?;
let result_array = self let result_array = self
@@ -49,6 +49,7 @@ impl PipelineStage for PreparationStage {
"RequestType::Responses reached regular preparation stage" "RequestType::Responses reached regular preparation stage"
); );
Err(grpc_error::internal_error( Err(grpc_error::internal_error(
"responses_in_wrong_pipeline",
"RequestType::Responses reached regular preparation stage", "RequestType::Responses reached regular preparation stage",
)) ))
} }
@@ -40,6 +40,7 @@ impl PipelineStage for RequestBuildingStage {
"RequestType::Responses reached regular request building stage" "RequestType::Responses reached regular request building stage"
); );
Err(grpc_error::internal_error( Err(grpc_error::internal_error(
"responses_in_wrong_pipeline",
"RequestType::Responses reached regular request building stage", "RequestType::Responses reached regular request building stage",
)) ))
} }
@@ -49,6 +49,7 @@ impl PipelineStage for ResponseProcessingStage {
"RequestType::Responses reached regular response processing stage" "RequestType::Responses reached regular response processing stage"
); );
Err(error::internal_error( Err(error::internal_error(
"responses_in_wrong_pipeline",
"RequestType::Responses reached regular response processing stage", "RequestType::Responses reached regular response processing stage",
)) ))
} }
+16 -11
View File
@@ -52,14 +52,20 @@ pub async fn get_grpc_client_from_worker(worker: &Arc<dyn Worker>) -> Result<Grp
error = %e, error = %e,
"Failed to get gRPC client from worker" "Failed to get gRPC client from worker"
); );
error::internal_error(format!("Failed to get gRPC client: {}", e)) error::internal_error(
"get_grpc_client_failed",
format!("Failed to get gRPC client: {}", e),
)
})? })?
.ok_or_else(|| { .ok_or_else(|| {
error!( error!(
function = "get_grpc_client_from_worker", function = "get_grpc_client_from_worker",
"Selected worker not configured for gRPC" "Selected worker not configured for gRPC"
); );
error::internal_error("Selected worker is not configured for gRPC") error::internal_error(
"worker_not_configured_for_grpc",
"Selected worker is not configured for gRPC",
)
})?; })?;
Ok((*client_arc).clone()) Ok((*client_arc).clone())
@@ -612,11 +618,10 @@ pub async fn collect_stream_responses(
ProtoResponseVariant::Error(err) => { ProtoResponseVariant::Error(err) => {
error!(function = "collect_stream_responses", worker = %worker_name, error = %err.message(), "Worker generation error"); error!(function = "collect_stream_responses", worker = %worker_name, error = %err.message(), "Worker generation error");
// Don't mark as completed - let Drop send abort for error cases // Don't mark as completed - let Drop send abort for error cases
return Err(error::internal_error(format!( return Err(error::internal_error(
"{} generation failed: {}", "worker_generation_failed",
worker_name, format!("{} generation failed: {}", worker_name, err.message()),
err.message() ));
)));
} }
ProtoResponseVariant::Chunk(_chunk) => { ProtoResponseVariant::Chunk(_chunk) => {
// Streaming chunk - no action needed // Streaming chunk - no action needed
@@ -629,10 +634,10 @@ pub async fn collect_stream_responses(
Err(e) => { Err(e) => {
error!(function = "collect_stream_responses", worker = %worker_name, error = ?e, "Worker stream error"); error!(function = "collect_stream_responses", worker = %worker_name, error = ?e, "Worker stream error");
// Don't mark as completed - let Drop send abort for error cases // Don't mark as completed - let Drop send abort for error cases
return Err(error::internal_error(format!( return Err(error::internal_error(
"{} stream failed: {}", "worker_stream_failed",
worker_name, e format!("{} stream failed: {}", worker_name, e),
))); ));
} }
} }
} }