fix: another fix for https://github.com/BeehiveInnovations/zen-mcp-server/issues/251
This commit is contained in:
@@ -238,38 +238,73 @@ class GeminiModelProvider(ModelProvider):
|
|||||||
|
|
||||||
if response.candidates:
|
if response.candidates:
|
||||||
candidate = response.candidates[0]
|
candidate = response.candidates[0]
|
||||||
finish_reason_enum = getattr(candidate, "finish_reason", None)
|
|
||||||
|
# Safely get finish reason
|
||||||
|
try:
|
||||||
|
finish_reason_enum = candidate.finish_reason
|
||||||
if finish_reason_enum:
|
if finish_reason_enum:
|
||||||
# Handle both enum objects and string values
|
# Handle both enum objects and string values
|
||||||
finish_reason_str = getattr(finish_reason_enum, "name", str(finish_reason_enum))
|
try:
|
||||||
|
finish_reason_str = finish_reason_enum.name
|
||||||
|
except AttributeError:
|
||||||
|
finish_reason_str = str(finish_reason_enum)
|
||||||
else:
|
else:
|
||||||
finish_reason_str = "STOP"
|
finish_reason_str = "STOP"
|
||||||
|
except AttributeError:
|
||||||
|
finish_reason_str = "STOP"
|
||||||
|
|
||||||
# If content is empty, check safety ratings for the definitive cause
|
# If content is empty, check safety ratings for the definitive cause
|
||||||
if not response.text and hasattr(candidate, "safety_ratings"):
|
if not response.text:
|
||||||
for rating in candidate.safety_ratings:
|
try:
|
||||||
if getattr(rating, "blocked", False):
|
safety_ratings = candidate.safety_ratings
|
||||||
|
if safety_ratings: # Check it's not None or empty
|
||||||
|
for rating in safety_ratings:
|
||||||
|
try:
|
||||||
|
if rating.blocked:
|
||||||
is_blocked_by_safety = True
|
is_blocked_by_safety = True
|
||||||
# Provide details for logging/debugging
|
# Provide details for logging/debugging
|
||||||
category_name = (
|
category_name = "UNKNOWN"
|
||||||
getattr(rating.category, "name", "UNKNOWN")
|
probability_name = "UNKNOWN"
|
||||||
if hasattr(rating, "category")
|
|
||||||
else "UNKNOWN"
|
try:
|
||||||
|
category_name = rating.category.name
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
probability_name = rating.probability.name
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
safety_feedback_details = (
|
||||||
|
f"Category: {category_name}, Probability: {probability_name}"
|
||||||
)
|
)
|
||||||
probability_name = (
|
|
||||||
getattr(rating.probability, "name", "UNKNOWN")
|
|
||||||
if hasattr(rating, "probability")
|
|
||||||
else "UNKNOWN"
|
|
||||||
)
|
|
||||||
safety_feedback_details = f"Category: {category_name}, Probability: {probability_name}"
|
|
||||||
break
|
break
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
# Individual rating doesn't have expected attributes
|
||||||
|
continue
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
# candidate doesn't have safety_ratings or it's not iterable
|
||||||
|
pass
|
||||||
|
|
||||||
# Also check for prompt-level blocking (request rejected entirely)
|
# Also check for prompt-level blocking (request rejected entirely)
|
||||||
elif hasattr(response, "prompt_feedback") and getattr(response.prompt_feedback, "block_reason", None):
|
elif response.candidates is not None and len(response.candidates) == 0:
|
||||||
|
# No candidates is the primary indicator of a prompt-level block
|
||||||
is_blocked_by_safety = True
|
is_blocked_by_safety = True
|
||||||
finish_reason_str = "SAFETY" # This is a clear safety block
|
finish_reason_str = "SAFETY"
|
||||||
block_reason_name = getattr(response.prompt_feedback.block_reason, "name", "UNKNOWN")
|
safety_feedback_details = "Prompt blocked, reason unavailable" # Default message
|
||||||
|
|
||||||
|
try:
|
||||||
|
prompt_feedback = response.prompt_feedback
|
||||||
|
if prompt_feedback and prompt_feedback.block_reason:
|
||||||
|
try:
|
||||||
|
block_reason_name = prompt_feedback.block_reason.name
|
||||||
|
except AttributeError:
|
||||||
|
block_reason_name = str(prompt_feedback.block_reason)
|
||||||
safety_feedback_details = f"Prompt blocked, reason: {block_reason_name}"
|
safety_feedback_details = f"Prompt blocked, reason: {block_reason_name}"
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
# prompt_feedback doesn't exist or has unexpected attributes; stick with the default message
|
||||||
|
pass
|
||||||
|
|
||||||
return ModelResponse(
|
return ModelResponse(
|
||||||
content=response.text,
|
content=response.text,
|
||||||
@@ -370,28 +405,35 @@ class GeminiModelProvider(ModelProvider):
|
|||||||
|
|
||||||
# Try to extract usage metadata from response
|
# Try to extract usage metadata from response
|
||||||
# Note: The actual structure depends on the SDK version and response format
|
# Note: The actual structure depends on the SDK version and response format
|
||||||
if hasattr(response, "usage_metadata"):
|
try:
|
||||||
metadata = response.usage_metadata
|
metadata = response.usage_metadata
|
||||||
|
if metadata:
|
||||||
# Extract token counts with explicit None checks
|
# Extract token counts with explicit None checks
|
||||||
input_tokens = None
|
input_tokens = None
|
||||||
output_tokens = None
|
output_tokens = None
|
||||||
|
|
||||||
if hasattr(metadata, "prompt_token_count"):
|
try:
|
||||||
value = metadata.prompt_token_count
|
value = metadata.prompt_token_count
|
||||||
if value is not None:
|
if value is not None:
|
||||||
input_tokens = value
|
input_tokens = value
|
||||||
usage["input_tokens"] = value
|
usage["input_tokens"] = value
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
if hasattr(metadata, "candidates_token_count"):
|
try:
|
||||||
value = metadata.candidates_token_count
|
value = metadata.candidates_token_count
|
||||||
if value is not None:
|
if value is not None:
|
||||||
output_tokens = value
|
output_tokens = value
|
||||||
usage["output_tokens"] = value
|
usage["output_tokens"] = value
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
# Calculate total only if both values are available and valid
|
# Calculate total only if both values are available and valid
|
||||||
if input_tokens is not None and output_tokens is not None:
|
if input_tokens is not None and output_tokens is not None:
|
||||||
usage["total_tokens"] = input_tokens + output_tokens
|
usage["total_tokens"] = input_tokens + output_tokens
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
# response doesn't have usage_metadata
|
||||||
|
pass
|
||||||
|
|
||||||
return usage
|
return usage
|
||||||
|
|
||||||
@@ -438,11 +480,17 @@ class GeminiModelProvider(ModelProvider):
|
|||||||
# Also check if this is a structured error from Gemini SDK
|
# Also check if this is a structured error from Gemini SDK
|
||||||
try:
|
try:
|
||||||
# Try to access error details if available
|
# Try to access error details if available
|
||||||
if hasattr(error, "details") or hasattr(error, "reason"):
|
error_details = None
|
||||||
# Gemini API errors may have structured details
|
try:
|
||||||
error_details = getattr(error, "details", "") or getattr(error, "reason", "")
|
error_details = error.details
|
||||||
error_details_str = str(error_details).lower()
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
error_details = error.reason
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if error_details:
|
||||||
|
error_details_str = str(error_details).lower()
|
||||||
# Check for non-retryable error codes/reasons
|
# Check for non-retryable error codes/reasons
|
||||||
if any(indicator in error_details_str for indicator in non_retryable_indicators):
|
if any(indicator in error_details_str for indicator in non_retryable_indicators):
|
||||||
logger.debug(f"Non-retryable Gemini error: {error_details}")
|
logger.debug(f"Non-retryable Gemini error: {error_details}")
|
||||||
|
|||||||
Reference in New Issue
Block a user