From 21af4ded8838189ed4c5e3371c8ab35ec34d02d1 Mon Sep 17 00:00:00 2001 From: Masahiro Hiramori Date: Sun, 14 Dec 2025 23:08:54 +0900 Subject: [PATCH 1/6] Fix memory_max_entries parameter to enforce positive integer constraint --- dspy/clients/__init__.py | 2 +- dspy/clients/cache.py | 4 +++- tests/clients/test_cache.py | 10 ++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/dspy/clients/__init__.py b/dspy/clients/__init__.py index 43a5c9403a..655151c33f 100644 --- a/dspy/clients/__init__.py +++ b/dspy/clients/__init__.py @@ -19,7 +19,7 @@ def configure_cache( enable_memory_cache: bool | None = True, disk_cache_dir: str | None = DISK_CACHE_DIR, disk_size_limit_bytes: int | None = DISK_CACHE_LIMIT, - memory_max_entries: int | None = 1000000, + memory_max_entries: int = 1000000, ): """Configure the cache for DSPy. diff --git a/dspy/clients/cache.py b/dspy/clients/cache.py index 4773306ccc..960c280cb1 100644 --- a/dspy/clients/cache.py +++ b/dspy/clients/cache.py @@ -29,7 +29,7 @@ def __init__( enable_memory_cache: bool, disk_cache_dir: str, disk_size_limit_bytes: int | None = 1024 * 1024 * 10, - memory_max_entries: int | None = 1000000, + memory_max_entries: int = 1000000, ): """ Args: @@ -43,6 +43,8 @@ def __init__( self.enable_disk_cache = enable_disk_cache self.enable_memory_cache = enable_memory_cache if self.enable_memory_cache: + if memory_max_entries <= 0: + raise ValueError("memory_max_entries must be a positive integer") self.memory_cache = LRUCache(maxsize=memory_max_entries) else: self.memory_cache = {} diff --git a/tests/clients/test_cache.py b/tests/clients/test_cache.py index 20e0af3232..52430aefd9 100644 --- a/tests/clients/test_cache.py +++ b/tests/clients/test_cache.py @@ -48,6 +48,16 @@ def test_initialization(tmp_path): assert memory_cache.memory_cache.maxsize == 50 assert memory_cache.disk_cache == {} + # Test memory-only cache with invalid memory_max_entries + with pytest.raises(ValueError): + memory_cache = Cache( + enable_disk_cache=False, + enable_memory_cache=True, + disk_cache_dir="", + disk_size_limit_bytes=0, + memory_max_entries=-1, + ) + # Test disk-only cache disk_cache = Cache( enable_disk_cache=True, From bbd92a655de84bbbbe0c2496aa5d914bf2caaec9 Mon Sep 17 00:00:00 2001 From: Masahiro Hiramori Date: Mon, 22 Dec 2025 11:28:26 +0900 Subject: [PATCH 2/6] update ValueError message Co-authored-by: Chen Qian --- dspy/clients/cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspy/clients/cache.py b/dspy/clients/cache.py index 960c280cb1..8e0cb5702b 100644 --- a/dspy/clients/cache.py +++ b/dspy/clients/cache.py @@ -44,7 +44,7 @@ def __init__( self.enable_memory_cache = enable_memory_cache if self.enable_memory_cache: if memory_max_entries <= 0: - raise ValueError("memory_max_entries must be a positive integer") + raise ValueError(f"`memory_max_entries` must be a positive integer, but received {memory_max_entries}") self.memory_cache = LRUCache(maxsize=memory_max_entries) else: self.memory_cache = {} From c0a85cc434445a595bbacd25192e78238a50fb9b Mon Sep 17 00:00:00 2001 From: Masahiro Hiramori Date: Mon, 22 Dec 2025 12:08:28 +0900 Subject: [PATCH 3/6] update docstring for `memory_max_entries` param --- dspy/clients/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspy/clients/__init__.py b/dspy/clients/__init__.py index 655151c33f..ee8eac9167 100644 --- a/dspy/clients/__init__.py +++ b/dspy/clients/__init__.py @@ -28,7 +28,8 @@ def configure_cache( enable_memory_cache: Whether to enable in-memory cache. disk_cache_dir: The directory to store the on-disk cache. disk_size_limit_bytes: The size limit of the on-disk cache. - memory_max_entries: The maximum number of entries in the in-memory cache. + memory_max_entries: The maximum number of entries in the in-memory cache. To allow the cache to grow without + bounds, set this parameter to `math.inf` or a similar value. """ DSPY_CACHE = Cache( From e9dd03560bdbad09a8b57e1b70a56e7c3558626c Mon Sep 17 00:00:00 2001 From: Masahiro Hiramori Date: Mon, 22 Dec 2025 15:17:29 +0900 Subject: [PATCH 4/6] update --- dspy/clients/__init__.py | 2 +- dspy/clients/cache.py | 8 +++++--- tests/clients/test_cache.py | 25 ++++++++++++++++++++++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/dspy/clients/__init__.py b/dspy/clients/__init__.py index ee8eac9167..5c6f5c9049 100644 --- a/dspy/clients/__init__.py +++ b/dspy/clients/__init__.py @@ -19,7 +19,7 @@ def configure_cache( enable_memory_cache: bool | None = True, disk_cache_dir: str | None = DISK_CACHE_DIR, disk_size_limit_bytes: int | None = DISK_CACHE_LIMIT, - memory_max_entries: int = 1000000, + memory_max_entries: int | float = 1000000, ): """Configure the cache for DSPy. diff --git a/dspy/clients/cache.py b/dspy/clients/cache.py index 8e0cb5702b..b42b1b3835 100644 --- a/dspy/clients/cache.py +++ b/dspy/clients/cache.py @@ -29,7 +29,7 @@ def __init__( enable_memory_cache: bool, disk_cache_dir: str, disk_size_limit_bytes: int | None = 1024 * 1024 * 10, - memory_max_entries: int = 1000000, + memory_max_entries: int | float = 1000000, ): """ Args: @@ -43,8 +43,10 @@ def __init__( self.enable_disk_cache = enable_disk_cache self.enable_memory_cache = enable_memory_cache if self.enable_memory_cache: - if memory_max_entries <= 0: - raise ValueError(f"`memory_max_entries` must be a positive integer, but received {memory_max_entries}") + if memory_max_entries is None: + raise ValueError("`memory_max_entries` cannot be None. Use `math.inf` if you need an unbounded cache.") + elif memory_max_entries <= 0: + raise ValueError(f"`memory_max_entries` must be a positive number, but received {memory_max_entries}") self.memory_cache = LRUCache(maxsize=memory_max_entries) else: self.memory_cache = {} diff --git a/tests/clients/test_cache.py b/tests/clients/test_cache.py index 52430aefd9..f56526fc75 100644 --- a/tests/clients/test_cache.py +++ b/tests/clients/test_cache.py @@ -49,7 +49,7 @@ def test_initialization(tmp_path): assert memory_cache.disk_cache == {} # Test memory-only cache with invalid memory_max_entries - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=r"`memory_max_entries` must be a positive number, but received -1"): memory_cache = Cache( enable_disk_cache=False, enable_memory_cache=True, @@ -58,6 +58,29 @@ def test_initialization(tmp_path): memory_max_entries=-1, ) + # Test memory-only cache with invalid memory_max_entries + with pytest.raises(ValueError, match=r"`memory_max_entries` cannot be None. Use `math.inf` if you need an unbounded cache."): + memory_cache = Cache( + enable_disk_cache=False, + enable_memory_cache=True, + disk_cache_dir="", + disk_size_limit_bytes=0, + memory_max_entries=None, + ) + + # Test memory-only cache with unbounded size + import math + memory_cache = Cache( + enable_disk_cache=False, + enable_memory_cache=True, + disk_cache_dir="", + disk_size_limit_bytes=0, + memory_max_entries=math.inf, + ) + assert isinstance(memory_cache.memory_cache, LRUCache) + assert memory_cache.memory_cache.maxsize == math.inf + assert memory_cache.disk_cache == {} + # Test disk-only cache disk_cache = Cache( enable_disk_cache=True, From 26b0000b5d5bdc2a0bb11aa1eff3d6e2b74f47fc Mon Sep 17 00:00:00 2001 From: Masahiro Hiramori Date: Mon, 22 Dec 2025 17:30:14 +0900 Subject: [PATCH 5/6] accept only int type --- dspy/clients/__init__.py | 2 +- dspy/clients/cache.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspy/clients/__init__.py b/dspy/clients/__init__.py index 5c6f5c9049..ee8eac9167 100644 --- a/dspy/clients/__init__.py +++ b/dspy/clients/__init__.py @@ -19,7 +19,7 @@ def configure_cache( enable_memory_cache: bool | None = True, disk_cache_dir: str | None = DISK_CACHE_DIR, disk_size_limit_bytes: int | None = DISK_CACHE_LIMIT, - memory_max_entries: int | float = 1000000, + memory_max_entries: int = 1000000, ): """Configure the cache for DSPy. diff --git a/dspy/clients/cache.py b/dspy/clients/cache.py index b42b1b3835..a46f27723e 100644 --- a/dspy/clients/cache.py +++ b/dspy/clients/cache.py @@ -29,7 +29,7 @@ def __init__( enable_memory_cache: bool, disk_cache_dir: str, disk_size_limit_bytes: int | None = 1024 * 1024 * 10, - memory_max_entries: int | float = 1000000, + memory_max_entries: int = 1000000, ): """ Args: From 71784259f49f222535d397a3e1251d1cad314b70 Mon Sep 17 00:00:00 2001 From: chenmoneygithub Date: Mon, 22 Dec 2025 21:38:12 -0700 Subject: [PATCH 6/6] split tests --- dspy/clients/__init__.py | 5 ++++ tests/clients/test_cache.py | 54 +++++++++++++++---------------------- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/dspy/clients/__init__.py b/dspy/clients/__init__.py index ee8eac9167..7972bba8a4 100644 --- a/dspy/clients/__init__.py +++ b/dspy/clients/__init__.py @@ -14,6 +14,8 @@ DISK_CACHE_DIR = os.environ.get("DSPY_CACHEDIR") or os.path.join(Path.home(), ".dspy_cache") DISK_CACHE_LIMIT = int(os.environ.get("DSPY_CACHE_LIMIT", 3e10)) # 30 GB default + + def configure_cache( enable_disk_cache: bool | None = True, enable_memory_cache: bool | None = True, @@ -41,6 +43,7 @@ def configure_cache( ) import dspy + # Update the reference to point to the new cache dspy.cache = DSPY_CACHE @@ -48,6 +51,7 @@ def configure_cache( litellm.telemetry = False litellm.cache = None # By default we disable LiteLLM cache and use DSPy on-disk cache. + def _get_dspy_cache(): disk_cache_dir = os.environ.get("DSPY_CACHEDIR") or os.path.join(Path.home(), ".dspy_cache") disk_cache_limit = int(os.environ.get("DSPY_CACHE_LIMIT", 3e10)) @@ -72,6 +76,7 @@ def _get_dspy_cache(): ) return _dspy_cache + DSPY_CACHE = _get_dspy_cache() if "LITELLM_LOCAL_MODEL_COST_MAP" not in os.environ: diff --git a/tests/clients/test_cache.py b/tests/clients/test_cache.py index f56526fc75..02fb27f01a 100644 --- a/tests/clients/test_cache.py +++ b/tests/clients/test_cache.py @@ -48,39 +48,6 @@ def test_initialization(tmp_path): assert memory_cache.memory_cache.maxsize == 50 assert memory_cache.disk_cache == {} - # Test memory-only cache with invalid memory_max_entries - with pytest.raises(ValueError, match=r"`memory_max_entries` must be a positive number, but received -1"): - memory_cache = Cache( - enable_disk_cache=False, - enable_memory_cache=True, - disk_cache_dir="", - disk_size_limit_bytes=0, - memory_max_entries=-1, - ) - - # Test memory-only cache with invalid memory_max_entries - with pytest.raises(ValueError, match=r"`memory_max_entries` cannot be None. Use `math.inf` if you need an unbounded cache."): - memory_cache = Cache( - enable_disk_cache=False, - enable_memory_cache=True, - disk_cache_dir="", - disk_size_limit_bytes=0, - memory_max_entries=None, - ) - - # Test memory-only cache with unbounded size - import math - memory_cache = Cache( - enable_disk_cache=False, - enable_memory_cache=True, - disk_cache_dir="", - disk_size_limit_bytes=0, - memory_max_entries=math.inf, - ) - assert isinstance(memory_cache.memory_cache, LRUCache) - assert memory_cache.memory_cache.maxsize == math.inf - assert memory_cache.disk_cache == {} - # Test disk-only cache disk_cache = Cache( enable_disk_cache=True, @@ -104,6 +71,27 @@ def test_initialization(tmp_path): assert disabled_cache.disk_cache == {} +def test_invalid_cache_initialization(): + with pytest.raises(ValueError, match=r"`memory_max_entries` must be a positive number, but received -1"): + Cache( + enable_disk_cache=False, + enable_memory_cache=True, + disk_cache_dir="", + disk_size_limit_bytes=0, + memory_max_entries=-1, + ) + with pytest.raises( + ValueError, match=r"`memory_max_entries` cannot be None. Use `math.inf` if you need an unbounded cache." + ): + Cache( + enable_disk_cache=False, + enable_memory_cache=True, + disk_cache_dir="", + disk_size_limit_bytes=0, + memory_max_entries=None, + ) + + def test_cache_key_generation(cache): """Test cache key generation with different types of inputs.""" # Test with simple dictionary