diff --git a/router/src/infer/chat_template.rs b/router/src/infer/chat_template.rs index 6a9289c0..12a79f6a 100644 --- a/router/src/infer/chat_template.rs +++ b/router/src/infer/chat_template.rs @@ -1,5 +1,5 @@ use crate::infer::InferError; -use crate::{ChatTemplateInputs, Message, MessageChunk, TextMessage, TokenizerConfigToken, Tool}; +use crate::{ChatTemplateInputs, Message, MessageChunk, MessageContent, TextMessage, TokenizerConfigToken, Tool}; use chrono::Local; use minijinja::{Environment, ErrorKind, Template}; use minijinja_contrib::pycompat; @@ -74,14 +74,38 @@ impl ChatTemplate { format!("\n---\n{}", tool_prompt) }; if let Some(last_message) = messages.last_mut() { - last_message.content.push(MessageChunk::Text { text }); + if let Some(ref mut content) = last_message.content { + content.push(MessageChunk::Text { text }); + } else { + last_message.content = Some(MessageContent::SingleText(text)); + } } Some(tools) } None => None, }; - let messages: Vec = messages.into_iter().map(|c| c.into()).collect(); + let messages: Vec = messages + .into_iter() + .map(|m| { + if m.role == "assistant" && m.tool_calls.is_some() && m.content.is_none() { + // For assistant messages with tool calls but no content, + // just use the standard conversion which will handle None content + Ok(m.into()) + } else if m.content.is_none() { + // For messages requiring content but having none, return error + return Err(InferError::TemplateError( + minijinja::Error::new( + minijinja::ErrorKind::SyntaxError, + "Content is required for this message type", + ) + )); + } else { + Ok(m.into()) + } + }) + .collect::>()?; + let final_message = messages.last().cloned(); let mut rendered_template = self .template diff --git a/router/src/lib.rs b/router/src/lib.rs index 959da7f4..e9d4bb59 100644 --- a/router/src/lib.rs +++ b/router/src/lib.rs @@ -1189,10 +1189,13 @@ pub struct Message { #[schema(example = "user")] role: String, #[schema(example = "My name is David and I")] - pub content: MessageContent, + #[serde(default, skip_serializing_if = "Option::is_none")] + content: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[schema(example = "\"David\"")] name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + tool_calls: Option>, } #[derive(Clone, Deserialize, Serialize, ToSchema, Debug, PartialEq)] @@ -1232,8 +1235,8 @@ impl From for TextMessage { TextMessage { role: value.role, content: match value.content { - MessageContent::SingleText(text) => text, - MessageContent::MultipleChunks(chunks) => chunks + Some(MessageContent::SingleText(text)) => text, + Some(MessageContent::MultipleChunks(chunks)) => chunks .into_iter() .map(|chunk| match chunk { MessageChunk::Text { text } => text, @@ -1241,6 +1244,7 @@ impl From for TextMessage { }) .collect::>() .join(""), + None => String::new(), }, } }