Spaces:
Paused
Paused
Commit
·
dd504cd
1
Parent(s):
2a81a94
added more error handling
Browse files- app/api_helpers.py +54 -20
- app/message_processing.py +29 -13
app/api_helpers.py
CHANGED
|
@@ -45,21 +45,39 @@ def create_generation_config(request: OpenAIRequest) -> Dict[str, Any]:
|
|
| 45 |
return config
|
| 46 |
|
| 47 |
def is_response_valid(response):
|
| 48 |
-
if response is None:
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
if hasattr(response, 'candidates') and response.candidates:
|
| 51 |
-
candidate
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
return False
|
| 64 |
|
| 65 |
async def fake_stream_generator(client_instance, model_name: str, prompt: Union[types.Content, List[types.Content]], current_gen_config: Dict[str, Any], request_obj: OpenAIRequest):
|
|
@@ -83,12 +101,20 @@ async def fake_stream_generator(client_instance, model_name: str, prompt: Union[
|
|
| 83 |
if not is_response_valid(response):
|
| 84 |
raise ValueError(f"Invalid/empty response in fake stream: {str(response)[:200]}")
|
| 85 |
full_text = ""
|
| 86 |
-
if hasattr(response, 'text'):
|
|
|
|
| 87 |
elif hasattr(response, 'candidates') and response.candidates:
|
|
|
|
| 88 |
candidate = response.candidates[0]
|
| 89 |
-
if hasattr(candidate, 'text'):
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
if request_obj.model.endswith("-encrypt-full"):
|
| 93 |
full_text = deobfuscate_text(full_text)
|
| 94 |
|
|
@@ -141,8 +167,16 @@ async def execute_gemini_call(
|
|
| 141 |
yield "data: [DONE]\n\n"
|
| 142 |
except Exception as e_stream_call:
|
| 143 |
print(f"Streaming Error in _execute_gemini_call: {e_stream_call}")
|
| 144 |
-
|
| 145 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
yield "data: [DONE]\n\n"
|
| 147 |
raise # Re-raise to be caught by retry logic if any
|
| 148 |
return StreamingResponse(_stream_generator_inner_for_execute(), media_type="text/event-stream")
|
|
|
|
| 45 |
return config
|
| 46 |
|
| 47 |
def is_response_valid(response):
|
| 48 |
+
if response is None:
|
| 49 |
+
print("DEBUG: Response is None, therefore invalid.")
|
| 50 |
+
return False
|
| 51 |
+
|
| 52 |
+
# Check for direct text attribute
|
| 53 |
+
if hasattr(response, 'text') and isinstance(response.text, str) and response.text.strip():
|
| 54 |
+
# print("DEBUG: Response valid due to response.text")
|
| 55 |
+
return True
|
| 56 |
+
|
| 57 |
+
# Check candidates for text content
|
| 58 |
if hasattr(response, 'candidates') and response.candidates:
|
| 59 |
+
for candidate in response.candidates: # Iterate through all candidates
|
| 60 |
+
if hasattr(candidate, 'text') and isinstance(candidate.text, str) and candidate.text.strip():
|
| 61 |
+
# print(f"DEBUG: Response valid due to candidate.text in candidate")
|
| 62 |
+
return True
|
| 63 |
+
if hasattr(candidate, 'content') and hasattr(candidate.content, 'parts') and candidate.content.parts:
|
| 64 |
+
for part in candidate.content.parts:
|
| 65 |
+
if hasattr(part, 'text') and isinstance(part.text, str) and part.text.strip():
|
| 66 |
+
# print(f"DEBUG: Response valid due to part.text in candidate's content part")
|
| 67 |
+
return True
|
| 68 |
+
|
| 69 |
+
# Check for prompt_feedback, which indicates the API processed the request,
|
| 70 |
+
# even if the content is empty (e.g. due to safety filtering).
|
| 71 |
+
# The fake_stream_generator should still attempt to process this to convey safety messages if present.
|
| 72 |
+
if hasattr(response, 'prompt_feedback'):
|
| 73 |
+
# Check if there's any block reason, which might be interesting to log or handle
|
| 74 |
+
if hasattr(response.prompt_feedback, 'block_reason') and response.prompt_feedback.block_reason:
|
| 75 |
+
print(f"DEBUG: Response has prompt_feedback with block_reason: {response.prompt_feedback.block_reason}, considering it valid for processing.")
|
| 76 |
+
else:
|
| 77 |
+
print("DEBUG: Response has prompt_feedback (no block_reason), considering it valid for processing.")
|
| 78 |
+
return True
|
| 79 |
+
|
| 80 |
+
print("DEBUG: Response is invalid, no usable text content or prompt_feedback found.")
|
| 81 |
return False
|
| 82 |
|
| 83 |
async def fake_stream_generator(client_instance, model_name: str, prompt: Union[types.Content, List[types.Content]], current_gen_config: Dict[str, Any], request_obj: OpenAIRequest):
|
|
|
|
| 101 |
if not is_response_valid(response):
|
| 102 |
raise ValueError(f"Invalid/empty response in fake stream: {str(response)[:200]}")
|
| 103 |
full_text = ""
|
| 104 |
+
if hasattr(response, 'text'):
|
| 105 |
+
full_text = response.text or "" # Coalesce None to empty string
|
| 106 |
elif hasattr(response, 'candidates') and response.candidates:
|
| 107 |
+
# Typically, we focus on the first candidate for non-streaming synthesis
|
| 108 |
candidate = response.candidates[0]
|
| 109 |
+
if hasattr(candidate, 'text'):
|
| 110 |
+
full_text = candidate.text or "" # Coalesce None to empty string
|
| 111 |
+
elif hasattr(candidate, 'content') and hasattr(candidate.content, 'parts') and candidate.content.parts:
|
| 112 |
+
# Ensure parts are iterated and text is joined correctly even if some parts have no text or part.text is None
|
| 113 |
+
texts = []
|
| 114 |
+
for part in candidate.content.parts:
|
| 115 |
+
if hasattr(part, 'text') and part.text is not None: # Check part.text exists and is not None
|
| 116 |
+
texts.append(part.text)
|
| 117 |
+
full_text = "".join(texts)
|
| 118 |
if request_obj.model.endswith("-encrypt-full"):
|
| 119 |
full_text = deobfuscate_text(full_text)
|
| 120 |
|
|
|
|
| 167 |
yield "data: [DONE]\n\n"
|
| 168 |
except Exception as e_stream_call:
|
| 169 |
print(f"Streaming Error in _execute_gemini_call: {e_stream_call}")
|
| 170 |
+
|
| 171 |
+
error_message_str = str(e_stream_call)
|
| 172 |
+
# Truncate very long error messages to prevent excessively large JSON payloads.
|
| 173 |
+
if len(error_message_str) > 1024: # Max length for the error string
|
| 174 |
+
error_message_str = error_message_str[:1024] + "..."
|
| 175 |
+
|
| 176 |
+
err_resp_content_call = create_openai_error_response(500, error_message_str, "server_error")
|
| 177 |
+
json_payload_for_error = json.dumps(err_resp_content_call)
|
| 178 |
+
print(f"DEBUG: Yielding error JSON payload during true streaming: {json_payload_for_error}")
|
| 179 |
+
yield f"data: {json_payload_for_error}\n\n"
|
| 180 |
yield "data: [DONE]\n\n"
|
| 181 |
raise # Re-raise to be caught by retry logic if any
|
| 182 |
return StreamingResponse(_stream_generator_inner_for_execute(), media_type="text/event-stream")
|
app/message_processing.py
CHANGED
|
@@ -344,11 +344,14 @@ def convert_to_openai_format(gemini_response, model: str) -> Dict[str, Any]:
|
|
| 344 |
for i, candidate in enumerate(gemini_response.candidates):
|
| 345 |
content = ""
|
| 346 |
if hasattr(candidate, 'text'):
|
| 347 |
-
content = candidate.text
|
| 348 |
elif hasattr(candidate, 'content') and hasattr(candidate.content, 'parts'):
|
|
|
|
|
|
|
| 349 |
for part_item in candidate.content.parts:
|
| 350 |
-
if hasattr(part_item, 'text'):
|
| 351 |
-
|
|
|
|
| 352 |
|
| 353 |
if is_encrypt_full:
|
| 354 |
content = deobfuscate_text(content)
|
|
@@ -359,9 +362,9 @@ def convert_to_openai_format(gemini_response, model: str) -> Dict[str, Any]:
|
|
| 359 |
"finish_reason": "stop"
|
| 360 |
})
|
| 361 |
elif hasattr(gemini_response, 'text'):
|
| 362 |
-
content = gemini_response.text
|
| 363 |
if is_encrypt_full:
|
| 364 |
-
content = deobfuscate_text(content)
|
| 365 |
choices.append({
|
| 366 |
"index": 0,
|
| 367 |
"message": {"role": "assistant", "content": content},
|
|
@@ -392,14 +395,27 @@ def convert_to_openai_format(gemini_response, model: str) -> Dict[str, Any]:
|
|
| 392 |
def convert_chunk_to_openai(chunk, model: str, response_id: str, candidate_index: int = 0) -> str:
|
| 393 |
"""Converts Gemini stream chunk to OpenAI format, applying deobfuscation if needed."""
|
| 394 |
is_encrypt_full = model.endswith("-encrypt-full")
|
| 395 |
-
|
| 396 |
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 403 |
|
| 404 |
if is_encrypt_full:
|
| 405 |
chunk_content = deobfuscate_text(chunk_content)
|
|
@@ -415,7 +431,7 @@ def convert_chunk_to_openai(chunk, model: str, response_id: str, candidate_index
|
|
| 415 |
"choices": [
|
| 416 |
{
|
| 417 |
"index": candidate_index,
|
| 418 |
-
"delta": {**({"content":
|
| 419 |
"finish_reason": finish_reason
|
| 420 |
}
|
| 421 |
]
|
|
|
|
| 344 |
for i, candidate in enumerate(gemini_response.candidates):
|
| 345 |
content = ""
|
| 346 |
if hasattr(candidate, 'text'):
|
| 347 |
+
content = candidate.text or "" # Coalesce None to empty string
|
| 348 |
elif hasattr(candidate, 'content') and hasattr(candidate.content, 'parts'):
|
| 349 |
+
# Ensure content remains a string even if parts have None text
|
| 350 |
+
parts_texts = []
|
| 351 |
for part_item in candidate.content.parts:
|
| 352 |
+
if hasattr(part_item, 'text') and part_item.text is not None:
|
| 353 |
+
parts_texts.append(part_item.text)
|
| 354 |
+
content = "".join(parts_texts)
|
| 355 |
|
| 356 |
if is_encrypt_full:
|
| 357 |
content = deobfuscate_text(content)
|
|
|
|
| 362 |
"finish_reason": "stop"
|
| 363 |
})
|
| 364 |
elif hasattr(gemini_response, 'text'):
|
| 365 |
+
content = gemini_response.text or "" # Coalesce None to empty string
|
| 366 |
if is_encrypt_full:
|
| 367 |
+
content = deobfuscate_text(content) # deobfuscate_text should also be robust to empty string
|
| 368 |
choices.append({
|
| 369 |
"index": 0,
|
| 370 |
"message": {"role": "assistant", "content": content},
|
|
|
|
| 395 |
def convert_chunk_to_openai(chunk, model: str, response_id: str, candidate_index: int = 0) -> str:
|
| 396 |
"""Converts Gemini stream chunk to OpenAI format, applying deobfuscation if needed."""
|
| 397 |
is_encrypt_full = model.endswith("-encrypt-full")
|
| 398 |
+
chunk_content_str = "" # Renamed for clarity and to ensure it's always a string
|
| 399 |
|
| 400 |
+
try:
|
| 401 |
+
if hasattr(chunk, 'parts') and chunk.parts:
|
| 402 |
+
current_parts_texts = []
|
| 403 |
+
for part_item in chunk.parts:
|
| 404 |
+
# Ensure part_item.text exists, is not None, and convert to string
|
| 405 |
+
if hasattr(part_item, 'text') and part_item.text is not None:
|
| 406 |
+
current_parts_texts.append(str(part_item.text))
|
| 407 |
+
chunk_content_str = "".join(current_parts_texts)
|
| 408 |
+
elif hasattr(chunk, 'text') and chunk.text is not None:
|
| 409 |
+
# Ensure chunk.text is converted to string if it's not None
|
| 410 |
+
chunk_content_str = str(chunk.text)
|
| 411 |
+
# If chunk has neither .parts nor .text, or if .text is None, chunk_content_str remains ""
|
| 412 |
+
except Exception as e_chunk_extract:
|
| 413 |
+
# Log the error and the problematic chunk structure
|
| 414 |
+
print(f"WARNING: Error extracting content from chunk in convert_chunk_to_openai: {e_chunk_extract}. Chunk type: {type(chunk)}. Chunk data: {str(chunk)[:200]}")
|
| 415 |
+
chunk_content_str = "" # Default to empty string in case of any error
|
| 416 |
+
|
| 417 |
+
if is_encrypt_full:
|
| 418 |
+
chunk_content_str = deobfuscate_text(chunk_content_str) # deobfuscate_text should handle empty string
|
| 419 |
|
| 420 |
if is_encrypt_full:
|
| 421 |
chunk_content = deobfuscate_text(chunk_content)
|
|
|
|
| 431 |
"choices": [
|
| 432 |
{
|
| 433 |
"index": candidate_index,
|
| 434 |
+
"delta": {**({"content": chunk_content_str} if chunk_content_str else {})},
|
| 435 |
"finish_reason": finish_reason
|
| 436 |
}
|
| 437 |
]
|