From 8a735264a06a5deb1f35d12d09df7ce69653a8c6 Mon Sep 17 00:00:00 2001 From: Mateo Wang <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 22 Jul 2026 15:49:52 -0700 Subject: [PATCH] =?UTF-8?q?Revert=20"fix(anthropic):=20strip=20uniqueItems?= =?UTF-8?q?=20+=20other=20unsupported=20array/object=20co=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit f05f079e134a545c6d44224c126180b75ea3ec0b. --- litellm/llms/anthropic/chat/transformation.py | 40 ++------ .../anthropic/test_anthropic_schema_filter.py | 91 ++----------------- .../test_anthropic_output_format_filter.py | 73 --------------- 3 files changed, 17 insertions(+), 187 deletions(-) delete mode 100644 tests/test_litellm/llms/anthropic/test_anthropic_output_format_filter.py diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 84f0c3ba7b9..0ec1f3eae13 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -478,15 +478,10 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): """ Filter out unsupported fields from JSON schema for Anthropic's output_format API. - Anthropic's output_format doesn't support certain JSON schema properties. - These are cross-element / count constraints that cannot be enforced by the - constrained-decoding grammar Anthropic compiles the schema into, so the API - rejects them with a 400 ``invalid_request_error`` (e.g. "output_format.schema: - For 'array' type, property 'uniqueItems' is not supported"): - - maxItems/minItems/uniqueItems/contains/minContains/maxContains: array constraints - - minimum/maximum/exclusiveMinimum/exclusiveMaximum: numeric constraints - - minLength/maxLength: string constraints - - minProperties/maxProperties: object constraints + Anthropic's output_format doesn't support certain JSON schema properties: + - maxItems/minItems: Not supported for array types + - minimum/maximum: Not supported for numeric types + - minLength/maxLength: Not supported for string types This mirrors the transformation done by the Anthropic Python SDK. See: https://platform.claude.com/docs/en/build-with-claude/structured-outputs#how-sdk-transformation-works @@ -507,22 +502,16 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): if not isinstance(schema, dict): return schema - # All numeric/string/array/object constraints not supported by Anthropic + # All numeric/string/array constraints not supported by Anthropic unsupported_fields = { "maxItems", - "minItems", - "uniqueItems", - "contains", - "minContains", - "maxContains", # array constraints + "minItems", # array constraints "minimum", "maximum", # numeric constraints "exclusiveMinimum", "exclusiveMaximum", # numeric constraints "minLength", "maxLength", # string constraints - "minProperties", - "maxProperties", # object constraints } # Build description additions from removed constraints @@ -530,31 +519,16 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): constraint_labels = { "minItems": "minimum number of items: {}", "maxItems": "maximum number of items: {}", - "uniqueItems": "all array items must be unique", - "contains": "array must contain an item matching: {}", - "minContains": "minimum number of matching items: {}", - "maxContains": "maximum number of matching items: {}", "minimum": "minimum value: {}", "maximum": "maximum value: {}", "exclusiveMinimum": "exclusive minimum value: {}", "exclusiveMaximum": "exclusive maximum value: {}", "minLength": "minimum length: {}", "maxLength": "maximum length: {}", - "minProperties": "minimum number of properties: {}", - "maxProperties": "maximum number of properties: {}", } for field in unsupported_fields: if field in schema: - value = schema[field] - # A falsy boolean constraint (e.g. ``uniqueItems: false``) imposes no - # real requirement, so don't add a misleading advisory note for it. - if isinstance(value, bool) and not value: - continue - # Sub-schema constraints (e.g. ``contains``) are serialized as JSON so - # the advisory note preserves what the constraint actually required, - # instead of just noting that it existed. - note_value = json.dumps(value) if isinstance(value, (dict, list)) else value - constraint_descriptions.append(constraint_labels[field].format(note_value)) + constraint_descriptions.append(constraint_labels[field].format(schema[field])) result: Dict[str, Any] = {} diff --git a/tests/litellm/llms/anthropic/test_anthropic_schema_filter.py b/tests/litellm/llms/anthropic/test_anthropic_schema_filter.py index 3785cb9bc9f..bd3b4198e9e 100644 --- a/tests/litellm/llms/anthropic/test_anthropic_schema_filter.py +++ b/tests/litellm/llms/anthropic/test_anthropic_schema_filter.py @@ -45,14 +45,21 @@ class TestFilterAnthropicOutputSchema: assert "minimum value: 0" in result["properties"]["age"]["description"] assert "maximum value: 150" in result["properties"]["age"]["description"] # Score had no description, should get one from constraints - assert "exclusive minimum value: 0" in result["properties"]["score"]["description"] - assert "exclusive maximum value: 100" in result["properties"]["score"]["description"] + assert ( + "exclusive minimum value: 0" in result["properties"]["score"]["description"] + ) + assert ( + "exclusive maximum value: 100" + in result["properties"]["score"]["description"] + ) def test_removes_string_constraints(self): """Test that minLength/maxLength are removed from string schemas.""" schema = { "type": "object", - "properties": {"name": {"type": "string", "minLength": 1, "maxLength": 100}}, + "properties": { + "name": {"type": "string", "minLength": 1, "maxLength": 100} + }, } result = AnthropicConfig.filter_anthropic_output_schema(schema) @@ -147,81 +154,3 @@ class TestFilterAnthropicOutputSchema: result = AnthropicConfig.filter_anthropic_output_schema(schema) assert result == schema # Should be unchanged - - def test_removes_uniqueitems(self): - """Test that uniqueItems is removed from array schemas. - - Reproduces the 400 ``invalid_request_error``: - "output_format.schema: For 'array' type, property 'uniqueItems' is not - supported". - """ - schema = { - "type": "object", - "properties": { - "tags": { - "type": "array", - "items": {"type": "string"}, - "uniqueItems": True, - } - }, - } - - result = AnthropicConfig.filter_anthropic_output_schema(schema) - - assert "uniqueItems" not in result["properties"]["tags"] - assert result["properties"]["tags"]["items"] == {"type": "string"} - # Constraint intent preserved in the description - assert "all array items must be unique" in result["properties"]["tags"]["description"] - - def test_removes_contains_constraints(self): - """Test that contains/minContains/maxContains are removed from arrays.""" - schema = { - "type": "array", - "items": {"type": "integer"}, - "contains": {"type": "integer", "const": 1}, - "minContains": 1, - "maxContains": 3, - } - - result = AnthropicConfig.filter_anthropic_output_schema(schema) - - assert "contains" not in result - assert "minContains" not in result - assert "maxContains" not in result - assert result["items"] == {"type": "integer"} - # The contains sub-schema is serialized into the advisory note so the model - # knows what item the array must contain. - assert "array must contain an item matching:" in result["description"] - assert '"const": 1' in result["description"] - assert "minimum number of matching items: 1" in result["description"] - assert "maximum number of matching items: 3" in result["description"] - - def test_removes_object_property_constraints(self): - """Test that minProperties/maxProperties are removed from object schemas.""" - schema = { - "type": "object", - "properties": {"a": {"type": "string"}}, - "minProperties": 1, - "maxProperties": 5, - } - - result = AnthropicConfig.filter_anthropic_output_schema(schema) - - assert "minProperties" not in result - assert "maxProperties" not in result - assert "minimum number of properties: 1" in result["description"] - assert "maximum number of properties: 5" in result["description"] - - def test_uniqueitems_false_skips_misleading_note(self): - """``uniqueItems: false`` is stripped but must not add a 'unique' note.""" - schema = { - "type": "array", - "items": {"type": "string"}, - "uniqueItems": False, - } - - result = AnthropicConfig.filter_anthropic_output_schema(schema) - - assert "uniqueItems" not in result - # A disabled constraint imposes no requirement -> no advisory note - assert "unique" not in result.get("description", "") diff --git a/tests/test_litellm/llms/anthropic/test_anthropic_output_format_filter.py b/tests/test_litellm/llms/anthropic/test_anthropic_output_format_filter.py deleted file mode 100644 index 6f0ff4c7ca5..00000000000 --- a/tests/test_litellm/llms/anthropic/test_anthropic_output_format_filter.py +++ /dev/null @@ -1,73 +0,0 @@ -""" -Coverage for filter_anthropic_output_schema's array/object constraint stripping. - -Mirrors tests/litellm/llms/anthropic/test_anthropic_schema_filter.py, but lives -under tests/test_litellm/ so the coverage-uploading CI job exercises the newly -added keyword handling (uniqueItems / contains / minProperties / maxProperties) -and the ``uniqueItems: false`` branch. -""" - -from litellm.llms.anthropic.chat.transformation import AnthropicConfig - - -class TestOutputFormatArrayObjectConstraints: - def test_removes_uniqueitems(self): - schema = { - "type": "object", - "properties": { - "tags": { - "type": "array", - "items": {"type": "string"}, - "uniqueItems": True, - } - }, - } - - result = AnthropicConfig.filter_anthropic_output_schema(schema) - - assert "uniqueItems" not in result["properties"]["tags"] - assert "all array items must be unique" in result["properties"]["tags"]["description"] - - def test_uniqueitems_false_skips_misleading_note(self): - schema = { - "type": "array", - "items": {"type": "string"}, - "uniqueItems": False, - } - - result = AnthropicConfig.filter_anthropic_output_schema(schema) - - assert "uniqueItems" not in result - assert "unique" not in result.get("description", "") - - def test_removes_contains_constraints(self): - schema = { - "type": "array", - "items": {"type": "integer"}, - "contains": {"type": "integer", "const": 1}, - "minContains": 1, - "maxContains": 3, - } - - result = AnthropicConfig.filter_anthropic_output_schema(schema) - - assert "contains" not in result - assert "minContains" not in result - assert "maxContains" not in result - assert "array must contain an item matching:" in result["description"] - assert '"const": 1' in result["description"] - - def test_removes_object_property_constraints(self): - schema = { - "type": "object", - "properties": {"a": {"type": "string"}}, - "minProperties": 1, - "maxProperties": 5, - } - - result = AnthropicConfig.filter_anthropic_output_schema(schema) - - assert "minProperties" not in result - assert "maxProperties" not in result - assert "minimum number of properties: 1" in result["description"] - assert "maximum number of properties: 5" in result["description"]