fix(gemini): strictly enforce alternating roles and improve message merging
- Merge all consecutive messages with the same role into a single GeminiContent object. - Ensure the first message is always 'user' by prepending a placeholder if necessary. - Add final check for empty contents to prevent sending malformed requests. - This addresses strict role-sequence requirements in Gemini 2.0/3.0 models.
This commit is contained in:
@@ -216,7 +216,7 @@ impl GeminiProvider {
|
|||||||
|
|
||||||
let role = match msg.role.as_str() {
|
let role = match msg.role.as_str() {
|
||||||
"assistant" => "model".to_string(),
|
"assistant" => "model".to_string(),
|
||||||
"tool" => "user".to_string(), // Tool results are technically from the user side in Gemini
|
"tool" => "user".to_string(), // Tool results are user-side in Gemini
|
||||||
_ => "user".to_string(),
|
_ => "user".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -233,7 +233,6 @@ impl GeminiProvider {
|
|||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
// Gemini function response MUST have a name. Fallback to tool_call_id if name is missing.
|
|
||||||
let name = msg.name.clone().or_else(|| msg.tool_call_id.clone()).unwrap_or_else(|| "unknown_function".to_string());
|
let name = msg.name.clone().or_else(|| msg.tool_call_id.clone()).unwrap_or_else(|| "unknown_function".to_string());
|
||||||
let response_value = serde_json::from_str::<Value>(&text_content)
|
let response_value = serde_json::from_str::<Value>(&text_content)
|
||||||
.unwrap_or_else(|_| serde_json::json!({ "result": text_content }));
|
.unwrap_or_else(|_| serde_json::json!({ "result": text_content }));
|
||||||
@@ -250,7 +249,6 @@ impl GeminiProvider {
|
|||||||
} else if msg.role == "assistant" && msg.tool_calls.is_some() {
|
} else if msg.role == "assistant" && msg.tool_calls.is_some() {
|
||||||
// Assistant messages with tool_calls
|
// Assistant messages with tool_calls
|
||||||
if let Some(tool_calls) = &msg.tool_calls {
|
if let Some(tool_calls) = &msg.tool_calls {
|
||||||
// Include text content if present
|
|
||||||
for p in &msg.content {
|
for p in &msg.content {
|
||||||
if let ContentPart::Text { text } = p {
|
if let ContentPart::Text { text } = p {
|
||||||
if !text.trim().is_empty() {
|
if !text.trim().is_empty() {
|
||||||
@@ -316,7 +314,8 @@ impl GeminiProvider {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge with previous message if role matches
|
// STRATEGY: Strictly enforce alternating roles.
|
||||||
|
// If current message has the same role as the last one, merge their parts.
|
||||||
if let Some(last_content) = contents.last_mut() {
|
if let Some(last_content) = contents.last_mut() {
|
||||||
if last_content.role.as_ref() == Some(&role) {
|
if last_content.role.as_ref() == Some(&role) {
|
||||||
last_content.parts.extend(parts);
|
last_content.parts.extend(parts);
|
||||||
@@ -331,7 +330,6 @@ impl GeminiProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Gemini requires the first message to be from "user".
|
// Gemini requires the first message to be from "user".
|
||||||
// If it starts with "model", we prepend a placeholder user message.
|
|
||||||
if let Some(first) = contents.first() {
|
if let Some(first) = contents.first() {
|
||||||
if first.role.as_deref() == Some("model") {
|
if first.role.as_deref() == Some("model") {
|
||||||
contents.insert(0, GeminiContent {
|
contents.insert(0, GeminiContent {
|
||||||
@@ -346,6 +344,12 @@ impl GeminiProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Final check: ensure we don't have empty contents after filtering.
|
||||||
|
// If the last message was merged or filtered, we might have an empty array.
|
||||||
|
if contents.is_empty() && system_parts.is_empty() {
|
||||||
|
return Err(AppError::ProviderError("No valid content parts after filtering".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
let system_instruction = if !system_parts.is_empty() {
|
let system_instruction = if !system_parts.is_empty() {
|
||||||
Some(GeminiContent {
|
Some(GeminiContent {
|
||||||
parts: system_parts,
|
parts: system_parts,
|
||||||
|
|||||||
Reference in New Issue
Block a user