open_ai: Classify Responses API send failures as transport errors (#62660)
OpenAI Responses API send failures were being converted into the catch-all `Other` error before they reached the language model layer. A DNS failure from the ChatGPT Subscription provider could therefore appear as an unexpected model error instead of using the existing transport-aware messaging and retry behavior. The shared Chat Completions transport already preserves this distinction. This change applies the same `RequestError::HttpSend` classification to Responses API streaming and compaction requests, allowing the existing conversion to produce `LanguageModelCompletionError::HttpSend` with the provider and underlying cause intact. Separate regression tests cover send failures in both request paths. Testing performed: - `cargo test -p open_ai reports_http_send_errors -- --nocapture` - `cargo test -p open_ai` - `cargo check -p openai_subscribed` - `cargo fmt --all --check` - `git diff --check` - `./script/clippy -p open_ai` Release Notes: - Fixed ChatGPT Subscription connection failures showing a generic error instead of a network-specific message.
This commit is contained in:
1 parent
477c81b92e
commit
1c9e06076c
1 file changed
+89
-2
@@ -743,7 +743,13 @@ pub async fn compact_response(
|
||||
))
|
||||
.map_err(|error| RequestError::Other(error.into()))?;
|
||||
|
||||
let mut response = client.send(request).await?;
|
||||
let mut response = client
|
||||
.send(request)
|
||||
.await
|
||||
.map_err(|error| RequestError::HttpSend {
|
||||
provider: provider_name.to_owned(),
|
||||
error,
|
||||
})?;
|
||||
let mut body = String::new();
|
||||
response
|
||||
.body_mut()
|
||||
@@ -784,7 +790,13 @@ pub async fn stream_response(
|
||||
))
|
||||
.map_err(|e| RequestError::Other(e.into()))?;
|
||||
|
||||
let mut response = client.send(request).await?;
|
||||
let mut response = client
|
||||
.send(request)
|
||||
.await
|
||||
.map_err(|error| RequestError::HttpSend {
|
||||
provider: provider_name.to_owned(),
|
||||
error,
|
||||
})?;
|
||||
if response.status().is_success() {
|
||||
if is_streaming {
|
||||
let reader = BufReader::new(response.into_body());
|
||||
@@ -1111,6 +1123,60 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_response_reports_http_send_errors() {
|
||||
let http_client =
|
||||
FakeHttpClient::create(|_| async move { Err(anyhow::anyhow!("DNS lookup failed")) });
|
||||
|
||||
let error = block_on(stream_response(
|
||||
http_client.as_ref(),
|
||||
"ChatGPT Subscription",
|
||||
"https://chatgpt.com/backend-api/codex",
|
||||
"secret",
|
||||
response_test_request(),
|
||||
&CustomHeaders::default(),
|
||||
));
|
||||
let error = match error {
|
||||
Ok(_) => panic!("expected request to fail"),
|
||||
Err(error) => language_model_core::LanguageModelCompletionError::from(error),
|
||||
};
|
||||
|
||||
match error {
|
||||
language_model_core::LanguageModelCompletionError::HttpSend { provider, error } => {
|
||||
assert_eq!(provider.0.as_ref(), "ChatGPT Subscription");
|
||||
assert_eq!(error.to_string(), "DNS lookup failed");
|
||||
}
|
||||
error => panic!("expected an HTTP send error, got {error:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compact_response_reports_http_send_errors() {
|
||||
let http_client =
|
||||
FakeHttpClient::create(|_| async move { Err(anyhow::anyhow!("DNS lookup failed")) });
|
||||
|
||||
let error = block_on(compact_response(
|
||||
http_client.as_ref(),
|
||||
"ChatGPT Subscription",
|
||||
"https://chatgpt.com/backend-api/codex",
|
||||
"secret",
|
||||
compact_test_request(),
|
||||
&CustomHeaders::default(),
|
||||
));
|
||||
let error = match error {
|
||||
Ok(_) => panic!("expected request to fail"),
|
||||
Err(error) => language_model_core::LanguageModelCompletionError::from(error),
|
||||
};
|
||||
|
||||
match error {
|
||||
language_model_core::LanguageModelCompletionError::HttpSend { provider, error } => {
|
||||
assert_eq!(provider.0.as_ref(), "ChatGPT Subscription");
|
||||
assert_eq!(error.to_string(), "DNS lookup failed");
|
||||
}
|
||||
error => panic!("expected an HTTP send error, got {error:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compacted_response_preserves_canonical_output_items() {
|
||||
let output = vec![
|
||||
@@ -1308,4 +1374,25 @@ mod tests {
|
||||
service_tier: Some(ServiceTier::Priority),
|
||||
}
|
||||
}
|
||||
|
||||
fn response_test_request() -> Request {
|
||||
Request {
|
||||
model: "gpt-5.4".to_string(),
|
||||
instructions: None,
|
||||
input: ResponseInput::new(Vec::new(), Vec::new()),
|
||||
include: Vec::new(),
|
||||
stream: true,
|
||||
temperature: None,
|
||||
top_p: None,
|
||||
max_output_tokens: None,
|
||||
parallel_tool_calls: None,
|
||||
tool_choice: None,
|
||||
tools: Vec::new(),
|
||||
prompt_cache_key: None,
|
||||
reasoning: None,
|
||||
store: None,
|
||||
service_tier: None,
|
||||
context_management: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user