mirror of
https://github.com/katanemo/plano.git
synced 2026-04-27 17:56:28 +02:00
making Messages.Content optional, and having the upstream LLM fail if the right fields aren't set (#699)
Co-authored-by: Salman Paracha <salmanparacha@MacBook-Pro-342.local>
This commit is contained in:
parent
626f556cc6
commit
cdc1d7cee2
17 changed files with 294 additions and 133 deletions
|
|
@ -188,7 +188,7 @@ pub fn convert_openai_message_to_anthropic_content(
|
|||
|
||||
// Handle regular content
|
||||
match &message.content {
|
||||
MessageContent::Text(text) => {
|
||||
Some(MessageContent::Text(text)) => {
|
||||
if !text.is_empty() {
|
||||
blocks.push(MessagesContentBlock::Text {
|
||||
text: text.clone(),
|
||||
|
|
@ -196,7 +196,7 @@ pub fn convert_openai_message_to_anthropic_content(
|
|||
});
|
||||
}
|
||||
}
|
||||
MessageContent::Parts(parts) => {
|
||||
Some(MessageContent::Parts(parts)) => {
|
||||
for part in parts {
|
||||
match part {
|
||||
ContentPart::Text { text } => {
|
||||
|
|
@ -212,6 +212,7 @@ pub fn convert_openai_message_to_anthropic_content(
|
|||
}
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
// Handle tool calls
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ impl TryFrom<MessagesMessage> for Vec<Message> {
|
|||
MessagesMessageContent::Single(text) => {
|
||||
result.push(Message {
|
||||
role: message.role.into(),
|
||||
content: MessageContent::Text(text),
|
||||
content: Some(MessageContent::Text(text)),
|
||||
name: None,
|
||||
tool_calls: None,
|
||||
tool_call_id: None,
|
||||
|
|
@ -186,7 +186,7 @@ impl TryFrom<MessagesMessage> for Vec<Message> {
|
|||
for (tool_use_id, result_text, _is_error) in tool_results {
|
||||
result.push(Message {
|
||||
role: Role::Tool,
|
||||
content: MessageContent::Text(result_text),
|
||||
content: Some(MessageContent::Text(result_text)),
|
||||
name: None,
|
||||
tool_calls: None,
|
||||
tool_call_id: Some(tool_use_id),
|
||||
|
|
@ -260,7 +260,7 @@ impl From<MessagesSystemPrompt> for Message {
|
|||
|
||||
Message {
|
||||
role: Role::System,
|
||||
content: system_content,
|
||||
content: Some(system_content),
|
||||
name: None,
|
||||
tool_calls: None,
|
||||
tool_call_id: None,
|
||||
|
|
@ -317,16 +317,19 @@ fn convert_anthropic_tool_choice(
|
|||
fn build_openai_content(
|
||||
content_parts: Vec<ContentPart>,
|
||||
tool_calls: &[ToolCall],
|
||||
) -> MessageContent {
|
||||
if content_parts.len() == 1 && tool_calls.is_empty() {
|
||||
) -> Option<MessageContent> {
|
||||
if content_parts.is_empty() && !tool_calls.is_empty() {
|
||||
// For assistant messages with only tool calls, content is optional
|
||||
None
|
||||
} else if content_parts.len() == 1 && tool_calls.is_empty() {
|
||||
match &content_parts[0] {
|
||||
ContentPart::Text { text } => MessageContent::Text(text.clone()),
|
||||
_ => MessageContent::Parts(content_parts),
|
||||
ContentPart::Text { text } => Some(MessageContent::Text(text.clone())),
|
||||
_ => Some(MessageContent::Parts(content_parts)),
|
||||
}
|
||||
} else if content_parts.is_empty() {
|
||||
MessageContent::Text("".to_string())
|
||||
Some(MessageContent::Text("".to_string()))
|
||||
} else {
|
||||
MessageContent::Parts(content_parts)
|
||||
Some(MessageContent::Parts(content_parts))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ use crate::apis::openai_responses::{
|
|||
ResponsesAPIRequest, Tool as ResponsesTool, ToolChoice as ResponsesToolChoice,
|
||||
};
|
||||
use crate::clients::TransformError;
|
||||
use crate::transforms::lib::ExtractText;
|
||||
use crate::transforms::lib::*;
|
||||
use crate::transforms::*;
|
||||
|
||||
|
|
@ -48,7 +47,7 @@ impl TryFrom<ResponsesInputConverter> for Vec<Message> {
|
|||
if let Some(instructions) = converter.instructions {
|
||||
messages.push(Message {
|
||||
role: Role::System,
|
||||
content: MessageContent::Text(instructions),
|
||||
content: Some(MessageContent::Text(instructions)),
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
|
|
@ -58,7 +57,7 @@ impl TryFrom<ResponsesInputConverter> for Vec<Message> {
|
|||
// Add the user message
|
||||
messages.push(Message {
|
||||
role: Role::User,
|
||||
content: MessageContent::Text(text),
|
||||
content: Some(MessageContent::Text(text)),
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
|
|
@ -74,7 +73,7 @@ impl TryFrom<ResponsesInputConverter> for Vec<Message> {
|
|||
if let Some(instructions) = converter.instructions {
|
||||
converted_messages.push(Message {
|
||||
role: Role::System,
|
||||
content: MessageContent::Text(instructions),
|
||||
content: Some(MessageContent::Text(instructions)),
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
|
|
@ -154,7 +153,7 @@ impl TryFrom<ResponsesInputConverter> for Vec<Message> {
|
|||
|
||||
converted_messages.push(Message {
|
||||
role,
|
||||
content,
|
||||
content: Some(content),
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
|
|
@ -174,11 +173,7 @@ impl TryFrom<ResponsesInputConverter> for Vec<Message> {
|
|||
|
||||
impl From<Message> for MessagesSystemPrompt {
|
||||
fn from(val: Message) -> Self {
|
||||
let system_text = match val.content {
|
||||
MessageContent::Text(text) => text,
|
||||
MessageContent::Parts(parts) => parts.extract_text(),
|
||||
};
|
||||
MessagesSystemPrompt::Single(system_text)
|
||||
MessagesSystemPrompt::Single(val.content.extract_text())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,6 +186,8 @@ impl TryFrom<Message> for MessagesMessage {
|
|||
Role::Assistant => MessagesRole::Assistant,
|
||||
Role::Tool => {
|
||||
// Tool messages become user messages with tool results
|
||||
// Extract content text first, before moving tool_call_id
|
||||
let content_text = message.content.extract_text();
|
||||
let tool_call_id = message.tool_call_id.ok_or_else(|| {
|
||||
TransformError::MissingField(
|
||||
"tool_call_id required for Tool messages".to_string(),
|
||||
|
|
@ -204,7 +201,7 @@ impl TryFrom<Message> for MessagesMessage {
|
|||
tool_use_id: tool_call_id,
|
||||
is_error: None,
|
||||
content: ToolResultContent::Blocks(vec![MessagesContentBlock::Text {
|
||||
text: message.content.extract_text(),
|
||||
text: content_text,
|
||||
cache_control: None,
|
||||
}]),
|
||||
cache_control: None,
|
||||
|
|
@ -248,12 +245,12 @@ impl TryFrom<Message> for BedrockMessage {
|
|||
Role::User => {
|
||||
// Convert user message content to content blocks
|
||||
match message.content {
|
||||
MessageContent::Text(text) => {
|
||||
Some(MessageContent::Text(text)) => {
|
||||
if !text.is_empty() {
|
||||
content_blocks.push(ContentBlock::Text { text });
|
||||
}
|
||||
}
|
||||
MessageContent::Parts(parts) => {
|
||||
Some(MessageContent::Parts(parts)) => {
|
||||
// Convert OpenAI content parts to Bedrock ContentBlocks
|
||||
for part in parts {
|
||||
match part {
|
||||
|
|
@ -293,6 +290,9 @@ impl TryFrom<Message> for BedrockMessage {
|
|||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Empty content for user - shouldn't happen but handle gracefully
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we have at least one content block
|
||||
|
|
@ -550,10 +550,7 @@ impl TryFrom<ChatCompletionsRequest> for ConverseRequest {
|
|||
for message in req.messages {
|
||||
match message.role {
|
||||
Role::System => {
|
||||
let system_text = match message.content {
|
||||
MessageContent::Text(text) => text,
|
||||
MessageContent::Parts(parts) => parts.extract_text(),
|
||||
};
|
||||
let system_text = message.content.extract_text();
|
||||
system_messages.push(SystemContentBlock::Text { text: system_text });
|
||||
}
|
||||
_ => {
|
||||
|
|
@ -778,14 +775,16 @@ mod tests {
|
|||
messages: vec![
|
||||
Message {
|
||||
role: Role::System,
|
||||
content: MessageContent::Text("You are a helpful assistant.".to_string()),
|
||||
content: Some(MessageContent::Text(
|
||||
"You are a helpful assistant.".to_string(),
|
||||
)),
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
},
|
||||
Message {
|
||||
role: Role::User,
|
||||
content: MessageContent::Text("Hello, how are you?".to_string()),
|
||||
content: Some(MessageContent::Text("Hello, how are you?".to_string())),
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
|
|
@ -840,7 +839,7 @@ mod tests {
|
|||
model: "gpt-4".to_string(),
|
||||
messages: vec![Message {
|
||||
role: Role::User,
|
||||
content: MessageContent::Text("What's the weather like?".to_string()),
|
||||
content: Some(MessageContent::Text("What's the weather like?".to_string())),
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
|
|
@ -907,7 +906,7 @@ mod tests {
|
|||
model: "gpt-4".to_string(),
|
||||
messages: vec![Message {
|
||||
role: Role::User,
|
||||
content: MessageContent::Text("Help me with something".to_string()),
|
||||
content: Some(MessageContent::Text("Help me with something".to_string())),
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
|
|
@ -950,28 +949,30 @@ mod tests {
|
|||
messages: vec![
|
||||
Message {
|
||||
role: Role::System,
|
||||
content: MessageContent::Text("Be concise".to_string()),
|
||||
content: Some(MessageContent::Text("Be concise".to_string())),
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
},
|
||||
Message {
|
||||
role: Role::User,
|
||||
content: MessageContent::Text("Hello".to_string()),
|
||||
content: Some(MessageContent::Text("Hello".to_string())),
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
},
|
||||
Message {
|
||||
role: Role::Assistant,
|
||||
content: MessageContent::Text("Hi there! How can I help you?".to_string()),
|
||||
content: Some(MessageContent::Text(
|
||||
"Hi there! How can I help you?".to_string(),
|
||||
)),
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
},
|
||||
Message {
|
||||
role: Role::User,
|
||||
content: MessageContent::Text("What's 2+2?".to_string()),
|
||||
content: Some(MessageContent::Text("What's 2+2?".to_string())),
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
|
|
@ -1009,7 +1010,7 @@ mod tests {
|
|||
fn test_openai_message_to_bedrock_conversion() {
|
||||
let openai_message = Message {
|
||||
role: Role::User,
|
||||
content: MessageContent::Text("Test message".to_string()),
|
||||
content: Some(MessageContent::Text("Test message".to_string())),
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue