From ee06b53f69c6b0ace050677099d39b3cd16a3050 Mon Sep 17 00:00:00 2001 From: Alex Z Date: Thu, 28 May 2026 13:19:25 -0700 Subject: [PATCH] determine where parse errors came from --- crates/braintrust-llm-router/src/error.rs | 7 +++- .../src/providers/bedrock.rs | 2 +- crates/braintrust-llm-router/src/streaming.rs | 2 +- crates/lingua/src/processing/stream.rs | 2 +- crates/lingua/src/processing/transform.rs | 42 ++++++++++++++++++- 5 files changed, 50 insertions(+), 5 deletions(-) diff --git a/crates/braintrust-llm-router/src/error.rs b/crates/braintrust-llm-router/src/error.rs index e737334f..fd2ab3ca 100644 --- a/crates/braintrust-llm-router/src/error.rs +++ b/crates/braintrust-llm-router/src/error.rs @@ -203,6 +203,9 @@ mod tests { // Client errors assert!(TransformError::UnableToDetectFormat.is_client_error()); + assert!(TransformError::UnableToDetectRequestFormat.is_client_error()); + assert!(TransformError::UnableToDetectResponseFormat.is_client_error()); + assert!(TransformError::UnableToDetectStreamFormat.is_client_error()); assert!(TransformError::ValidationFailed { target: ProviderFormat::ChatCompletions, reason: "test".into() @@ -233,7 +236,9 @@ mod tests { assert!(Error::UnknownModel("gpt-5".into()).is_client_error()); assert!(Error::NoProvider(ProviderFormat::ChatCompletions).is_client_error()); assert!(Error::InvalidRequest("bad".into()).is_client_error()); - assert!(Error::Lingua(lingua::TransformError::UnableToDetectFormat).is_client_error()); + assert!( + Error::Lingua(lingua::TransformError::UnableToDetectRequestFormat).is_client_error() + ); // Auth errors assert!(Error::NoAuth("openai".into()).is_auth_error()); diff --git a/crates/braintrust-llm-router/src/providers/bedrock.rs b/crates/braintrust-llm-router/src/providers/bedrock.rs index 55e9825a..1683f3ec 100644 --- a/crates/braintrust-llm-router/src/providers/bedrock.rs +++ b/crates/braintrust-llm-router/src/providers/bedrock.rs @@ -87,7 +87,7 @@ where .find(|adapter| adapter.detect_request(&payload)) { Some(adapter) => adapter, - None => return Err(TransformError::UnableToDetectFormat.into()), + None => return Err(TransformError::UnableToDetectRequestFormat.into()), }; if source_adapter.format() == format { diff --git a/crates/braintrust-llm-router/src/streaming.rs b/crates/braintrust-llm-router/src/streaming.rs index ddc8bdae..0e3ec619 100644 --- a/crates/braintrust-llm-router/src/streaming.rs +++ b/crates/braintrust-llm-router/src/streaming.rs @@ -154,7 +154,7 @@ impl Stream for SessionTransformStream { } continue; } - Err(lingua::TransformError::UnableToDetectFormat) => { + Err(lingua::TransformError::UnableToDetectStreamFormat) => { #[cfg(feature = "tracing")] log_stream_transform_detection_failure( &data, diff --git a/crates/lingua/src/processing/stream.rs b/crates/lingua/src/processing/stream.rs index b904d6a9..d1babe6a 100644 --- a/crates/lingua/src/processing/stream.rs +++ b/crates/lingua/src/processing/stream.rs @@ -927,7 +927,7 @@ mod tests { assert!(matches!( session.push(full_response), - Err(TransformError::UnableToDetectFormat) + Err(TransformError::UnableToDetectStreamFormat) )); } diff --git a/crates/lingua/src/processing/transform.rs b/crates/lingua/src/processing/transform.rs index 6eaf0141..93fe8be2 100644 --- a/crates/lingua/src/processing/transform.rs +++ b/crates/lingua/src/processing/transform.rs @@ -39,6 +39,15 @@ pub enum TransformError { #[error("Unable to detect source format")] UnableToDetectFormat, + #[error("Unable to detect request source format")] + UnableToDetectRequestFormat, + + #[error("Unable to detect response source format")] + UnableToDetectResponseFormat, + + #[error("Unable to detect stream source format")] + UnableToDetectStreamFormat, + #[error("Validation failed for target format {target:?}: {reason}")] ValidationFailed { target: ProviderFormat, @@ -78,6 +87,9 @@ impl TransformError { matches!( self, TransformError::UnableToDetectFormat + | TransformError::UnableToDetectRequestFormat + | TransformError::UnableToDetectResponseFormat + | TransformError::UnableToDetectStreamFormat | TransformError::ValidationFailed { .. } | TransformError::DeserializationFailed(_) | TransformError::UnsupportedTargetFormat(_) @@ -603,7 +615,15 @@ fn detect_adapter_with_kind( } } - Err(TransformError::UnableToDetectFormat) + Err(unable_to_detect_format_error(kind)) +} + +fn unable_to_detect_format_error(kind: DetectKind) -> TransformError { + match kind { + DetectKind::Request => TransformError::UnableToDetectRequestFormat, + DetectKind::Response => TransformError::UnableToDetectResponseFormat, + DetectKind::Stream => TransformError::UnableToDetectStreamFormat, + } } fn detect_adapter_exact(payload: &Value, kind: DetectKind) -> Option<&'static dyn ProviderAdapter> { @@ -942,6 +962,16 @@ mod tests { )); } + #[test] + fn test_transform_request_unable_to_detect_mentions_request() { + let input = Bytes::from_static(br#"{"not":"a supported request shape"}"#); + + let err = transform_request(input, ProviderFormat::ChatCompletions, None).unwrap_err(); + + assert!(matches!(err, TransformError::UnableToDetectRequestFormat)); + assert_eq!(err.to_string(), "Unable to detect request source format"); + } + #[test] #[cfg(all(feature = "openai", feature = "anthropic"))] fn test_transform_response() { @@ -973,6 +1003,16 @@ mod tests { assert!(output.get("content").is_some() || output.get("choices").is_some()); } + #[test] + fn test_transform_response_unable_to_detect_mentions_response() { + let input = Bytes::from_static(br#"{"not":"a supported response shape"}"#); + + let err = transform_response(input, ProviderFormat::ChatCompletions).unwrap_err(); + + assert!(matches!(err, TransformError::UnableToDetectResponseFormat)); + assert_eq!(err.to_string(), "Unable to detect response source format"); + } + #[test] #[cfg(all(feature = "openai", feature = "anthropic"))] fn test_stream_detection_falls_back_to_full_response() {