diff --git a/lib/crewai/src/crewai/utilities/config.py b/lib/crewai/src/crewai/utilities/config.py index 95a542c5ec..5c3813112e 100644 --- a/lib/crewai/src/crewai/utilities/config.py +++ b/lib/crewai/src/crewai/utilities/config.py @@ -1,7 +1,6 @@ from typing import Any - from pydantic import BaseModel - +import copy def process_config( values: dict[str, Any], model_class: type[BaseModel] @@ -22,17 +21,24 @@ def process_config( # Copy values from config (originally from YAML) to the model's attributes. # Only copy if the attribute isn't already set, preserving any explicitly defined values. for key, value in config.items(): - if key not in model_class.model_fields or values.get(key) is not None: + if key not in model_class.model_fields: continue - - if isinstance(value, dict): - if isinstance(values.get(key), dict): - values[key].update(value) - else: - values[key] = value + if (override_value := values.get(key)) is not None: + if isinstance(override_value, dict) and isinstance(value, dict): + config_value = copy.deepcopy(value) + _dict_deep_update(config_value, override_value) + values[key] = config_value else: - values[key] = value + values[key] = copy.deepcopy(value) if isinstance(value, (dict, list)) else value # Remove the config from values to avoid duplicate processing values.pop("config", None) return values + +def _dict_deep_update(to_dict: dict[str, Any], from_dict: dict[str, Any]) -> None: + """Internal helper to recursively update to_dict with from_dict values in-place.""" + for key, value in from_dict.items(): + if key in to_dict and isinstance(to_dict[key], dict) and isinstance(value, dict): + _dict_deep_update(to_dict[key], value) + else: + to_dict[key] = value