open_ai: Make Assistant message content optional (#31418)

Fixes regression caused by:
https://github.com/zed-industries/zed/pull/30639

Assistant messages can come back with no content, and we no longer
allowed that in the deserialization.

Release Notes:

- open_ai: fixed deserialization issue if assistant content was empty
This commit is contained in:
Ben Brandt 2025-05-26 11:59:39 +02:00 committed by GitHub
parent c73af0a52f
commit ef0e1cb2ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 17 additions and 10 deletions

View file

@ -400,7 +400,7 @@ pub fn into_open_ai(
tool_calls.push(tool_call); tool_calls.push(tool_call);
} else { } else {
messages.push(open_ai::RequestMessage::Assistant { messages.push(open_ai::RequestMessage::Assistant {
content: open_ai::MessageContent::empty(), content: None,
tool_calls: vec![tool_call], tool_calls: vec![tool_call],
}); });
} }
@ -474,7 +474,13 @@ fn add_message_content_part(
) { ) {
match (role, messages.last_mut()) { match (role, messages.last_mut()) {
(Role::User, Some(open_ai::RequestMessage::User { content })) (Role::User, Some(open_ai::RequestMessage::User { content }))
| (Role::Assistant, Some(open_ai::RequestMessage::Assistant { content, .. })) | (
Role::Assistant,
Some(open_ai::RequestMessage::Assistant {
content: Some(content),
..
}),
)
| (Role::System, Some(open_ai::RequestMessage::System { content, .. })) => { | (Role::System, Some(open_ai::RequestMessage::System { content, .. })) => {
content.push_part(new_part); content.push_part(new_part);
} }
@ -484,7 +490,7 @@ fn add_message_content_part(
content: open_ai::MessageContent::from(vec![new_part]), content: open_ai::MessageContent::from(vec![new_part]),
}, },
Role::Assistant => open_ai::RequestMessage::Assistant { Role::Assistant => open_ai::RequestMessage::Assistant {
content: open_ai::MessageContent::from(vec![new_part]), content: Some(open_ai::MessageContent::from(vec![new_part])),
tool_calls: Vec::new(), tool_calls: Vec::new(),
}, },
Role::System => open_ai::RequestMessage::System { Role::System => open_ai::RequestMessage::System {

View file

@ -278,7 +278,7 @@ pub struct FunctionDefinition {
#[serde(tag = "role", rename_all = "lowercase")] #[serde(tag = "role", rename_all = "lowercase")]
pub enum RequestMessage { pub enum RequestMessage {
Assistant { Assistant {
content: MessageContent, content: Option<MessageContent>,
#[serde(default, skip_serializing_if = "Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
tool_calls: Vec<ToolCall>, tool_calls: Vec<ToolCall>,
}, },
@ -562,16 +562,16 @@ fn adapt_response_to_stream(response: Response) -> ResponseStreamEvent {
.into_iter() .into_iter()
.map(|choice| { .map(|choice| {
let content = match &choice.message { let content = match &choice.message {
RequestMessage::Assistant { content, .. } => content, RequestMessage::Assistant { content, .. } => content.as_ref(),
RequestMessage::User { content } => content, RequestMessage::User { content } => Some(content),
RequestMessage::System { content } => content, RequestMessage::System { content } => Some(content),
RequestMessage::Tool { content, .. } => content, RequestMessage::Tool { content, .. } => Some(content),
}; };
let mut text_content = String::new(); let mut text_content = String::new();
match content { match content {
MessageContent::Plain(text) => text_content.push_str(&text), Some(MessageContent::Plain(text)) => text_content.push_str(&text),
MessageContent::Multipart(parts) => { Some(MessageContent::Multipart(parts)) => {
for part in parts { for part in parts {
match part { match part {
MessagePart::Text { text } => text_content.push_str(&text), MessagePart::Text { text } => text_content.push_str(&text),
@ -579,6 +579,7 @@ fn adapt_response_to_stream(response: Response) -> ResponseStreamEvent {
} }
} }
} }
None => {}
}; };
ChoiceDelta { ChoiceDelta {