Spaces:
Running
Running
Update main.py
Browse files
main.py
CHANGED
@@ -12,6 +12,7 @@ from dotenv import load_dotenv
|
|
12 |
from starlette.responses import StreamingResponse
|
13 |
from openai import OpenAI
|
14 |
from typing import List, Optional, Dict, Any
|
|
|
15 |
|
16 |
load_dotenv()
|
17 |
|
@@ -47,6 +48,12 @@ app.add_middleware(
|
|
47 |
allow_headers=["*"],
|
48 |
)
|
49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
# Validazione API
|
51 |
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
|
52 |
def verify_api_key(api_key: str = Depends(api_key_header)):
|
@@ -57,17 +64,119 @@ def verify_api_key(api_key: str = Depends(api_key_header)):
|
|
57 |
raise HTTPException(status_code=403, detail="API key non valida")
|
58 |
return api_key
|
59 |
|
60 |
-
#
|
61 |
-
def
|
62 |
-
|
63 |
-
|
64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
|
|
|
66 |
# Chiama API (senza Streaming)
|
67 |
def call_api_sync(params: ChatCompletionRequest):
|
68 |
''' Chiamata API senza streaming. Se da errore 429 lo rifa'''
|
69 |
try:
|
70 |
client = get_openai_client()
|
|
|
|
|
|
|
|
|
71 |
response_format = getattr(params, 'response_format', None)
|
72 |
if response_format and getattr(response_format, 'type', None) == 'json_schema':
|
73 |
response = client.beta.chat.completions.parse(**params.model_dump())
|
@@ -87,6 +196,9 @@ async def _resp_async_generator(params: ChatCompletionRequest):
|
|
87 |
client = get_openai_client()
|
88 |
try:
|
89 |
response = client.chat.completions.create(**params.model_dump())
|
|
|
|
|
|
|
90 |
for chunk in response:
|
91 |
chunk_data = chunk.to_dict() if hasattr(chunk, "to_dict") else chunk
|
92 |
yield f"data: {json.dumps(chunk_data)}\n\n"
|
@@ -112,7 +224,6 @@ async def health_check():
|
|
112 |
|
113 |
@app.post("/v1/chat/completions", dependencies=[Depends(verify_api_key)])
|
114 |
async def chat_completions(req: ChatCompletionRequest):
|
115 |
-
print(req)
|
116 |
try:
|
117 |
if not req.messages:
|
118 |
raise HTTPException(status_code=400, detail="Nessun messaggio fornito")
|
|
|
12 |
from starlette.responses import StreamingResponse
|
13 |
from openai import OpenAI
|
14 |
from typing import List, Optional, Dict, Any
|
15 |
+
import copy
|
16 |
|
17 |
load_dotenv()
|
18 |
|
|
|
48 |
allow_headers=["*"],
|
49 |
)
|
50 |
|
51 |
+
# Client OpenAI
|
52 |
+
def get_openai_client():
|
53 |
+
''' Client OpenAI passando in modo RANDOM le Chiavi API. In questo modo posso aggirare i limiti "Quota Exceeded" '''
|
54 |
+
api_key = random.choice(API_KEYS)
|
55 |
+
return OpenAI(api_key=api_key, base_url=BASE_URL)
|
56 |
+
|
57 |
# Validazione API
|
58 |
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
|
59 |
def verify_api_key(api_key: str = Depends(api_key_header)):
|
|
|
64 |
raise HTTPException(status_code=403, detail="API key non valida")
|
65 |
return api_key
|
66 |
|
67 |
+
# Correzione payload con content=None
|
68 |
+
def sanitize_messages(messages):
|
69 |
+
"""Convert None content to empty string to avoid Gemini API errors"""
|
70 |
+
if not messages:
|
71 |
+
return messages
|
72 |
+
for message in messages:
|
73 |
+
if message.get('content') is None:
|
74 |
+
message['content'] = " "
|
75 |
+
return messages
|
76 |
+
|
77 |
+
# Funzione per conversione Payload OpenAI to GEMINI (anomalia per ACTION) AnyOf, e property: {}
|
78 |
+
def convert_openai_schema_for_gemini(tools_schema):
|
79 |
+
if isinstance(tools_schema, str):
|
80 |
+
try:
|
81 |
+
tools_schema = json.loads(tools_schema)
|
82 |
+
except json.JSONDecodeError:
|
83 |
+
raise ValueError("Stringa JSON non valida fornita")
|
84 |
+
converted_schema = []
|
85 |
+
for tool in tools_schema:
|
86 |
+
if tool.get("type") != "function":
|
87 |
+
converted_schema.append(tool)
|
88 |
+
continue
|
89 |
+
converted_tool = {"type": "function", "function": {}}
|
90 |
+
func_def = tool.get("function", {})
|
91 |
+
if not func_def:
|
92 |
+
continue
|
93 |
+
converted_tool["function"]["name"] = func_def.get("name", "")
|
94 |
+
converted_tool["function"]["description"] = func_def.get("description", "")
|
95 |
+
if "parameters" in func_def:
|
96 |
+
params = func_def["parameters"]
|
97 |
+
converted_params = {"type": "object"}
|
98 |
+
if "properties" in params:
|
99 |
+
converted_properties = {}
|
100 |
+
for prop_name, prop_value in params["properties"].items():
|
101 |
+
cleaned = clean_schema_property(prop_value)
|
102 |
+
if cleaned: # Aggiungi solo se il dizionario non è vuoto
|
103 |
+
converted_properties[prop_name] = cleaned
|
104 |
+
if converted_properties:
|
105 |
+
converted_params["properties"] = converted_properties
|
106 |
+
if "required" in params:
|
107 |
+
converted_params["required"] = params["required"]
|
108 |
+
converted_tool["function"]["parameters"] = converted_params
|
109 |
+
converted_schema.append(converted_tool)
|
110 |
+
return converted_schema
|
111 |
+
|
112 |
+
def clean_schema_property(prop):
|
113 |
+
if not isinstance(prop, dict):
|
114 |
+
return prop
|
115 |
+
result = {}
|
116 |
+
for key, value in prop.items():
|
117 |
+
if key in ("title", "default"):
|
118 |
+
continue
|
119 |
+
elif key == "anyOf":
|
120 |
+
if isinstance(value, list):
|
121 |
+
for item in value:
|
122 |
+
if isinstance(item, dict) and item.get("type") != "null":
|
123 |
+
cleaned_item = clean_schema_property(item)
|
124 |
+
for k, v in cleaned_item.items():
|
125 |
+
if k not in result:
|
126 |
+
result[k] = v
|
127 |
+
break
|
128 |
+
elif key == "oneOf":
|
129 |
+
if isinstance(value, list) and len(value) > 0:
|
130 |
+
cleaned_item = clean_schema_property(value[0])
|
131 |
+
for k, v in cleaned_item.items():
|
132 |
+
if k not in result:
|
133 |
+
result[k] = v
|
134 |
+
elif isinstance(value, dict):
|
135 |
+
cleaned_item = clean_schema_property(value)
|
136 |
+
for k, v in cleaned_item.items():
|
137 |
+
if k not in result:
|
138 |
+
result[k] = v
|
139 |
+
elif key == "properties" and isinstance(value, dict):
|
140 |
+
new_props = {}
|
141 |
+
for prop_name, prop_value in value.items():
|
142 |
+
cleaned_prop = clean_schema_property(prop_value)
|
143 |
+
if cleaned_prop:
|
144 |
+
new_props[prop_name] = cleaned_prop
|
145 |
+
if not new_props: # Se vuoto, sostituisci con dummy
|
146 |
+
new_props = {"dummy": {"type": "string"}}
|
147 |
+
result[key] = new_props
|
148 |
+
elif key == "items" and isinstance(value, dict):
|
149 |
+
result[key] = clean_schema_property(value)
|
150 |
+
elif isinstance(value, list):
|
151 |
+
result[key] = [clean_schema_property(item) if isinstance(item, dict) else item for item in value]
|
152 |
+
else:
|
153 |
+
result[key] = value
|
154 |
+
if "type" not in result and "properties" in result and result["properties"]:
|
155 |
+
result["type"] = "object"
|
156 |
+
return result
|
157 |
+
|
158 |
+
def convert_payload_for_gemini(payload: ChatCompletionRequest):
|
159 |
+
if hasattr(payload, "model_dump"):
|
160 |
+
payload_converted = json.loads(payload.model_dump_json())
|
161 |
+
elif isinstance(payload, dict):
|
162 |
+
payload_converted = payload.copy()
|
163 |
+
else:
|
164 |
+
raise ValueError("Formato payload non supportato")
|
165 |
+
if "tools" in payload_converted:
|
166 |
+
payload_converted["tools"] = convert_openai_schema_for_gemini(payload_converted["tools"])
|
167 |
+
new_payload = ChatCompletionRequest.model_validate(payload_converted)
|
168 |
+
return new_payload
|
169 |
|
170 |
+
# ---------------------------------- Funzioni per Chat Completion ---------------------------------------
|
171 |
# Chiama API (senza Streaming)
|
172 |
def call_api_sync(params: ChatCompletionRequest):
|
173 |
''' Chiamata API senza streaming. Se da errore 429 lo rifa'''
|
174 |
try:
|
175 |
client = get_openai_client()
|
176 |
+
if params.messages:
|
177 |
+
params.messages = sanitize_messages(params.messages)
|
178 |
+
params = convert_payload_for_gemini(params)
|
179 |
+
print(params)
|
180 |
response_format = getattr(params, 'response_format', None)
|
181 |
if response_format and getattr(response_format, 'type', None) == 'json_schema':
|
182 |
response = client.beta.chat.completions.parse(**params.model_dump())
|
|
|
196 |
client = get_openai_client()
|
197 |
try:
|
198 |
response = client.chat.completions.create(**params.model_dump())
|
199 |
+
if params.messages:
|
200 |
+
params.messages = sanitize_messages(params.messages)
|
201 |
+
params = convert_payload_for_gemini(params)
|
202 |
for chunk in response:
|
203 |
chunk_data = chunk.to_dict() if hasattr(chunk, "to_dict") else chunk
|
204 |
yield f"data: {json.dumps(chunk_data)}\n\n"
|
|
|
224 |
|
225 |
@app.post("/v1/chat/completions", dependencies=[Depends(verify_api_key)])
|
226 |
async def chat_completions(req: ChatCompletionRequest):
|
|
|
227 |
try:
|
228 |
if not req.messages:
|
229 |
raise HTTPException(status_code=400, detail="Nessun messaggio fornito")
|