From b2274e7379f0dd0339ae9a5baaea0d8d77e54e51 Mon Sep 17 00:00:00 2001 From: Shanu Date: Wed, 20 May 2026 11:28:33 +0530 Subject: [PATCH 1/3] fix(observability): update error reporting for OpenAI embedding API to handle transient failures gracefully --- src/openhuman/embeddings/openai.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/openhuman/embeddings/openai.rs b/src/openhuman/embeddings/openai.rs index 1d74e49a52..369ca25b79 100644 --- a/src/openhuman/embeddings/openai.rs +++ b/src/openhuman/embeddings/openai.rs @@ -128,8 +128,12 @@ impl EmbeddingProvider for OpenAiEmbedding { target: "openai::embed", "[openai] embed error: status={status}, body={text}" ); - let message = format!("Embedding API error {status}: {text}"); - crate::core::observability::report_error( + let message = format!("Embedding API error ({status}): {text}"); + // Use `report_error_or_expected` so transient upstream HTTP failures + // (e.g. 429 Too Many Requests, which the memory_tree job runner + // already retries with backoff) log a warning breadcrumb instead of + // firing a Sentry error event per attempt. + crate::core::observability::report_error_or_expected( message.as_str(), "embeddings", "openai_embed", From 91c7f38ffec965c960167932d5b230408a861a98 Mon Sep 17 00:00:00 2001 From: Shanu Date: Wed, 20 May 2026 11:29:02 +0530 Subject: [PATCH 2/3] test(embeddings): add test for canonical transient format on 429 rate-limit responses --- src/openhuman/embeddings/openai_tests.rs | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/openhuman/embeddings/openai_tests.rs b/src/openhuman/embeddings/openai_tests.rs index 527bdb9fc6..234985dd8b 100644 --- a/src/openhuman/embeddings/openai_tests.rs +++ b/src/openhuman/embeddings/openai_tests.rs @@ -224,6 +224,36 @@ async fn embed_server_error() { assert!(msg.contains("rate limited"), "body: {msg}"); } +/// 429 rate-limit responses must format their message in the canonical +/// `"... API error (): "` shape so the shared +/// `is_transient_upstream_http_message` classifier in `core::observability` +/// demotes them to a warning breadcrumb instead of a Sentry error event. +#[tokio::test] +async fn embed_429_uses_canonical_transient_format() { + let app = Router::new().route( + "/v1/embeddings", + post(|| async { + ( + StatusCode::TOO_MANY_REQUESTS, + r#"{"error":{"message":"Rate limit exceeded.","type":"rate_limit_error"}}"#, + ) + }), + ); + let url = start_mock(app).await; + let p = OpenAiEmbedding::new(&url, "k", "m", 1); + + let err = p.embed(&["hi"]).await.unwrap_err(); + let msg = err.to_string(); + assert!( + msg.contains("(429 Too Many Requests)"), + "expected canonical transient HTTP shape, got: {msg}" + ); + assert!( + crate::core::observability::is_transient_message_failure(&msg), + "message should classify as transient: {msg}" + ); +} + #[tokio::test] async fn embed_missing_data_field() { let app = Router::new().route( From 6acfcd02c00a41d03c8159eed91c723b068eb515 Mon Sep 17 00:00:00 2001 From: Shanu Date: Wed, 20 May 2026 13:57:41 +0530 Subject: [PATCH 3/3] test(embeddings): enhance 429 error message assertion to ensure format consistency --- src/openhuman/embeddings/openai_tests.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/openhuman/embeddings/openai_tests.rs b/src/openhuman/embeddings/openai_tests.rs index 573258f903..41d633a795 100644 --- a/src/openhuman/embeddings/openai_tests.rs +++ b/src/openhuman/embeddings/openai_tests.rs @@ -248,6 +248,16 @@ async fn embed_429_uses_canonical_transient_format() { msg.contains("(429 Too Many Requests)"), "expected canonical transient HTTP shape, got: {msg}" ); + // Pin the shape to the exact substring `is_transient_upstream_http_message` + // matches on (`"api error ( "`). The broader + // `is_transient_message_failure` classifier below also passes for the *old* + // `"Embedding API error 429 …"` format, so without this assertion a future + // refactor could silently revert the format and the test would still go + // green. + assert!( + msg.to_ascii_lowercase().contains("api error (429 "), + "message must match is_transient_upstream_http_message classifier arm: {msg}" + ); assert!( crate::core::observability::is_transient_message_failure(&msg), "message should classify as transient: {msg}"