mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-05-01 07:13:00 +00:00
fix: enforce additionalProperties=false in schemas
Some checks failed
Some checks failed
* fix: enforce additionalProperties=false in schemas * fix: ensure nested items have required properties
This commit is contained in:
@@ -16,6 +16,7 @@ from crewai.agents.agent_adapters.openai_agents.protocols import (
|
|||||||
)
|
)
|
||||||
from crewai.tools import BaseTool
|
from crewai.tools import BaseTool
|
||||||
from crewai.utilities.import_utils import require
|
from crewai.utilities.import_utils import require
|
||||||
|
from crewai.utilities.pydantic_schema_utils import force_additional_properties_false
|
||||||
from crewai.utilities.string_utils import sanitize_tool_name
|
from crewai.utilities.string_utils import sanitize_tool_name
|
||||||
|
|
||||||
|
|
||||||
@@ -135,7 +136,9 @@ class OpenAIAgentToolAdapter(BaseToolAdapter):
|
|||||||
for tool in tools:
|
for tool in tools:
|
||||||
schema: dict[str, Any] = tool.args_schema.model_json_schema()
|
schema: dict[str, Any] = tool.args_schema.model_json_schema()
|
||||||
|
|
||||||
schema.update({"additionalProperties": False, "type": "object"})
|
schema = force_additional_properties_false(schema)
|
||||||
|
|
||||||
|
schema.update({"type": "object"})
|
||||||
|
|
||||||
openai_tool: OpenAIFunctionTool = cast(
|
openai_tool: OpenAIFunctionTool = cast(
|
||||||
OpenAIFunctionTool,
|
OpenAIFunctionTool,
|
||||||
|
|||||||
@@ -1521,13 +1521,16 @@ class OpenAICompletion(BaseLLM):
|
|||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
"""Convert CrewAI tool format to OpenAI function calling format."""
|
"""Convert CrewAI tool format to OpenAI function calling format."""
|
||||||
from crewai.llms.providers.utils.common import safe_tool_conversion
|
from crewai.llms.providers.utils.common import safe_tool_conversion
|
||||||
|
from crewai.utilities.pydantic_schema_utils import (
|
||||||
|
force_additional_properties_false,
|
||||||
|
)
|
||||||
|
|
||||||
openai_tools = []
|
openai_tools = []
|
||||||
|
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
name, description, parameters = safe_tool_conversion(tool, "OpenAI")
|
name, description, parameters = safe_tool_conversion(tool, "OpenAI")
|
||||||
|
|
||||||
openai_tool = {
|
openai_tool: dict[str, Any] = {
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": name,
|
"name": name,
|
||||||
@@ -1537,10 +1540,11 @@ class OpenAICompletion(BaseLLM):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if parameters:
|
if parameters:
|
||||||
if isinstance(parameters, dict):
|
params_dict = (
|
||||||
openai_tool["function"]["parameters"] = parameters # type: ignore
|
parameters if isinstance(parameters, dict) else dict(parameters)
|
||||||
else:
|
)
|
||||||
openai_tool["function"]["parameters"] = dict(parameters)
|
params_dict = force_additional_properties_false(params_dict)
|
||||||
|
openai_tool["function"]["parameters"] = params_dict
|
||||||
|
|
||||||
openai_tools.append(openai_tool)
|
openai_tools.append(openai_tool)
|
||||||
return openai_tools
|
return openai_tools
|
||||||
|
|||||||
@@ -127,6 +127,36 @@ def add_key_in_dict_recursively(
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def force_additional_properties_false(d: Any) -> Any:
|
||||||
|
"""Force additionalProperties=false on all object-type dicts recursively.
|
||||||
|
|
||||||
|
OpenAI strict mode requires all objects to have additionalProperties=false.
|
||||||
|
This function overwrites any existing value to ensure compliance.
|
||||||
|
|
||||||
|
Also ensures objects have properties and required arrays, even if empty,
|
||||||
|
as OpenAI strict mode requires these for all object types.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
d: The dictionary/list to modify.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The modified dictionary/list.
|
||||||
|
"""
|
||||||
|
if isinstance(d, dict):
|
||||||
|
if d.get("type") == "object":
|
||||||
|
d["additionalProperties"] = False
|
||||||
|
if "properties" not in d:
|
||||||
|
d["properties"] = {}
|
||||||
|
if "required" not in d:
|
||||||
|
d["required"] = []
|
||||||
|
for v in d.values():
|
||||||
|
force_additional_properties_false(v)
|
||||||
|
elif isinstance(d, list):
|
||||||
|
for i in d:
|
||||||
|
force_additional_properties_false(i)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
def fix_discriminator_mappings(schema: dict[str, Any]) -> dict[str, Any]:
|
def fix_discriminator_mappings(schema: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""Replace '#/$defs/...' references in discriminator.mapping with just the model name.
|
"""Replace '#/$defs/...' references in discriminator.mapping with just the model name.
|
||||||
|
|
||||||
@@ -278,13 +308,7 @@ def generate_model_description(model: type[BaseModel]) -> dict[str, Any]:
|
|||||||
"""
|
"""
|
||||||
json_schema = model.model_json_schema(ref_template="#/$defs/{model}")
|
json_schema = model.model_json_schema(ref_template="#/$defs/{model}")
|
||||||
|
|
||||||
json_schema = add_key_in_dict_recursively(
|
json_schema = force_additional_properties_false(json_schema)
|
||||||
json_schema,
|
|
||||||
key="additionalProperties",
|
|
||||||
value=False,
|
|
||||||
criteria=lambda d: d.get("type") == "object"
|
|
||||||
and "additionalProperties" not in d,
|
|
||||||
)
|
|
||||||
|
|
||||||
json_schema = resolve_refs(json_schema)
|
json_schema = resolve_refs(json_schema)
|
||||||
|
|
||||||
@@ -378,6 +402,9 @@ def create_model_from_schema( # type: ignore[no-any-unimported]
|
|||||||
"""
|
"""
|
||||||
effective_root = root_schema or json_schema
|
effective_root = root_schema or json_schema
|
||||||
|
|
||||||
|
json_schema = force_additional_properties_false(json_schema)
|
||||||
|
effective_root = force_additional_properties_false(effective_root)
|
||||||
|
|
||||||
if "allOf" in json_schema:
|
if "allOf" in json_schema:
|
||||||
json_schema = _merge_all_of_schemas(json_schema["allOf"], effective_root)
|
json_schema = _merge_all_of_schemas(json_schema["allOf"], effective_root)
|
||||||
if "title" not in json_schema and "title" in (root_schema or {}):
|
if "title" not in json_schema and "title" in (root_schema or {}):
|
||||||
|
|||||||
Reference in New Issue
Block a user