play-faults fix: read the body as bytes so a cut body is a transport failure
reqwest's json() reports a body cut mid-stream as a decode error, so the is_decode branch in send_fault misclassed it BadReply. The body is now read with bytes() through send_fault (timeout / connect / no complete reply), and only a whole body that fails serde_json is classed by bad_reply(). Not yet built at this commit; verified by the following job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6db5d0873e
commit
5a876bf615
1 changed files with 18 additions and 12 deletions
|
|
@ -197,16 +197,18 @@ impl LocalInferencePort {
|
|||
}
|
||||
.classed(class));
|
||||
}
|
||||
response.json::<LocalChatResponse>().await.map_err(send_fault)
|
||||
let body = response.bytes().await.map_err(send_fault)?;
|
||||
serde_json::from_slice(&body).map_err(|_| bad_reply())
|
||||
}
|
||||
}
|
||||
|
||||
/// The one classifier of a send that failed before a status arrived or while
|
||||
/// its body was read. A timeout is not retried, because the endpoint may still
|
||||
/// be generating. A body that arrived whole but does not decode is `BadReply`,
|
||||
/// an integrity violation. Anything else means no complete reply came (a
|
||||
/// refused or closed connection, a body cut off mid-stream), so retrying is
|
||||
/// safe. The fault carries the kind and never the error's own text:
|
||||
/// The classifier of every transport failure of a send: one that failed before
|
||||
/// a status arrived, or while the reply body was read. A timeout is not
|
||||
/// retried, because the endpoint may still be generating. Anything else means
|
||||
/// no complete reply came (a refused or closed connection, a body cut off
|
||||
/// mid-stream), so retrying is safe. A body that arrived whole but is not the
|
||||
/// declared shape is not a transport failure: `bad_reply` below classes it.
|
||||
/// The fault carries the kind and never the error's own text:
|
||||
/// `reqwest::Error`'s `Display` ends with the request URL, so formatting it
|
||||
/// would put the endpoint into every log line and card that renders the
|
||||
/// detail. Tests pin this for each class.
|
||||
|
|
@ -214,11 +216,6 @@ fn send_fault(error: reqwest::Error) -> InferenceFault {
|
|||
if error.is_timeout() {
|
||||
InferenceFault::new("the local inference request timed out")
|
||||
.classed(InferenceFaultClass::Timeout)
|
||||
} else if error.is_decode() {
|
||||
InferenceFault::integrity_violation(
|
||||
"the local inference endpoint's reply was not the declared shape",
|
||||
)
|
||||
.classed(InferenceFaultClass::BadReply)
|
||||
} else if error.is_connect() {
|
||||
InferenceFault::retryable("the local inference endpoint refused the connection")
|
||||
.classed(InferenceFaultClass::Connect)
|
||||
|
|
@ -228,6 +225,15 @@ fn send_fault(error: reqwest::Error) -> InferenceFault {
|
|||
}
|
||||
}
|
||||
|
||||
/// A complete reply body that is not the declared shape: an integrity
|
||||
/// violation, never retried. The decode error's text is not carried.
|
||||
fn bad_reply() -> InferenceFault {
|
||||
InferenceFault::integrity_violation(
|
||||
"the local inference endpoint's reply was not the declared shape",
|
||||
)
|
||||
.classed(InferenceFaultClass::BadReply)
|
||||
}
|
||||
|
||||
/// Lowers one prepared request to the OpenAI chat-completions body: the
|
||||
/// system message is the request's instructions, every input item maps to
|
||||
/// one message in order, and every offered tool is declared `strict`. No
|
||||
|
|
|
|||
Loading…
Reference in a new issue