agent: Show a warning when some tools are incompatible with the selected model (#28755)
WIP <img width="644" alt="image" src="https://github.com/user-attachments/assets/b24e1a57-f82e-457c-b788-1b314ade7c84" /> <img width="644" alt="image" src="https://github.com/user-attachments/assets/b158953c-2015-4cc8-b8ed-35c6fcbe162d" /> Release Notes: - agent: Improve compatibility with Gemini Tool Calling APIs. When a tool is incompatible with the Gemini APIs a warning indicator will be displayed. Incompatible tools will be automatically excluded from the conversation --------- Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
This commit is contained in:
parent
ff4334efc7
commit
c381a500f8
4 changed files with 229 additions and 87 deletions
|
@ -18,6 +18,7 @@ mod terminal_inline_assistant;
|
||||||
mod thread;
|
mod thread;
|
||||||
mod thread_history;
|
mod thread_history;
|
||||||
mod thread_store;
|
mod thread_store;
|
||||||
|
mod tool_compatibility;
|
||||||
mod tool_use;
|
mod tool_use;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::collections::BTreeMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::assistant_model_selector::ModelType;
|
use crate::assistant_model_selector::ModelType;
|
||||||
|
use crate::tool_compatibility::{IncompatibleToolsState, IncompatibleToolsTooltip};
|
||||||
use buffer_diff::BufferDiff;
|
use buffer_diff::BufferDiff;
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use editor::actions::MoveUp;
|
use editor::actions::MoveUp;
|
||||||
|
@ -41,6 +42,7 @@ use crate::{
|
||||||
|
|
||||||
pub struct MessageEditor {
|
pub struct MessageEditor {
|
||||||
thread: Entity<Thread>,
|
thread: Entity<Thread>,
|
||||||
|
incompatible_tools_state: Entity<IncompatibleToolsState>,
|
||||||
editor: Entity<Editor>,
|
editor: Entity<Editor>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
|
@ -124,6 +126,9 @@ impl MessageEditor {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let incompatible_tools =
|
||||||
|
cx.new(|cx| IncompatibleToolsState::new(thread.read(cx).tools().clone(), cx));
|
||||||
|
|
||||||
let subscriptions =
|
let subscriptions =
|
||||||
vec![cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event)];
|
vec![cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event)];
|
||||||
|
|
||||||
|
@ -131,6 +136,7 @@ impl MessageEditor {
|
||||||
editor: editor.clone(),
|
editor: editor.clone(),
|
||||||
project: thread.read(cx).project().clone(),
|
project: thread.read(cx).project().clone(),
|
||||||
thread,
|
thread,
|
||||||
|
incompatible_tools_state: incompatible_tools.clone(),
|
||||||
workspace,
|
workspace,
|
||||||
context_store,
|
context_store,
|
||||||
context_strip,
|
context_strip,
|
||||||
|
@ -368,6 +374,23 @@ impl MessageEditor {
|
||||||
let is_model_selected = self.is_model_selected(cx);
|
let is_model_selected = self.is_model_selected(cx);
|
||||||
let is_editor_empty = self.is_editor_empty(cx);
|
let is_editor_empty = self.is_editor_empty(cx);
|
||||||
|
|
||||||
|
let model = LanguageModelRegistry::read_global(cx)
|
||||||
|
.default_model()
|
||||||
|
.map(|default| default.model.clone());
|
||||||
|
|
||||||
|
let incompatible_tools = model
|
||||||
|
.as_ref()
|
||||||
|
.map(|model| {
|
||||||
|
self.incompatible_tools_state.update(cx, |state, cx| {
|
||||||
|
state
|
||||||
|
.incompatible_tools(model, cx)
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let is_editor_expanded = self.editor_is_expanded;
|
let is_editor_expanded = self.editor_is_expanded;
|
||||||
let expand_icon = if is_editor_expanded {
|
let expand_icon = if is_editor_expanded {
|
||||||
IconName::Minimize
|
IconName::Minimize
|
||||||
|
@ -472,7 +495,30 @@ impl MessageEditor {
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(h_flex().gap_2().child(self.profile_selector.clone()))
|
.child(h_flex().gap_2().child(self.profile_selector.clone()))
|
||||||
.child(h_flex().gap_1().child(self.model_selector.clone()).map({
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.when(!incompatible_tools.is_empty(), |this| {
|
||||||
|
this.child(
|
||||||
|
IconButton::new(
|
||||||
|
"tools-incompatible-warning",
|
||||||
|
IconName::Warning,
|
||||||
|
)
|
||||||
|
.icon_color(Color::Warning)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.tooltip({
|
||||||
|
move |_, cx| {
|
||||||
|
cx.new(|_| IncompatibleToolsTooltip {
|
||||||
|
incompatible_tools: incompatible_tools
|
||||||
|
.clone(),
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child(self.model_selector.clone())
|
||||||
|
.map({
|
||||||
let focus_handle = focus_handle.clone();
|
let focus_handle = focus_handle.clone();
|
||||||
move |parent| {
|
move |parent| {
|
||||||
if is_generating {
|
if is_generating {
|
||||||
|
@ -496,7 +542,8 @@ impl MessageEditor {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.on_click({
|
.on_click({
|
||||||
let focus_handle = focus_handle.clone();
|
let focus_handle =
|
||||||
|
focus_handle.clone();
|
||||||
move |_event, window, cx| {
|
move |_event, window, cx| {
|
||||||
focus_handle.dispatch_action(
|
focus_handle.dispatch_action(
|
||||||
&editor::actions::Cancel,
|
&editor::actions::Cancel,
|
||||||
|
@ -507,7 +554,9 @@ impl MessageEditor {
|
||||||
})
|
})
|
||||||
.with_animation(
|
.with_animation(
|
||||||
"pulsating-label",
|
"pulsating-label",
|
||||||
Animation::new(Duration::from_secs(2))
|
Animation::new(
|
||||||
|
Duration::from_secs(2),
|
||||||
|
)
|
||||||
.repeat()
|
.repeat()
|
||||||
.with_easing(pulsating_between(
|
.with_easing(pulsating_between(
|
||||||
0.4, 1.0,
|
0.4, 1.0,
|
||||||
|
@ -554,13 +603,15 @@ impl MessageEditor {
|
||||||
.disabled(
|
.disabled(
|
||||||
is_editor_empty
|
is_editor_empty
|
||||||
|| !is_model_selected
|
|| !is_model_selected
|
||||||
|| self.waiting_for_summaries_to_send,
|
|| self
|
||||||
|
.waiting_for_summaries_to_send,
|
||||||
)
|
)
|
||||||
.on_click({
|
.on_click({
|
||||||
let focus_handle = focus_handle.clone();
|
let focus_handle = focus_handle.clone();
|
||||||
move |_event, window, cx| {
|
move |_event, window, cx| {
|
||||||
focus_handle
|
focus_handle.dispatch_action(
|
||||||
.dispatch_action(&Chat, window, cx);
|
&Chat, window, cx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.when(
|
.when(
|
||||||
|
@ -586,7 +637,8 @@ impl MessageEditor {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})),
|
}),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
89
crates/agent/src/tool_compatibility.rs
Normal file
89
crates/agent/src/tool_compatibility.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use assistant_tool::{Tool, ToolWorkingSet, ToolWorkingSetEvent};
|
||||||
|
use collections::HashMap;
|
||||||
|
use gpui::{App, Context, Entity, IntoElement, Render, Subscription, Window};
|
||||||
|
use language_model::{LanguageModel, LanguageModelToolSchemaFormat};
|
||||||
|
use ui::prelude::*;
|
||||||
|
|
||||||
|
pub struct IncompatibleToolsState {
|
||||||
|
cache: HashMap<LanguageModelToolSchemaFormat, Vec<Arc<dyn Tool>>>,
|
||||||
|
tool_working_set: Entity<ToolWorkingSet>,
|
||||||
|
_tool_working_set_subscription: Subscription,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IncompatibleToolsState {
|
||||||
|
pub fn new(tool_working_set: Entity<ToolWorkingSet>, cx: &mut Context<Self>) -> Self {
|
||||||
|
let _tool_working_set_subscription =
|
||||||
|
cx.subscribe(&tool_working_set, |this, _, event, _| match event {
|
||||||
|
ToolWorkingSetEvent::EnabledToolsChanged => {
|
||||||
|
this.cache.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
cache: HashMap::default(),
|
||||||
|
tool_working_set,
|
||||||
|
_tool_working_set_subscription,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn incompatible_tools(
|
||||||
|
&mut self,
|
||||||
|
model: &Arc<dyn LanguageModel>,
|
||||||
|
cx: &App,
|
||||||
|
) -> &[Arc<dyn Tool>] {
|
||||||
|
self.cache
|
||||||
|
.entry(model.tool_input_format())
|
||||||
|
.or_insert_with(|| {
|
||||||
|
self.tool_working_set
|
||||||
|
.read(cx)
|
||||||
|
.enabled_tools(cx)
|
||||||
|
.iter()
|
||||||
|
.filter(|tool| tool.input_schema(model.tool_input_format()).is_err())
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct IncompatibleToolsTooltip {
|
||||||
|
pub incompatible_tools: Vec<Arc<dyn Tool>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for IncompatibleToolsTooltip {
|
||||||
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
ui::tooltip_container(window, cx, |container, _, cx| {
|
||||||
|
container
|
||||||
|
.w_72()
|
||||||
|
.child(Label::new("Incompatible Tools").size(LabelSize::Small))
|
||||||
|
.child(
|
||||||
|
Label::new(
|
||||||
|
"This model is incompatible with the following tools from your MCPs:",
|
||||||
|
)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.my_1p5()
|
||||||
|
.py_0p5()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().colors().border_variant)
|
||||||
|
.children(
|
||||||
|
self.incompatible_tools
|
||||||
|
.iter()
|
||||||
|
.map(|tool| Label::new(tool.name()).size(LabelSize::Small).buffer_font(cx)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(Label::new("What To Do Instead").size(LabelSize::Small))
|
||||||
|
.child(
|
||||||
|
Label::new(
|
||||||
|
"Every other tool continues to work with this model, but to specifically use those, switch to another model.",
|
||||||
|
)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -67,7 +67,7 @@ pub enum LanguageModelCompletionEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates the format used to define the input schema for a language model tool.
|
/// Indicates the format used to define the input schema for a language model tool.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
pub enum LanguageModelToolSchemaFormat {
|
pub enum LanguageModelToolSchemaFormat {
|
||||||
/// A JSON schema, see https://json-schema.org
|
/// A JSON schema, see https://json-schema.org
|
||||||
JsonSchema,
|
JsonSchema,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue