mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-25 16:55:11 +00:00
fix: preserve strict tool schema property names (#6628)
This commit is contained in:
@@ -9,7 +9,7 @@ authors = [
|
||||
requires-python = ">=3.10, <3.14"
|
||||
dependencies = [
|
||||
"Pillow~=12.3.0",
|
||||
"pypdf~=6.13.3",
|
||||
"pypdf~=6.14.2",
|
||||
"python-magic>=0.4.27",
|
||||
"aiocache~=0.12.3",
|
||||
"aiofiles~=24.1.0",
|
||||
@@ -19,8 +19,8 @@ dependencies = [
|
||||
|
||||
[tool.uv]
|
||||
exclude-newer = "3 days"
|
||||
# pypdf 6.13.3 is a security fix newer than the global supply-chain cutoff.
|
||||
exclude-newer-package = { pypdf = "2026-06-18T00:00:00Z" }
|
||||
# pypdf 6.14.2 is a security fix newer than the global supply-chain cutoff.
|
||||
exclude-newer-package = { pypdf = "2026-06-24T00:00:00Z" }
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
|
||||
@@ -549,7 +549,13 @@ _CLAUDE_STRICT_UNSUPPORTED: Final[tuple[str, ...]] = (
|
||||
def _strip_keys_recursive(
|
||||
d: Any, keys: tuple[str, ...], _seen: set[int] | None = None
|
||||
) -> Any:
|
||||
"""Recursively delete a fixed set of keys from a schema."""
|
||||
"""Recursively delete schema metadata keys without deleting property names.
|
||||
|
||||
JSON Schema stores fields under a ``properties`` map. A user/tool argument can
|
||||
legitimately be named ``title``, ``default``, or another metadata key. Those
|
||||
entries must stay in the map; only metadata inside the schema nodes should be
|
||||
stripped.
|
||||
"""
|
||||
if _seen is None:
|
||||
_seen = set()
|
||||
if isinstance(d, dict):
|
||||
@@ -558,8 +564,12 @@ def _strip_keys_recursive(
|
||||
_seen.add(id(d))
|
||||
for key in keys:
|
||||
d.pop(key, None)
|
||||
for v in d.values():
|
||||
_strip_keys_recursive(v, keys, _seen)
|
||||
for key, value in d.items():
|
||||
if key == "properties" and isinstance(value, dict):
|
||||
for property_schema in value.values():
|
||||
_strip_keys_recursive(property_schema, keys, _seen)
|
||||
else:
|
||||
_strip_keys_recursive(value, keys, _seen)
|
||||
elif isinstance(d, list):
|
||||
if id(d) in _seen:
|
||||
return d
|
||||
|
||||
@@ -933,7 +933,49 @@ class TestResolveRefsRecursive:
|
||||
assert resolved["properties"]["x"]["type"] == "integer"
|
||||
|
||||
|
||||
class TestSanitizeRecursiveSchemas:
|
||||
class TestSanitizeStrictSchemas:
|
||||
def test_openai_strict_preserves_property_named_title(self) -> None:
|
||||
from crewai.utilities.pydantic_schema_utils import sanitize_tool_params_for_openai_strict
|
||||
|
||||
schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {"title": "Title", "type": "string"},
|
||||
"url": {"title": "Url", "type": "string"},
|
||||
},
|
||||
"required": ["title", "url"],
|
||||
}
|
||||
|
||||
san = sanitize_tool_params_for_openai_strict(deepcopy(schema))
|
||||
|
||||
assert "title" in san["properties"]
|
||||
assert set(san["required"]) == set(san["properties"].keys())
|
||||
assert "title" not in san["properties"]["title"]
|
||||
|
||||
def test_openai_strict_preserves_nested_property_named_title(self) -> None:
|
||||
from crewai.utilities.pydantic_schema_utils import sanitize_tool_params_for_openai_strict
|
||||
|
||||
schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {"title": "Nested Title", "type": "string"},
|
||||
},
|
||||
"required": ["title"],
|
||||
},
|
||||
},
|
||||
"required": ["payload"],
|
||||
}
|
||||
|
||||
san = sanitize_tool_params_for_openai_strict(deepcopy(schema))
|
||||
payload = san["properties"]["payload"]
|
||||
|
||||
assert "title" in payload["properties"]
|
||||
assert payload["required"] == ["title"]
|
||||
assert "title" not in payload["properties"]["title"]
|
||||
|
||||
def test_anthropic_strict_preserves_recursive_type(self) -> None:
|
||||
from crewai.utilities.pydantic_schema_utils import sanitize_tool_params_for_anthropic_strict
|
||||
|
||||
|
||||
Reference in New Issue
Block a user