fix: validate function call json schemas for openai (#1156)

Co-authored-by: angiejones <jones.angie@gmail.com>
This commit is contained in:
Wendy Tang
2025-02-11 23:20:27 -08:00
committed by GitHub
parent 5e8a8bae3d
commit 80b694d6c6
2 changed files with 129 additions and 5 deletions

View File

@@ -253,6 +253,55 @@ pub fn get_usage(data: &Value) -> Result<Usage, ProviderError> {
Ok(Usage::new(input_tokens, output_tokens, total_tokens)) Ok(Usage::new(input_tokens, output_tokens, total_tokens))
} }
/// Validates and fixes tool schemas to ensure they have proper parameter structure.
/// If parameters exist, ensures they have properties and required fields, or removes parameters entirely.
pub fn validate_tool_schemas(tools: &mut [Value]) {
for tool in tools.iter_mut() {
if let Some(function) = tool.get_mut("function") {
if let Some(parameters) = function.get_mut("parameters") {
if parameters.is_object() {
ensure_valid_json_schema(parameters);
}
}
}
}
}
/// Ensures that the given JSON value follows the expected JSON Schema structure.
fn ensure_valid_json_schema(schema: &mut Value) {
if let Some(params_obj) = schema.as_object_mut() {
// Check if this is meant to be an object type schema
let is_object_type = params_obj
.get("type")
.and_then(|t| t.as_str())
.map_or(true, |t| t == "object"); // Default to true if no type is specified
// Only apply full schema validation to object types
if is_object_type {
// Ensure required fields exist with default values
params_obj.entry("properties").or_insert_with(|| json!({}));
params_obj.entry("required").or_insert_with(|| json!([]));
params_obj.entry("type").or_insert_with(|| json!("object"));
// Recursively validate properties if it exists
if let Some(properties) = params_obj.get_mut("properties") {
if let Some(properties_obj) = properties.as_object_mut() {
for (_key, prop) in properties_obj.iter_mut() {
if prop.is_object()
&& prop
.get("type")
.and_then(|t| t.as_str())
.map_or(false, |t| t == "object")
{
ensure_valid_json_schema(prop);
}
}
}
}
}
}
}
pub fn create_request( pub fn create_request(
model_config: &ModelConfig, model_config: &ModelConfig,
system: &str, system: &str,
@@ -275,12 +324,15 @@ pub fn create_request(
}); });
let messages_spec = format_messages(messages, image_format); let messages_spec = format_messages(messages, image_format);
let tools_spec = if !tools.is_empty() { let mut tools_spec = if !tools.is_empty() {
format_tools(tools)? format_tools(tools)?
} else { } else {
vec![] vec![]
}; };
// Validate tool schemas
validate_tool_schemas(&mut tools_spec);
let mut messages_array = vec![system_message]; let mut messages_array = vec![system_message];
messages_array.extend(messages_spec); messages_array.extend(messages_spec);
@@ -326,6 +378,82 @@ mod tests {
use mcp_core::content::Content; use mcp_core::content::Content;
use serde_json::json; use serde_json::json;
#[test]
fn test_validate_tool_schemas() {
// Test case 1: Empty parameters object
// Input JSON with an incomplete parameters object
let mut actual = vec![json!({
"type": "function",
"function": {
"name": "test_func",
"description": "test description",
"parameters": {
"type": "object"
}
}
})];
// Run the function to validate and update schemas
validate_tool_schemas(&mut actual);
// Expected JSON after validation
let expected = vec![json!({
"type": "function",
"function": {
"name": "test_func",
"description": "test description",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
})];
// Compare entire JSON structures instead of individual fields
assert_eq!(actual, expected);
// Test case 2: Missing type field
let mut tools = vec![json!({
"type": "function",
"function": {
"name": "test_func",
"description": "test description",
"parameters": {
"properties": {}
}
}
})];
validate_tool_schemas(&mut tools);
let params = tools[0]["function"]["parameters"].as_object().unwrap();
assert_eq!(params["type"], "object");
// Test case 3: Complete valid schema should remain unchanged
let original_schema = json!({
"type": "function",
"function": {
"name": "test_func",
"description": "test description",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and country"
}
},
"required": ["location"]
}
}
});
let mut tools = vec![original_schema.clone()];
validate_tool_schemas(&mut tools);
assert_eq!(tools[0], original_schema);
}
const OPENAI_TOOL_USE_RESPONSE: &str = r#"{ const OPENAI_TOOL_USE_RESPONSE: &str = r#"{
"choices": [{ "choices": [{
"role": "assistant", "role": "assistant",

View File

@@ -13,10 +13,6 @@ The JetBrains extension is designed to work within your IDE. Goose can accomplis
This tutorial covers how to enable and use the JetBrains MCP Server as a built-in Goose extension to integrate with any JetBrains IDE. This tutorial covers how to enable and use the JetBrains MCP Server as a built-in Goose extension to integrate with any JetBrains IDE.
:::warning Known Limitation
The JetBrains extension [does not work](https://github.com/block/goose/issues/933) with OpenAI models (e.g. gpt-4o).
:::
## Configuration ## Configuration
1. Add the [MCP Server plugin](https://plugins.jetbrains.com/plugin/26071-mcp-server) to your IDE. 1. Add the [MCP Server plugin](https://plugins.jetbrains.com/plugin/26071-mcp-server) to your IDE.