diff --git a/lib/crewai/src/crewai/a2a/config.py b/lib/crewai/src/crewai/a2a/config.py index 499248046..51c5c4adc 100644 --- a/lib/crewai/src/crewai/a2a/config.py +++ b/lib/crewai/src/crewai/a2a/config.py @@ -16,6 +16,7 @@ from pydantic import ( FilePath, PrivateAttr, SecretStr, + field_serializer, model_validator, ) from typing_extensions import Self, deprecated @@ -24,6 +25,7 @@ from crewai.a2a.auth.client_schemes import ClientAuthScheme from crewai.a2a.auth.server_schemes import ServerAuthScheme from crewai.a2a.extensions.base import ValidatedA2AExtension from crewai.a2a.types import ProtocolVersion, TransportType, Url +from crewai.utilities.pydantic_schema_utils import serialize_model_class try: @@ -399,6 +401,11 @@ class A2AConfig(BaseModel): default=None, description="Optional Pydantic model for structured A2A agent responses", ) + + @field_serializer("response_model", when_used="json") + def _serialize_response_model(self, value: Any) -> Any: + return serialize_model_class(value) + fail_fast: bool = Field( default=True, description="If True, raise error when agent unreachable; if False, skip", @@ -488,6 +495,11 @@ class A2AClientConfig(BaseModel): default=None, description="Optional Pydantic model for structured A2A agent responses", ) + + @field_serializer("response_model", when_used="json") + def _serialize_response_model(self, value: Any) -> Any: + return serialize_model_class(value) + fail_fast: bool = Field( default=True, description="If True, raise error when agent unreachable; if False, skip", diff --git a/lib/crewai/src/crewai/lite_agent.py b/lib/crewai/src/crewai/lite_agent.py index cd9823e15..3aff8ea35 100644 --- a/lib/crewai/src/crewai/lite_agent.py +++ b/lib/crewai/src/crewai/lite_agent.py @@ -23,6 +23,7 @@ from pydantic import ( BaseModel, Field, PrivateAttr, + field_serializer, field_validator, model_validator, ) @@ -94,7 +95,10 @@ from crewai.utilities.guardrail import process_guardrail, serialize_guardrail_fo from crewai.utilities.guardrail_types import GuardrailCallable, GuardrailType from crewai.utilities.i18n import I18N_DEFAULT from crewai.utilities.llm_utils import create_llm -from crewai.utilities.pydantic_schema_utils import generate_model_description +from crewai.utilities.pydantic_schema_utils import ( + generate_model_description, + serialize_model_class, +) from crewai.utilities.token_counter_callback import TokenCalcHandler from crewai.utilities.tool_utils import execute_tool_and_check_finality from crewai.utilities.types import LLMMessage @@ -235,6 +239,11 @@ class LiteAgent(FlowTrackable, BaseModel): response_format: type[BaseModel] | None = Field( default=None, description="Pydantic model for structured output" ) + + @field_serializer("response_format", when_used="json") + def _serialize_response_format(self, value: Any) -> Any: + return serialize_model_class(value) + verbose: bool = Field( default=False, description="Whether to print execution details" ) diff --git a/lib/crewai/src/crewai/llms/base_llm.py b/lib/crewai/src/crewai/llms/base_llm.py index 8c2993d3a..3e6c4f828 100644 --- a/lib/crewai/src/crewai/llms/base_llm.py +++ b/lib/crewai/src/crewai/llms/base_llm.py @@ -23,6 +23,7 @@ from pydantic import ( ConfigDict, Field, PrivateAttr, + field_serializer, model_validator, ) from typing_extensions import TypedDict @@ -42,6 +43,7 @@ from crewai.events.types.tool_usage_events import ( ToolUsageStartedEvent, ) from crewai.types.usage_metrics import UsageMetrics +from crewai.utilities.pydantic_schema_utils import serialize_model_class try: @@ -159,6 +161,10 @@ class BaseLLM(BaseModel, ABC): ) additional_params: dict[str, Any] = Field(default_factory=dict) + @field_serializer("response_format", when_used="json", check_fields=False) + def _serialize_response_format(self, value: Any) -> Any: + return serialize_model_class(value) + def __setattr__(self, name: str, value: Any) -> None: if name in ("stop", "stop_sequences"): if value is None: diff --git a/lib/crewai/src/crewai/tools/base_tool.py b/lib/crewai/src/crewai/tools/base_tool.py index e1dc8f2ee..31c5009bd 100644 --- a/lib/crewai/src/crewai/tools/base_tool.py +++ b/lib/crewai/src/crewai/tools/base_tool.py @@ -8,7 +8,6 @@ from inspect import Parameter, signature import json import threading from typing import ( - Annotated, Any, Generic, ParamSpec, @@ -22,10 +21,10 @@ from pydantic import ( ConfigDict, Field, GetCoreSchemaHandler, - PlainSerializer, PrivateAttr, computed_field, create_model, + field_serializer, field_validator, ) from pydantic_core import CoreSchema, core_schema @@ -145,15 +144,18 @@ class BaseTool(BaseModel, ABC): default_factory=list, description="List of environment variables used by the tool.", ) - args_schema: Annotated[ - type[PydanticBaseModel], - PlainSerializer(_serialize_schema, return_type=dict | None, when_used="json"), - ] = Field( + args_schema: type[PydanticBaseModel] = Field( default=_ArgsSchemaPlaceholder, validate_default=True, description="The schema for the arguments that the tool accepts.", ) + @field_serializer("args_schema", when_used="json") + def _serialize_args_schema( + self, schema: type[PydanticBaseModel] | None + ) -> dict[str, Any] | None: + return _serialize_schema(schema) + description_updated: bool = Field( default=False, description="Flag to check if the description has been updated." ) diff --git a/lib/crewai/src/crewai/utilities/pydantic_schema_utils.py b/lib/crewai/src/crewai/utilities/pydantic_schema_utils.py index a45c1635a..ff1d5529b 100644 --- a/lib/crewai/src/crewai/utilities/pydantic_schema_utils.py +++ b/lib/crewai/src/crewai/utilities/pydantic_schema_utils.py @@ -782,6 +782,20 @@ def _inline_top_level_ref(schema: dict[str, Any]) -> dict[str, Any]: return schema +def serialize_model_class(value: Any) -> Any: + """Serialize a ``type[BaseModel]`` field value as its JSON schema. + + Args: + value: A ``type[BaseModel]`` subclass, ``None``, or another union member. + + Returns: + ``value.model_json_schema()`` for model classes, ``value`` otherwise. + """ + if isinstance(value, type) and issubclass(value, BaseModel): + return value.model_json_schema() + return value + + def create_model_from_schema( # type: ignore[no-any-unimported] json_schema: dict[str, Any], *,