Quardo commited on
Commit
3655a2f
1 Parent(s): 177e4c2

Updated Space

Browse files
Files changed (3) hide show
  1. app.py +115 -41
  2. index.html +47 -22
  3. requirements.txt +0 -1
app.py CHANGED
@@ -1,6 +1,8 @@
1
- from sse_starlette.sse import EventSourceResponse
2
  from starlette.responses import JSONResponse, FileResponse
 
 
3
  from fastapi import FastAPI, Request
 
4
  import gradio as gr
5
  import requests
6
  import argparse
@@ -16,9 +18,10 @@ import os
16
  # --- === CONFIG === ---
17
 
18
  IMAGE_HANDLE = "url"# or "base64"
19
- API_BASE = "openai"# or "env"
20
  api_key = os.environ['OPENAI_API_KEY']
21
  base_url = os.environ.get('OPENAI_BASE_URL', "https://api.openai.com/v1")
 
22
 
23
  # --- === CONFIG === ---
24
 
@@ -29,11 +32,57 @@ if API_BASE == "env":
29
  models = response.json()
30
  if not ('data' in models):
31
  base_url = "https://api.openai.com/v1"
 
32
  except Exception as e:
33
  print(f"Error testing API endpoint: {e}")
34
  else:
35
  base_url = "https://api.openai.com/v1"
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  async def streamChat(params):
38
  async with aiohttp.ClientSession() as session:
39
  async with session.post(f"{base_url}/chat/completions", headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, json=params) as r:
@@ -55,20 +104,27 @@ def rnd(length=8):
55
  letters = string.ascii_letters + string.digits
56
  return ''.join(random.choice(letters) for i in range(length))
57
 
58
- def getModels():
59
- response = requests.get(f"{base_url}/models", headers={"Authorization": f"Bearer {api_key}",})
60
- response.raise_for_status()
61
- models = response.json()
62
- return sorted([
63
- model['id'] for model in models['data']
64
- if 'gpt' in model['id'] and model['id'] not in {"gpt-3.5-turbo-instruct", "gpt-3.5-turbo-instruct-0914"}
65
- ])
66
-
67
  def handleMultimodalData(model, role, data):
68
- if type(data) == str:
69
- return {"role": role, "content": str(data)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  elif isinstance(data, str):
71
- return {"role": role, "content": data.text}
72
  elif hasattr(data, 'files') and data.files and len(data.files) > 0 and model in {"gpt-4-1106-vision-preview", "gpt-4-vision-preview", "gpt-4-turbo", "gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-mini", "gpt-4o-mini-2024-07-18"}:
73
  result, handler, hasFoundFile = [], ["[System: This message contains files; the system will be splitting it.]"], False
74
  for file in data.files:
@@ -81,8 +137,11 @@ def handleMultimodalData(model, role, data):
81
  result.append({"type": "image_url", "image_url": {"url": file.url}})
82
  if file.mime_type.startswith("text/") or file.mime_type.startswith("application/"):
83
  hasFoundFile = True
84
- with open(file.path, "rb") as data_file:
85
- handler.append("<|file_start|>" + file.orig_name + "\n" + data_file.read().decode('utf-8') + "<|file_end|>")
 
 
 
86
  if hasFoundFile:
87
  handler.append(data.text)
88
  return {"role": role, "content": [{"type": "text", "text": "\n\n".join(handler)}] + result}
@@ -93,21 +152,30 @@ def handleMultimodalData(model, role, data):
93
  for file in data.files:
94
  if file.mime_type.startswith("text/") or file.mime_type.startswith("application/"):
95
  hasFoundFile = True
96
- with open(file.path, "rb") as data_file:
97
- handler.append("<|file_start|>" + file.orig_name + "\n" + data_file.read().decode('utf-8') + "<|file_end|>")
98
- if hasFoundFile:
99
- handler.append(data.text)
100
- return {"role": role, "content": "\n\n".join(handler)}
101
- else:
102
- return {"role": role, "content": data.text}
103
  else:
104
  if isinstance(data, tuple):
105
  return {"role": role, "content": str(data)}
106
  return {"role": role, "content": getattr(data, 'text', str(data))}
107
 
 
 
 
 
 
 
 
 
108
  async def respond(
109
  message,
110
- history: list[tuple[str, str]],
 
 
 
111
  system_message,
112
  model_name,
113
  max_tokens,
@@ -116,16 +184,27 @@ async def respond(
116
  seed,
117
  random_seed
118
  ):
119
- messages = [{"role": "system", "content": "If user submits any file that file will be visible only that turn. This is not due to privacy related things but rather due to developer's lazyness; Ask user to upload the file again if they ask a follow-up question without the data."}, {"role": "system", "content": system_message}]
120
 
121
  for val in history:
122
- if val[0]:
123
- messages.append(handleMultimodalData(model_name,"user",val[0]))
124
- if val[1]:
125
- messages.append(handleMultimodalData(model_name,"assistant",val[1]))
126
-
127
- messages.append(handleMultimodalData(model_name,"user",message))
 
 
128
 
 
 
 
 
 
 
 
 
 
129
  response = ""
130
 
131
  completion = streamChat({
@@ -146,11 +225,11 @@ async def respond(
146
  demo = gr.ChatInterface(
147
  respond,
148
  title="GPT-4O-mini",
149
- description="A simple proxy to OpenAI!<br/>You can use this space as a proxy! click [here](/api/v1/docs) to view the documents.<br/>Also you can only submit images to vision/4o models but can submit txt/code/etc. files to all models.<br/>###### Also the file queries are only shown to model for 1 round cuz gradio.",
150
  multimodal=True,
151
  additional_inputs=[
152
  gr.Textbox(value="You are a helpful assistant.", label="System message"),
153
- gr.Dropdown(choices=getModels(), value="gpt-4o-mini-2024-07-18", label="Model"),
154
  gr.Slider(minimum=1, maximum=4096, value=4096, step=1, label="Max new tokens"),
155
  gr.Slider(minimum=0.1, maximum=2.0, value=0.7, step=0.05, label="Temperature"),
156
  gr.Slider(
@@ -173,14 +252,7 @@ def html():
173
 
174
  @app.get("/api/v1/models")
175
  async def test_endpoint():
176
- response = requests.get(f"{base_url}/models", headers={"Authorization": f"Bearer {api_key}"})
177
- response.raise_for_status()
178
- models = response.json()
179
- models['data'] = sorted(
180
- [model for model in models['data'] if 'gpt' in model['id'] and model['id'] not in {"gpt-3.5-turbo-instruct", "gpt-3.5-turbo-instruct-0914"}],
181
- key=lambda x: x['id']
182
- )
183
- return JSONResponse(content=models)
184
 
185
  @app.post("/api/v1/chat/completions")
186
  async def chat_completion(request: Request):
@@ -189,6 +261,8 @@ async def chat_completion(request: Request):
189
 
190
  if not body.get("messages") or not body.get("model"):
191
  return JSONResponse(content={"error": { "code": "MISSING_VALUE", "message": "Both 'messages' and 'model' are required fields."}}, status_code=400)
 
 
192
 
193
  params = {
194
  key: value for key, value in {
 
 
1
  from starlette.responses import JSONResponse, FileResponse
2
+ from gradio.data_classes import FileData, GradioModel
3
+ from sse_starlette.sse import EventSourceResponse
4
  from fastapi import FastAPI, Request
5
+ from typing import (List, Tuple, Optional)
6
  import gradio as gr
7
  import requests
8
  import argparse
 
18
  # --- === CONFIG === ---
19
 
20
  IMAGE_HANDLE = "url"# or "base64"
21
+ API_BASE = "env"# or "env"
22
  api_key = os.environ['OPENAI_API_KEY']
23
  base_url = os.environ.get('OPENAI_BASE_URL', "https://api.openai.com/v1")
24
+ def_models = '["gpt-3.5-turbo", "gpt-3.5-turbo-0125", "gpt-3.5-turbo-1106", "gpt-3.5-turbo-16k", "gpt-4", "gpt-4-0125-preview", "gpt-4-0314", "gpt-4-0613", "gpt-4-1106-preview", "gpt-4-1106-vision-preview", "gpt-4-32k-0314", "gpt-4-turbo", "gpt-4-turbo-2024-04-09", "gpt-4-turbo-preview", "gpt-4-vision-preview", "gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-mini", "gpt-4o-mini-2024-07-18"]'
25
 
26
  # --- === CONFIG === ---
27
 
 
32
  models = response.json()
33
  if not ('data' in models):
34
  base_url = "https://api.openai.com/v1"
35
+ print("no models?")
36
  except Exception as e:
37
  print(f"Error testing API endpoint: {e}")
38
  else:
39
  base_url = "https://api.openai.com/v1"
40
 
41
+ try:
42
+ models = json.loads(os.environ.get('OPENAI_API_MODELS', def_models))
43
+ except json.JSONDecodeError:
44
+ models = json.loads(def_models)
45
+
46
+ models = sorted(models)
47
+
48
+ modelList = {
49
+ "object": "list",
50
+ "data": []
51
+ }
52
+
53
+ for i, v in enumerate(models):
54
+ modelList["data"].append({"id": v, "object": "model", "created": 0, "owned_by": "system"})
55
+
56
+ def encodeChat(messages):
57
+ output = []
58
+ for message in messages:
59
+ role = message['role']
60
+ name = f" [{message['name']}]" if 'name' in message else ''
61
+ content = message['content']
62
+ formatted_message = f"<|im_start|>{role}{name}\n{content}<|end_of_text|>"
63
+ output.append(formatted_message)
64
+ return "\n".join(output)
65
+
66
+ def moderate(messages):
67
+ try:
68
+ response = requests.post(
69
+ f"{base_url}/moderations",
70
+ headers={
71
+ "Content-Type": "application/json",
72
+ "Authorization": f"Bearer {api_key}"
73
+ },
74
+ json={"input": encodeChat(messages)}
75
+ )
76
+ response.raise_for_status()
77
+ moderation_result = response.json()
78
+ try:
79
+ return any(result["flagged"] for result in moderation_result["results"])
80
+ except KeyError:
81
+ return moderation_result["flagged"]
82
+ except requests.exceptions.RequestException as e:
83
+ print(f"Error during moderation request: {e}")
84
+ return False
85
+
86
  async def streamChat(params):
87
  async with aiohttp.ClientSession() as session:
88
  async with session.post(f"{base_url}/chat/completions", headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, json=params) as r:
 
104
  letters = string.ascii_letters + string.digits
105
  return ''.join(random.choice(letters) for i in range(length))
106
 
 
 
 
 
 
 
 
 
 
107
  def handleMultimodalData(model, role, data):
108
+ if isinstance(data, tuple):
109
+ data = data[0]
110
+
111
+ if isinstance(data, FileData):
112
+ if data.mime_type.startswith("image/"):
113
+ if IMAGE_HANDLE == "base64":
114
+ with open(data.path, "rb") as image_file:
115
+ b64image = base64.b64encode(image_file.read()).decode('utf-8')
116
+ image_file.close()
117
+ return {"role": role, "content": [{"type": "image_url", "image_url": {"url": "data:" + data.mime_type + ";base64," + b64image}}]}
118
+ else:
119
+ return {"role": role, "content": [{"type": "image_url", "image_url": {"url": data.url}}]}
120
+ elif data.mime_type.startswith("text/") or data.mime_type.startswith("application/"):
121
+ try:
122
+ with open(data.path, "rb") as data_file:
123
+ return {"role": role, "content": "[System: This message contains file.]\n\n<|file_start|>" + data.orig_name + "\n" + data_file.read().decode('utf-8') + "<|file_end|>"}
124
+ except UnicodeDecodeError:
125
+ pass
126
  elif isinstance(data, str):
127
+ return {"role": role, "content": data}
128
  elif hasattr(data, 'files') and data.files and len(data.files) > 0 and model in {"gpt-4-1106-vision-preview", "gpt-4-vision-preview", "gpt-4-turbo", "gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-mini", "gpt-4o-mini-2024-07-18"}:
129
  result, handler, hasFoundFile = [], ["[System: This message contains files; the system will be splitting it.]"], False
130
  for file in data.files:
 
137
  result.append({"type": "image_url", "image_url": {"url": file.url}})
138
  if file.mime_type.startswith("text/") or file.mime_type.startswith("application/"):
139
  hasFoundFile = True
140
+ try:
141
+ with open(file.path, "rb") as data_file:
142
+ handler.append("<|file_start|>" + file.orig_name + "\n" + data_file.read().decode('utf-8') + "<|file_end|>")
143
+ except UnicodeDecodeError:
144
+ continue
145
  if hasFoundFile:
146
  handler.append(data.text)
147
  return {"role": role, "content": [{"type": "text", "text": "\n\n".join(handler)}] + result}
 
152
  for file in data.files:
153
  if file.mime_type.startswith("text/") or file.mime_type.startswith("application/"):
154
  hasFoundFile = True
155
+ try:
156
+ with open(file.path, "rb") as data_file:
157
+ return {"role": role, "content": "<|file_start|>" + file.orig_name + "\n" + data_file.read().decode('utf-8') + "<|file_end|>"}
158
+ except UnicodeDecodeError:
159
+ continue
 
 
160
  else:
161
  if isinstance(data, tuple):
162
  return {"role": role, "content": str(data)}
163
  return {"role": role, "content": getattr(data, 'text', str(data))}
164
 
165
+ class FileMessage(GradioModel):
166
+ file: FileData
167
+ alt_text: Optional[str] = None
168
+
169
+ class MultimodalMessage(GradioModel):
170
+ text: Optional[str] = None
171
+ files: Optional[List[FileMessage]]
172
+
173
  async def respond(
174
  message,
175
+ history: List[Tuple[
176
+ Optional[MultimodalMessage],
177
+ Optional[MultimodalMessage],
178
+ ]],
179
  system_message,
180
  model_name,
181
  max_tokens,
 
184
  seed,
185
  random_seed
186
  ):
187
+ messages = [{"role": "system", "content": system_message}]
188
 
189
  for val in history:
190
+ if val[0] is not None:
191
+ user_message = handleMultimodalData(model_name, "user", val[0])
192
+ if user_message:
193
+ messages.append(user_message)
194
+ if val[1] is not None:
195
+ assistant_message = handleMultimodalData(model_name, "assistant", val[1])
196
+ if assistant_message:
197
+ messages.append(assistant_message)
198
 
199
+ user_message = handleMultimodalData(model_name, "user", message)
200
+ if user_message:
201
+ messages.append(user_message)
202
+
203
+ if moderate(messages):
204
+ response = "[MODERATION] I'm sorry, but I can't assist with that."
205
+ yield response
206
+ return
207
+
208
  response = ""
209
 
210
  completion = streamChat({
 
225
  demo = gr.ChatInterface(
226
  respond,
227
  title="GPT-4O-mini",
228
+ description="A simple proxy to OpenAI!<br/>You can use this space as a proxy! click [here](/api/v1/docs) to view the documents. <strong>[last update: Fixed file problems.]</strong><br/>Also you can only submit images to vision/4o models but can submit txt/code/etc. files to all models.",
229
  multimodal=True,
230
  additional_inputs=[
231
  gr.Textbox(value="You are a helpful assistant.", label="System message"),
232
+ gr.Dropdown(choices=models, value="gpt-4o-mini-2024-07-18", label="Model"),
233
  gr.Slider(minimum=1, maximum=4096, value=4096, step=1, label="Max new tokens"),
234
  gr.Slider(minimum=0.1, maximum=2.0, value=0.7, step=0.05, label="Temperature"),
235
  gr.Slider(
 
252
 
253
  @app.get("/api/v1/models")
254
  async def test_endpoint():
255
+ return JSONResponse(content=modelList)
 
 
 
 
 
 
 
256
 
257
  @app.post("/api/v1/chat/completions")
258
  async def chat_completion(request: Request):
 
261
 
262
  if not body.get("messages") or not body.get("model"):
263
  return JSONResponse(content={"error": { "code": "MISSING_VALUE", "message": "Both 'messages' and 'model' are required fields."}}, status_code=400)
264
+ if not body.get("model") in models:
265
+ return JSONResponse(content={"error": { "code": "INVALID_MODEL", "message": "The model name provided in the request does not exists in predefined list of models."}}, status_code=400)
266
 
267
  params = {
268
  key: value for key, value in {
index.html CHANGED
@@ -36,9 +36,27 @@
36
  <h1>Documentation of the API/updates</h1>
37
  <hr/>
38
  <h2>Updates</h2>
39
- <p><strong id="w" alt="yes i love fear"> * </strong>yippeeee</p>
40
- <script>let q=25;setInterval(_=>{q+=.001;w.style.fontSize=`${q}px`},100);</script>
41
- <label style="font-size: 2px;">The message above is edited by a script every 100ms. you can check it via view-source.</label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  <hr/>
43
  <h2>API Endpoints</h2>
44
  <p>Here are some example codes to interact with the API:</p>
@@ -46,21 +64,21 @@
46
  from openai import OpenAI
47
 
48
  client = OpenAI(
49
- # Or use the `OPENAI_BASE_URL` env var
50
- base_url="https://quardo-gpt-4o-mini.hf.space/api/v1",
51
-
52
- # No key is needed cause this is a proxy
53
- api_key="none"
54
  )
55
 
56
  completion = client.chat.completions.create( # or openai.ChatCompletion.create (idk)
57
- model="gpt-4o-mini",
58
- messages=[
59
- {
60
- "role": "user",
61
- "content": "Say this is a test",
62
- }
63
- ],
64
  )
65
 
66
  print(completion.choices[0].message.content)</textarea>
@@ -72,13 +90,13 @@ openai.api_key = 'none'
72
  openai.base_url = "https://quardo-gpt-4o-mini.hf.space/api/v1"
73
 
74
  completion = openai.chat.completions.create( # or openai.ChatCompletion.create (idk)
75
- model="gpt-4o-mini",
76
- messages=[
77
- {
78
- "role": "user",
79
- "content": "Say this is a test",
80
- },
81
- ],
82
  )
83
 
84
  print(completion.choices[0].message.content)</textarea>
@@ -87,6 +105,13 @@ print(completion.choices[0].message.content)</textarea>
87
  # I am too lazy to look into openai's docs
88
  # so please kindly take a look at github there is probably examples there.</textarea>
89
  <hr/>
 
 
 
 
 
 
 
90
  </div>
91
  <script>
92
  CodeMirror.fromTextArea(document.getElementById("example1"), { lineNumbers: true, mode: "python", theme: "monokai", readOnly: true });
 
36
  <h1>Documentation of the API/updates</h1>
37
  <hr/>
38
  <h2>Updates</h2>
39
+ <div>
40
+ <div>
41
+ <strong> - 3. Update.</strong>
42
+ <p> * Managed to fix the file being non-visible to the AI model after one round.</p>
43
+ <p> * And fix the div error on this page.</p>
44
+ </div><hr/>
45
+ <div>
46
+ <strong> - 2. Update.</strong>
47
+ <p> * Added light moderation. [Might add more strict detection later]</p>
48
+ </div><hr/>
49
+ <div>
50
+ <strong> - 1. Update.</strong>
51
+ <p> * Fixed a bug where file encoding could trash the entire chat.</p>
52
+ <p> * Made models allowed set by the maintainer. [OPENAI_API_MODELS variable]</p>
53
+ </div><hr/>
54
+ <div>
55
+ <p><strong id="w" alt="yes i love fear"> * </strong>yippeeee</p>
56
+ <script>let q=25;setInterval(_=>{q+=.001;w.style.fontSize=`${q}px`},100);</script>
57
+ <label style="font-size: 2px;">The message above is edited by a script every 100ms. you can check it via view-source.</label>
58
+ </div>
59
+ </div>
60
  <hr/>
61
  <h2>API Endpoints</h2>
62
  <p>Here are some example codes to interact with the API:</p>
 
64
  from openai import OpenAI
65
 
66
  client = OpenAI(
67
+ # Or use the `OPENAI_BASE_URL` env var
68
+ base_url="https://quardo-gpt-4o-mini.hf.space/api/v1",
69
+
70
+ # No key is needed cause this is a proxy
71
+ api_key="none"
72
  )
73
 
74
  completion = client.chat.completions.create( # or openai.ChatCompletion.create (idk)
75
+ model="gpt-4o-mini",
76
+ messages=[
77
+ {
78
+ "role": "user",
79
+ "content": "Say this is a test",
80
+ }
81
+ ],
82
  )
83
 
84
  print(completion.choices[0].message.content)</textarea>
 
90
  openai.base_url = "https://quardo-gpt-4o-mini.hf.space/api/v1"
91
 
92
  completion = openai.chat.completions.create( # or openai.ChatCompletion.create (idk)
93
+ model="gpt-4o-mini",
94
+ messages=[
95
+ {
96
+ "role": "user",
97
+ "content": "Say this is a test",
98
+ },
99
+ ],
100
  )
101
 
102
  print(completion.choices[0].message.content)</textarea>
 
105
  # I am too lazy to look into openai's docs
106
  # so please kindly take a look at github there is probably examples there.</textarea>
107
  <hr/>
108
+ <div>
109
+ <h3>Actual endpoints:</h3>
110
+ <p><strong>1. [GET]</strong> <a href="https://quardo-gpt-4o-mini.hf.space/api/v1/models">/api/v1/models</a></p>
111
+ <label>Simply shows you the available models</label>
112
+ <p><strong>3. [POST]</strong> <a href="https://quardo-gpt-4o-mini.hf.space/api/v1/chat/completions">/api/v1/chat/completions</a></p>
113
+ <label>Generates a chat completion based on the provided messages and model</label>
114
+ </div>
115
  </div>
116
  <script>
117
  CodeMirror.fromTextArea(document.getElementById("example1"), { lineNumbers: true, mode: "python", theme: "monokai", readOnly: true });
requirements.txt CHANGED
@@ -1,6 +1,5 @@
1
  uvicorn==0.27.1
2
  starlette==0.37.2
3
  sse-starlette==2.1.2
4
- gradio==3.1.4
5
  requests==2.31.0
6
  aiohttp==3.8.5
 
1
  uvicorn==0.27.1
2
  starlette==0.37.2
3
  sse-starlette==2.1.2
 
4
  requests==2.31.0
5
  aiohttp==3.8.5