Update main.py
Browse files
main.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
import re
|
2 |
import random
|
3 |
import string
|
@@ -5,12 +6,22 @@ import uuid
|
|
5 |
import json
|
6 |
import logging
|
7 |
import asyncio
|
|
|
8 |
from aiohttp import ClientSession, ClientTimeout, ClientError
|
9 |
-
from fastapi import FastAPI, HTTPException, Request
|
10 |
-
from
|
|
|
|
|
11 |
from typing import List, Dict, Any, Optional, AsyncGenerator
|
12 |
from datetime import datetime
|
13 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
# Configure logging
|
16 |
logging.basicConfig(
|
@@ -22,6 +33,57 @@ logging.basicConfig(
|
|
22 |
)
|
23 |
logger = logging.getLogger(__name__)
|
24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
# Custom exception for model not working
|
26 |
class ModelNotWorkingException(Exception):
|
27 |
def __init__(self, model: str):
|
@@ -29,18 +91,34 @@ class ModelNotWorkingException(Exception):
|
|
29 |
self.message = f"The model '{model}' is currently not working. Please try another model or wait for it to be fixed."
|
30 |
super().__init__(self.message)
|
31 |
|
32 |
-
# Mock implementations for ImageResponse and to_data_uri
|
33 |
class ImageResponse:
|
34 |
def __init__(self, url: str, alt: str):
|
35 |
self.url = url
|
36 |
self.alt = alt
|
37 |
|
38 |
def to_data_uri(image: Any) -> str:
|
39 |
-
return "data:image/png;base64,..." # Replace with actual base64 data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
|
|
|
41 |
class Blackbox:
|
42 |
url = "https://www.blackbox.ai"
|
43 |
-
api_endpoint = "https://www.blackbox.ai/api/chat"
|
44 |
working = True
|
45 |
supports_stream = True
|
46 |
supports_system_message = True
|
@@ -100,13 +178,13 @@ class Blackbox:
|
|
100 |
'XcodeAgent': {'mode': True, 'id': "Xcode Agent"},
|
101 |
'AngularJSAgent': {'mode': True, 'id': "AngularJS Agent"},
|
102 |
}
|
103 |
-
|
104 |
userSelectedModel = {
|
105 |
"gpt-4o": "gpt-4o",
|
106 |
"gemini-pro": "gemini-pro",
|
107 |
'claude-sonnet-3.5': "claude-sonnet-3.5",
|
108 |
}
|
109 |
-
|
110 |
model_prefixes = {
|
111 |
'gpt-4o': '@GPT-4o',
|
112 |
'gemini-pro': '@Gemini-PRO',
|
@@ -128,14 +206,14 @@ class Blackbox:
|
|
128 |
'ImageGeneration': '@Image Generation',
|
129 |
'Niansuh': '@Niansuh',
|
130 |
}
|
131 |
-
|
132 |
model_referers = {
|
133 |
"blackboxai": f"{url}/?model=blackboxai",
|
134 |
"gpt-4o": f"{url}/?model=gpt-4o",
|
135 |
"gemini-pro": f"{url}/?model=gemini-pro",
|
136 |
"claude-sonnet-3.5": f"{url}/?model=claude-sonnet-3.5"
|
137 |
}
|
138 |
-
|
139 |
model_aliases = {
|
140 |
"gemini-flash": "gemini-1.5-flash",
|
141 |
"claude-3.5-sonnet": "claude-sonnet-3.5",
|
@@ -218,7 +296,7 @@ class Blackbox:
|
|
218 |
"trendingAgentMode": {},
|
219 |
"isMicMode": False,
|
220 |
"userSystemPrompt": None,
|
221 |
-
"maxTokens":
|
222 |
"playgroundTopP": 0.9,
|
223 |
"playgroundTemperature": 0.5,
|
224 |
"isChromeExt": False,
|
@@ -294,29 +372,41 @@ class Blackbox:
|
|
294 |
except ClientError as ce:
|
295 |
logger.error(f"Client error occurred: {ce}. Retrying attempt {attempt + 1}/{retry_attempts}")
|
296 |
if attempt == retry_attempts - 1:
|
297 |
-
raise HTTPException(status_code=502, detail="Error communicating with the external API.
|
298 |
except asyncio.TimeoutError:
|
299 |
logger.error(f"Request timed out. Retrying attempt {attempt + 1}/{retry_attempts}")
|
300 |
if attempt == retry_attempts - 1:
|
301 |
-
raise HTTPException(status_code=504, detail="External API request timed out.
|
302 |
except Exception as e:
|
303 |
logger.error(f"Unexpected error: {e}. Retrying attempt {attempt + 1}/{retry_attempts}")
|
304 |
if attempt == retry_attempts - 1:
|
305 |
raise HTTPException(status_code=500, detail=str(e))
|
306 |
|
307 |
-
#
|
308 |
-
app = FastAPI()
|
309 |
-
|
310 |
class Message(BaseModel):
|
311 |
-
role: str
|
312 |
-
content: str
|
313 |
|
314 |
class ChatRequest(BaseModel):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
315 |
model: str
|
316 |
-
|
317 |
-
|
318 |
-
webSearchMode: Optional[bool] = False
|
319 |
|
|
|
320 |
def create_response(content: str, model: str, finish_reason: Optional[str] = None) -> Dict[str, Any]:
|
321 |
return {
|
322 |
"id": f"chatcmpl-{uuid.uuid4()}",
|
@@ -330,19 +420,22 @@ def create_response(content: str, model: str, finish_reason: Optional[str] = Non
|
|
330 |
"finish_reason": finish_reason,
|
331 |
}
|
332 |
],
|
333 |
-
"usage": None,
|
334 |
}
|
335 |
|
336 |
-
|
337 |
-
|
|
|
|
|
338 |
logger.info(f"Received chat completions request: {request}")
|
339 |
try:
|
340 |
messages = [{"role": msg.role, "content": msg.content} for msg in request.messages]
|
341 |
-
|
|
|
342 |
async_generator = Blackbox.create_async_generator(
|
343 |
model=request.model,
|
344 |
messages=messages,
|
345 |
-
image=None,
|
346 |
image_name=None,
|
347 |
webSearchMode=request.webSearchMode
|
348 |
)
|
@@ -350,15 +443,17 @@ async def chat_completions(request: ChatRequest, req: Request):
|
|
350 |
if request.stream:
|
351 |
async def generate():
|
352 |
try:
|
|
|
353 |
async for chunk in async_generator:
|
354 |
if isinstance(chunk, ImageResponse):
|
355 |
image_markdown = f""
|
356 |
response_chunk = create_response(image_markdown, request.model)
|
|
|
|
|
357 |
else:
|
358 |
response_chunk = create_response(chunk, request.model)
|
359 |
-
|
360 |
-
|
361 |
-
yield f"data: {json.dumps(response_chunk)}\n\n"
|
362 |
|
363 |
# Signal the end of the stream
|
364 |
yield "data: [DONE]\n\n"
|
@@ -373,34 +468,36 @@ async def chat_completions(request: ChatRequest, req: Request):
|
|
373 |
return StreamingResponse(generate(), media_type="text/event-stream")
|
374 |
else:
|
375 |
response_content = ""
|
|
|
376 |
async for chunk in async_generator:
|
377 |
if isinstance(chunk, ImageResponse):
|
378 |
response_content += f"\n"
|
|
|
379 |
else:
|
380 |
response_content += chunk
|
|
|
|
|
|
|
381 |
|
382 |
logger.info("Completed non-streaming response generation.")
|
383 |
-
return
|
384 |
-
|
385 |
-
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
"finish_reason": "stop",
|
395 |
-
"index": 0
|
396 |
-
}
|
397 |
],
|
398 |
-
|
399 |
-
"prompt_tokens":
|
400 |
-
"completion_tokens":
|
401 |
-
"total_tokens":
|
402 |
-
}
|
403 |
-
|
404 |
except ModelNotWorkingException as e:
|
405 |
logger.warning(f"Model not working: {e}")
|
406 |
raise HTTPException(status_code=503, detail=str(e))
|
@@ -411,19 +508,17 @@ async def chat_completions(request: ChatRequest, req: Request):
|
|
411 |
logger.exception("An unexpected error occurred while processing the chat completions request.")
|
412 |
raise HTTPException(status_code=500, detail=str(e))
|
413 |
|
|
|
414 |
@app.get("/v1/models")
|
415 |
-
|
|
|
416 |
logger.info("Fetching available models.")
|
417 |
return {"data": [{"id": model} for model in Blackbox.models]}
|
418 |
|
419 |
-
#
|
420 |
-
@app.get("/v1/health")
|
421 |
-
async def health_check():
|
422 |
-
"""Health check endpoint to verify the service is running."""
|
423 |
-
return {"status": "ok"}
|
424 |
-
|
425 |
@app.get("/v1/models/{model}/status")
|
426 |
-
|
|
|
427 |
"""Check if a specific model is available."""
|
428 |
if model in Blackbox.models:
|
429 |
return {"model": model, "status": "available"}
|
@@ -433,6 +528,14 @@ async def model_status(model: str):
|
|
433 |
else:
|
434 |
raise HTTPException(status_code=404, detail="Model not found")
|
435 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
436 |
if __name__ == "__main__":
|
437 |
import uvicorn
|
438 |
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
|
1 |
+
import os
|
2 |
import re
|
3 |
import random
|
4 |
import string
|
|
|
6 |
import json
|
7 |
import logging
|
8 |
import asyncio
|
9 |
+
import time
|
10 |
from aiohttp import ClientSession, ClientTimeout, ClientError
|
11 |
+
from fastapi import FastAPI, HTTPException, Request, Depends, Header, status
|
12 |
+
from fastapi.responses import StreamingResponse, JSONResponse
|
13 |
+
from fastapi.middleware.cors import CORSMiddleware
|
14 |
+
from pydantic import BaseModel, Field
|
15 |
from typing import List, Dict, Any, Optional, AsyncGenerator
|
16 |
from datetime import datetime
|
17 |
+
from slowapi import Limiter, _rate_limit_exceeded_handler
|
18 |
+
from slowapi.util import get_remote_address
|
19 |
+
from slowapi.errors import RateLimitExceeded
|
20 |
+
import tiktoken
|
21 |
+
from dotenv import load_dotenv
|
22 |
+
|
23 |
+
# Load environment variables from .env file
|
24 |
+
load_dotenv()
|
25 |
|
26 |
# Configure logging
|
27 |
logging.basicConfig(
|
|
|
33 |
)
|
34 |
logger = logging.getLogger(__name__)
|
35 |
|
36 |
+
# Initialize FastAPI app
|
37 |
+
app = FastAPI(title="OpenAI-Compatible API")
|
38 |
+
|
39 |
+
# Configure CORS (adjust origins as needed)
|
40 |
+
origins = [
|
41 |
+
"*", # Allow all origins; replace with specific origins in production
|
42 |
+
]
|
43 |
+
|
44 |
+
app.add_middleware(
|
45 |
+
CORSMiddleware,
|
46 |
+
allow_origins=origins,
|
47 |
+
allow_credentials=True,
|
48 |
+
allow_methods=["*"],
|
49 |
+
allow_headers=["*"],
|
50 |
+
)
|
51 |
+
|
52 |
+
# Initialize Rate Limiter from environment variable
|
53 |
+
RATE_LIMIT = os.getenv("RATE_LIMIT", "60/minute") # Default to 60 requests per minute
|
54 |
+
limiter = Limiter(key_func=get_remote_address, default_limits=[RATE_LIMIT])
|
55 |
+
app.state.limiter = limiter
|
56 |
+
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
57 |
+
|
58 |
+
# API Key Authentication
|
59 |
+
API_KEYS = set(os.getenv("API_KEYS", "").split(",")) # Load API keys from environment variable
|
60 |
+
|
61 |
+
async def get_api_key(authorization: Optional[str] = Header(None)):
|
62 |
+
"""
|
63 |
+
Dependency to validate API Key from the Authorization header.
|
64 |
+
"""
|
65 |
+
if authorization is None:
|
66 |
+
raise HTTPException(
|
67 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
68 |
+
detail="Authorization header missing",
|
69 |
+
headers={"WWW-Authenticate": "Bearer"},
|
70 |
+
)
|
71 |
+
parts = authorization.split()
|
72 |
+
if parts[0].lower() != "bearer" or len(parts) != 2:
|
73 |
+
raise HTTPException(
|
74 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
75 |
+
detail="Invalid authorization header format",
|
76 |
+
headers={"WWW-Authenticate": "Bearer"},
|
77 |
+
)
|
78 |
+
token = parts[1]
|
79 |
+
if token not in API_KEYS:
|
80 |
+
raise HTTPException(
|
81 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
82 |
+
detail="Invalid API Key",
|
83 |
+
headers={"WWW-Authenticate": "Bearer"},
|
84 |
+
)
|
85 |
+
return token
|
86 |
+
|
87 |
# Custom exception for model not working
|
88 |
class ModelNotWorkingException(Exception):
|
89 |
def __init__(self, model: str):
|
|
|
91 |
self.message = f"The model '{model}' is currently not working. Please try another model or wait for it to be fixed."
|
92 |
super().__init__(self.message)
|
93 |
|
94 |
+
# Mock implementations for ImageResponse and to_data_uri (custom functionality)
|
95 |
class ImageResponse:
|
96 |
def __init__(self, url: str, alt: str):
|
97 |
self.url = url
|
98 |
self.alt = alt
|
99 |
|
100 |
def to_data_uri(image: Any) -> str:
|
101 |
+
return "data:image/png;base64,..." # Replace with actual base64 data if needed
|
102 |
+
|
103 |
+
# Token Counting using tiktoken
|
104 |
+
def count_tokens(messages: List[Dict[str, str]], model: str) -> int:
|
105 |
+
"""
|
106 |
+
Counts the number of tokens in the messages using tiktoken.
|
107 |
+
Adjust the encoding based on the model.
|
108 |
+
"""
|
109 |
+
try:
|
110 |
+
encoding = tiktoken.get_encoding("cl100k_base") # Adjust encoding as per model
|
111 |
+
except:
|
112 |
+
encoding = tiktoken.get_encoding("cl100k_base") # Default encoding
|
113 |
+
tokens = 0
|
114 |
+
for message in messages:
|
115 |
+
tokens += len(encoding.encode(message['content']))
|
116 |
+
return tokens
|
117 |
|
118 |
+
# Blackbox Class: Handles interaction with the external AI service
|
119 |
class Blackbox:
|
120 |
url = "https://www.blackbox.ai"
|
121 |
+
api_endpoint = os.getenv("EXTERNAL_API_ENDPOINT", "https://www.blackbox.ai/api/chat")
|
122 |
working = True
|
123 |
supports_stream = True
|
124 |
supports_system_message = True
|
|
|
178 |
'XcodeAgent': {'mode': True, 'id': "Xcode Agent"},
|
179 |
'AngularJSAgent': {'mode': True, 'id': "AngularJS Agent"},
|
180 |
}
|
181 |
+
|
182 |
userSelectedModel = {
|
183 |
"gpt-4o": "gpt-4o",
|
184 |
"gemini-pro": "gemini-pro",
|
185 |
'claude-sonnet-3.5': "claude-sonnet-3.5",
|
186 |
}
|
187 |
+
|
188 |
model_prefixes = {
|
189 |
'gpt-4o': '@GPT-4o',
|
190 |
'gemini-pro': '@Gemini-PRO',
|
|
|
206 |
'ImageGeneration': '@Image Generation',
|
207 |
'Niansuh': '@Niansuh',
|
208 |
}
|
209 |
+
|
210 |
model_referers = {
|
211 |
"blackboxai": f"{url}/?model=blackboxai",
|
212 |
"gpt-4o": f"{url}/?model=gpt-4o",
|
213 |
"gemini-pro": f"{url}/?model=gemini-pro",
|
214 |
"claude-sonnet-3.5": f"{url}/?model=claude-sonnet-3.5"
|
215 |
}
|
216 |
+
|
217 |
model_aliases = {
|
218 |
"gemini-flash": "gemini-1.5-flash",
|
219 |
"claude-3.5-sonnet": "claude-sonnet-3.5",
|
|
|
296 |
"trendingAgentMode": {},
|
297 |
"isMicMode": False,
|
298 |
"userSystemPrompt": None,
|
299 |
+
"maxTokens": int(os.getenv("MAX_TOKENS", "4096")),
|
300 |
"playgroundTopP": 0.9,
|
301 |
"playgroundTemperature": 0.5,
|
302 |
"isChromeExt": False,
|
|
|
372 |
except ClientError as ce:
|
373 |
logger.error(f"Client error occurred: {ce}. Retrying attempt {attempt + 1}/{retry_attempts}")
|
374 |
if attempt == retry_attempts - 1:
|
375 |
+
raise HTTPException(status_code=502, detail="Error communicating with the external API.")
|
376 |
except asyncio.TimeoutError:
|
377 |
logger.error(f"Request timed out. Retrying attempt {attempt + 1}/{retry_attempts}")
|
378 |
if attempt == retry_attempts - 1:
|
379 |
+
raise HTTPException(status_code=504, detail="External API request timed out.")
|
380 |
except Exception as e:
|
381 |
logger.error(f"Unexpected error: {e}. Retrying attempt {attempt + 1}/{retry_attempts}")
|
382 |
if attempt == retry_attempts - 1:
|
383 |
raise HTTPException(status_code=500, detail=str(e))
|
384 |
|
385 |
+
# Pydantic Models
|
|
|
|
|
386 |
class Message(BaseModel):
|
387 |
+
role: str = Field(..., description="The role of the message author.")
|
388 |
+
content: str = Field(..., description="The content of the message.")
|
389 |
|
390 |
class ChatRequest(BaseModel):
|
391 |
+
model: str = Field(..., description="ID of the model to use.")
|
392 |
+
messages: List[Message] = Field(..., description="A list of messages comprising the conversation.")
|
393 |
+
stream: Optional[bool] = Field(False, description="Whether to stream the response.")
|
394 |
+
webSearchMode: Optional[bool] = Field(False, description="Whether to enable web search mode.")
|
395 |
+
|
396 |
+
class ChatCompletionChoice(BaseModel):
|
397 |
+
index: int
|
398 |
+
delta: Dict[str, Any]
|
399 |
+
finish_reason: Optional[str] = None
|
400 |
+
|
401 |
+
class ChatCompletionResponse(BaseModel):
|
402 |
+
id: str
|
403 |
+
object: str
|
404 |
+
created: int
|
405 |
model: str
|
406 |
+
choices: List[ChatCompletionChoice]
|
407 |
+
usage: Optional[Dict[str, int]] = None
|
|
|
408 |
|
409 |
+
# Utility Function to Create Response
|
410 |
def create_response(content: str, model: str, finish_reason: Optional[str] = None) -> Dict[str, Any]:
|
411 |
return {
|
412 |
"id": f"chatcmpl-{uuid.uuid4()}",
|
|
|
420 |
"finish_reason": finish_reason,
|
421 |
}
|
422 |
],
|
423 |
+
"usage": None, # To be populated if usage metrics are available
|
424 |
}
|
425 |
|
426 |
+
# Endpoint: Chat Completions
|
427 |
+
@app.post("/v1/chat/completions", response_model=ChatCompletionResponse)
|
428 |
+
@limiter.limit("60/minute") # Example: 60 requests per minute per IP
|
429 |
+
async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
|
430 |
logger.info(f"Received chat completions request: {request}")
|
431 |
try:
|
432 |
messages = [{"role": msg.role, "content": msg.content} for msg in request.messages]
|
433 |
+
prompt_tokens = count_tokens(messages, request.model)
|
434 |
+
|
435 |
async_generator = Blackbox.create_async_generator(
|
436 |
model=request.model,
|
437 |
messages=messages,
|
438 |
+
image=None, # Adjust if image handling is required
|
439 |
image_name=None,
|
440 |
webSearchMode=request.webSearchMode
|
441 |
)
|
|
|
443 |
if request.stream:
|
444 |
async def generate():
|
445 |
try:
|
446 |
+
completion_tokens = 0
|
447 |
async for chunk in async_generator:
|
448 |
if isinstance(chunk, ImageResponse):
|
449 |
image_markdown = f""
|
450 |
response_chunk = create_response(image_markdown, request.model)
|
451 |
+
yield f"data: {json.dumps(response_chunk)}\n\n"
|
452 |
+
completion_tokens += len(image_markdown.split())
|
453 |
else:
|
454 |
response_chunk = create_response(chunk, request.model)
|
455 |
+
yield f"data: {json.dumps(response_chunk)}\n\n"
|
456 |
+
completion_tokens += len(chunk.split())
|
|
|
457 |
|
458 |
# Signal the end of the stream
|
459 |
yield "data: [DONE]\n\n"
|
|
|
468 |
return StreamingResponse(generate(), media_type="text/event-stream")
|
469 |
else:
|
470 |
response_content = ""
|
471 |
+
completion_tokens = 0
|
472 |
async for chunk in async_generator:
|
473 |
if isinstance(chunk, ImageResponse):
|
474 |
response_content += f"\n"
|
475 |
+
completion_tokens += len(f"\n".split())
|
476 |
else:
|
477 |
response_content += chunk
|
478 |
+
completion_tokens += len(chunk.split())
|
479 |
+
|
480 |
+
total_tokens = prompt_tokens + completion_tokens
|
481 |
|
482 |
logger.info("Completed non-streaming response generation.")
|
483 |
+
return ChatCompletionResponse(
|
484 |
+
id=f"chatcmpl-{uuid.uuid4()}",
|
485 |
+
object="chat.completion",
|
486 |
+
created=int(datetime.now().timestamp()),
|
487 |
+
model=request.model,
|
488 |
+
choices=[
|
489 |
+
ChatCompletionChoice(
|
490 |
+
index=0,
|
491 |
+
delta={"content": response_content, "role": "assistant"},
|
492 |
+
finish_reason="stop"
|
493 |
+
)
|
|
|
|
|
|
|
494 |
],
|
495 |
+
usage={
|
496 |
+
"prompt_tokens": prompt_tokens,
|
497 |
+
"completion_tokens": completion_tokens,
|
498 |
+
"total_tokens": total_tokens
|
499 |
+
}
|
500 |
+
)
|
501 |
except ModelNotWorkingException as e:
|
502 |
logger.warning(f"Model not working: {e}")
|
503 |
raise HTTPException(status_code=503, detail=str(e))
|
|
|
508 |
logger.exception("An unexpected error occurred while processing the chat completions request.")
|
509 |
raise HTTPException(status_code=500, detail=str(e))
|
510 |
|
511 |
+
# Endpoint: List Models
|
512 |
@app.get("/v1/models")
|
513 |
+
@limiter.limit("60/minute")
|
514 |
+
async def get_models(api_key: str = Depends(get_api_key)):
|
515 |
logger.info("Fetching available models.")
|
516 |
return {"data": [{"id": model} for model in Blackbox.models]}
|
517 |
|
518 |
+
# Endpoint: Model Status
|
|
|
|
|
|
|
|
|
|
|
519 |
@app.get("/v1/models/{model}/status")
|
520 |
+
@limiter.limit("60/minute")
|
521 |
+
async def model_status(model: str, api_key: str = Depends(get_api_key)):
|
522 |
"""Check if a specific model is available."""
|
523 |
if model in Blackbox.models:
|
524 |
return {"model": model, "status": "available"}
|
|
|
528 |
else:
|
529 |
raise HTTPException(status_code=404, detail="Model not found")
|
530 |
|
531 |
+
# Endpoint: Health Check
|
532 |
+
@app.get("/v1/health")
|
533 |
+
@limiter.limit("60/minute")
|
534 |
+
async def health_check():
|
535 |
+
"""Health check endpoint to verify the service is running."""
|
536 |
+
return {"status": "ok"}
|
537 |
+
|
538 |
+
# Run the application
|
539 |
if __name__ == "__main__":
|
540 |
import uvicorn
|
541 |
uvicorn.run(app, host="0.0.0.0", port=8000)
|