mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-07 08:27:12 +00:00
refactor(llm): tighten provider error classification
This commit is contained in:
parent
dc46d183b0
commit
97aeb5631d
4 changed files with 39 additions and 16 deletions
|
|
@ -8,7 +8,7 @@
|
|||
use super::SYNTHETIC_TOOL_NAME;
|
||||
use super::decode::{convert_synthetic_tool_to_text, map_finish_reason, refusal_error};
|
||||
use crate::codec::{RawEvent, StreamDecoder, parse_tool_arguments_or_empty};
|
||||
use crate::error::{Error, ProviderErrorDetail, ProviderErrorKind, kind_from_error_code};
|
||||
use crate::error::{self, Error, ProviderErrorDetail, ProviderErrorKind};
|
||||
use crate::types::{
|
||||
ContentPart, FinishReason, Message, RateLimitInfo, Response, Role, StreamEvent, ThinkingData,
|
||||
TokenCounts, ToolCall,
|
||||
|
|
@ -358,7 +358,7 @@ fn stream_error_event_to_provider_error(data: &serde_json::Value, provider_name:
|
|||
// overloaded_error, api_error, and unknown stream errors are transient.
|
||||
let kind = error_code
|
||||
.as_deref()
|
||||
.and_then(kind_from_error_code)
|
||||
.and_then(error::kind_from_error_code)
|
||||
.unwrap_or(ProviderErrorKind::Server);
|
||||
|
||||
Error::Provider {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use serde::Deserialize;
|
|||
use super::decode::{map_finish_reason, token_counts_from_api_usage, tool_call_from_item};
|
||||
use super::wire::ApiUsage;
|
||||
use crate::codec::{CodecCtx, RawEvent, StreamDecoder};
|
||||
use crate::error::{Error, ProviderErrorDetail, ProviderErrorKind, kind_from_error_code};
|
||||
use crate::error::{self, Error, ProviderErrorDetail, ProviderErrorKind};
|
||||
use crate::types::{
|
||||
ContentPart, FinishReason, Message, RateLimitInfo, Response, Role, StreamEvent, TokenCounts,
|
||||
ToolCall,
|
||||
|
|
@ -38,7 +38,7 @@ fn provider_error_from_openai_error_json(error: &serde_json::Value, provider: &s
|
|||
|
||||
// Unrecognized and absent codes are treated as transient.
|
||||
let kind = classifier
|
||||
.and_then(kind_from_error_code)
|
||||
.and_then(error::kind_from_error_code)
|
||||
.unwrap_or(ProviderErrorKind::Server);
|
||||
|
||||
Error::Provider {
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ impl Error {
|
|||
/// `insufficient_quota` means the same thing whether it arrives in an HTTP
|
||||
/// error body or in a mid-stream error event.
|
||||
#[must_use]
|
||||
pub fn kind_from_error_code(code: &str) -> Option<ProviderErrorKind> {
|
||||
pub(crate) fn kind_from_error_code(code: &str) -> Option<ProviderErrorKind> {
|
||||
Some(match code {
|
||||
// Out of credit, or over a billing cap. Distinct from RateLimit:
|
||||
// backoff never clears it, but another provider has its own quota.
|
||||
|
|
@ -726,17 +726,6 @@ mod tests {
|
|||
None,
|
||||
);
|
||||
assert_eq!(err.provider_kind(), Some(ProviderErrorKind::QuotaExceeded));
|
||||
|
||||
// No code, so the message fallback still runs.
|
||||
let err = error_from_status_code(
|
||||
400,
|
||||
"This model's maximum context length is 4096 tokens".into(),
|
||||
"openai".into(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert_eq!(err.provider_kind(), Some(ProviderErrorKind::ContextLength));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -520,6 +520,40 @@ mod tests {
|
|||
assert!(matches!(err, Error::Configuration { .. }));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn complete_classifies_insufficient_quota_as_quota_exceeded() {
|
||||
let server = MockServer::start();
|
||||
let mock = server.mock(|when, then| {
|
||||
when.method(POST).path("/responses");
|
||||
then.status(429)
|
||||
.header("content-type", "application/json")
|
||||
.json_body(serde_json::json!({
|
||||
"error": {
|
||||
"message": "You exceeded your current quota.",
|
||||
"type": "insufficient_quota"
|
||||
}
|
||||
}));
|
||||
});
|
||||
let adapter = Adapter::new("sk-test").with_base_url(server.base_url());
|
||||
|
||||
let err = adapter
|
||||
.complete(&minimal_request())
|
||||
.await
|
||||
.expect_err("spent quota should fail the completion");
|
||||
|
||||
mock.assert();
|
||||
assert_eq!(err.provider_kind(), Some(ProviderErrorKind::QuotaExceeded));
|
||||
assert_eq!(err.status_code(), Some(429));
|
||||
assert!(!err.retryable());
|
||||
assert!(err.failover_eligible());
|
||||
match err {
|
||||
Error::Provider { detail, .. } => {
|
||||
assert_eq!(detail.error_code.as_deref(), Some("insufficient_quota"));
|
||||
}
|
||||
other => panic!("expected provider error, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn codex_complete_via_stream_propagates_stream_errors() {
|
||||
let server = MockServer::start();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue