fix(dspy): add opt-in model_id to Embedder to disambiguate callable-model cache keys - #90
Conversation
Greptile SummaryThis PR adds an opt-in
Confidence Score: 4/5The PR should not merge until the newly reserved keyword no longer breaks custom embedding callables that previously accepted The cache-key flow itself is consistent and well tested, but the public passthrough contract changes for both constructor and per-call keyword arguments, causing realistic existing callable models to fail or behave differently. Files Needing Attention: dspy/clients/embedding.py Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Embedder call] --> B[Resolve instance or per-call model_id]
B --> C{Caching enabled?}
C -- Yes --> D[Include model_id in DSPy cache request]
D --> E{Cache hit?}
E -- Yes --> F[Return cached embeddings]
E -- No --> G[Strip model_id]
C -- No --> G
G --> H[Callable model or LiteLLM]
H --> I[Return embeddings]
Reviews (1): Last reviewed commit: "fix(dspy): add opt-in model_id to Embedd..." | Re-trigger Greptile |
| batch_size: int = 200, | ||
| caching: bool = True, | ||
| model_id: str | None = None, | ||
| **kwargs: dict[str, Any], |
There was a problem hiding this comment.
Reserved Keyword Breaks Callables
If an existing custom embedding callable accepts model_id as a construction-time or per-call keyword, this change now consumes that value as cache metadata instead of forwarding it as before. A callable that requires the argument will fail with TypeError, while one that uses it for model routing may silently select different behavior. Use a cache-specific parameter name or preserve an explicit way to forward a model argument named model_id.
Knowledge Base Used: Language model and embedding clients
Warning
GitHub issue creation failed
Detail attempted to publish this bug to GitHub, but the issue could not be created. This fix PR was created without that issue, and missing tracker references are shown as
Unknown issue.You can review and merge this PR normally. Please review your tracker integration settings before the next publish run.
Detail bug report: View on Detail
📝 Changes Description
This MR/PR contains the following changes:
dspy.Embedderis backed by a callable model withcaching=True(the default), the@request_cachecache key indspy/clients/cache.py::_transform_valuecollapses distinct callable models to the same value. The source branch (<callable_source:{inspect.getsource(value)}>) is byte-identical across instances/checkpoints of the same class (the documenteddspy.Embedder(model.encode)pattern — e.g. twoSentenceTransformercheckpoints), and the fallback branch (<callable:{value.__name__ or 'lambda'}>) collapses callable instances /functools.partials to<callable:lambda>. Two different configured models then silently return each other's cached embeddings (their underlying callable is never invoked), including across separate processes via the default on-disk cache (~/.dspy_cache).model_id: str | Noneparameter todspy.Embedder. When set,model_idis folded into the@request_cacherequest dict (and thus the cache key) but is never forwarded to the callable model nor to litellm. Distinctmodel_idvalues never collide; the samemodel_idreuses the cache across processes (preserving the on-disk reuse benefit). WhenNone(the default), it is omitted from the key entirely, preserving existing cache keys — so this is an opt-in mitigation; the residual default-path limitation is documented in theEmbedder/KNN/KNNFewShotdocstrings.dspy/clients/cache.pyis intentionally left unchanged (anid()-based mitigation was rejected because it would break on-disk reuse and not fix the cross-process case).Closes Unknown issue
✅ Contributor Checklist
model_id(e.g. the checkpoint name) when switching callable models of the same class while sharing a cache namespace. The default path (nomodel_id) is unchanged to avoid silently invalidating existing on-disk caches; the docstrings ofEmbedder,KNN, andKNNFewShotnow document this guidance.tests/clients/test_embedding.pycovering: the buggy_transform_valuebranches still collapse distinct callables (premise guard);model_iddisambiguating the source branch (bound methods) and the fallback branch (callable instances); same-model_idon-disk reuse across a simulated fresh process; distinct-model_idpreventing cross-cache collision;model_id=Nonepreserving existing cache keys;model_idnot being forwarded to the callable nor to litellm; per-callmodel_idoverriding the instance-level one; and async-parity viaacall. The full existingtests/clients,tests/predict,tests/teleprompt, andtests/retrieverssuites pass (512 passed, 81 skipped), as does the CI-mirror command (1263 passed, 252 skipped, 2 xfailed).SentenceTransformer(installedsentence-transformers==6.0.1): confirmed two real checkpoints (all-MiniLM-L6-v2vsparaphrase-MiniLM-L6-v2) provably share adspy.cache.cache_keywithoutmodel_idand are disambiguated with distinctmodel_ids; verified in-process (each embedder returns its own ground-truth vectors vs a colliding baseline), cross-process via a shared on-disk cache (a second fresh process with a distinctmodel_idcomputes its own vectors and does not read the first's), and cross-process reuse with the samemodel_id(the second process hits the on-disk cache withencodecall count 0). KNN end-to-end with amodel_id-taggedEmbedderretrieves correct neighbors and reuses the on-disk cache on a second run.KNNFewShotinitialization with a realmodel_id-taggedEmbedderworks; theKNNFewShot.compilepath was not exercised end-to-end because its existing repo test (_test_knn_few_shot_compile) is a disabled TODO unrelated to this cache-key bug.Automatic Fixes PRs can be configured here.