Copilot fix o1 model (#30581)

Release Notes:

- Fixed an issue where the `o1` model would not work when using Copilot
Chat
This commit is contained in:
Bennet Bo Fenner 2025-05-12 17:27:24 +02:00 committed by GitHub
parent 3173f87dc3
commit 3ea86da16f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 135 additions and 138 deletions

View file

@ -237,7 +237,6 @@ pub struct FunctionContent {
#[serde(tag = "type", rename_all = "snake_case")] #[serde(tag = "type", rename_all = "snake_case")]
pub struct ResponseEvent { pub struct ResponseEvent {
pub choices: Vec<ResponseChoice>, pub choices: Vec<ResponseChoice>,
pub created: u64,
pub id: String, pub id: String,
} }

View file

@ -264,7 +264,7 @@ impl LanguageModel for CopilotChatLanguageModel {
} }
} }
let copilot_request = match self.to_copilot_chat_request(request) { let copilot_request = match into_copilot_chat(&self.model, request) {
Ok(request) => request, Ok(request) => request,
Err(err) => return futures::future::ready(Err(err)).boxed(), Err(err) => return futures::future::ready(Err(err)).boxed(),
}; };
@ -423,163 +423,161 @@ pub fn map_to_language_model_completion_events(
.flat_map(futures::stream::iter) .flat_map(futures::stream::iter)
} }
impl CopilotChatLanguageModel { fn into_copilot_chat(
pub fn to_copilot_chat_request( model: &copilot::copilot_chat::Model,
&self, request: LanguageModelRequest,
request: LanguageModelRequest, ) -> Result<CopilotChatRequest> {
) -> Result<CopilotChatRequest> { let mut request_messages: Vec<LanguageModelRequestMessage> = Vec::new();
let mut request_messages: Vec<LanguageModelRequestMessage> = Vec::new(); for message in request.messages {
for message in request.messages { if let Some(last_message) = request_messages.last_mut() {
if let Some(last_message) = request_messages.last_mut() { if last_message.role == message.role {
if last_message.role == message.role { last_message.content.extend(message.content);
last_message.content.extend(message.content);
} else {
request_messages.push(message);
}
} else { } else {
request_messages.push(message); request_messages.push(message);
} }
} else {
request_messages.push(message);
} }
}
let mut tool_called = false; let mut tool_called = false;
let mut messages: Vec<ChatMessage> = Vec::new(); let mut messages: Vec<ChatMessage> = Vec::new();
for message in request_messages { for message in request_messages {
match message.role { match message.role {
Role::User => { Role::User => {
for content in &message.content { for content in &message.content {
if let MessageContent::ToolResult(tool_result) = content { if let MessageContent::ToolResult(tool_result) = content {
messages.push(ChatMessage::Tool { messages.push(ChatMessage::Tool {
tool_call_id: tool_result.tool_use_id.to_string(), tool_call_id: tool_result.tool_use_id.to_string(),
content: tool_result.content.to_string(), content: tool_result.content.to_string(),
});
}
}
let mut content_parts = Vec::new();
for content in &message.content {
match content {
MessageContent::Text(text) | MessageContent::Thinking { text, .. }
if !text.is_empty() =>
{
if let Some(ChatMessageContent::Text { text: text_content }) =
content_parts.last_mut()
{
text_content.push_str(text);
} else {
content_parts.push(ChatMessageContent::Text {
text: text.to_string(),
});
}
}
MessageContent::Image(image) if self.model.supports_vision() => {
content_parts.push(ChatMessageContent::Image {
image_url: ImageUrl {
url: image.to_base64_url(),
},
});
}
_ => {}
}
}
if !content_parts.is_empty() {
messages.push(ChatMessage::User {
content: content_parts,
}); });
} }
} }
Role::Assistant => {
let mut tool_calls = Vec::new(); let mut content_parts = Vec::new();
for content in &message.content { for content in &message.content {
if let MessageContent::ToolUse(tool_use) = content { match content {
tool_called = true; MessageContent::Text(text) | MessageContent::Thinking { text, .. }
tool_calls.push(ToolCall { if !text.is_empty() =>
id: tool_use.id.to_string(), {
content: copilot::copilot_chat::ToolCallContent::Function { if let Some(ChatMessageContent::Text { text: text_content }) =
function: copilot::copilot_chat::FunctionContent { content_parts.last_mut()
name: tool_use.name.to_string(), {
arguments: serde_json::to_string(&tool_use.input)?, text_content.push_str(text);
}, } else {
content_parts.push(ChatMessageContent::Text {
text: text.to_string(),
});
}
}
MessageContent::Image(image) if model.supports_vision() => {
content_parts.push(ChatMessageContent::Image {
image_url: ImageUrl {
url: image.to_base64_url(),
}, },
}); });
} }
_ => {}
} }
}
let text_content = { if !content_parts.is_empty() {
let mut buffer = String::new(); messages.push(ChatMessage::User {
for string in message.content.iter().filter_map(|content| match content { content: content_parts,
MessageContent::Text(text) | MessageContent::Thinking { text, .. } => {
Some(text.as_str())
}
MessageContent::ToolUse(_)
| MessageContent::RedactedThinking(_)
| MessageContent::ToolResult(_)
| MessageContent::Image(_) => None,
}) {
buffer.push_str(string);
}
buffer
};
messages.push(ChatMessage::Assistant {
content: if text_content.is_empty() {
None
} else {
Some(text_content)
},
tool_calls,
}); });
} }
Role::System => messages.push(ChatMessage::System {
content: message.string_contents(),
}),
} }
} Role::Assistant => {
let mut tool_calls = Vec::new();
for content in &message.content {
if let MessageContent::ToolUse(tool_use) = content {
tool_called = true;
tool_calls.push(ToolCall {
id: tool_use.id.to_string(),
content: copilot::copilot_chat::ToolCallContent::Function {
function: copilot::copilot_chat::FunctionContent {
name: tool_use.name.to_string(),
arguments: serde_json::to_string(&tool_use.input)?,
},
},
});
}
}
let mut tools = request let text_content = {
.tools let mut buffer = String::new();
.iter() for string in message.content.iter().filter_map(|content| match content {
.map(|tool| Tool::Function { MessageContent::Text(text) | MessageContent::Thinking { text, .. } => {
function: copilot::copilot_chat::Function { Some(text.as_str())
name: tool.name.clone(), }
description: tool.description.clone(), MessageContent::ToolUse(_)
parameters: tool.input_schema.clone(), | MessageContent::RedactedThinking(_)
}, | MessageContent::ToolResult(_)
}) | MessageContent::Image(_) => None,
.collect::<Vec<_>>(); }) {
buffer.push_str(string);
}
// The API will return a Bad Request (with no error message) when tools buffer
// were used previously in the conversation but no tools are provided as };
// part of this request. Inserting a dummy tool seems to circumvent this
// error.
if tool_called && tools.is_empty() {
tools.push(Tool::Function {
function: copilot::copilot_chat::Function {
name: "noop".to_string(),
description: "No operation".to_string(),
parameters: serde_json::json!({
"type": "object"
}),
},
});
}
Ok(CopilotChatRequest { messages.push(ChatMessage::Assistant {
intent: true, content: if text_content.is_empty() {
n: 1, None
stream: self.model.uses_streaming(), } else {
temperature: 0.1, Some(text_content)
model: self.model.id().to_string(), },
messages, tool_calls,
tools, });
tool_choice: request.tool_choice.map(|choice| match choice { }
LanguageModelToolChoice::Auto => copilot::copilot_chat::ToolChoice::Auto, Role::System => messages.push(ChatMessage::System {
LanguageModelToolChoice::Any => copilot::copilot_chat::ToolChoice::Any, content: message.string_contents(),
LanguageModelToolChoice::None => copilot::copilot_chat::ToolChoice::None,
}), }),
}) }
} }
let mut tools = request
.tools
.iter()
.map(|tool| Tool::Function {
function: copilot::copilot_chat::Function {
name: tool.name.clone(),
description: tool.description.clone(),
parameters: tool.input_schema.clone(),
},
})
.collect::<Vec<_>>();
// The API will return a Bad Request (with no error message) when tools
// were used previously in the conversation but no tools are provided as
// part of this request. Inserting a dummy tool seems to circumvent this
// error.
if tool_called && tools.is_empty() {
tools.push(Tool::Function {
function: copilot::copilot_chat::Function {
name: "noop".to_string(),
description: "No operation".to_string(),
parameters: serde_json::json!({
"type": "object"
}),
},
});
}
Ok(CopilotChatRequest {
intent: true,
n: 1,
stream: model.uses_streaming(),
temperature: 0.1,
model: model.id().to_string(),
messages,
tools,
tool_choice: request.tool_choice.map(|choice| match choice {
LanguageModelToolChoice::Auto => copilot::copilot_chat::ToolChoice::Auto,
LanguageModelToolChoice::Any => copilot::copilot_chat::ToolChoice::Any,
LanguageModelToolChoice::None => copilot::copilot_chat::ToolChoice::None,
}),
})
} }
struct ConfigurationView { struct ConfigurationView {