refactor: consolidate duplicate code and fix deadlock bug

- Extract duplicate attack/defense config merging into _merge_component_configs()
- Extract duplicate lazy loading logic into _get_component()
- Move content policy detection to BaseModel base class
- Fix BatchSaveManager deadlock by splitting flush logic
- Add TypeError to ValueError conversion for consistent config errors
- Move _determine_load_model() to BaseComponent (explicit field only)
This commit is contained in:
Liao, Jie
2026-01-23 12:55:34 +08:00
parent 84a4d1708e
commit 04a1cbe8d1
12 changed files with 384 additions and 400 deletions
+10 -27
View File
@@ -7,6 +7,11 @@ class AnthropicModel(BaseModel):
default_output = "I'm sorry, but I cannot assist with that request."
# Anthropic-specific content policy keywords
PROVIDER_SPECIFIC_KEYWORDS = [
"output blocked by content filtering policy",
]
def __init__(self, model_name: str, api_key: str) -> None:
super().__init__(model_name, api_key)
@@ -53,15 +58,8 @@ class AnthropicModel(BaseModel):
)
return response
except Exception as e:
error_str = str(e).lower()
if "Output blocked by content filtering policy" in str(e):
return self.API_CONTENT_REJECTION_OUTPUT
# Handle BadRequestError specifically
if (
"badrequesterror" in error_str
and "data_inspection_failed" in error_str
):
return self.API_CONTENT_REJECTION_OUTPUT
if self._is_content_policy_rejection(e):
return self._handle_content_rejection()
raise
return self._retry_with_backoff(_api_call)
@@ -100,23 +98,8 @@ class AnthropicModel(BaseModel):
)
return stream
except Exception as e:
error_str = str(e).lower()
if "Output blocked by content filtering policy" in str(e):
# Return a generator that yields the content rejection placeholder
def error_generator():
yield self.API_CONTENT_REJECTION_OUTPUT
return error_generator()
# Handle BadRequestError specifically
if (
"badrequesterror" in error_str
and "data_inspection_failed" in error_str
):
def error_generator():
yield self.API_CONTENT_REJECTION_OUTPUT
return error_generator()
if self._is_content_policy_rejection(e):
return self._handle_content_rejection_stream()
raise
try:
@@ -128,4 +111,4 @@ class AnthropicModel(BaseModel):
elif hasattr(chunk, "completion") and chunk.completion:
yield chunk.completion
except Exception:
yield self.API_ERROR_OUTPUT
yield self._handle_api_error()
+55
View File
@@ -18,6 +18,21 @@ class BaseModel(CoreBaseModel):
API_MAX_RETRY = 3
API_TIMEOUT = 600
# Content policy detection keywords (common across providers)
CONTENT_POLICY_KEYWORDS = [
"content policy",
"safety",
"harmful",
"unsafe",
"violation",
"moderation",
"data_inspection_failed",
"inappropriate content",
]
# Provider-specific additional keywords (subclasses can override)
PROVIDER_SPECIFIC_KEYWORDS = []
def __init__(self, model_name: str, api_key: str = None, base_url: str = None):
# Call parent class __init__, pass empty configuration
super().__init__(config={})
@@ -76,6 +91,46 @@ class BaseModel(CoreBaseModel):
else:
return "local"
def _is_content_policy_rejection(self, error: Exception) -> bool:
"""Check if an exception represents a content policy rejection.
Args:
error: The exception to check
Returns:
True if the error indicates content policy rejection
"""
error_str = str(error).lower()
# Combine common and provider-specific keywords
all_keywords = self.CONTENT_POLICY_KEYWORDS + self.PROVIDER_SPECIFIC_KEYWORDS
return any(keyword in error_str for keyword in all_keywords)
def _handle_content_rejection(self) -> str:
"""Return the standard content rejection output."""
return self.API_CONTENT_REJECTION_OUTPUT
def _handle_content_rejection_stream(self):
"""Return a generator that yields the content rejection placeholder."""
def error_generator():
yield self.API_CONTENT_REJECTION_OUTPUT
return error_generator()
def _handle_api_error(self) -> str:
"""Return the standard API error output."""
return self.API_ERROR_OUTPUT
def _handle_api_error_stream(self):
"""Return a generator that yields the API error placeholder."""
def error_generator():
yield self.API_ERROR_OUTPUT
return error_generator()
def _retry_with_backoff(self, func, *args, **kwargs):
"""Execute function with retry logic and exponential backoff using backoff library."""
+5 -43
View File
@@ -33,20 +33,8 @@ class DoubaoModel(BaseModel):
)
return response
except Exception as e:
# Check for content policy violations in ByteDance models
error_str = str(e).lower()
content_keywords = [
"content policy",
"safety",
"harmful",
"unsafe",
"violation",
"moderation",
"data_inspection_failed",
"inappropriate content",
]
if any(keyword in error_str for keyword in content_keywords):
return self.API_CONTENT_REJECTION_OUTPUT
if self._is_content_policy_rejection(e):
return self._handle_content_rejection()
raise
return self._retry_with_backoff(_api_call)
@@ -68,34 +56,8 @@ class DoubaoModel(BaseModel):
)
return stream
except Exception as e:
# Check for content policy violations in ByteDance models
error_str = str(e).lower()
content_keywords = [
"content policy",
"safety",
"harmful",
"unsafe",
"violation",
"moderation",
"data_inspection_failed",
"inappropriate content",
]
if any(keyword in error_str for keyword in content_keywords):
# Return a generator that yields the content rejection placeholder
def error_generator():
yield self.API_CONTENT_REJECTION_OUTPUT
return error_generator()
# Handle BadRequestError specifically
if (
"badrequesterror" in error_str
and "data_inspection_failed" in error_str
):
def error_generator():
yield self.API_CONTENT_REJECTION_OUTPUT
return error_generator()
if self._is_content_policy_rejection(e):
return self._handle_content_rejection_stream()
raise
try:
@@ -104,4 +66,4 @@ class DoubaoModel(BaseModel):
if chunk.choices[0].delta.content is not None:
yield chunk.choices[0].delta.content
except Exception:
yield self.API_ERROR_OUTPUT
yield self._handle_api_error()
+8 -22
View File
@@ -7,6 +7,11 @@ class GoogleModel(BaseModel):
default_output = "I'm sorry, but I cannot assist with that request."
# Google-specific content policy keywords
PROVIDER_SPECIFIC_KEYWORDS = [
"blocked",
]
def __init__(self, model_name: str, api_key: str, base_url: str = None) -> None:
super().__init__(model_name=model_name, api_key=api_key, base_url=base_url)
@@ -37,27 +42,8 @@ class GoogleModel(BaseModel):
)
return response
except Exception as e:
# Check for content policy violations in Gemini models
error_str = str(e).lower()
content_keywords = [
"content policy",
"safety",
"harmful",
"unsafe",
"violation",
"moderation",
"blocked",
"data_inspection_failed",
"inappropriate content",
]
if any(keyword in error_str for keyword in content_keywords):
return self.API_CONTENT_REJECTION_OUTPUT
# Handle BadRequestError specifically
if (
"badrequesterror" in error_str
and "data_inspection_failed" in error_str
):
return self.API_CONTENT_REJECTION_OUTPUT
if self._is_content_policy_rejection(e):
return self._handle_content_rejection()
raise
return self._retry_with_backoff(_api_call)
@@ -86,4 +72,4 @@ class GoogleModel(BaseModel):
if chunk.text:
yield chunk.text
except Exception:
yield self.API_ERROR_OUTPUT
yield self._handle_api_error()
+5 -49
View File
@@ -47,26 +47,8 @@ class MistralModel(BaseModel):
)
return chat_response
except Exception as e:
# Check for content policy violations in Mistral models
error_str = str(e).lower()
content_keywords = [
"content policy",
"safety",
"harmful",
"unsafe",
"violation",
"moderation",
"data_inspection_failed",
"inappropriate content",
]
if any(keyword in error_str for keyword in content_keywords):
return self.API_CONTENT_REJECTION_OUTPUT
# Handle BadRequestError specifically
if (
"badrequesterror" in error_str
and "data_inspection_failed" in error_str
):
return self.API_CONTENT_REJECTION_OUTPUT
if self._is_content_policy_rejection(e):
return self._handle_content_rejection()
raise
return self._retry_with_backoff(_api_call)
@@ -99,34 +81,8 @@ class MistralModel(BaseModel):
)
return stream
except Exception as e:
# Check for content policy violations in Mistral models
error_str = str(e).lower()
content_keywords = [
"content policy",
"safety",
"harmful",
"unsafe",
"violation",
"moderation",
"data_inspection_failed",
"inappropriate content",
]
if any(keyword in error_str for keyword in content_keywords):
# Return a generator that yields the content rejection placeholder
def error_generator():
yield self.API_CONTENT_REJECTION_OUTPUT
return error_generator()
# Handle BadRequestError specifically
if (
"badrequesterror" in error_str
and "data_inspection_failed" in error_str
):
def error_generator():
yield self.API_CONTENT_REJECTION_OUTPUT
return error_generator()
if self._is_content_policy_rejection(e):
return self._handle_content_rejection_stream()
raise
try:
@@ -136,4 +92,4 @@ class MistralModel(BaseModel):
if chunk.choices[0].delta.content is not None:
yield chunk.choices[0].delta.content
except Exception:
yield self.API_ERROR_OUTPUT
yield self._handle_api_error()
+16 -50
View File
@@ -5,6 +5,14 @@ from .base_model import BaseModel
class OpenAIModel(BaseModel):
"""OpenAI model implementation using OpenAI API."""
# OpenAI-specific content policy keywords
PROVIDER_SPECIFIC_KEYWORDS = [
"invalid",
"inappropriate",
"invalid_prompt",
"limited access",
]
def __init__(self, model_name: str, api_key: str, base_url: Optional[str] = None):
super().__init__(model_name=model_name, api_key=api_key, base_url=base_url)
@@ -41,27 +49,11 @@ class OpenAIModel(BaseModel):
return response
except Exception as e:
# Check for content policy violations in GPT models
error_str = str(e).lower()
print("Error during API call:", error_str)
content_keywords = [
"content policy",
"invalid",
"safety",
"harmful",
"unsafe",
"violation",
"moderation",
"data_inspection_failed",
"inappropriate",
"invalid_prompt",
"limited access",
]
if any(keyword in error_str for keyword in content_keywords):
print("✓ Content rejection triggered")
return self.API_CONTENT_REJECTION_OUTPUT
print("✗ No content keywords matched, raising exception")
raise e
print("Error during API call:", str(e).lower())
if self._is_content_policy_rejection(e):
print("Content rejection triggered")
return self._handle_content_rejection()
raise
return self._retry_with_backoff(_api_call)
@@ -86,34 +78,8 @@ class OpenAIModel(BaseModel):
)
return stream
except Exception as e:
# Check for content policy violations in GPT models
error_str = str(e).lower()
content_keywords = [
"content policy",
"safety",
"harmful",
"unsafe",
"violation",
"moderation",
"data_inspection_failed",
"inappropriate content",
]
if any(keyword in error_str for keyword in content_keywords):
# Return a generator that yields the content rejection placeholder
def error_generator():
yield self.API_CONTENT_REJECTION_OUTPUT
return error_generator()
# Handle BadRequestError specifically
if (
"badrequesterror" in error_str
and "data_inspection_failed" in error_str
):
def error_generator():
yield self.API_CONTENT_REJECTION_OUTPUT
return error_generator()
if self._is_content_policy_rejection(e):
return self._handle_content_rejection_stream()
raise
try:
@@ -122,4 +88,4 @@ class OpenAIModel(BaseModel):
if chunk.choices[0].delta.content is not None:
yield chunk.choices[0].delta.content
except Exception:
yield self.API_ERROR_OUTPUT
yield self._handle_api_error()