Skip to content

feat: make numpy an optional dependency#34

Open
isaacbmiller wants to merge 8 commits intomainfrom
isaac/numpy-opt
Open

feat: make numpy an optional dependency#34
isaacbmiller wants to merge 8 commits intomainfrom
isaac/numpy-opt

Conversation

@isaacbmiller
Copy link
Copy Markdown

Summary

Move numpy from a required runtime dependency to an optional extra.

numpy-dependent features (KNN, Embeddings, SIMBA, MIPROv2, InferRules, DummyVectorizer, Embedder post-processing, etc.) raise a clear ImportError pointing at pip install dspy[numpy] only when actually invoked.

Changes

  • Created dspy/utils/_numpy.py with require_numpy() lazy import helper
  • Converted all top-level import numpy as np to lazy require_numpy() calls inside method bodies
  • Replaced numpy with stdlib where possible:
    • math.log2 in mipro_optimizer_v2, gepa
    • math.inf in infer_rules
    • statistics.pstdev in copro_optimizer
    • sum()/len() in teleprompt/utils
    • Removed dead np.random.seed() in mipro (optuna uses its own seed param)
  • Added early require_numpy() in SIMBA.__init__ for fast failure
  • Moved numpy to [project.optional-dependencies] with numpy extra
  • Added numpy to dev and test_extras so CI still gets it

Notes

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 7, 2026

Greptile Summary

This PR makes numpy an optional dependency by introducing a require_optional() lazy-import helper and converting all top-level import numpy as np calls to per-method guarded imports, replacing trivial numpy usage with stdlib equivalents where possible.

  • dspy/utils/_optional.py adds the require_optional() helper that raises a clear ImportError with a pip install dspy[numpy] hint when numpy is absent.
  • pyproject.toml moves numpy>=1.26.0 to [project.optional-dependencies] and keeps it in dev/test_extras so CI is unaffected.
  • One missed stdlib replacement remains in get_program_with_highest_avg_score (teleprompt/utils.py), where np.array + np.average could be replaced with sum()/len(), eliminating the numpy requirement on MIPROv2's minibatch path.

Confidence Score: 4/5

Safe to merge after fixing the remaining numpy call in get_program_with_highest_avg_score; all other conversions are correct.

One function in teleprompt/utils.py still calls require_optional('numpy') to compute a simple mean using np.array + np.average on a plain Python list, forcing numpy to be installed for MIPROv2's minibatch optimization path even though no numpy-specific functionality is needed.

dspy/teleprompt/utils.py — get_program_with_highest_avg_score still has a numpy call that should be replaced with stdlib sum()/len().

Important Files Changed

Filename Overview
dspy/utils/_optional.py New lazy-import helper for optional dependencies; clean implementation with a helpful install hint in the ImportError message.
dspy/teleprompt/utils.py Converted np.sum to stdlib sum() in get_token_usage, but get_program_with_highest_avg_score still calls require_optional("numpy") for np.array+np.average, which are easily replaced with sum()/len().
dspy/teleprompt/mipro_optimizer_v2.py Replaced np.log2 with math.log2 and removed np.random.seed; no remaining numpy usage in the file itself.
dspy/teleprompt/simba.py require_optional("numpy") placed at the top of compile() correctly gates all np.percentile/np.exp/rng_np usage; np captured by closure for nested softmax_sample.
dspy/teleprompt/infer_rules.py np.inf replaced with math.inf; numpy dependency fully removed from this file.
dspy/clients/embedding.py require_optional("numpy") added inside _postprocess; TYPE_CHECKING guard used for annotation-only np import.
dspy/predict/knn.py require_optional("numpy") added at the start of init and call; correct lazy-init pattern.
dspy/retrievers/embeddings.py require_optional("numpy") added at the top of each method that uses np; TYPE_CHECKING guard correctly handles np.ndarray annotations.
dspy/utils/dummies.py require_optional("numpy") added inside DummyVectorizer.call; TYPE_CHECKING guard used for np.ndarray annotation in return type.
pyproject.toml numpy moved from required dependencies to [project.optional-dependencies]; added to dev and test_extras so CI continues to receive it.

Comments Outside Diff (1)

  1. dspy/teleprompt/utils.py, line 120-125 (link)

    P1 get_program_with_highest_avg_score calls require_optional("numpy") solely to compute an array mean with np.array + np.average. Both operations are trivially replicated with stdlib sum()/len(), which the PR already applies in get_token_usage. As written, any caller of MIPROv2's minibatch path must have numpy installed even though no actual numpy computation is needed — the same class of issue that was fixed in mipro_optimizer_v2.py (math.log2) and infer_rules.py (math.inf).

Reviews (6): Last reviewed commit: "refactor(_optional): centralize dspy-ext..." | Re-trigger Greptile

Comment thread dspy/teleprompt/mipro_optimizer_v2.py
isaacbmiller and others added 4 commits May 8, 2026 08:38
Move numpy from required dependencies to a new [numpy] optional extra.
Add dspy/_numpy.require_numpy() helper so numpy-using features raise a
clear ImportError pointing to 'pip install dspy[numpy]' when numpy is
absent. Convert all module-level 'import numpy as np' statements to
lazy imports inside the functions that actually need numpy, so
'import dspy' continues to work when the extra is not installed.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
CI installs individual extras (uv sync --dev --extra dev, then --extra
test_extras) rather than --all-extras, so the new public [numpy] extra
alone doesn't cover test collection. Several test files import numpy
at module top, so numpy must be present in the extras CI actually
installs.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
isaacbmiller and others added 4 commits May 8, 2026 11:12
Replace dspy/utils/_numpy.require_numpy() with a generic
dspy/utils/_optional.require_optional(module, extra=...) helper modeled
on pandas.compat._optional.import_optional_dependency. dspy already
exposes other capability-gated extras (anthropic, weaviate, mcp,
langchain, optuna), so a generic helper avoids per-dep boilerplate as
new optional integrations are added.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- mipro_optimizer_v2._set_num_trials_from_num_candidates: np.log2 -> math.log2.
- mipro_optimizer_v2._set_random_seeds: drop legacy np.random.seed call;
  MIPROv2 itself doesn't read numpy's global RNG state.
- infer_rules.compile: -np.inf -> -math.inf.

Removes the require_optional("numpy") call from both files entirely;
neither code path needs numpy now.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
require_optional previously defaulted extra=module, so a caller passing
just require_optional("foo") would advertise `pip install dspy[foo]`
even if no such extra was declared in pyproject.toml. Make extra opt-in:
when omitted, the error message only suggests `pip install <module>`.
Update the existing numpy callers to pass extra="numpy" explicitly.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…xtra arg

Modeled on transformers' BACKENDS_MAPPING. _DSPY_EXTRAS in
dspy/utils/_optional.py maps each importable module name to its
declared install extra; require_optional(module) consults it to add
`pip install dspy[<extra>]` to the error message when an extra exists,
or falls back to `pip install <module>` otherwise. Callers no longer
write redundant extra="numpy".

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant