Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
A
aisbf
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexlab
aisbf
Commits
08187ec4
Commit
08187ec4
authored
Jul 25, 2026
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'fix/request-scoped-errors-no-cooldown'
parents
28e9643d
fae38ea0
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
79 additions
and
5 deletions
+79
-5
handlers.py
aisbf/handlers.py
+13
-2
base.py
aisbf/providers/base.py
+47
-0
kilo.py
aisbf/providers/kilo.py
+19
-3
No files found.
aisbf/handlers.py
View file @
08187ec4
...
@@ -39,6 +39,7 @@ from fastapi import HTTPException, Request
...
@@ -39,6 +39,7 @@ from fastapi import HTTPException, Request
from
fastapi.responses
import
JSONResponse
,
StreamingResponse
,
Response
from
fastapi.responses
import
JSONResponse
,
StreamingResponse
,
Response
from
.models
import
ChatCompletionRequest
,
ChatCompletionResponse
from
.models
import
ChatCompletionRequest
,
ChatCompletionResponse
from
.providers
import
get_provider_handler
,
RateLimitError
from
.providers
import
get_provider_handler
,
RateLimitError
from
.providers.base
import
is_request_scoped_error
from
.config
import
config
from
.config
import
config
from
.studio
import
infer_model_capabilities
from
.studio
import
infer_model_capabilities
from
.studio_adapters
import
effective_studio_adapter
,
infer_studio_adapter_profile
,
adapt_studio_payload_with_profile
from
.studio_adapters
import
effective_studio_adapter
,
infer_studio_adapter_profile
,
adapt_studio_payload_with_profile
...
@@ -97,8 +98,18 @@ def _is_upstream_rate_limit(error) -> bool:
...
@@ -97,8 +98,18 @@ def _is_upstream_rate_limit(error) -> bool:
def
_should_record_failure
(
error
)
->
bool
:
def
_should_record_failure
(
error
)
->
bool
:
"""Whether an exception should count against the provider's failure budget."""
"""Whether an exception should count against the provider's failure budget.
return
not
_is_coderai_warmup_error
(
error
)
and
not
_is_upstream_rate_limit
(
error
)
Excludes coderai warm-up errors, upstream rate limits, and request-scoped
errors (bad input / unsupported capability such as a 404 "No endpoints found
that support image input") — none of which indicate the provider is unhealthy,
so they must not trip the cooldown that disables it for all traffic.
"""
return
(
not
_is_coderai_warmup_error
(
error
)
and
not
_is_upstream_rate_limit
(
error
)
and
not
is_request_scoped_error
(
error
)
)
def
_raise_if_provider_unavailable
(
handler
,
provider_id
:
str
)
->
None
:
def
_raise_if_provider_unavailable
(
handler
,
provider_id
:
str
)
->
None
:
...
...
aisbf/providers/base.py
View file @
08187ec4
...
@@ -193,6 +193,53 @@ class RateLimitError(Exception):
...
@@ -193,6 +193,53 @@ class RateLimitError(Exception):
self
.
status_code
=
status_code
self
.
status_code
=
status_code
# HTTP statuses that describe a problem with THIS request — bad input, an
# unsupported capability for the chosen model, or a rate limit — rather than the
# provider being unhealthy. They must not count toward the consecutive-failure
# budget that disables the whole provider: otherwise a handful of request-scoped
# errors (e.g. a model that lacks an image endpoint returning 404 "No endpoints
# found that support image input", or a bad image returning 400) take the whole
# provider offline for every request for the cooldown window.
_REQUEST_SCOPED_STATUSES
=
frozenset
({
400
,
404
,
413
,
415
,
422
,
429
})
def
is_request_scoped_status
(
status
)
->
bool
:
"""True when an HTTP status code is about the request, not provider health."""
try
:
return
int
(
status
)
in
_REQUEST_SCOPED_STATUSES
except
(
TypeError
,
ValueError
):
return
False
def
extract_http_status
(
error
)
->
Optional
[
int
]:
"""Best-effort HTTP status code from a provider exception."""
status
=
getattr
(
error
,
'status_code'
,
None
)
if
status
is
None
:
status
=
getattr
(
getattr
(
error
,
'response'
,
None
),
'status_code'
,
None
)
try
:
return
int
(
status
)
if
status
is
not
None
else
None
except
(
TypeError
,
ValueError
):
return
None
def
is_request_scoped_error
(
error
)
->
bool
:
"""True when the error is about the specific request (bad input / unsupported
capability / rate limit) rather than the provider's health, so it must not
count toward the failure cooldown that disables the whole provider.
Falls back to message sniffing for handlers that wrap the upstream error in a
plain Exception without preserving the status code (e.g. the streaming path).
"""
if
is_request_scoped_status
(
extract_http_status
(
error
)):
return
True
text
=
str
(
error
or
''
)
return
(
'No endpoints found that support image input'
in
text
or
'Cannot decode visions'
in
text
or
'429 Too Many Requests'
in
text
)
class
AnthropicFormatConverter
:
class
AnthropicFormatConverter
:
"""
"""
Shared utility class for converting between OpenAI and Anthropic message formats.
Shared utility class for converting between OpenAI and Anthropic message formats.
...
...
aisbf/providers/kilo.py
View file @
08187ec4
...
@@ -411,8 +411,19 @@ class KiloProviderHandler(BaseProviderHandler):
...
@@ -411,8 +411,19 @@ class KiloProviderHandler(BaseProviderHandler):
return
response
return
response
except
Exception
as
e
:
except
Exception
as
e
:
import
logging
import
logging
from
.base
import
is_request_scoped_error
,
extract_http_status
logging
.
error
(
f
"KiloProviderHandler: Error: {str(e)}"
,
exc_info
=
True
)
logging
.
error
(
f
"KiloProviderHandler: Error: {str(e)}"
,
exc_info
=
True
)
self
.
record_failure
()
# A per-request error (bad input, or the chosen model lacking an image
# endpoint → 404 "No endpoints found that support image input", or a
# 429 rate limit) says nothing about provider health. Counting it toward
# the three-strike cooldown would take kilo offline for every request.
if
is_request_scoped_error
(
e
):
logging
.
warning
(
f
"KiloProviderHandler: request-scoped error (status={extract_http_status(e)}) "
f
"not counted toward the provider failure budget"
)
else
:
self
.
record_failure
()
raise
e
raise
e
async
def
_handle_streaming_request
(
self
,
request_params
:
Dict
,
token
:
str
,
model
:
str
):
async
def
_handle_streaming_request
(
self
,
request_params
:
Dict
,
token
:
str
,
model
:
str
):
...
@@ -462,8 +473,13 @@ class KiloProviderHandler(BaseProviderHandler):
...
@@ -462,8 +473,13 @@ class KiloProviderHandler(BaseProviderHandler):
error_json
if
'error_json'
in
locals
()
else
error_message
,
error_json
if
'error_json'
in
locals
()
else
error_message
,
dict
(
response
.
headers
)
dict
(
response
.
headers
)
)
)
self
.
record_failure
()
# Request-scoped statuses (bad input, unsupported capability, rate
# limit) must not trip the provider failure cooldown — see
# is_request_scoped_status. Only genuine provider faults count.
from
.base
import
is_request_scoped_status
if
not
is_request_scoped_status
(
response
.
status_code
):
self
.
record_failure
()
raise
Exception
(
f
"Kilo API streaming error ({response.status_code}): {error_message}"
)
raise
Exception
(
f
"Kilo API streaming error ({response.status_code}): {error_message}"
)
except
Exception
:
except
Exception
:
await
streaming_client
.
aclose
()
await
streaming_client
.
aclose
()
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment