AIMaster7 commited on
Commit
a294533
·
verified ·
1 Parent(s): 17bfc41

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +222 -155
main.py CHANGED
@@ -1,32 +1,57 @@
1
- from fastapi import FastAPI
2
- from fastapi.responses import StreamingResponse, JSONResponse
3
- from pydantic import BaseModel
4
- from typing import List, Optional
5
- import time
6
  import json
7
  import os
8
- import base64
 
 
9
  import httpx
10
  from dotenv import load_dotenv
 
 
 
11
 
12
- load_dotenv()
13
 
14
- # Dummy imports for example
15
- from models import AVAILABLE_MODELS, MODEL_ALIASES # Ensure these are defined properly
16
 
17
- # Env variables
18
- IMAGE_API_URL = os.environ["IMAGE_API_URL"]
19
  SNAPZION_UPLOAD_URL = "https://upload.snapzion.com/api/public-upload"
20
- SNAPZION_API_KEY = os.environ["SNAP"]
21
 
22
- app = FastAPI()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
  def unix_id():
 
25
  return str(int(time.time() * 1000))
26
 
27
- # === Models ===
 
28
  @app.get("/v1/models")
29
  async def list_models():
 
30
  return {"object": "list", "data": AVAILABLE_MODELS}
31
 
32
  # === Chat Completion ===
@@ -39,9 +64,18 @@ class ChatRequest(BaseModel):
39
  messages: List[Message]
40
  model: str
41
  stream: Optional[bool] = False
 
 
 
 
 
42
 
43
  @app.post("/v1/chat/completions")
44
  async def chat_completion(request: ChatRequest):
 
 
 
 
45
  model_id = MODEL_ALIASES.get(request.model, request.model)
46
 
47
  headers = {
@@ -61,82 +95,140 @@ async def chat_completion(request: ChatRequest):
61
  async def event_stream():
62
  chat_id = f"chatcmpl-{unix_id()}"
63
  created = int(time.time())
64
- sent_done = False
 
 
65
 
66
- async with httpx.AsyncClient(timeout=120) as client:
67
- async with client.stream("POST", "https://www.chatwithmono.xyz/api/chat", headers=headers, json=payload) as response:
68
- async for line in response.aiter_lines():
69
- if line.startswith("0:"):
70
- try:
71
- content_piece = json.loads(line[2:])
72
- chunk_data = {
73
- "id": chat_id,
74
- "object": "chat.completion.chunk",
75
- "created": created,
76
- "model": model_id,
77
- "choices": [{
78
- "delta": {"content": content_piece},
79
- "index": 0,
80
- "finish_reason": None
81
- }]
82
- }
83
- yield f"data: {json.dumps(chunk_data)}\n\n"
84
- except:
85
  continue
86
- elif line.startswith(("e:", "d:")) and not sent_done:
87
- sent_done = True
88
- done_chunk = {
89
- "id": chat_id,
90
- "object": "chat.completion.chunk",
91
- "created": created,
92
- "model": model_id,
93
- "choices": [{
94
- "delta": {},
95
- "index": 0,
96
- "finish_reason": "stop"
97
- }]
98
- }
99
- yield f"data: {json.dumps(done_chunk)}\n\ndata: [DONE]\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  return StreamingResponse(event_stream(), media_type="text/event-stream")
101
  else:
 
102
  assistant_response = ""
103
  usage_info = {}
104
 
105
- async with httpx.AsyncClient(timeout=120) as client:
106
- async with client.stream("POST", "https://www.chatwithmono.xyz/api/chat", headers=headers, json=payload) as response:
107
- async for chunk in response.aiter_lines():
108
- if chunk.startswith("0:"):
109
- try:
110
- piece = json.loads(chunk[2:])
111
- assistant_response += piece
112
- except:
113
- continue
114
- elif chunk.startswith(("e:", "d:")):
115
- try:
116
- data = json.loads(chunk[2:])
117
- usage_info = data.get("usage", {})
118
- except:
119
- continue
120
-
121
- return JSONResponse(content={
122
- "id": f"chatcmpl-{unix_id()}",
123
- "object": "chat.completion",
124
- "created": int(time.time()),
125
- "model": model_id,
126
- "choices": [{
127
- "index": 0,
128
- "message": {
129
- "role": "assistant",
130
- "content": assistant_response
131
- },
132
- "finish_reason": "stop"
133
- }],
134
- "usage": {
135
- "prompt_tokens": usage_info.get("promptTokens", 0),
136
- "completion_tokens": usage_info.get("completionTokens", 0),
137
- "total_tokens": usage_info.get("promptTokens", 0) + usage_info.get("completionTokens", 0),
138
- }
139
- })
 
 
 
 
 
 
 
 
 
 
140
 
141
  # === Image Generation ===
142
 
@@ -149,83 +241,58 @@ class ImageGenerationRequest(BaseModel):
149
 
150
  @app.post("/v1/images/generations")
151
  async def generate_images(request: ImageGenerationRequest):
 
152
  results = []
153
 
154
- async with httpx.AsyncClient(timeout=60) as client:
155
- for _ in range(request.n):
156
- model = request.model or "default"
157
-
158
- if model in ["gpt-image-1", "dall-e-3", "dall-e-2", "nextlm-image-1"]:
159
- headers = {
160
- 'Content-Type': 'application/json',
161
- 'User-Agent': 'Mozilla/5.0',
162
- 'Referer': 'https://www.chatwithmono.xyz/',
163
- 'sec-ch-ua-platform': '"Windows"',
164
- 'sec-ch-ua': '"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"',
165
- 'sec-ch-ua-mobile': '?0',
166
- }
167
-
168
- payload = {
169
- "prompt": request.prompt,
170
- "model": model
171
- }
172
 
173
- resp = await client.post("https://www.chatwithmono.xyz/api/image", headers=headers, json=payload)
174
- if resp.status_code != 200:
175
- return JSONResponse(
176
- status_code=502,
177
- content={"error": f"{model} generation failed", "details": resp.text}
178
- )
 
 
 
 
 
 
 
179
 
180
- data = resp.json()
181
- b64_image = data.get("image")
182
- if not b64_image:
183
- return JSONResponse(status_code=502, content={"error": "Missing base64 image in response"})
 
 
 
 
 
184
 
185
- data_uri = f"data:image/png;base64,{b64_image}"
 
 
 
 
 
 
 
 
 
 
 
 
 
186
 
187
- upload_headers = {"Authorization": SNAPZION_API_KEY}
188
- upload_files = {
189
- 'file': ('image.png', base64.b64decode(b64_image), 'image/png')
190
- }
191
 
192
- upload_resp = await client.post(SNAPZION_UPLOAD_URL, headers=upload_headers, files=upload_files)
193
- if upload_resp.status_code != 200:
194
- return JSONResponse(
195
- status_code=502,
196
- content={"error": "Upload failed", "details": upload_resp.text}
197
- )
198
-
199
- upload_data = upload_resp.json()
200
- results.append({
201
- "url": upload_data.get("url"),
202
- "b64_json": b64_image,
203
- "data_uri": data_uri,
204
- "model": model
205
- })
206
- else:
207
- params = {
208
- "prompt": request.prompt,
209
- "aspect_ratio": request.aspect_ratio,
210
- "link": "typegpt.net"
211
- }
212
 
213
- resp = await client.get(IMAGE_API_URL, params=params)
214
- if resp.status_code != 200:
215
- return JSONResponse(
216
- status_code=502,
217
- content={"error": "Image generation failed", "details": resp.text}
218
- )
219
-
220
- data = resp.json()
221
- results.append({
222
- "url": data.get("image_link"),
223
- "b64_json": data.get("base64_output"),
224
- "retries": data.get("attempt"),
225
- "model": "default"
226
- })
227
-
228
- return {
229
- "created": int(time.time()),
230
- "data": results
231
- }
 
1
+ import base64
 
 
 
 
2
  import json
3
  import os
4
+ import time
5
+ from typing import List, Optional
6
+
7
  import httpx
8
  from dotenv import load_dotenv
9
+ from fastapi import FastAPI
10
+ from fastapi.responses import JSONResponse, StreamingResponse
11
+ from pydantic import BaseModel
12
 
13
+ # --- Configuration ---
14
 
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") # Add a default for safety
19
  SNAPZION_UPLOAD_URL = "https://upload.snapzion.com/api/public-upload"
20
+ SNAPZION_API_KEY = os.environ.get("SNAP", "") # Add a default for safety
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
+
40
+ app = FastAPI(
41
+ title="OpenAI Compatible API",
42
+ description="An adapter for various services to be compatible with the OpenAI API specification.",
43
+ version="1.0.0"
44
+ )
45
 
46
  def unix_id():
47
+ """Generates a Unix timestamp-based ID."""
48
  return str(int(time.time() * 1000))
49
 
50
+ # === API Endpoints ===
51
+
52
  @app.get("/v1/models")
53
  async def list_models():
54
+ """Lists the available models."""
55
  return {"object": "list", "data": AVAILABLE_MODELS}
56
 
57
  # === Chat Completion ===
 
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 is updated to match the OpenAI streaming chunk format precisely.
78
+ """
79
  model_id = MODEL_ALIASES.get(request.model, request.model)
80
 
81
  headers = {
 
95
  async def event_stream():
96
  chat_id = f"chatcmpl-{unix_id()}"
97
  created = int(time.time())
98
+
99
+ is_first_chunk = True
100
+ usage_info = None
101
 
102
+ try:
103
+ async with httpx.AsyncClient(timeout=120) as client:
104
+ async with client.stream("POST", "https://www.chatwithmono.xyz/api/chat", headers=headers, json=payload) as response:
105
+ response.raise_for_status()
106
+
107
+ async for line in response.aiter_lines():
108
+ if not line:
 
 
 
 
 
 
 
 
 
 
 
 
109
  continue
110
+
111
+ if line.startswith("0:"):
112
+ try:
113
+ content_piece = json.loads(line[2:])
114
+ delta = {
115
+ "content": content_piece,
116
+ "function_call": None,
117
+ "tool_calls": None
118
+ }
119
+ if is_first_chunk:
120
+ delta["role"] = "assistant"
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,
128
+ "choices": [{"index": 0, "delta": delta, "finish_reason": None}],
129
+ "usage": None
130
+ }
131
+ yield f"data: {json.dumps(chunk_data)}\n\n"
132
+ except json.JSONDecodeError:
133
+ continue
134
+
135
+ elif line.startswith(("e:", "d:")):
136
+ try:
137
+ end_data = json.loads(line[2:])
138
+ usage_info = end_data.get("usage")
139
+ except (json.JSONDecodeError, AttributeError):
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)
147
+ completion_tokens = usage_info.get("completionTokens", 0)
148
+ final_usage = {
149
+ "prompt_tokens": prompt_tokens,
150
+ "completion_tokens": completion_tokens,
151
+ "total_tokens": prompt_tokens + completion_tokens,
152
+ }
153
+
154
+ done_chunk = {
155
+ "id": chat_id,
156
+ "object": "chat.completion.chunk",
157
+ "created": created,
158
+ "model": model_id,
159
+ "choices": [{
160
+ "index": 0,
161
+ "delta": {"role": "assistant", "content": None, "function_call": None, "tool_calls": None},
162
+ "finish_reason": "stop"
163
+ }],
164
+ "usage": final_usage
165
+ }
166
+ yield f"data: {json.dumps(done_chunk)}\n\n"
167
+
168
+ except httpx.HTTPStatusError as e:
169
+ error_content = {
170
+ "error": {
171
+ "message": f"Upstream API error: {e.response.status_code}. Details: {e.response.text}",
172
+ "type": "upstream_error",
173
+ "code": str(e.response.status_code)
174
+ }
175
+ }
176
+ yield f"data: {json.dumps(error_content)}\n\n"
177
+
178
+ finally:
179
+ yield "data: [DONE]\n\n"
180
+
181
  return StreamingResponse(event_stream(), media_type="text/event-stream")
182
  else:
183
+ # Non-streaming logic remains the same
184
  assistant_response = ""
185
  usage_info = {}
186
 
187
+ try:
188
+ async with httpx.AsyncClient(timeout=120) as client:
189
+ async with client.stream("POST", "https://www.chatwithmono.xyz/api/chat", headers=headers, json=payload) as response:
190
+ response.raise_for_status()
191
+ async for chunk in response.aiter_lines():
192
+ if chunk.startswith("0:"):
193
+ try:
194
+ piece = json.loads(chunk[2:])
195
+ assistant_response += piece
196
+ except:
197
+ continue
198
+ elif chunk.startswith(("e:", "d:")):
199
+ try:
200
+ data = json.loads(chunk[2:])
201
+ usage_info = data.get("usage", {})
202
+ except:
203
+ continue
204
+
205
+ return JSONResponse(content={
206
+ "id": f"chatcmpl-{unix_id()}",
207
+ "object": "chat.completion",
208
+ "created": int(time.time()),
209
+ "model": model_id,
210
+ "choices": [{
211
+ "index": 0,
212
+ "message": {"role": "assistant", "content": assistant_response},
213
+ "finish_reason": "stop"
214
+ }],
215
+ "usage": {
216
+ "prompt_tokens": usage_info.get("promptTokens", 0),
217
+ "completion_tokens": usage_info.get("completionTokens", 0),
218
+ "total_tokens": usage_info.get("promptTokens", 0) + usage_info.get("completionTokens", 0),
219
+ }
220
+ })
221
+ except httpx.HTTPStatusError as e:
222
+ return JSONResponse(
223
+ status_code=e.response.status_code,
224
+ content={
225
+ "error": {
226
+ "message": f"Upstream API error. Details: {e.response.text}",
227
+ "type": "upstream_error"
228
+ }
229
+ }
230
+ )
231
+
232
 
233
  # === Image Generation ===
234
 
 
241
 
242
  @app.post("/v1/images/generations")
243
  async def generate_images(request: ImageGenerationRequest):
244
+ """Handles image generation requests."""
245
  results = []
246
 
247
+ try:
248
+ async with httpx.AsyncClient(timeout=120) as client:
249
+ for _ in range(request.n):
250
+ model = request.model or "default"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
252
+ if model in ["gpt-image-1", "dall-e-3", "dall-e-2", "nextlm-image-1"]:
253
+ headers = {
254
+ 'Content-Type': 'application/json',
255
+ 'User-Agent': 'Mozilla/5.0',
256
+ 'Referer': 'https://www.chatwithmono.xyz/',
257
+ }
258
+ payload = {"prompt": request.prompt, "model": model}
259
+ resp = await client.post("https://www.chatwithmono.xyz/api/image", headers=headers, json=payload)
260
+ resp.raise_for_status()
261
+ data = resp.json()
262
+ b64_image = data.get("image")
263
+ if not b64_image:
264
+ return JSONResponse(status_code=502, content={"error": "Missing base64 image in response"})
265
 
266
+ if SNAPZION_API_KEY:
267
+ upload_headers = {"Authorization": SNAPZION_API_KEY}
268
+ upload_files = {'file': ('image.png', base64.b64decode(b64_image), 'image/png')}
269
+ upload_resp = await client.post(SNAPZION_UPLOAD_URL, headers=upload_headers, files=upload_files)
270
+ upload_resp.raise_for_status()
271
+ upload_data = upload_resp.json()
272
+ image_url = upload_data.get("url")
273
+ else:
274
+ image_url = f"data:image/png;base64,{b64_image}"
275
 
276
+ results.append({"url": image_url, "b64_json": b64_image, "revised_prompt": data.get("revised_prompt")})
277
+ else:
278
+ params = {"prompt": request.prompt, "aspect_ratio": request.aspect_ratio, "link": "typegpt.net"}
279
+ resp = await client.get(IMAGE_API_URL, params=params)
280
+ resp.raise_for_status()
281
+ data = resp.json()
282
+ results.append({"url": data.get("image_link"), "b64_json": data.get("base64_output")})
283
+ except httpx.HTTPStatusError as e:
284
+ return JSONResponse(
285
+ status_code=502,
286
+ content={"error": f"Image generation failed. Upstream error: {e.response.status_code}", "details": e.response.text}
287
+ )
288
+ except Exception as e:
289
+ return JSONResponse(status_code=500, content={"error": "An internal error occurred.", "details": str(e)})
290
 
 
 
 
 
291
 
292
+ return {"created": int(time.time()), "data": results}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
 
294
+ if __name__ == "__main__":
295
+ import uvicorn
296
+ # Make sure you have a .env file with SNAP and IMAGE_API_URL
297
+ # Example: uvicorn your_script_name:app --reload --port 8000
298
+ uvicorn.run(app, host="0.0.0.0", port=8000)