save request payload on any API error, not just timeouts
Serialize request JSON before send_and_check so it's available for both HTTP errors and stream errors. Extracted save logic into save_failed_request helper on SseReader. Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
64dbcbf061
commit
e0a54a3b43
2 changed files with 40 additions and 22 deletions
|
|
@ -192,6 +192,7 @@ pub(crate) async fn send_and_check(
|
||||||
extra_headers: &[(&str, &str)],
|
extra_headers: &[(&str, &str)],
|
||||||
ui_tx: &UiSender,
|
ui_tx: &UiSender,
|
||||||
debug_label: &str,
|
debug_label: &str,
|
||||||
|
request_json: Option<&str>,
|
||||||
) -> Result<reqwest::Response> {
|
) -> Result<reqwest::Response> {
|
||||||
let debug = std::env::var("POC_DEBUG").is_ok();
|
let debug = std::env::var("POC_DEBUG").is_ok();
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
@ -262,6 +263,18 @@ pub(crate) async fn send_and_check(
|
||||||
url,
|
url,
|
||||||
&body[..body.len().min(500)]
|
&body[..body.len().min(500)]
|
||||||
)));
|
)));
|
||||||
|
if let Some(json) = request_json {
|
||||||
|
let log_dir = dirs::home_dir()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.join(".consciousness/logs");
|
||||||
|
let ts = chrono::Local::now().format("%Y%m%dT%H%M%S");
|
||||||
|
let path = log_dir.join(format!("failed-request-{}.json", ts));
|
||||||
|
if std::fs::write(&path, json).is_ok() {
|
||||||
|
let _ = ui_tx.send(UiMessage::Debug(format!(
|
||||||
|
"saved failed request to {} (HTTP {})", path.display(), status
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
anyhow::bail!("HTTP {} ({}): {}", status, url, &body[..body.len().min(1000)]);
|
anyhow::bail!("HTTP {} ({}): {}", status, url, &body[..body.len().min(1000)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,8 +304,8 @@ pub(crate) struct SseReader {
|
||||||
debug: bool,
|
debug: bool,
|
||||||
ui_tx: UiSender,
|
ui_tx: UiSender,
|
||||||
done: bool,
|
done: bool,
|
||||||
/// Serialized request payload — saved to disk on timeout for replay debugging.
|
/// Serialized request payload — saved to disk on errors for replay debugging.
|
||||||
request_json: Option<String>,
|
pub(crate) request_json: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseReader {
|
impl SseReader {
|
||||||
|
|
@ -312,8 +325,19 @@ impl SseReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attach the serialized request payload for error diagnostics.
|
/// Attach the serialized request payload for error diagnostics.
|
||||||
pub fn set_request(&mut self, request: &impl serde::Serialize) {
|
/// Save the request payload to disk for replay debugging.
|
||||||
self.request_json = serde_json::to_string_pretty(request).ok();
|
fn save_failed_request(&self, reason: &str) {
|
||||||
|
let Some(ref json) = self.request_json else { return };
|
||||||
|
let log_dir = dirs::home_dir()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.join(".consciousness/logs");
|
||||||
|
let ts = chrono::Local::now().format("%Y%m%dT%H%M%S");
|
||||||
|
let path = log_dir.join(format!("failed-request-{}.json", ts));
|
||||||
|
if std::fs::write(&path, json).is_ok() {
|
||||||
|
let _ = self.ui_tx.send(UiMessage::Debug(format!(
|
||||||
|
"saved failed request to {} ({})", path.display(), reason
|
||||||
|
)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the next SSE event from the response stream.
|
/// Read the next SSE event from the response stream.
|
||||||
|
|
@ -374,27 +398,19 @@ impl SseReader {
|
||||||
self.line_buf.push_str(&String::from_utf8_lossy(&chunk));
|
self.line_buf.push_str(&String::from_utf8_lossy(&chunk));
|
||||||
}
|
}
|
||||||
Ok(Ok(None)) => return Ok(None),
|
Ok(Ok(None)) => return Ok(None),
|
||||||
Ok(Err(e)) => return Err(e.into()),
|
Ok(Err(e)) => {
|
||||||
|
self.save_failed_request(&format!("stream error: {}", e));
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let _ = self.ui_tx.send(UiMessage::Debug(format!(
|
let msg = format!(
|
||||||
"TIMEOUT: no data for {}s ({} chunks, {:.1}s elapsed)",
|
"stream timeout: no data for {}s ({} chunks, {:.1}s elapsed)",
|
||||||
self.chunk_timeout.as_secs(),
|
self.chunk_timeout.as_secs(),
|
||||||
self.chunks_received,
|
self.chunks_received,
|
||||||
self.stream_start.elapsed().as_secs_f64()
|
self.stream_start.elapsed().as_secs_f64()
|
||||||
)));
|
);
|
||||||
// Save the request for replay debugging
|
let _ = self.ui_tx.send(UiMessage::Debug(msg.clone()));
|
||||||
if let Some(ref json) = self.request_json {
|
self.save_failed_request(&msg);
|
||||||
let log_dir = dirs::home_dir()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.join(".consciousness/logs");
|
|
||||||
let ts = chrono::Local::now().format("%Y%m%dT%H%M%S");
|
|
||||||
let path = log_dir.join(format!("failed-request-{}.json", ts));
|
|
||||||
if std::fs::write(&path, json).is_ok() {
|
|
||||||
let _ = self.ui_tx.send(UiMessage::Debug(format!(
|
|
||||||
"saved failed request to {}", path.display()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"stream timeout: no data for {}s ({} chunks received)",
|
"stream timeout: no data for {}s ({} chunks received)",
|
||||||
self.chunk_timeout.as_secs(),
|
self.chunk_timeout.as_secs(),
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ pub async fn stream_events(
|
||||||
None => String::new(),
|
None => String::new(),
|
||||||
};
|
};
|
||||||
let debug_label = format!("{} messages, model={}{}", msg_count, model, pri_label);
|
let debug_label = format!("{} messages, model={}{}", msg_count, model, pri_label);
|
||||||
|
let request_json = serde_json::to_string_pretty(&request).ok();
|
||||||
|
|
||||||
let mut response = super::send_and_check(
|
let mut response = super::send_and_check(
|
||||||
client,
|
client,
|
||||||
|
|
@ -64,11 +65,12 @@ pub async fn stream_events(
|
||||||
&[],
|
&[],
|
||||||
ui_tx,
|
ui_tx,
|
||||||
&debug_label,
|
&debug_label,
|
||||||
|
request_json.as_deref(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut reader = super::SseReader::new(ui_tx);
|
let mut reader = super::SseReader::new(ui_tx);
|
||||||
reader.set_request(&request);
|
reader.request_json = request_json;
|
||||||
|
|
||||||
let mut content_len: usize = 0;
|
let mut content_len: usize = 0;
|
||||||
let mut reasoning_chars: usize = 0;
|
let mut reasoning_chars: usize = 0;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue