fix(gemini): resolve 400 errors by refining safety settings and sanitizing stop sequences
- Exclude 'HARM_CATEGORY_CIVIC_INTEGRITY' when using v1 endpoint (v1beta only). - Filter out empty strings from 'stop_sequences' which are rejected by Gemini. - Update error probe to use non-streaming endpoint for better JSON error diagnostics.
This commit is contained in:
@@ -531,15 +531,19 @@ impl GeminiProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Default safety settings to avoid blocking responses.
|
/// Default safety settings to avoid blocking responses.
|
||||||
fn get_safety_settings(&self) -> Vec<GeminiSafetySetting> {
|
fn get_safety_settings(&self, base_url: &str) -> Vec<GeminiSafetySetting> {
|
||||||
let categories = vec![
|
let mut categories = vec![
|
||||||
"HARM_CATEGORY_HARASSMENT",
|
"HARM_CATEGORY_HARASSMENT",
|
||||||
"HARM_CATEGORY_HATE_SPEECH",
|
"HARM_CATEGORY_HATE_SPEECH",
|
||||||
"HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
"HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
||||||
"HARM_CATEGORY_DANGEROUS_CONTENT",
|
"HARM_CATEGORY_DANGEROUS_CONTENT",
|
||||||
"HARM_CATEGORY_CIVIC_INTEGRITY",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Civic integrity is only available in v1beta
|
||||||
|
if base_url.contains("v1beta") {
|
||||||
|
categories.push("HARM_CATEGORY_CIVIC_INTEGRITY");
|
||||||
|
}
|
||||||
|
|
||||||
categories
|
categories
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|c| GeminiSafetySetting {
|
.map(|c| GeminiSafetySetting {
|
||||||
@@ -589,12 +593,21 @@ impl super::Provider for GeminiProvider {
|
|||||||
return Err(AppError::ProviderError("No valid messages to send".to_string()));
|
return Err(AppError::ProviderError("No valid messages to send".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let base_url = self.get_base_url(&model);
|
||||||
|
|
||||||
|
// Sanitize stop sequences: Gemini rejects empty strings
|
||||||
|
let stop_sequences = request.stop.map(|s| {
|
||||||
|
s.into_iter()
|
||||||
|
.filter(|seq| !seq.is_empty())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}).filter(|s| !s.is_empty());
|
||||||
|
|
||||||
let generation_config = Some(GeminiGenerationConfig {
|
let generation_config = Some(GeminiGenerationConfig {
|
||||||
temperature: request.temperature,
|
temperature: request.temperature,
|
||||||
top_p: request.top_p,
|
top_p: request.top_p,
|
||||||
top_k: request.top_k,
|
top_k: request.top_k,
|
||||||
max_output_tokens: request.max_tokens.map(|t| t.min(8192)),
|
max_output_tokens: request.max_tokens.map(|t| t.min(8192)),
|
||||||
stop_sequences: request.stop,
|
stop_sequences,
|
||||||
candidate_count: request.n,
|
candidate_count: request.n,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -604,10 +617,9 @@ impl super::Provider for GeminiProvider {
|
|||||||
generation_config,
|
generation_config,
|
||||||
tools,
|
tools,
|
||||||
tool_config,
|
tool_config,
|
||||||
safety_settings: Some(self.get_safety_settings()),
|
safety_settings: Some(self.get_safety_settings(&base_url)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let base_url = self.get_base_url(&model);
|
|
||||||
let url = format!("{}/models/{}:generateContent", base_url, model);
|
let url = format!("{}/models/{}:generateContent", base_url, model);
|
||||||
tracing::debug!("Calling Gemini API: {}", url);
|
tracing::debug!("Calling Gemini API: {}", url);
|
||||||
|
|
||||||
@@ -729,12 +741,21 @@ impl super::Provider for GeminiProvider {
|
|||||||
return Err(AppError::ProviderError("No valid messages to send".to_string()));
|
return Err(AppError::ProviderError("No valid messages to send".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let base_url = self.get_base_url(&model);
|
||||||
|
|
||||||
|
// Sanitize stop sequences: Gemini rejects empty strings
|
||||||
|
let stop_sequences = request.stop.map(|s| {
|
||||||
|
s.into_iter()
|
||||||
|
.filter(|seq| !seq.is_empty())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}).filter(|s| !s.is_empty());
|
||||||
|
|
||||||
let generation_config = Some(GeminiGenerationConfig {
|
let generation_config = Some(GeminiGenerationConfig {
|
||||||
temperature: request.temperature,
|
temperature: request.temperature,
|
||||||
top_p: request.top_p,
|
top_p: request.top_p,
|
||||||
top_k: request.top_k,
|
top_k: request.top_k,
|
||||||
max_output_tokens: request.max_tokens.map(|t| t.min(8192)),
|
max_output_tokens: request.max_tokens.map(|t| t.min(8192)),
|
||||||
stop_sequences: request.stop,
|
stop_sequences,
|
||||||
candidate_count: request.n,
|
candidate_count: request.n,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -744,10 +765,9 @@ impl super::Provider for GeminiProvider {
|
|||||||
generation_config,
|
generation_config,
|
||||||
tools,
|
tools,
|
||||||
tool_config,
|
tool_config,
|
||||||
safety_settings: Some(self.get_safety_settings()),
|
safety_settings: Some(self.get_safety_settings(&base_url)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let base_url = self.get_base_url(&model);
|
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/models/{}:streamGenerateContent?alt=sse",
|
"{}/models/{}:streamGenerateContent?alt=sse",
|
||||||
base_url, model,
|
base_url, model,
|
||||||
@@ -757,7 +777,8 @@ impl super::Provider for GeminiProvider {
|
|||||||
// Capture a clone of the request to probe for errors (Gemini 400s are common)
|
// Capture a clone of the request to probe for errors (Gemini 400s are common)
|
||||||
let probe_request = gemini_request.clone();
|
let probe_request = gemini_request.clone();
|
||||||
let probe_client = self.client.clone();
|
let probe_client = self.client.clone();
|
||||||
let probe_url = url.clone();
|
// Use non-streaming URL for probing to get a valid JSON error body
|
||||||
|
let probe_url = format!("{}/models/{}:generateContent", base_url, model);
|
||||||
let probe_api_key = self.api_key.clone();
|
let probe_api_key = self.api_key.clone();
|
||||||
|
|
||||||
// Create the EventSource first (it doesn't send until polled)
|
// Create the EventSource first (it doesn't send until polled)
|
||||||
|
|||||||
Reference in New Issue
Block a user