diff --git a/rust/cocoindex/src/base/json_schema.rs b/rust/cocoindex/src/base/json_schema.rs index e001e9dfe..1a663337e 100644 --- a/rust/cocoindex/src/base/json_schema.rs +++ b/rust/cocoindex/src/base/json_schema.rs @@ -21,6 +21,10 @@ pub struct ToJsonSchemaOptions { /// If true, the top level must be a JSON object. pub top_level_must_be_object: bool, + + /// If true, include `additionalProperties: false` in object schemas. + /// Some LLM APIs (e.g., Gemini) do not support this constraint and will error. + pub supports_additional_properties: bool, } struct JsonSchemaBuilder { @@ -263,7 +267,11 @@ impl JsonSchemaBuilder { .filter(|&f| self.options.fields_always_required || !f.value_type.nullable) .map(|f| f.name.to_string()) .collect(), - additional_properties: Some(Schema::Bool(false).into()), + additional_properties: if self.options.supports_additional_properties { + Some(Schema::Bool(false).into()) + } else { + None + }, ..Default::default() })); schema @@ -406,6 +414,7 @@ mod tests { supports_format: true, extract_descriptions: false, top_level_must_be_object: false, + supports_additional_properties: true, } } @@ -415,6 +424,7 @@ mod tests { supports_format: true, extract_descriptions: true, top_level_must_be_object: false, + supports_additional_properties: true, } } @@ -424,6 +434,7 @@ mod tests { supports_format: true, extract_descriptions: false, top_level_must_be_object: false, + supports_additional_properties: true, } } @@ -433,6 +444,7 @@ mod tests { supports_format: true, extract_descriptions: false, top_level_must_be_object: true, + supports_additional_properties: true, } } @@ -1357,6 +1369,7 @@ mod tests { supports_format: false, extract_descriptions: false, top_level_must_be_object: false, + supports_additional_properties: true, }; let result = build_json_schema(value_type, options).unwrap(); let json_schema = schema_to_json(&result.schema); @@ -1396,6 +1409,7 @@ mod tests { supports_format: true, extract_descriptions: false, // We want to see the description in the schema top_level_must_be_object: false, + supports_additional_properties: true, }; let result = build_json_schema(enriched_value_type, options).unwrap(); diff --git a/rust/cocoindex/src/llm/anthropic.rs b/rust/cocoindex/src/llm/anthropic.rs index c84e2c483..3a3b4baa1 100644 --- a/rust/cocoindex/src/llm/anthropic.rs +++ b/rust/cocoindex/src/llm/anthropic.rs @@ -164,6 +164,7 @@ impl LlmGenerationClient for Client { supports_format: false, extract_descriptions: false, top_level_must_be_object: true, + supports_additional_properties: true, } } } diff --git a/rust/cocoindex/src/llm/bedrock.rs b/rust/cocoindex/src/llm/bedrock.rs index 0f5f99035..00baccd45 100644 --- a/rust/cocoindex/src/llm/bedrock.rs +++ b/rust/cocoindex/src/llm/bedrock.rs @@ -174,6 +174,7 @@ impl LlmGenerationClient for Client { supports_format: false, extract_descriptions: false, top_level_must_be_object: true, + supports_additional_properties: true, } } } diff --git a/rust/cocoindex/src/llm/gemini.rs b/rust/cocoindex/src/llm/gemini.rs index 0e6ce1bce..cd1b49e8d 100644 --- a/rust/cocoindex/src/llm/gemini.rs +++ b/rust/cocoindex/src/llm/gemini.rs @@ -49,24 +49,6 @@ impl AiStudioClient { } } -// Recursively remove all `additionalProperties` fields from a JSON value -fn remove_additional_properties(value: &mut Value) { - match value { - Value::Object(map) => { - map.remove("additionalProperties"); - for v in map.values_mut() { - remove_additional_properties(v); - } - } - Value::Array(arr) => { - for v in arr { - remove_additional_properties(v); - } - } - _ => {} - } -} - impl AiStudioClient { fn get_api_url(&self, model: &str, api_name: &str) -> String { format!( @@ -149,8 +131,7 @@ impl LlmGenerationClient for AiStudioClient { // If structured output is requested, add schema and responseMimeType if let Some(OutputFormat::JsonSchema { schema, .. }) = &request.output_format { - let mut schema_json = serde_json::to_value(schema)?; - remove_additional_properties(&mut schema_json); + let schema_json = serde_json::to_value(schema)?; payload["generationConfig"] = serde_json::json!({ "responseMimeType": "application/json", "responseSchema": schema_json @@ -181,6 +162,7 @@ impl LlmGenerationClient for AiStudioClient { supports_format: false, extract_descriptions: false, top_level_must_be_object: true, + supports_additional_properties: false, } } } @@ -379,6 +361,7 @@ impl LlmGenerationClient for VertexAiClient { supports_format: false, extract_descriptions: false, top_level_must_be_object: true, + supports_additional_properties: false, } } } diff --git a/rust/cocoindex/src/llm/ollama.rs b/rust/cocoindex/src/llm/ollama.rs index b02a6ddc3..35c93bfc8 100644 --- a/rust/cocoindex/src/llm/ollama.rs +++ b/rust/cocoindex/src/llm/ollama.rs @@ -120,6 +120,7 @@ impl LlmGenerationClient for Client { supports_format: true, extract_descriptions: true, top_level_must_be_object: false, + supports_additional_properties: true, } } } diff --git a/rust/cocoindex/src/llm/openai.rs b/rust/cocoindex/src/llm/openai.rs index 67a23be35..853166a2a 100644 --- a/rust/cocoindex/src/llm/openai.rs +++ b/rust/cocoindex/src/llm/openai.rs @@ -162,6 +162,7 @@ impl LlmGenerationClient for Client { supports_format: false, extract_descriptions: false, top_level_must_be_object: true, + supports_additional_properties: true, } } }