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:
parent
3173f87dc3
commit
3ea86da16f
2 changed files with 135 additions and 138 deletions
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue