Update main.py
Browse files
main.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1 |
import base64
|
2 |
import json
|
3 |
import os
|
|
|
|
|
4 |
import time
|
5 |
from typing import List, Optional
|
6 |
|
@@ -15,25 +17,20 @@ from pydantic import BaseModel
|
|
15 |
load_dotenv()
|
16 |
|
17 |
# Env variables for external services
|
18 |
-
IMAGE_API_URL = os.environ.get("IMAGE_API_URL", "https://image.api.example.com")
|
19 |
SNAPZION_UPLOAD_URL = "https://upload.snapzion.com/api/public-upload"
|
20 |
-
SNAPZION_API_KEY = os.environ.get("SNAP", "")
|
21 |
|
22 |
# --- Dummy Model Definitions ---
|
23 |
# In a real application, these would be defined properly.
|
24 |
-
# For this example, we define them here.
|
25 |
-
|
26 |
AVAILABLE_MODELS = [
|
27 |
{"id": "gpt-4-turbo", "object": "model", "created": int(time.time()), "owned_by": "system"},
|
28 |
{"id": "gpt-4o", "object": "model", "created": int(time.time()), "owned_by": "system"},
|
29 |
{"id": "gpt-3.5-turbo", "object": "model", "created": int(time.time()), "owned_by": "system"},
|
30 |
{"id": "dall-e-3", "object": "model", "created": int(time.time()), "owned_by": "system"},
|
31 |
-
# Add any other models you support
|
32 |
]
|
33 |
|
34 |
-
MODEL_ALIASES = {
|
35 |
-
# Example: "gpt-4": "gpt-4-turbo"
|
36 |
-
}
|
37 |
|
38 |
# --- FastAPI Application ---
|
39 |
|
@@ -43,9 +40,19 @@ app = FastAPI(
|
|
43 |
version="1.0.0"
|
44 |
)
|
45 |
|
46 |
-
|
47 |
-
|
48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
|
50 |
# === API Endpoints ===
|
51 |
|
@@ -54,6 +61,7 @@ async def list_models():
|
|
54 |
"""Lists the available models."""
|
55 |
return {"object": "list", "data": AVAILABLE_MODELS}
|
56 |
|
|
|
57 |
# === Chat Completion ===
|
58 |
|
59 |
class Message(BaseModel):
|
@@ -64,19 +72,17 @@ class ChatRequest(BaseModel):
|
|
64 |
messages: List[Message]
|
65 |
model: str
|
66 |
stream: Optional[bool] = False
|
67 |
-
# Add other common parameters for compatibility if needed
|
68 |
-
# max_tokens: Optional[int] = None
|
69 |
-
# temperature: Optional[float] = None
|
70 |
-
# user: Optional[str] = None
|
71 |
-
|
72 |
|
73 |
@app.post("/v1/chat/completions")
|
74 |
async def chat_completion(request: ChatRequest):
|
75 |
"""
|
76 |
Handles chat completion requests, supporting both streaming and non-streaming responses.
|
77 |
-
This endpoint
|
78 |
"""
|
79 |
model_id = MODEL_ALIASES.get(request.model, request.model)
|
|
|
|
|
|
|
80 |
|
81 |
headers = {
|
82 |
'accept': 'text/event-stream',
|
@@ -93,7 +99,6 @@ async def chat_completion(request: ChatRequest):
|
|
93 |
|
94 |
if request.stream:
|
95 |
async def event_stream():
|
96 |
-
chat_id = f"chatcmpl-{unix_id()}"
|
97 |
created = int(time.time())
|
98 |
|
99 |
is_first_chunk = True
|
@@ -121,7 +126,7 @@ async def chat_completion(request: ChatRequest):
|
|
121 |
is_first_chunk = False
|
122 |
|
123 |
chunk_data = {
|
124 |
-
"id": chat_id,
|
125 |
"object": "chat.completion.chunk",
|
126 |
"created": created,
|
127 |
"model": model_id,
|
@@ -140,7 +145,6 @@ async def chat_completion(request: ChatRequest):
|
|
140 |
pass
|
141 |
break
|
142 |
|
143 |
-
# After the loop, send the final chunk with finish_reason and usage
|
144 |
final_usage = None
|
145 |
if usage_info:
|
146 |
prompt_tokens = usage_info.get("promptTokens", 0)
|
@@ -152,7 +156,7 @@ async def chat_completion(request: ChatRequest):
|
|
152 |
}
|
153 |
|
154 |
done_chunk = {
|
155 |
-
"id": chat_id,
|
156 |
"object": "chat.completion.chunk",
|
157 |
"created": created,
|
158 |
"model": model_id,
|
@@ -180,7 +184,7 @@ async def chat_completion(request: ChatRequest):
|
|
180 |
|
181 |
return StreamingResponse(event_stream(), media_type="text/event-stream")
|
182 |
else:
|
183 |
-
# Non-streaming logic
|
184 |
assistant_response = ""
|
185 |
usage_info = {}
|
186 |
|
@@ -203,7 +207,7 @@ async def chat_completion(request: ChatRequest):
|
|
203 |
continue
|
204 |
|
205 |
return JSONResponse(content={
|
206 |
-
"id":
|
207 |
"object": "chat.completion",
|
208 |
"created": int(time.time()),
|
209 |
"model": model_id,
|
@@ -291,8 +295,10 @@ async def generate_images(request: ImageGenerationRequest):
|
|
291 |
|
292 |
return {"created": int(time.time()), "data": results}
|
293 |
|
|
|
294 |
if __name__ == "__main__":
|
295 |
import uvicorn
|
296 |
-
#
|
297 |
-
#
|
|
|
298 |
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
|
1 |
import base64
|
2 |
import json
|
3 |
import os
|
4 |
+
import secrets # <-- Import secrets
|
5 |
+
import string # <-- Import string
|
6 |
import time
|
7 |
from typing import List, Optional
|
8 |
|
|
|
17 |
load_dotenv()
|
18 |
|
19 |
# Env variables for external services
|
20 |
+
IMAGE_API_URL = os.environ.get("IMAGE_API_URL", "https://image.api.example.com")
|
21 |
SNAPZION_UPLOAD_URL = "https://upload.snapzion.com/api/public-upload"
|
22 |
+
SNAPZION_API_KEY = os.environ.get("SNAP", "")
|
23 |
|
24 |
# --- Dummy Model Definitions ---
|
25 |
# In a real application, these would be defined properly.
|
|
|
|
|
26 |
AVAILABLE_MODELS = [
|
27 |
{"id": "gpt-4-turbo", "object": "model", "created": int(time.time()), "owned_by": "system"},
|
28 |
{"id": "gpt-4o", "object": "model", "created": int(time.time()), "owned_by": "system"},
|
29 |
{"id": "gpt-3.5-turbo", "object": "model", "created": int(time.time()), "owned_by": "system"},
|
30 |
{"id": "dall-e-3", "object": "model", "created": int(time.time()), "owned_by": "system"},
|
|
|
31 |
]
|
32 |
|
33 |
+
MODEL_ALIASES = {}
|
|
|
|
|
34 |
|
35 |
# --- FastAPI Application ---
|
36 |
|
|
|
40 |
version="1.0.0"
|
41 |
)
|
42 |
|
43 |
+
|
44 |
+
# --- Helper Function for Random ID Generation ---
|
45 |
+
def generate_random_id(prefix: str, length: int = 29) -> str:
|
46 |
+
"""
|
47 |
+
Generates a cryptographically secure, random alphanumeric ID.
|
48 |
+
The default length of 29 characters is common for OpenAI IDs.
|
49 |
+
The example 'bwvaLjbI0KEKMadGmFbSsjYNLgaI' is 30 characters.
|
50 |
+
You can adjust the length as needed.
|
51 |
+
"""
|
52 |
+
population = string.ascii_letters + string.digits
|
53 |
+
random_part = "".join(secrets.choice(population) for _ in range(length))
|
54 |
+
return f"{prefix}{random_part}"
|
55 |
+
|
56 |
|
57 |
# === API Endpoints ===
|
58 |
|
|
|
61 |
"""Lists the available models."""
|
62 |
return {"object": "list", "data": AVAILABLE_MODELS}
|
63 |
|
64 |
+
|
65 |
# === Chat Completion ===
|
66 |
|
67 |
class Message(BaseModel):
|
|
|
72 |
messages: List[Message]
|
73 |
model: str
|
74 |
stream: Optional[bool] = False
|
|
|
|
|
|
|
|
|
|
|
75 |
|
76 |
@app.post("/v1/chat/completions")
|
77 |
async def chat_completion(request: ChatRequest):
|
78 |
"""
|
79 |
Handles chat completion requests, supporting both streaming and non-streaming responses.
|
80 |
+
This endpoint now uses a long, random ID for completions.
|
81 |
"""
|
82 |
model_id = MODEL_ALIASES.get(request.model, request.model)
|
83 |
+
|
84 |
+
# Generate the ID once for the entire request
|
85 |
+
chat_id = generate_random_id("chatcmpl-")
|
86 |
|
87 |
headers = {
|
88 |
'accept': 'text/event-stream',
|
|
|
99 |
|
100 |
if request.stream:
|
101 |
async def event_stream():
|
|
|
102 |
created = int(time.time())
|
103 |
|
104 |
is_first_chunk = True
|
|
|
126 |
is_first_chunk = False
|
127 |
|
128 |
chunk_data = {
|
129 |
+
"id": chat_id, # Use the pre-generated ID
|
130 |
"object": "chat.completion.chunk",
|
131 |
"created": created,
|
132 |
"model": model_id,
|
|
|
145 |
pass
|
146 |
break
|
147 |
|
|
|
148 |
final_usage = None
|
149 |
if usage_info:
|
150 |
prompt_tokens = usage_info.get("promptTokens", 0)
|
|
|
156 |
}
|
157 |
|
158 |
done_chunk = {
|
159 |
+
"id": chat_id, # Use the pre-generated ID
|
160 |
"object": "chat.completion.chunk",
|
161 |
"created": created,
|
162 |
"model": model_id,
|
|
|
184 |
|
185 |
return StreamingResponse(event_stream(), media_type="text/event-stream")
|
186 |
else:
|
187 |
+
# Non-streaming logic
|
188 |
assistant_response = ""
|
189 |
usage_info = {}
|
190 |
|
|
|
207 |
continue
|
208 |
|
209 |
return JSONResponse(content={
|
210 |
+
"id": chat_id, # Use the pre-generated ID
|
211 |
"object": "chat.completion",
|
212 |
"created": int(time.time()),
|
213 |
"model": model_id,
|
|
|
295 |
|
296 |
return {"created": int(time.time()), "data": results}
|
297 |
|
298 |
+
|
299 |
if __name__ == "__main__":
|
300 |
import uvicorn
|
301 |
+
# To run this file:
|
302 |
+
# 1. Make sure you have a .env file with your SNAP key.
|
303 |
+
# 2. Run in your terminal: uvicorn your_script_name:app --reload --port 8000
|
304 |
uvicorn.run(app, host="0.0.0.0", port=8000)
|