MatteoScript commited on
Commit
ae6b74a
·
verified ·
1 Parent(s): 479cb13

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +117 -6
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
- # Client OpenAI
61
- def get_openai_client():
62
- ''' Client OpenAI passando in modo RANDOM le Chiavi API. In questo modo posso aggirare i limiti "Quota Exceeded" '''
63
- api_key = random.choice(API_KEYS)
64
- return OpenAI(api_key=api_key, base_url=BASE_URL)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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")