Remove unnecessary fields from the tool schemas (#29381)

Max Brunsfeld created

This PR removes two fields from JSON schemas (`$schema` and `title`),
which are not expected by any model provider, but were spuriously
included by our JSON schema library, `schemars`.

These added noise to requests and cost wasted input tokens.

### Old

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "FetchToolInput",
  "type": "object",
  "required": [
    "url"
  ],
  "properties": {
    "url": {
      "description": "The URL to fetch.",
      "type": "string"
    }
  }
}
```

### New:

```json
{
  "properties": {
    "url": {
      "description": "The URL to fetch.",
      "type": "string"
    }
  },
  "required": [
    "url"
  ],
  "type": "object"
}
```

- N/A

Change summary

crates/agent/src/thread.rs                    | 35 +++++++++------------
crates/assistant_tool/src/tool_schema.rs      | 10 +++--
crates/assistant_tools/src/assistant_tools.rs | 29 ++++++++++++++++
3 files changed, 49 insertions(+), 25 deletions(-)

Detailed changes

crates/agent/src/thread.rs 🔗

@@ -931,26 +931,21 @@ impl Thread {
 
         let mut request = self.to_completion_request(cx);
         if model.supports_tools() {
-            request.tools = {
-                let mut tools = Vec::new();
-                tools.extend(
-                    self.tools()
-                        .read(cx)
-                        .enabled_tools(cx)
-                        .into_iter()
-                        .filter_map(|tool| {
-                            // Skip tools that cannot be supported
-                            let input_schema = tool.input_schema(model.tool_input_format()).ok()?;
-                            Some(LanguageModelRequestTool {
-                                name: tool.name(),
-                                description: tool.description(),
-                                input_schema,
-                            })
-                        }),
-                );
-
-                tools
-            };
+            request.tools = self
+                .tools()
+                .read(cx)
+                .enabled_tools(cx)
+                .into_iter()
+                .filter_map(|tool| {
+                    // Skip tools that cannot be supported
+                    let input_schema = tool.input_schema(model.tool_input_format()).ok()?;
+                    Some(LanguageModelRequestTool {
+                        name: tool.name(),
+                        description: tool.description(),
+                        input_schema,
+                    })
+                })
+                .collect();
         }
 
         self.stream_completion(request, model, window, cx);

crates/assistant_tool/src/tool_schema.rs 🔗

@@ -10,6 +10,11 @@ pub fn adapt_schema_to_format(
     json: &mut Value,
     format: LanguageModelToolSchemaFormat,
 ) -> Result<()> {
+    if let Value::Object(obj) = json {
+        obj.remove("$schema");
+        obj.remove("title");
+    }
+
     match format {
         LanguageModelToolSchemaFormat::JsonSchema => Ok(()),
         LanguageModelToolSchemaFormat::JsonSchemaSubset => adapt_to_json_schema_subset(json),
@@ -30,10 +35,7 @@ fn adapt_to_json_schema_subset(json: &mut Value) -> Result<()> {
             }
         }
 
-        const KEYS_TO_REMOVE: [&str; 2] = ["format", "$schema"];
-        for key in KEYS_TO_REMOVE {
-            obj.remove(key);
-        }
+        obj.remove("format");
 
         if let Some(default) = obj.get("default") {
             let is_null = default.is_null();

crates/assistant_tools/src/assistant_tools.rs 🔗

@@ -110,11 +110,38 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
 
 #[cfg(test)]
 mod tests {
+    use super::*;
     use client::Client;
     use clock::FakeSystemClock;
     use http_client::FakeHttpClient;
+    use schemars::JsonSchema;
+    use serde::Serialize;
 
-    use super::*;
+    #[test]
+    fn test_json_schema() {
+        #[derive(Serialize, JsonSchema)]
+        struct GetWeatherTool {
+            location: String,
+        }
+
+        let schema = schema::json_schema_for::<GetWeatherTool>(
+            language_model::LanguageModelToolSchemaFormat::JsonSchema,
+        )
+        .unwrap();
+
+        assert_eq!(
+            schema,
+            serde_json::json!({
+                "type": "object",
+                "properties": {
+                    "location": {
+                        "type": "string"
+                    }
+                },
+                "required": ["location"],
+            })
+        );
+    }
 
     #[gpui::test]
     fn test_builtin_tool_schema_compatibility(cx: &mut App) {