agent: Only include tool guidance in system prompt when profile has tools enabled (#40413)

Christian Durรกn Carvajal and Bennet Bo Fenner created

Was previously sending all of the tools to the LLM on the first message
of a conversation regardless of the selected agent profile. This added
extra context, and tended to create scenarios where the LLM would
attempt to use the tool and it would fail since it was not available.

To reproduce, create a new conversation where you ask the Minimal mode
which tools it has access to, or try to write to a file.

Release Notes:

- Fixed an issue in the agent where all tools would be presented as
available even when using the `Minimal` profile

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>

Change summary

crates/agent/src/tests/mod.rs | 36 ++++++++++++++++++++++++++++++++++++
crates/agent/src/thread.rs    | 18 ++++++++++++++----
2 files changed, 50 insertions(+), 4 deletions(-)

Detailed changes

crates/agent/src/tests/mod.rs ๐Ÿ”—

@@ -160,6 +160,42 @@ async fn test_system_prompt(cx: &mut TestAppContext) {
     );
 }
 
+#[gpui::test]
+async fn test_system_prompt_without_tools(cx: &mut TestAppContext) {
+    let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
+    let fake_model = model.as_fake();
+
+    thread
+        .update(cx, |thread, cx| {
+            thread.send(UserMessageId::new(), ["abc"], cx)
+        })
+        .unwrap();
+    cx.run_until_parked();
+    let mut pending_completions = fake_model.pending_completions();
+    assert_eq!(
+        pending_completions.len(),
+        1,
+        "unexpected pending completions: {:?}",
+        pending_completions
+    );
+
+    let pending_completion = pending_completions.pop().unwrap();
+    assert_eq!(pending_completion.messages[0].role, Role::System);
+
+    let system_message = &pending_completion.messages[0];
+    let system_prompt = system_message.content[0].to_str().unwrap();
+    assert!(
+        !system_prompt.contains("## Tool Use"),
+        "unexpected system message: {:?}",
+        system_message
+    );
+    assert!(
+        !system_prompt.contains("## Fixing Diagnostics"),
+        "unexpected system message: {:?}",
+        system_message
+    );
+}
+
 #[gpui::test]
 async fn test_prompt_caching(cx: &mut TestAppContext) {
     let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;

crates/agent/src/thread.rs ๐Ÿ”—

@@ -1816,9 +1816,15 @@ impl Thread {
         log::debug!("Completion intent: {:?}", completion_intent);
         log::debug!("Completion mode: {:?}", self.completion_mode);
 
-        let messages = self.build_request_messages(cx);
+        let available_tools: Vec<_> = self
+            .running_turn
+            .as_ref()
+            .map(|turn| turn.tools.keys().cloned().collect())
+            .unwrap_or_default();
+
+        log::debug!("Request includes {} tools", available_tools.len());
+        let messages = self.build_request_messages(available_tools, cx);
         log::debug!("Request will include {} messages", messages.len());
-        log::debug!("Request includes {} tools", tools.len());
 
         let request = LanguageModelRequest {
             thread_id: Some(self.id.to_string()),
@@ -1909,7 +1915,11 @@ impl Thread {
         self.running_turn.as_ref()?.tools.get(name).cloned()
     }
 
-    fn build_request_messages(&self, cx: &App) -> Vec<LanguageModelRequestMessage> {
+    fn build_request_messages(
+        &self,
+        available_tools: Vec<SharedString>,
+        cx: &App,
+    ) -> Vec<LanguageModelRequestMessage> {
         log::trace!(
             "Building request messages from {} thread messages",
             self.messages.len()
@@ -1917,7 +1927,7 @@ impl Thread {
 
         let system_prompt = SystemPromptTemplate {
             project: self.project_context.read(cx),
-            available_tools: self.tools.keys().cloned().collect(),
+            available_tools,
         }
         .render(&self.templates)
         .context("failed to build system prompt")