address greptile feedback: expand sanitizer traversal, use object type

- Traverse propertyNames, prefixItems, and definitions subschemas
  (Greptile P1: propertyNames could retain rejected keywords)
- Use `object` instead of `Any` for the sanitizer parameter
  (Greptile P2: reduces untyped boundary)
- Combine anyOf/allOf/oneOf/prefixItems branches (ruff SIM114)
- Add tests for propertyNames, prefixItems, and definitions traversal

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Zara Thomas 2026-08-19 09:26:15 +01:00
parent daf12e19a7
commit 4f18a08ea7
2 changed files with 53 additions and 5 deletions

View file

@ -468,12 +468,13 @@ class FireworksAIConfig(FireworksAIMixin, OpenAIGPTConfig):
_FIREWORKS_TOOL_SCHEMA_STRIP_KEYS: Final = frozenset({"pattern", "title"})
@staticmethod
def _sanitize_tool_schema(schema: Any) -> None:
def _sanitize_tool_schema(schema: object) -> None:
"""Recursively strip JSON Schema keywords Fireworks rejects from tool
parameter schemas, in place.
Walks ``properties``, ``items``, ``anyOf``/``allOf``/``oneOf``, and
``$defs``. Removes ``pattern`` and ``title`` everywhere, and ``default``
Walks ``properties``, ``items``, ``anyOf``/``allOf``/``oneOf``,
``$defs``, ``definitions``, ``prefixItems``, and ``propertyNames``.
Removes ``pattern`` and ``title`` everywhere, and ``default``
only when its value is ``None``. Non-null defaults, enums, and all other
keywords are preserved.
"""
@ -489,12 +490,14 @@ class FireworksAIConfig(FireworksAIMixin, OpenAIGPTConfig):
FireworksAIConfig._sanitize_tool_schema(prop)
elif key == "items" and isinstance(schema[key], dict):
FireworksAIConfig._sanitize_tool_schema(schema[key])
elif key == "$defs" and isinstance(schema[key], dict):
elif key in ("$defs", "definitions") and isinstance(schema[key], dict):
for defn in schema[key].values():
FireworksAIConfig._sanitize_tool_schema(defn)
elif key in ("anyOf", "allOf", "oneOf") and isinstance(schema[key], list):
elif key in ("anyOf", "allOf", "oneOf", "prefixItems") and isinstance(schema[key], list):
for item in schema[key]:
FireworksAIConfig._sanitize_tool_schema(item)
elif key == "propertyNames" and isinstance(schema[key], dict):
FireworksAIConfig._sanitize_tool_schema(schema[key])
def _transform_messages_helper(
self, messages: list[AllMessageValues], model: str, litellm_params: dict

View file

@ -228,6 +228,51 @@ def test_sanitize_deeply_nested():
assert "pattern" not in deep
def test_sanitize_recurses_into_propertyNames():
"""propertyNames subschema must be traversed (Greptile P1)."""
config = FireworksAIConfig()
schema = {
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[a-z]+$",
"title": "Prop Name",
},
}
config._sanitize_tool_schema(schema)
assert "pattern" not in schema["propertyNames"]
assert "title" not in schema["propertyNames"]
def test_sanitize_recurses_into_prefixItems():
"""prefixItems array must be traversed."""
config = FireworksAIConfig()
schema = {
"type": "array",
"prefixItems": [
{"type": "string", "pattern": "^[A-Z]+$"},
{"type": "string", "title": "Second"},
],
}
config._sanitize_tool_schema(schema)
assert "pattern" not in schema["prefixItems"][0]
assert "title" not in schema["prefixItems"][1]
def test_sanitize_recurses_into_definitions():
"""Legacy draft-04 definitions must be traversed."""
config = FireworksAIConfig()
schema = {
"type": "object",
"definitions": {
"Foo": {"type": "string", "pattern": "^[a-z]+$", "title": "Foo"},
},
}
config._sanitize_tool_schema(schema)
assert "pattern" not in schema["definitions"]["Foo"]
assert "title" not in schema["definitions"]["Foo"]
# ---------------------------------------------------------------------------
# _transform_tools integration tests
# ---------------------------------------------------------------------------