Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion lib/crewai/src/crewai/utilities/pydantic_schema_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,22 @@ def _resolve(node: Any) -> Any:
def_name = ref.replace("#/$defs/", "")
if def_name not in defs:
raise KeyError(f"Definition '{def_name}' not found in $defs.")
siblings = {k: v for k, v in node.items() if k != "$ref"}
if def_name in expanding:
def_schema = defs[def_name]
stub: dict[str, Any] = {"type": def_schema.get("type", "object")}
if "description" in def_schema:
stub["description"] = def_schema["description"]
stub.update({k: _resolve(v) for k, v in siblings.items()})
return stub
expanding.add(def_name)
try:
return _resolve(deepcopy(defs[def_name]))
resolved_def = _resolve(deepcopy(defs[def_name]))
if isinstance(resolved_def, dict):
resolved_def.update(
{k: _resolve(v) for k, v in siblings.items()}
)
return resolved_def
finally:
expanding.discard(def_name)
return {k: _resolve(v) for k, v in node.items()}
Expand Down
48 changes: 47 additions & 1 deletion lib/crewai/tests/utilities/test_pydantic_schema_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from typing import Any

import pytest
from pydantic import BaseModel
from pydantic import BaseModel, Field

from crewai.utilities.pydantic_schema_utils import (
build_rich_field_description,
Expand All @@ -25,6 +25,7 @@
ensure_all_properties_required,
ensure_type_in_schemas,
force_additional_properties_false,
generate_model_description,
resolve_refs,
strip_null_from_types,
strip_unsupported_formats,
Expand Down Expand Up @@ -803,6 +804,51 @@ def test_no_refs_unchanged(self) -> None:
resolved = resolve_refs(schema)
assert resolved == schema

def test_sibling_keys_of_ref_are_kept(self) -> None:
schema = {
"type": "object",
"properties": {
"item": {
"$ref": "#/$defs/Item",
"description": "the item",
"default": None,
"title": "My Item",
},
},
"$defs": {
"Item": {
"type": "object",
"description": "Item model docstring.",
"properties": {"id": {"type": "integer"}},
},
},
}
resolved = resolve_refs(schema)
item = resolved["properties"]["item"]
assert "$ref" not in item
assert item["type"] == "object"
# Siblings survive and win over the definition's same-named keys.
assert item["description"] == "the item"
assert item["title"] == "My Item"
assert item["default"] is None
assert item["properties"] == {"id": {"type": "integer"}}

def test_field_description_survives_generate_model_description(self) -> None:
class Leaf(BaseModel):
"""A leaf model."""

value: int = Field(description="the value")

class Holder(BaseModel):
"""Holder model."""

nested: Leaf = Field(description="the leaf")

description = generate_model_description(Holder)
nested = description["json_schema"]["schema"]["properties"]["nested"]
assert nested["description"] == "the leaf"
assert nested["properties"]["value"]["description"] == "the value"


class TestForceAdditionalPropertiesFalse:
def test_adds_to_object(self) -> None:
Expand Down
Loading