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
e6065d17
Commit
e6065d17
authored
Jul 21, 2026
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update
parent
da972924
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
155 additions
and
16 deletions
+155
-16
base.py
aisbf/providers/base.py
+101
-0
codex.py
aisbf/providers/codex.py
+27
-8
openai.py
aisbf/providers/openai.py
+27
-8
aisbf-oauth2-extension.zip
static/aisbf-oauth2-extension.zip
+0
-0
No files found.
aisbf/providers/base.py
View file @
e6065d17
...
...
@@ -35,6 +35,107 @@ from ..batching import get_request_batcher
AISBF_DEBUG
=
os
.
environ
.
get
(
'AISBF_DEBUG'
,
''
)
.
lower
()
in
(
'true'
,
'1'
,
'yes'
)
# Newer OpenAI models (reasoning models and the GPT-5/codex families) rejected the
# legacy `max_tokens` parameter and only accept `max_completion_tokens`. Older
# models (gpt-4o, gpt-4.1, gpt-3.5, and most OpenAI-compatible third-party
# endpoints) still expect `max_tokens`, so we cannot simply rename it everywhere.
_MAX_COMPLETION_TOKENS_PREFIXES
=
(
'o1'
,
'o3'
,
'o4'
,
'gpt-5'
,
'codex-'
,
'gpt-image'
,
)
# Models discovered at runtime to reject a parameter, so the retry below is paid
# at most once per model name per process.
_max_completion_tokens_models
=
set
()
_no_temperature_models
=
set
()
def
_normalize_model_name
(
model
:
str
)
->
str
:
"""Strip vendor/route prefixes ('openai/gpt-5', 'azure/o3') and date suffixes."""
name
=
(
model
or
''
)
.
strip
()
.
lower
()
if
'/'
in
name
:
name
=
name
.
rsplit
(
'/'
,
1
)[
1
]
return
name
def
model_requires_max_completion_tokens
(
model
:
str
)
->
bool
:
name
=
_normalize_model_name
(
model
)
if
name
in
_max_completion_tokens_models
:
return
True
return
name
.
startswith
(
_MAX_COMPLETION_TOKENS_PREFIXES
)
def
apply_max_tokens_param
(
request_params
:
Dict
,
model
:
str
,
max_tokens
:
Optional
[
int
])
->
None
:
"""Set the output-token limit under whichever name the model accepts."""
if
max_tokens
is
None
:
return
if
model_requires_max_completion_tokens
(
model
):
request_params
[
'max_completion_tokens'
]
=
max_tokens
else
:
request_params
[
'max_tokens'
]
=
max_tokens
def
apply_temperature_param
(
request_params
:
Dict
,
model
:
str
,
temperature
:
Optional
[
float
])
->
None
:
"""Set `temperature` only when the model accepts the requested value.
Reasoning models accept the default (1) but reject any other value, and some
deployments reject the parameter outright. In both cases we omit it rather
than fail the request — the upstream then applies its own default, which is
exactly the value those models insist on.
"""
if
temperature
is
None
:
return
if
model_rejects_temperature
(
model
,
temperature
):
import
logging
logging
.
warning
(
f
"Model '{model}' does not accept temperature={temperature}; "
f
"omitting it, so the upstream default (1) applies instead"
)
return
request_params
[
'temperature'
]
=
temperature
def
model_rejects_temperature
(
model
:
str
,
temperature
:
Optional
[
float
])
->
bool
:
name
=
_normalize_model_name
(
model
)
if
name
in
_no_temperature_models
:
return
True
if
temperature
is
None
or
float
(
temperature
)
==
1.0
:
return
False
return
name
.
startswith
(
_MAX_COMPLETION_TOKENS_PREFIXES
)
def
adapt_request_for_unsupported_param
(
request_params
:
Dict
,
model
:
str
,
exc
:
Exception
)
->
Optional
[
str
]:
"""Rewrite a request that the upstream rejected over an unsupported parameter.
The prefix heuristics above cannot know every model that dropped support
(new releases, custom deployments, proxies), so we also learn from the error
and remember the model, making the retry a once-per-model cost.
Returns a short description of what was changed, or None when the error is
unrelated — in which case the caller must re-raise instead of retrying.
"""
message
=
str
(
exc
)
.
lower
()
name
=
_normalize_model_name
(
model
)
if
'max_completion_tokens'
in
message
and
'max_tokens'
in
request_params
:
request_params
[
'max_completion_tokens'
]
=
request_params
.
pop
(
'max_tokens'
)
_max_completion_tokens_models
.
add
(
name
)
return
'max_tokens -> max_completion_tokens'
if
(
'temperature'
in
message
and
'temperature'
in
request_params
and
(
'not supported'
in
message
or
'unsupported'
in
message
or
'does not support'
in
message
)):
dropped
=
request_params
.
pop
(
'temperature'
)
_no_temperature_models
.
add
(
name
)
import
logging
logging
.
warning
(
f
"Model '{model}' rejected temperature={dropped}; retrying without it, "
f
"so the upstream default applies instead"
)
return
'dropped temperature'
return
None
def
is_provider_disabled_cheap
(
provider_id
:
str
,
user_id
:
Optional
[
int
]
=
None
)
->
bool
:
"""Return True if a provider is currently disabled — either manually (via the
dashboard toggle) or by an auto-disable cooldown — WITHOUT constructing the
...
...
aisbf/providers/codex.py
View file @
e6065d17
...
...
@@ -33,7 +33,13 @@ import httpx
from
..models
import
Model
from
..config
import
config
from
..utils
import
count_messages_tokens
from
.base
import
BaseProviderHandler
,
AISBF_DEBUG
from
.base
import
(
BaseProviderHandler
,
AISBF_DEBUG
,
adapt_request_for_unsupported_param
,
apply_max_tokens_param
,
apply_temperature_param
,
)
from
..auth.codex
import
CodexOAuth2
logger
=
logging
.
getLogger
(
__name__
)
...
...
@@ -286,13 +292,13 @@ class CodexProviderHandler(BaseProviderHandler):
request_params
=
{
"model"
:
model
,
"messages"
:
[],
"temperature"
:
temperature
,
"stream"
:
stream
}
# Only add max_tokens if it's not None
if
max_tokens
is
not
None
:
request_params
[
"max_tokens"
]
=
max_tokens
# Add temperature and the output-token limit only in the form this
# model accepts (see the helpers for why they differ per model).
apply_temperature_param
(
request_params
,
model
,
temperature
)
apply_max_tokens_param
(
request_params
,
model
,
max_tokens
)
# Build messages with all fields
for
msg
in
messages
:
...
...
@@ -318,6 +324,19 @@ class CodexProviderHandler(BaseProviderHandler):
if
tool_choice
is
not
None
:
request_params
[
"tool_choice"
]
=
tool_choice
# Retry once per adaptable parameter (max_tokens, temperature) when the
# upstream rejects it; adapt_request_for_unsupported_param returns None
# for any other error, which we re-raise.
for
_
in
range
(
2
):
try
:
response
=
self
.
client
.
chat
.
completions
.
create
(
**
request_params
)
break
except
Exception
as
e
:
adaptation
=
adapt_request_for_unsupported_param
(
request_params
,
model
,
e
)
if
adaptation
is
None
:
raise
logger
.
info
(
f
"CodexProviderHandler: {model} rejected a parameter, retrying ({adaptation})"
)
else
:
response
=
self
.
client
.
chat
.
completions
.
create
(
**
request_params
)
return
response
...
...
aisbf/providers/openai.py
View file @
e6065d17
...
...
@@ -26,7 +26,13 @@ from openai import OpenAI
from
..models
import
Model
from
..config
import
config
from
..utils
import
count_messages_tokens
from
.base
import
BaseProviderHandler
,
AISBF_DEBUG
from
.base
import
(
BaseProviderHandler
,
AISBF_DEBUG
,
adapt_request_for_unsupported_param
,
apply_max_tokens_param
,
apply_temperature_param
,
)
class
OpenAIProviderHandler
(
BaseProviderHandler
):
...
...
@@ -90,13 +96,13 @@ class OpenAIProviderHandler(BaseProviderHandler):
request_params
=
{
"model"
:
model
,
"messages"
:
[],
"temperature"
:
temperature
,
"stream"
:
stream
}
# Only add max_tokens if it's not None
if
max_tokens
is
not
None
:
request_params
[
"max_tokens"
]
=
max_tokens
# Add temperature and the output-token limit only in the form this
# model accepts (see the helpers for why they differ per model).
apply_temperature_param
(
request_params
,
model
,
temperature
)
apply_max_tokens_param
(
request_params
,
model
,
max_tokens
)
# Add prompt_cache_key if provided (for OpenAI's load balancer routing optimization)
if
enable_native_caching
and
prompt_cache_key
:
...
...
@@ -167,6 +173,19 @@ class OpenAIProviderHandler(BaseProviderHandler):
if
tool_choice
is
not
None
:
request_params
[
"tool_choice"
]
=
tool_choice
# Retry once per adaptable parameter (max_tokens, temperature) when
# the upstream rejects it; adapt_request_for_unsupported_param
# returns None for any other error, which we re-raise.
for
_
in
range
(
2
):
try
:
response
=
self
.
client
.
chat
.
completions
.
create
(
**
request_params
)
break
except
Exception
as
e
:
adaptation
=
adapt_request_for_unsupported_param
(
request_params
,
model
,
e
)
if
adaptation
is
None
:
raise
logging
.
info
(
f
"OpenAIProviderHandler: {model} rejected a parameter, retrying ({adaptation})"
)
else
:
response
=
self
.
client
.
chat
.
completions
.
create
(
**
request_params
)
logging
.
info
(
f
"OpenAIProviderHandler: Response received: {response}"
)
# Streaming returns a lazy iterator; the upstream call has not been
...
...
static/aisbf-oauth2-extension.zip
View file @
e6065d17
No preview for this file type
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