language_models: Dynamically detect Copilot Chat models (#29027)
I noticed the discussion in #28881, and had thought of exactly the same a few days prior. This implementation should preserve existing functionality fairly well. I've added a dependency (serde_with) to allow the deserializer to skip models which cannot be deserialized, which could occur if a future provider, for instance, is added. Without this modification, such a change could break all models. If extra dependencies aren't desired, a manual implementation could be used instead. - Closes #29369 Release Notes: - Dynamically detect available Copilot Chat models, including all models with tool support --------- Co-authored-by: AidanV <aidanvanduyne@gmail.com> Co-authored-by: imumesh18 <umesh4257@gmail.com> Co-authored-by: Bennet Bo Fenner <bennet@zed.dev> Co-authored-by: Agus Zubiaga <hi@aguz.me>
This commit is contained in:
parent
634b275931
commit
f14e48d202
5 changed files with 320 additions and 182 deletions
|
@ -15,13 +15,15 @@ path = "src/language_models.rs"
|
|||
anthropic = { workspace = true, features = ["schemars"] }
|
||||
anyhow.workspace = true
|
||||
aws-config = { workspace = true, features = ["behavior-version-latest"] }
|
||||
aws-credential-types = { workspace = true, features = ["hardcoded-credentials"] }
|
||||
aws-credential-types = { workspace = true, features = [
|
||||
"hardcoded-credentials",
|
||||
] }
|
||||
aws_http_client.workspace = true
|
||||
bedrock.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
credentials_provider.workspace = true
|
||||
copilot = { workspace = true, features = ["schemars"] }
|
||||
copilot.workspace = true
|
||||
deepseek = { workspace = true, features = ["schemars"] }
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
|
|
|
@ -5,8 +5,8 @@ use std::sync::Arc;
|
|||
use anyhow::{Result, anyhow};
|
||||
use collections::HashMap;
|
||||
use copilot::copilot_chat::{
|
||||
ChatMessage, CopilotChat, Model as CopilotChatModel, Request as CopilotChatRequest,
|
||||
ResponseEvent, Tool, ToolCall,
|
||||
ChatMessage, CopilotChat, Model as CopilotChatModel, ModelVendor,
|
||||
Request as CopilotChatRequest, ResponseEvent, Tool, ToolCall,
|
||||
};
|
||||
use copilot::{Copilot, Status};
|
||||
use futures::future::BoxFuture;
|
||||
|
@ -20,12 +20,11 @@ use language_model::{
|
|||
AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||
LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,
|
||||
LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelToolChoice, LanguageModelToolUse, MessageContent,
|
||||
RateLimiter, Role, StopReason,
|
||||
LanguageModelRequestMessage, LanguageModelToolChoice, LanguageModelToolSchemaFormat,
|
||||
LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason,
|
||||
};
|
||||
use settings::SettingsStore;
|
||||
use std::time::Duration;
|
||||
use strum::IntoEnumIterator;
|
||||
use ui::prelude::*;
|
||||
|
||||
use super::anthropic::count_anthropic_tokens;
|
||||
|
@ -100,17 +99,26 @@ impl LanguageModelProvider for CopilotChatLanguageModelProvider {
|
|||
IconName::Copilot
|
||||
}
|
||||
|
||||
fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {
|
||||
Some(self.create_language_model(CopilotChatModel::default()))
|
||||
fn default_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> {
|
||||
let models = CopilotChat::global(cx).and_then(|m| m.read(cx).models())?;
|
||||
models
|
||||
.first()
|
||||
.map(|model| self.create_language_model(model.clone()))
|
||||
}
|
||||
|
||||
fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {
|
||||
Some(self.create_language_model(CopilotChatModel::default_fast()))
|
||||
fn default_fast_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> {
|
||||
// The default model should be Copilot Chat's 'base model', which is likely a relatively fast
|
||||
// model (e.g. 4o) and a sensible choice when considering premium requests
|
||||
self.default_model(cx)
|
||||
}
|
||||
|
||||
fn provided_models(&self, _cx: &App) -> Vec<Arc<dyn LanguageModel>> {
|
||||
CopilotChatModel::iter()
|
||||
.map(|model| self.create_language_model(model))
|
||||
fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {
|
||||
let Some(models) = CopilotChat::global(cx).and_then(|m| m.read(cx).models()) else {
|
||||
return Vec::new();
|
||||
};
|
||||
models
|
||||
.iter()
|
||||
.map(|model| self.create_language_model(model.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
@ -187,13 +195,15 @@ impl LanguageModel for CopilotChatLanguageModel {
|
|||
}
|
||||
|
||||
fn supports_tools(&self) -> bool {
|
||||
match self.model {
|
||||
CopilotChatModel::Gpt4o
|
||||
| CopilotChatModel::Gpt4_1
|
||||
| CopilotChatModel::O4Mini
|
||||
| CopilotChatModel::Claude3_5Sonnet
|
||||
| CopilotChatModel::Claude3_7Sonnet => true,
|
||||
_ => false,
|
||||
self.model.supports_tools()
|
||||
}
|
||||
|
||||
fn tool_input_format(&self) -> LanguageModelToolSchemaFormat {
|
||||
match self.model.vendor() {
|
||||
ModelVendor::OpenAI | ModelVendor::Anthropic => {
|
||||
LanguageModelToolSchemaFormat::JsonSchema
|
||||
}
|
||||
ModelVendor::Google => LanguageModelToolSchemaFormat::JsonSchemaSubset,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,25 +228,13 @@ impl LanguageModel for CopilotChatLanguageModel {
|
|||
request: LanguageModelRequest,
|
||||
cx: &App,
|
||||
) -> BoxFuture<'static, Result<usize>> {
|
||||
match self.model {
|
||||
CopilotChatModel::Claude3_5Sonnet
|
||||
| CopilotChatModel::Claude3_7Sonnet
|
||||
| CopilotChatModel::Claude3_7SonnetThinking => count_anthropic_tokens(request, cx),
|
||||
CopilotChatModel::Gemini20Flash | CopilotChatModel::Gemini25Pro => {
|
||||
count_google_tokens(request, cx)
|
||||
match self.model.vendor() {
|
||||
ModelVendor::Anthropic => count_anthropic_tokens(request, cx),
|
||||
ModelVendor::Google => count_google_tokens(request, cx),
|
||||
ModelVendor::OpenAI => {
|
||||
let model = open_ai::Model::from_id(self.model.id()).unwrap_or_default();
|
||||
count_open_ai_tokens(request, model, cx)
|
||||
}
|
||||
CopilotChatModel::Gpt4o => count_open_ai_tokens(request, open_ai::Model::FourOmni, cx),
|
||||
CopilotChatModel::Gpt4 => count_open_ai_tokens(request, open_ai::Model::Four, cx),
|
||||
CopilotChatModel::Gpt4_1 => {
|
||||
count_open_ai_tokens(request, open_ai::Model::FourPointOne, cx)
|
||||
}
|
||||
CopilotChatModel::Gpt3_5Turbo => {
|
||||
count_open_ai_tokens(request, open_ai::Model::ThreePointFiveTurbo, cx)
|
||||
}
|
||||
CopilotChatModel::O1 => count_open_ai_tokens(request, open_ai::Model::O1, cx),
|
||||
CopilotChatModel::O3Mini => count_open_ai_tokens(request, open_ai::Model::O3Mini, cx),
|
||||
CopilotChatModel::O3 => count_open_ai_tokens(request, open_ai::Model::O3, cx),
|
||||
CopilotChatModel::O4Mini => count_open_ai_tokens(request, open_ai::Model::O4Mini, cx),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -430,8 +428,6 @@ impl CopilotChatLanguageModel {
|
|||
&self,
|
||||
request: LanguageModelRequest,
|
||||
) -> Result<CopilotChatRequest> {
|
||||
let model = self.model.clone();
|
||||
|
||||
let mut request_messages: Vec<LanguageModelRequestMessage> = Vec::new();
|
||||
for message in request.messages {
|
||||
if let Some(last_message) = request_messages.last_mut() {
|
||||
|
@ -545,9 +541,9 @@ impl CopilotChatLanguageModel {
|
|||
Ok(CopilotChatRequest {
|
||||
intent: true,
|
||||
n: 1,
|
||||
stream: model.uses_streaming(),
|
||||
stream: self.model.uses_streaming(),
|
||||
temperature: 0.1,
|
||||
model,
|
||||
model: self.model.id().to_string(),
|
||||
messages,
|
||||
tools,
|
||||
tool_choice: request.tool_choice.map(|choice| match choice {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue