Spaces:
Runtime error
Runtime error
refactor rate limiting logic to simplify API key handling and remove OpenWebUI specific checks
Browse files- src/main.py +12 -36
src/main.py
CHANGED
|
@@ -414,29 +414,20 @@ async def rate_limit_api_key(key: str = Depends(API_KEY_HEADER)):
|
|
| 414 |
api_key_str = key[7:].strip()
|
| 415 |
config = get_config()
|
| 416 |
|
| 417 |
-
|
| 418 |
-
# If so, strip it for authentication but preserve it in the returned key_data
|
| 419 |
-
base_api_key = api_key_str
|
| 420 |
-
is_openwebui_mode = api_key_str.endswith("-openwebui")
|
| 421 |
-
if is_openwebui_mode:
|
| 422 |
-
base_api_key = api_key_str[:-11] # Remove "-openwebui" suffix (11 characters)
|
| 423 |
-
debug_print(f"๐ OpenWebUI mode detected - stripped suffix from key")
|
| 424 |
-
|
| 425 |
-
# Look up the key without the suffix
|
| 426 |
-
key_data = next((k for k in config["api_keys"] if k["key"] == base_api_key), None)
|
| 427 |
if not key_data:
|
| 428 |
raise HTTPException(status_code=401, detail="Invalid API Key.")
|
| 429 |
|
| 430 |
-
# Rate Limiting
|
| 431 |
rate_limit = key_data.get("rpm", 60)
|
| 432 |
current_time = time.time()
|
| 433 |
|
| 434 |
# Clean up old timestamps (older than 60 seconds)
|
| 435 |
-
api_key_usage[
|
| 436 |
|
| 437 |
-
if len(api_key_usage[
|
| 438 |
# Calculate seconds until oldest request expires (60 seconds window)
|
| 439 |
-
oldest_timestamp = min(api_key_usage[
|
| 440 |
retry_after = int(60 - (current_time - oldest_timestamp))
|
| 441 |
retry_after = max(1, retry_after) # At least 1 second
|
| 442 |
|
|
@@ -446,11 +437,9 @@ async def rate_limit_api_key(key: str = Depends(API_KEY_HEADER)):
|
|
| 446 |
headers={"Retry-After": str(retry_after)}
|
| 447 |
)
|
| 448 |
|
| 449 |
-
api_key_usage[
|
| 450 |
|
| 451 |
-
|
| 452 |
-
# This allows downstream code to check for OpenWebUI mode
|
| 453 |
-
return {**key_data, "key": api_key_str}
|
| 454 |
|
| 455 |
# --- Core Logic ---
|
| 456 |
|
|
@@ -1394,26 +1383,14 @@ async def health_check():
|
|
| 1394 |
async def list_models(api_key: dict = Depends(rate_limit_api_key)):
|
| 1395 |
models = get_models()
|
| 1396 |
|
| 1397 |
-
# Check if API key ends with -openwebui
|
| 1398 |
-
api_key_str = api_key.get("key", "")
|
| 1399 |
-
is_openwebui = api_key_str.endswith("-openwebui")
|
| 1400 |
-
|
| 1401 |
-
debug_print(f"๐ API key check: ends with -openwebui = {is_openwebui}")
|
| 1402 |
-
|
| 1403 |
# Filter for models with text OR search OR image output capability and an organization (exclude stealth models)
|
| 1404 |
-
#
|
| 1405 |
valid_models = [m for m in models
|
| 1406 |
if (m.get('capabilities', {}).get('outputCapabilities', {}).get('text')
|
| 1407 |
or m.get('capabilities', {}).get('outputCapabilities', {}).get('search')
|
| 1408 |
-
or
|
| 1409 |
and m.get('organization')]
|
| 1410 |
|
| 1411 |
-
# Log image models when using openwebui key
|
| 1412 |
-
if is_openwebui:
|
| 1413 |
-
image_models = [m.get("publicName") for m in valid_models
|
| 1414 |
-
if m.get('capabilities', {}).get('outputCapabilities', {}).get('image')]
|
| 1415 |
-
debug_print(f"๐ผ๏ธ Image models available for OpenWebUI key: {image_models}")
|
| 1416 |
-
|
| 1417 |
return {
|
| 1418 |
"object": "list",
|
| 1419 |
"data": [
|
|
@@ -2132,11 +2109,10 @@ async def api_chat_completions(request: Request, api_key: dict = Depends(rate_li
|
|
| 2132 |
unique_citations.append(citation)
|
| 2133 |
message_obj["citations"] = unique_citations
|
| 2134 |
|
| 2135 |
-
# For
|
| 2136 |
-
is_openwebui = api_key_str.endswith("-openwebui")
|
| 2137 |
is_image_model = modality == "image"
|
| 2138 |
|
| 2139 |
-
if
|
| 2140 |
debug_print(f"๐ผ๏ธ Converting image URL to base64 for OpenWebUI compatibility")
|
| 2141 |
try:
|
| 2142 |
# Download the image
|
|
@@ -2153,7 +2129,7 @@ async def api_chat_completions(request: Request, api_key: dict = Depends(rate_li
|
|
| 2153 |
# Create data URL
|
| 2154 |
data_url = f"data:{content_type};base64,{img_base64}"
|
| 2155 |
|
| 2156 |
-
# Format as markdown
|
| 2157 |
message_obj["content"] = f""
|
| 2158 |
|
| 2159 |
debug_print(f"โ
Image converted to base64 data URL ({len(img_base64)} chars)")
|
|
|
|
| 414 |
api_key_str = key[7:].strip()
|
| 415 |
config = get_config()
|
| 416 |
|
| 417 |
+
key_data = next((k for k in config["api_keys"] if k["key"] == api_key_str), None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
if not key_data:
|
| 419 |
raise HTTPException(status_code=401, detail="Invalid API Key.")
|
| 420 |
|
| 421 |
+
# Rate Limiting
|
| 422 |
rate_limit = key_data.get("rpm", 60)
|
| 423 |
current_time = time.time()
|
| 424 |
|
| 425 |
# Clean up old timestamps (older than 60 seconds)
|
| 426 |
+
api_key_usage[api_key_str] = [t for t in api_key_usage[api_key_str] if current_time - t < 60]
|
| 427 |
|
| 428 |
+
if len(api_key_usage[api_key_str]) >= rate_limit:
|
| 429 |
# Calculate seconds until oldest request expires (60 seconds window)
|
| 430 |
+
oldest_timestamp = min(api_key_usage[api_key_str])
|
| 431 |
retry_after = int(60 - (current_time - oldest_timestamp))
|
| 432 |
retry_after = max(1, retry_after) # At least 1 second
|
| 433 |
|
|
|
|
| 437 |
headers={"Retry-After": str(retry_after)}
|
| 438 |
)
|
| 439 |
|
| 440 |
+
api_key_usage[api_key_str].append(current_time)
|
| 441 |
|
| 442 |
+
return key_data
|
|
|
|
|
|
|
| 443 |
|
| 444 |
# --- Core Logic ---
|
| 445 |
|
|
|
|
| 1383 |
async def list_models(api_key: dict = Depends(rate_limit_api_key)):
|
| 1384 |
models = get_models()
|
| 1385 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1386 |
# Filter for models with text OR search OR image output capability and an organization (exclude stealth models)
|
| 1387 |
+
# Always include image models - no special key needed
|
| 1388 |
valid_models = [m for m in models
|
| 1389 |
if (m.get('capabilities', {}).get('outputCapabilities', {}).get('text')
|
| 1390 |
or m.get('capabilities', {}).get('outputCapabilities', {}).get('search')
|
| 1391 |
+
or m.get('capabilities', {}).get('outputCapabilities', {}).get('image'))
|
| 1392 |
and m.get('organization')]
|
| 1393 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1394 |
return {
|
| 1395 |
"object": "list",
|
| 1396 |
"data": [
|
|
|
|
| 2109 |
unique_citations.append(citation)
|
| 2110 |
message_obj["citations"] = unique_citations
|
| 2111 |
|
| 2112 |
+
# For image models used in chat endpoint, convert URL to markdown with base64
|
|
|
|
| 2113 |
is_image_model = modality == "image"
|
| 2114 |
|
| 2115 |
+
if is_image_model and response_text.startswith("http"):
|
| 2116 |
debug_print(f"๐ผ๏ธ Converting image URL to base64 for OpenWebUI compatibility")
|
| 2117 |
try:
|
| 2118 |
# Download the image
|
|
|
|
| 2129 |
# Create data URL
|
| 2130 |
data_url = f"data:{content_type};base64,{img_base64}"
|
| 2131 |
|
| 2132 |
+
# Format as markdown
|
| 2133 |
message_obj["content"] = f""
|
| 2134 |
|
| 2135 |
debug_print(f"โ
Image converted to base64 data URL ({len(img_base64)} chars)")
|