api: proper error messages for connection failures and HTTP errors
- Connection errors now show cause (refused/timeout/request error), URL, and the underlying error without redundant URL repetition - HTTP errors show status code, URL, and up to 1000 chars of body - Unparseable SSE events logged with content preview instead of silently dropped — may contain error info from vllm/server - Stream errors already had good context (kept as-is) You can't debug what you can't see. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b1d83b55c0
commit
b28b7def19
2 changed files with 26 additions and 6 deletions
|
|
@ -138,7 +138,18 @@ pub(crate) async fn send_and_check(
|
||||||
.json(body)
|
.json(body)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to send request to API: {}", e))?;
|
.map_err(|e| {
|
||||||
|
let cause = if e.is_connect() {
|
||||||
|
"connection refused"
|
||||||
|
} else if e.is_timeout() {
|
||||||
|
"request timed out"
|
||||||
|
} else if e.is_request() {
|
||||||
|
"request error"
|
||||||
|
} else {
|
||||||
|
"unknown"
|
||||||
|
};
|
||||||
|
anyhow::anyhow!("{} ({}): {}", cause, url, e.without_url())
|
||||||
|
})?;
|
||||||
|
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
let elapsed = start.elapsed();
|
let elapsed = start.elapsed();
|
||||||
|
|
@ -164,12 +175,13 @@ pub(crate) async fn send_and_check(
|
||||||
if !status.is_success() {
|
if !status.is_success() {
|
||||||
let body = response.text().await.unwrap_or_default();
|
let body = response.text().await.unwrap_or_default();
|
||||||
let _ = ui_tx.send(UiMessage::Debug(format!(
|
let _ = ui_tx.send(UiMessage::Debug(format!(
|
||||||
"API error {} after {:.1}s: {}",
|
"HTTP {} after {:.1}s ({}): {}",
|
||||||
status,
|
status,
|
||||||
elapsed.as_secs_f64(),
|
elapsed.as_secs_f64(),
|
||||||
&body[..body.len().min(300)]
|
url,
|
||||||
|
&body[..body.len().min(500)]
|
||||||
)));
|
)));
|
||||||
anyhow::bail!("API error {}: {}", status, &body[..body.len().min(500)]);
|
anyhow::bail!("HTTP {} ({}): {}", status, url, &body[..body.len().min(1000)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
|
|
|
||||||
|
|
@ -79,9 +79,17 @@ pub async fn stream(
|
||||||
anyhow::bail!("API error in stream: {} {}", err_msg, raw);
|
anyhow::bail!("API error in stream: {} {}", err_msg, raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
let chunk: ChatCompletionChunk = match serde_json::from_value(event) {
|
let chunk: ChatCompletionChunk = match serde_json::from_value(event.clone()) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(_) => continue,
|
Err(e) => {
|
||||||
|
// Log unparseable events — they may contain error info
|
||||||
|
let preview = event.to_string();
|
||||||
|
let _ = ui_tx.send(UiMessage::Debug(format!(
|
||||||
|
"unparseable SSE event ({}): {}",
|
||||||
|
e, &preview[..preview.len().min(300)]
|
||||||
|
)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if chunk.usage.is_some() {
|
if chunk.usage.is_some() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue