agent: Allow customizing temperature by provider/model (#30033)
Adds a new `agent.model_parameters` setting that allows the user to specify a custom temperature for a provider AND/OR model: ```json5 "model_parameters": [ // To set parameters for all requests to OpenAI models: { "provider": "openai", "temperature": 0.5 }, // To set parameters for all requests in general: { "temperature": 0 }, // To set parameters for a specific provider and model: { "provider": "zed.dev", "model": "claude-3-7-sonnet-latest", "temperature": 1.0 } ], ``` Release Notes: - agent: Allow customizing temperature by provider/model --------- Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com> Co-authored-by: Marshall Bowers <git@maxdeviant.com>
This commit is contained in:
parent
44ef5bd95f
commit
42894f6c8b
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -595,6 +595,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anthropic",
|
||||
"anyhow",
|
||||
"collections",
|
||||
"deepseek",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
@ -3006,6 +3007,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"assistant",
|
||||
"assistant_context_editor",
|
||||
"assistant_settings",
|
||||
"assistant_slash_command",
|
||||
"assistant_tool",
|
||||
"async-stripe",
|
||||
|
@ -605,13 +605,11 @@
|
||||
//
|
||||
// Default: main
|
||||
"fallback_branch_name": "main",
|
||||
|
||||
// Whether to sort entries in the panel by path
|
||||
// or by status (the default).
|
||||
//
|
||||
// Default: false
|
||||
"sort_by_path": false,
|
||||
|
||||
"scrollbar": {
|
||||
// When to show the scrollbar in the git panel.
|
||||
//
|
||||
@ -661,6 +659,28 @@
|
||||
// The model to use.
|
||||
"model": "claude-3-7-sonnet-latest"
|
||||
},
|
||||
// Additional parameters for language model requests. When making a request to a model, parameters will be taken
|
||||
// from the last entry in this list that matches the model's provider and name. In each entry, both provider
|
||||
// and model are optional, so that you can specify parameters for either one.
|
||||
"model_parameters": [
|
||||
// To set parameters for all requests to OpenAI models:
|
||||
// {
|
||||
// "provider": "openai",
|
||||
// "temperature": 0.5
|
||||
// }
|
||||
//
|
||||
// To set parameters for all requests in general:
|
||||
// {
|
||||
// "temperature": 0
|
||||
// }
|
||||
//
|
||||
// To set parameters for a specific provider and model:
|
||||
// {
|
||||
// "provider": "zed.dev",
|
||||
// "model": "claude-3-7-sonnet-latest",
|
||||
// "temperature": 1.0
|
||||
// }
|
||||
],
|
||||
// When enabled, the agent can run potentially destructive actions without asking for your confirmation.
|
||||
"always_allow_tool_actions": false,
|
||||
// When enabled, the agent will stream edits.
|
||||
|
@ -1417,7 +1417,10 @@ impl ActiveThread {
|
||||
messages: vec![request_message],
|
||||
tools: vec![],
|
||||
stop: vec![],
|
||||
temperature: None,
|
||||
temperature: AssistantSettings::temperature_for_model(
|
||||
&configured_model.model,
|
||||
cx,
|
||||
),
|
||||
};
|
||||
|
||||
Some(configured_model.model.count_tokens(request, cx))
|
||||
|
@ -2,6 +2,7 @@ use crate::context::ContextLoadResult;
|
||||
use crate::inline_prompt_editor::CodegenStatus;
|
||||
use crate::{context::load_context, context_store::ContextStore};
|
||||
use anyhow::Result;
|
||||
use assistant_settings::AssistantSettings;
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::HashSet;
|
||||
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
|
||||
@ -383,7 +384,7 @@ impl CodegenAlternative {
|
||||
if user_prompt.trim().to_lowercase() == "delete" {
|
||||
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
||||
} else {
|
||||
let request = self.build_request(user_prompt, cx)?;
|
||||
let request = self.build_request(&model, user_prompt, cx)?;
|
||||
cx.spawn(async move |_, cx| model.stream_completion_text(request.await, &cx).await)
|
||||
.boxed_local()
|
||||
};
|
||||
@ -393,6 +394,7 @@ impl CodegenAlternative {
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
model: &Arc<dyn LanguageModel>,
|
||||
user_prompt: String,
|
||||
cx: &mut App,
|
||||
) -> Result<Task<LanguageModelRequest>> {
|
||||
@ -441,6 +443,8 @@ impl CodegenAlternative {
|
||||
}
|
||||
});
|
||||
|
||||
let temperature = AssistantSettings::temperature_for_model(&model, cx);
|
||||
|
||||
Ok(cx.spawn(async move |_cx| {
|
||||
let mut request_message = LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
@ -463,7 +467,7 @@ impl CodegenAlternative {
|
||||
mode: None,
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: None,
|
||||
temperature,
|
||||
messages: vec![request_message],
|
||||
}
|
||||
}))
|
||||
|
@ -8,7 +8,7 @@ use crate::ui::{
|
||||
AnimatedLabel, MaxModeTooltip,
|
||||
preview::{AgentPreview, UsageCallout},
|
||||
};
|
||||
use assistant_settings::CompletionMode;
|
||||
use assistant_settings::{AssistantSettings, CompletionMode};
|
||||
use buffer_diff::BufferDiff;
|
||||
use client::UserStore;
|
||||
use collections::{HashMap, HashSet};
|
||||
@ -1273,7 +1273,7 @@ impl MessageEditor {
|
||||
messages: vec![request_message],
|
||||
tools: vec![],
|
||||
stop: vec![],
|
||||
temperature: None,
|
||||
temperature: AssistantSettings::temperature_for_model(&model.model, cx),
|
||||
};
|
||||
|
||||
Some(model.model.count_tokens(request, cx))
|
||||
|
@ -6,6 +6,7 @@ use crate::inline_prompt_editor::{
|
||||
use crate::terminal_codegen::{CLEAR_INPUT, CodegenEvent, TerminalCodegen};
|
||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||
use anyhow::{Context as _, Result};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, VecDeque};
|
||||
use editor::{MultiBuffer, actions::SelectAll};
|
||||
@ -266,6 +267,12 @@ impl TerminalInlineAssistant {
|
||||
load_context(contexts, project, &assist.prompt_store, cx)
|
||||
})?;
|
||||
|
||||
let ConfiguredModel { model, .. } = LanguageModelRegistry::read_global(cx)
|
||||
.inline_assistant_model()
|
||||
.context("No inline assistant model")?;
|
||||
|
||||
let temperature = AssistantSettings::temperature_for_model(&model, cx);
|
||||
|
||||
Ok(cx.background_spawn(async move {
|
||||
let mut request_message = LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
@ -287,7 +294,7 @@ impl TerminalInlineAssistant {
|
||||
messages: vec![request_message],
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: None,
|
||||
temperature,
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
@ -1145,7 +1145,7 @@ impl Thread {
|
||||
messages: vec![],
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: None,
|
||||
temperature: AssistantSettings::temperature_for_model(&model, cx),
|
||||
};
|
||||
|
||||
let available_tools = self.available_tools(cx, model.clone());
|
||||
@ -1251,7 +1251,12 @@ impl Thread {
|
||||
request
|
||||
}
|
||||
|
||||
fn to_summarize_request(&self, added_user_message: String) -> LanguageModelRequest {
|
||||
fn to_summarize_request(
|
||||
&self,
|
||||
model: &Arc<dyn LanguageModel>,
|
||||
added_user_message: String,
|
||||
cx: &App,
|
||||
) -> LanguageModelRequest {
|
||||
let mut request = LanguageModelRequest {
|
||||
thread_id: None,
|
||||
prompt_id: None,
|
||||
@ -1259,7 +1264,7 @@ impl Thread {
|
||||
messages: vec![],
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: None,
|
||||
temperature: AssistantSettings::temperature_for_model(model, cx),
|
||||
};
|
||||
|
||||
for message in &self.messages {
|
||||
@ -1696,7 +1701,7 @@ impl Thread {
|
||||
If the conversation is about a specific subject, include it in the title. \
|
||||
Be descriptive. DO NOT speak in the first person.";
|
||||
|
||||
let request = self.to_summarize_request(added_user_message.into());
|
||||
let request = self.to_summarize_request(&model.model, added_user_message.into(), cx);
|
||||
|
||||
self.pending_summary = cx.spawn(async move |this, cx| {
|
||||
async move {
|
||||
@ -1782,7 +1787,7 @@ impl Thread {
|
||||
4. Any action items or next steps if any\n\
|
||||
Format it in Markdown with headings and bullet points.";
|
||||
|
||||
let request = self.to_summarize_request(added_user_message.into());
|
||||
let request = self.to_summarize_request(&model, added_user_message.into(), cx);
|
||||
|
||||
*self.detailed_summary_tx.borrow_mut() = DetailedSummaryState::Generating {
|
||||
message_id: last_message_id,
|
||||
@ -2655,7 +2660,7 @@ struct PendingCompletion {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{ThreadStore, context::load_context, context_store::ContextStore, thread_store};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use assistant_settings::{AssistantSettings, LanguageModelParameters};
|
||||
use assistant_tool::ToolRegistry;
|
||||
use editor::EditorSettings;
|
||||
use gpui::TestAppContext;
|
||||
@ -3066,6 +3071,100 @@ fn main() {{
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_temperature_setting(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(
|
||||
cx,
|
||||
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let (_workspace, _thread_store, thread, _context_store, model) =
|
||||
setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Both model and provider
|
||||
cx.update(|cx| {
|
||||
AssistantSettings::override_global(
|
||||
AssistantSettings {
|
||||
model_parameters: vec![LanguageModelParameters {
|
||||
provider: Some(model.provider_id().0.to_string().into()),
|
||||
model: Some(model.id().0.clone()),
|
||||
temperature: Some(0.66),
|
||||
}],
|
||||
..AssistantSettings::get_global(cx).clone()
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let request = thread.update(cx, |thread, cx| {
|
||||
thread.to_completion_request(model.clone(), cx)
|
||||
});
|
||||
assert_eq!(request.temperature, Some(0.66));
|
||||
|
||||
// Only model
|
||||
cx.update(|cx| {
|
||||
AssistantSettings::override_global(
|
||||
AssistantSettings {
|
||||
model_parameters: vec![LanguageModelParameters {
|
||||
provider: None,
|
||||
model: Some(model.id().0.clone()),
|
||||
temperature: Some(0.66),
|
||||
}],
|
||||
..AssistantSettings::get_global(cx).clone()
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let request = thread.update(cx, |thread, cx| {
|
||||
thread.to_completion_request(model.clone(), cx)
|
||||
});
|
||||
assert_eq!(request.temperature, Some(0.66));
|
||||
|
||||
// Only provider
|
||||
cx.update(|cx| {
|
||||
AssistantSettings::override_global(
|
||||
AssistantSettings {
|
||||
model_parameters: vec![LanguageModelParameters {
|
||||
provider: Some(model.provider_id().0.to_string().into()),
|
||||
model: None,
|
||||
temperature: Some(0.66),
|
||||
}],
|
||||
..AssistantSettings::get_global(cx).clone()
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let request = thread.update(cx, |thread, cx| {
|
||||
thread.to_completion_request(model.clone(), cx)
|
||||
});
|
||||
assert_eq!(request.temperature, Some(0.66));
|
||||
|
||||
// Same model name, different provider
|
||||
cx.update(|cx| {
|
||||
AssistantSettings::override_global(
|
||||
AssistantSettings {
|
||||
model_parameters: vec![LanguageModelParameters {
|
||||
provider: Some("anthropic".into()),
|
||||
model: Some(model.id().0.clone()),
|
||||
temperature: Some(0.66),
|
||||
}],
|
||||
..AssistantSettings::get_global(cx).clone()
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let request = thread.update(cx, |thread, cx| {
|
||||
thread.to_completion_request(model.clone(), cx)
|
||||
});
|
||||
assert_eq!(request.temperature, None);
|
||||
}
|
||||
|
||||
fn init_test_settings(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
|
@ -163,7 +163,7 @@ fn update_active_language_model_from_settings(cx: &mut App) {
|
||||
|
||||
fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
|
||||
language_model::SelectedModel {
|
||||
provider: LanguageModelProviderId::from(selection.provider.clone()),
|
||||
provider: LanguageModelProviderId::from(selection.provider.0.clone()),
|
||||
model: LanguageModelId::from(selection.model.clone()),
|
||||
}
|
||||
}
|
||||
|
@ -2484,7 +2484,7 @@ impl InlineAssist {
|
||||
.read(cx)
|
||||
.active_context(cx)?
|
||||
.read(cx)
|
||||
.to_completion_request(RequestType::Chat, cx),
|
||||
.to_completion_request(None, RequestType::Chat, cx),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@ -2870,7 +2870,8 @@ impl CodegenAlternative {
|
||||
if let Some(ConfiguredModel { model, .. }) =
|
||||
LanguageModelRegistry::read_global(cx).inline_assistant_model()
|
||||
{
|
||||
let request = self.build_request(user_prompt, assistant_panel_context.clone(), cx);
|
||||
let request =
|
||||
self.build_request(&model, user_prompt, assistant_panel_context.clone(), cx);
|
||||
match request {
|
||||
Ok(request) => {
|
||||
let total_count = model.count_tokens(request.clone(), cx);
|
||||
@ -2915,7 +2916,8 @@ impl CodegenAlternative {
|
||||
if user_prompt.trim().to_lowercase() == "delete" {
|
||||
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
||||
} else {
|
||||
let request = self.build_request(user_prompt, assistant_panel_context, cx)?;
|
||||
let request =
|
||||
self.build_request(&model, user_prompt, assistant_panel_context, cx)?;
|
||||
self.request = Some(request.clone());
|
||||
|
||||
cx.spawn(async move |_, cx| model.stream_completion_text(request, &cx).await)
|
||||
@ -2927,6 +2929,7 @@ impl CodegenAlternative {
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
model: &Arc<dyn LanguageModel>,
|
||||
user_prompt: String,
|
||||
assistant_panel_context: Option<LanguageModelRequest>,
|
||||
cx: &App,
|
||||
@ -2981,7 +2984,7 @@ impl CodegenAlternative {
|
||||
messages,
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: None,
|
||||
temperature: AssistantSettings::temperature_for_model(&model, cx),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -261,7 +261,7 @@ impl TerminalInlineAssistant {
|
||||
.read(cx)
|
||||
.active_context(cx)?
|
||||
.read(cx)
|
||||
.to_completion_request(RequestType::Chat, cx),
|
||||
.to_completion_request(None, RequestType::Chat, cx),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
|
@ -3,6 +3,7 @@ mod context_tests;
|
||||
|
||||
use crate::patch::{AssistantEdit, AssistantPatch, AssistantPatchStatus};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use assistant_slash_command::{
|
||||
SlashCommandContent, SlashCommandEvent, SlashCommandLine, SlashCommandOutputSection,
|
||||
SlashCommandResult, SlashCommandWorkingSet,
|
||||
@ -1273,10 +1274,10 @@ impl AssistantContext {
|
||||
pub(crate) fn count_remaining_tokens(&mut self, cx: &mut Context<Self>) {
|
||||
// Assume it will be a Chat request, even though that takes fewer tokens (and risks going over the limit),
|
||||
// because otherwise you see in the UI that your empty message has a bunch of tokens already used.
|
||||
let request = self.to_completion_request(RequestType::Chat, cx);
|
||||
let Some(model) = LanguageModelRegistry::read_global(cx).default_model() else {
|
||||
return;
|
||||
};
|
||||
let request = self.to_completion_request(Some(&model.model), RequestType::Chat, cx);
|
||||
let debounce = self.token_count.is_some();
|
||||
self.pending_token_count = cx.spawn(async move |this, cx| {
|
||||
async move {
|
||||
@ -1422,7 +1423,7 @@ impl AssistantContext {
|
||||
}
|
||||
|
||||
let request = {
|
||||
let mut req = self.to_completion_request(RequestType::Chat, cx);
|
||||
let mut req = self.to_completion_request(Some(&model), RequestType::Chat, cx);
|
||||
// Skip the last message because it's likely to change and
|
||||
// therefore would be a waste to cache.
|
||||
req.messages.pop();
|
||||
@ -2321,7 +2322,7 @@ impl AssistantContext {
|
||||
// Compute which messages to cache, including the last one.
|
||||
self.mark_cache_anchors(&model.cache_configuration(), false, cx);
|
||||
|
||||
let request = self.to_completion_request(request_type, cx);
|
||||
let request = self.to_completion_request(Some(&model), request_type, cx);
|
||||
|
||||
let assistant_message = self
|
||||
.insert_message_after(last_message_id, Role::Assistant, MessageStatus::Pending, cx)
|
||||
@ -2561,6 +2562,7 @@ impl AssistantContext {
|
||||
|
||||
pub fn to_completion_request(
|
||||
&self,
|
||||
model: Option<&Arc<dyn LanguageModel>>,
|
||||
request_type: RequestType,
|
||||
cx: &App,
|
||||
) -> LanguageModelRequest {
|
||||
@ -2584,7 +2586,8 @@ impl AssistantContext {
|
||||
messages: Vec::new(),
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: None,
|
||||
temperature: model
|
||||
.and_then(|model| AssistantSettings::temperature_for_model(model, cx)),
|
||||
};
|
||||
for message in self.messages(cx) {
|
||||
if message.status != MessageStatus::Done {
|
||||
@ -2981,7 +2984,7 @@ impl AssistantContext {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut request = self.to_completion_request(RequestType::Chat, cx);
|
||||
let mut request = self.to_completion_request(Some(&model.model), RequestType::Chat, cx);
|
||||
request.messages.push(LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![
|
||||
|
@ -43,9 +43,8 @@ use workspace::Workspace;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_inserting_and_removing_messages(cx: &mut App) {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
LanguageModelRegistry::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
init_test(cx);
|
||||
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new(|cx| {
|
||||
@ -182,9 +181,8 @@ fn test_inserting_and_removing_messages(cx: &mut App) {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_message_splitting(cx: &mut App) {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
LanguageModelRegistry::test(cx);
|
||||
init_test(cx);
|
||||
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
@ -285,9 +283,8 @@ fn test_message_splitting(cx: &mut App) {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_messages_for_offsets(cx: &mut App) {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
LanguageModelRegistry::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
init_test(cx);
|
||||
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new(|cx| {
|
||||
@ -378,10 +375,8 @@ fn test_messages_for_offsets(cx: &mut App) {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
let settings_store = cx.update(SettingsStore::test);
|
||||
cx.set_global(settings_store);
|
||||
cx.update(LanguageModelRegistry::test);
|
||||
cx.update(Project::init_settings);
|
||||
cx.update(init_test);
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
|
||||
fs.insert_tree(
|
||||
@ -671,22 +666,19 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
cx.update(prompt_store::init);
|
||||
let mut settings_store = cx.update(SettingsStore::test);
|
||||
cx.update(|cx| {
|
||||
settings_store
|
||||
.set_user_settings(
|
||||
r#"{ "assistant": { "enable_experimental_live_diffs": true } }"#,
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
init_test(cx);
|
||||
cx.update_global(|settings_store: &mut SettingsStore, cx| {
|
||||
settings_store
|
||||
.set_user_settings(
|
||||
r#"{ "assistant": { "enable_experimental_live_diffs": true } }"#,
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
});
|
||||
cx.set_global(settings_store);
|
||||
cx.update(language::init);
|
||||
cx.update(Project::init_settings);
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let project = Project::test(fs, [Path::new("/root")], cx).await;
|
||||
cx.update(LanguageModelRegistry::test);
|
||||
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
|
||||
@ -1069,9 +1061,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_serialization(cx: &mut TestAppContext) {
|
||||
let settings_store = cx.update(SettingsStore::test);
|
||||
cx.set_global(settings_store);
|
||||
cx.update(LanguageModelRegistry::test);
|
||||
cx.update(init_test);
|
||||
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new(|cx| {
|
||||
@ -1147,6 +1138,8 @@ async fn test_serialization(cx: &mut TestAppContext) {
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
cx.update(init_test);
|
||||
|
||||
let min_peers = env::var("MIN_PEERS")
|
||||
.map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
|
||||
.unwrap_or(2);
|
||||
@ -1157,10 +1150,6 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(50);
|
||||
|
||||
let settings_store = cx.update(SettingsStore::test);
|
||||
cx.set_global(settings_store);
|
||||
cx.update(LanguageModelRegistry::test);
|
||||
|
||||
let slash_commands = cx.update(SlashCommandRegistry::default_global);
|
||||
slash_commands.register_command(FakeSlashCommand("cmd-1".into()), false);
|
||||
slash_commands.register_command(FakeSlashCommand("cmd-2".into()), false);
|
||||
@ -1429,9 +1418,8 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
|
||||
#[gpui::test]
|
||||
fn test_mark_cache_anchors(cx: &mut App) {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
LanguageModelRegistry::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
init_test(cx);
|
||||
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new(|cx| {
|
||||
@ -1606,6 +1594,16 @@ fn messages_cache(
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut App) {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
prompt_store::init(cx);
|
||||
LanguageModelRegistry::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
language::init(cx);
|
||||
assistant_settings::init(cx);
|
||||
Project::init_settings(cx);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct FakeSlashCommand(String);
|
||||
|
||||
|
@ -14,6 +14,7 @@ path = "src/assistant_settings.rs"
|
||||
[dependencies]
|
||||
anthropic = { workspace = true, features = ["schemars"] }
|
||||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
feature_flags.workspace = true
|
||||
gpui.workspace = true
|
||||
indexmap.workspace = true
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use collections::IndexMap;
|
||||
use gpui::SharedString;
|
||||
use indexmap::IndexMap;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -5,10 +5,10 @@ use std::sync::Arc;
|
||||
use ::open_ai::Model as OpenAiModel;
|
||||
use anthropic::Model as AnthropicModel;
|
||||
use anyhow::{Result, bail};
|
||||
use collections::IndexMap;
|
||||
use deepseek::Model as DeepseekModel;
|
||||
use feature_flags::{AgentStreamEditsFeatureFlag, Assistant2FeatureFlag, FeatureFlagAppExt};
|
||||
use gpui::{App, Pixels};
|
||||
use indexmap::IndexMap;
|
||||
use gpui::{App, Pixels, SharedString};
|
||||
use language_model::{CloudModel, LanguageModel};
|
||||
use lmstudio::Model as LmStudioModel;
|
||||
use ollama::Model as OllamaModel;
|
||||
@ -18,6 +18,10 @@ use settings::{Settings, SettingsSources};
|
||||
|
||||
pub use crate::agent_profile::*;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
AssistantSettings::register(cx);
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AssistantDockPosition {
|
||||
@ -89,10 +93,20 @@ pub struct AssistantSettings {
|
||||
pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
|
||||
pub stream_edits: bool,
|
||||
pub single_file_review: bool,
|
||||
pub model_parameters: Vec<LanguageModelParameters>,
|
||||
pub preferred_completion_mode: CompletionMode,
|
||||
}
|
||||
|
||||
impl AssistantSettings {
|
||||
pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
|
||||
let settings = Self::get_global(cx);
|
||||
settings
|
||||
.model_parameters
|
||||
.iter()
|
||||
.rfind(|setting| setting.matches(model))
|
||||
.and_then(|m| m.temperature)
|
||||
}
|
||||
|
||||
pub fn stream_edits(&self, cx: &App) -> bool {
|
||||
cx.has_flag::<AgentStreamEditsFeatureFlag>() || self.stream_edits
|
||||
}
|
||||
@ -106,15 +120,47 @@ impl AssistantSettings {
|
||||
}
|
||||
|
||||
pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
|
||||
self.inline_assistant_model = Some(LanguageModelSelection { provider, model });
|
||||
self.inline_assistant_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_commit_message_model(&mut self, provider: String, model: String) {
|
||||
self.commit_message_model = Some(LanguageModelSelection { provider, model });
|
||||
self.commit_message_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
|
||||
self.thread_summary_model = Some(LanguageModelSelection { provider, model });
|
||||
self.thread_summary_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub struct LanguageModelParameters {
|
||||
pub provider: Option<LanguageModelProviderSetting>,
|
||||
pub model: Option<SharedString>,
|
||||
pub temperature: Option<f32>,
|
||||
}
|
||||
|
||||
impl LanguageModelParameters {
|
||||
pub fn matches(&self, model: &Arc<dyn LanguageModel>) -> bool {
|
||||
if let Some(provider) = &self.provider {
|
||||
if provider.0 != model.provider_id().0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(setting_model) = &self.model {
|
||||
if *setting_model != model.id().0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,37 +227,37 @@ impl AssistantSettingsContent {
|
||||
.and_then(|provider| match provider {
|
||||
AssistantProviderContentV1::ZedDotDev { default_model } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "zed.dev".to_string(),
|
||||
provider: "zed.dev".into(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
AssistantProviderContentV1::OpenAi { default_model, .. } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "openai".to_string(),
|
||||
provider: "openai".into(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
AssistantProviderContentV1::Anthropic { default_model, .. } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "anthropic".to_string(),
|
||||
provider: "anthropic".into(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
AssistantProviderContentV1::Ollama { default_model, .. } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "ollama".to_string(),
|
||||
provider: "ollama".into(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
AssistantProviderContentV1::LmStudio { default_model, .. } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "lmstudio".to_string(),
|
||||
provider: "lmstudio".into(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
AssistantProviderContentV1::DeepSeek { default_model, .. } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "deepseek".to_string(),
|
||||
provider: "deepseek".into(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
@ -227,6 +273,7 @@ impl AssistantSettingsContent {
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
model_parameters: Vec::new(),
|
||||
preferred_completion_mode: None,
|
||||
},
|
||||
VersionedAssistantSettingsContent::V2(ref settings) => settings.clone(),
|
||||
@ -238,7 +285,7 @@ impl AssistantSettingsContent {
|
||||
default_width: settings.default_width,
|
||||
default_height: settings.default_height,
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: "openai".to_string(),
|
||||
provider: "openai".into(),
|
||||
model: settings
|
||||
.default_open_ai_model
|
||||
.clone()
|
||||
@ -257,6 +304,7 @@ impl AssistantSettingsContent {
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
model_parameters: Vec::new(),
|
||||
preferred_completion_mode: None,
|
||||
},
|
||||
None => AssistantSettingsContentV2::default(),
|
||||
@ -370,7 +418,10 @@ impl AssistantSettingsContent {
|
||||
}
|
||||
}
|
||||
VersionedAssistantSettingsContent::V2(ref mut settings) => {
|
||||
settings.default_model = Some(LanguageModelSelection { provider, model });
|
||||
settings.default_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
}
|
||||
},
|
||||
Some(AssistantSettingsContentInner::Legacy(settings)) => {
|
||||
@ -381,7 +432,10 @@ impl AssistantSettingsContent {
|
||||
None => {
|
||||
self.inner = Some(AssistantSettingsContentInner::for_v2(
|
||||
AssistantSettingsContentV2 {
|
||||
default_model: Some(LanguageModelSelection { provider, model }),
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
@ -391,7 +445,10 @@ impl AssistantSettingsContent {
|
||||
|
||||
pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
|
||||
self.v2_setting(|setting| {
|
||||
setting.inline_assistant_model = Some(LanguageModelSelection { provider, model });
|
||||
setting.inline_assistant_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
@ -399,7 +456,10 @@ impl AssistantSettingsContent {
|
||||
|
||||
pub fn set_commit_message_model(&mut self, provider: String, model: String) {
|
||||
self.v2_setting(|setting| {
|
||||
setting.commit_message_model = Some(LanguageModelSelection { provider, model });
|
||||
setting.commit_message_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
@ -427,7 +487,10 @@ impl AssistantSettingsContent {
|
||||
|
||||
pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
|
||||
self.v2_setting(|setting| {
|
||||
setting.thread_summary_model = Some(LanguageModelSelection { provider, model });
|
||||
setting.thread_summary_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
@ -523,6 +586,7 @@ impl Default for VersionedAssistantSettingsContent {
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
model_parameters: Vec::new(),
|
||||
preferred_completion_mode: None,
|
||||
})
|
||||
}
|
||||
@ -587,6 +651,15 @@ pub struct AssistantSettingsContentV2 {
|
||||
///
|
||||
/// Default: true
|
||||
single_file_review: Option<bool>,
|
||||
/// Additional parameters for language model requests. When making a request
|
||||
/// to a model, parameters will be taken from the last entry in this list
|
||||
/// that matches the model's provider and name. In each entry, both provider
|
||||
/// and model are optional, so that you can specify parameters for either
|
||||
/// one.
|
||||
///
|
||||
/// Default: []
|
||||
#[serde(default)]
|
||||
model_parameters: Vec<LanguageModelParameters>,
|
||||
|
||||
/// What completion mode to enable for new threads
|
||||
///
|
||||
@ -613,33 +686,53 @@ impl From<CompletionMode> for zed_llm_client::CompletionMode {
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub struct LanguageModelSelection {
|
||||
#[schemars(schema_with = "providers_schema")]
|
||||
pub provider: String,
|
||||
pub provider: LanguageModelProviderSetting,
|
||||
pub model: String,
|
||||
}
|
||||
|
||||
fn providers_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
schemars::schema::SchemaObject {
|
||||
enum_values: Some(vec![
|
||||
"anthropic".into(),
|
||||
"bedrock".into(),
|
||||
"google".into(),
|
||||
"lmstudio".into(),
|
||||
"ollama".into(),
|
||||
"openai".into(),
|
||||
"zed.dev".into(),
|
||||
"copilot_chat".into(),
|
||||
"deepseek".into(),
|
||||
]),
|
||||
..Default::default()
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct LanguageModelProviderSetting(pub String);
|
||||
|
||||
impl JsonSchema for LanguageModelProviderSetting {
|
||||
fn schema_name() -> String {
|
||||
"LanguageModelProviderSetting".into()
|
||||
}
|
||||
|
||||
fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> Schema {
|
||||
schemars::schema::SchemaObject {
|
||||
enum_values: Some(vec![
|
||||
"anthropic".into(),
|
||||
"bedrock".into(),
|
||||
"google".into(),
|
||||
"lmstudio".into(),
|
||||
"ollama".into(),
|
||||
"openai".into(),
|
||||
"zed.dev".into(),
|
||||
"copilot_chat".into(),
|
||||
"deepseek".into(),
|
||||
]),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for LanguageModelProviderSetting {
|
||||
fn from(provider: String) -> Self {
|
||||
Self(provider)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for LanguageModelProviderSetting {
|
||||
fn from(provider: &str) -> Self {
|
||||
Self(provider.to_string())
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
impl Default for LanguageModelSelection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
provider: "openai".to_string(),
|
||||
provider: LanguageModelProviderSetting("openai".to_string()),
|
||||
model: "gpt-4".to_string(),
|
||||
}
|
||||
}
|
||||
@ -781,6 +874,10 @@ impl Settings for AssistantSettings {
|
||||
value.preferred_completion_mode,
|
||||
);
|
||||
|
||||
settings
|
||||
.model_parameters
|
||||
.extend_from_slice(&value.model_parameters);
|
||||
|
||||
if let Some(profiles) = value.profiles {
|
||||
settings
|
||||
.profiles
|
||||
@ -913,6 +1010,7 @@ mod tests {
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
model_parameters: Vec::new(),
|
||||
preferred_completion_mode: None,
|
||||
},
|
||||
)),
|
||||
@ -976,7 +1074,7 @@ mod tests {
|
||||
AssistantSettingsContentV2 {
|
||||
enabled: Some(false),
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: "xai".to_owned(),
|
||||
provider: "xai".to_owned().into(),
|
||||
model: "grok".to_owned(),
|
||||
}),
|
||||
..Default::default()
|
||||
|
@ -78,6 +78,7 @@ zed_llm_client.workspace = true
|
||||
[dev-dependencies]
|
||||
assistant = { workspace = true, features = ["test-support"] }
|
||||
assistant_context_editor.workspace = true
|
||||
assistant_settings.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
async-trait.workspace = true
|
||||
|
@ -307,6 +307,7 @@ impl TestServer {
|
||||
);
|
||||
language_model::LanguageModelRegistry::test(cx);
|
||||
assistant_context_editor::init(client.clone(), cx);
|
||||
assistant_settings::init(cx);
|
||||
});
|
||||
|
||||
client
|
||||
|
@ -1735,6 +1735,8 @@ impl GitPanel {
|
||||
}
|
||||
});
|
||||
|
||||
let temperature = AssistantSettings::temperature_for_model(&model, cx);
|
||||
|
||||
self.generate_commit_message_task = Some(cx.spawn(async move |this, cx| {
|
||||
async move {
|
||||
let _defer = cx.on_drop(&this, |this, _cx| {
|
||||
@ -1773,7 +1775,7 @@ impl GitPanel {
|
||||
}],
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: None,
|
||||
temperature,
|
||||
};
|
||||
|
||||
let stream = model.stream_completion_text(request, &cx);
|
||||
|
@ -87,8 +87,8 @@ pub struct AllLanguageModelSettingsContent {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum AnthropicSettingsContent {
|
||||
Legacy(LegacyAnthropicSettingsContent),
|
||||
Versioned(VersionedAnthropicSettingsContent),
|
||||
Legacy(LegacyAnthropicSettingsContent),
|
||||
}
|
||||
|
||||
impl AnthropicSettingsContent {
|
||||
@ -197,8 +197,8 @@ pub struct MistralSettingsContent {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum OpenAiSettingsContent {
|
||||
Legacy(LegacyOpenAiSettingsContent),
|
||||
Versioned(VersionedOpenAiSettingsContent),
|
||||
Legacy(LegacyOpenAiSettingsContent),
|
||||
}
|
||||
|
||||
impl OpenAiSettingsContent {
|
||||
|
@ -3522,7 +3522,7 @@ impl LspStore {
|
||||
)
|
||||
.detach();
|
||||
} else {
|
||||
log::info!("No extension events global found. Skipping JSON schema auto-reload setup");
|
||||
log::debug!("No extension events global found. Skipping JSON schema auto-reload setup");
|
||||
}
|
||||
cx.observe_global::<SettingsStore>(Self::on_settings_changed)
|
||||
.detach();
|
||||
|
@ -3871,7 +3871,7 @@ impl BackgroundScanner {
|
||||
Some(ancestor_dot_git)
|
||||
});
|
||||
|
||||
log::info!("containing git repository: {containing_git_repository:?}");
|
||||
log::trace!("containing git repository: {containing_git_repository:?}");
|
||||
|
||||
let (scan_job_tx, scan_job_rx) = channel::unbounded();
|
||||
{
|
||||
|
@ -3058,14 +3058,14 @@ Run the `theme selector: toggle` action in the command palette to see a current
|
||||
}
|
||||
```
|
||||
|
||||
## Assistant Panel
|
||||
## Agent
|
||||
|
||||
- Description: Customize assistant panel
|
||||
- Setting: `assistant`
|
||||
- Description: Customize agent behavior
|
||||
- Setting: `agent`
|
||||
- Default:
|
||||
|
||||
```json
|
||||
"assistant": {
|
||||
"agent": {
|
||||
"version": "2",
|
||||
"enabled": true,
|
||||
"button": true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user