Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c6fb042
feat: make numpy an optional dependency
isaacbmiller Apr 22, 2026
40d5c92
Update dspy/teleprompt/utils.py
isaacbmiller Apr 22, 2026
1c8a95b
fix(ci): add numpy to dev and test_extras
isaacbmiller Apr 22, 2026
787a37c
refactor: move _numpy helper under dspy/utils/
isaacbmiller Apr 22, 2026
1b10976
refactor: generalize numpy helper into require_optional
isaacbmiller May 8, 2026
e3e7183
refactor: replace gratuitous numpy with stdlib math in mipro/infer_rules
isaacbmiller May 8, 2026
0224c48
fix(_optional): only suggest dspy[extra] when caller asserts it exists
isaacbmiller May 8, 2026
23e0b7a
refactor(_optional): centralize dspy-extras registry, drop per-call e…
isaacbmiller May 8, 2026
b078a23
refactor: replace _optional with lazy_import, stdlib math in teleprom…
isaacbmiller May 12, 2026
39a8f84
refactor: replace np.log2 with math.log2 in gepa auto_budget
isaacbmiller May 12, 2026
5bd8647
fix: skip tomllib test on Python < 3.11
isaacbmiller May 12, 2026
b7fc979
refactor: replace numpy.std with statistics.pstdev in copro_optimizer
isaacbmiller May 12, 2026
1b3224c
refactor: vendor lazy_loader pattern into require() with _MissingModu…
isaacbmiller May 13, 2026
ccee0e3
docs: add numpy install notes to API pages and tutorials
isaacbmiller May 13, 2026
330361d
refactor: detect dspy/dspy-ai dist for install hints, move math impor…
isaacbmiller May 13, 2026
efecfc9
test(lazy_import): tighten brittle lazy_import tests
isaacbmiller May 13, 2026
ddc1ac3
docs(lazy_import): switch RST double backticks to mkdocs single backt…
isaacbmiller May 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/docs/api/models/Embedder.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# dspy.Embedder

!!! note "Requires numpy"
`dspy.Embedder` requires numpy. Install it with `pip install dspy[numpy]`.

<!-- START_API_REF -->
::: dspy.Embedder
handler: python
Expand Down
3 changes: 3 additions & 0 deletions docs/docs/api/optimizers/KNN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# dspy.KNN

!!! note "Requires numpy"
`dspy.KNN` requires numpy. Install it with `pip install dspy[numpy]`.

<!-- START_API_REF -->
::: dspy.KNN
handler: python
Expand Down
3 changes: 3 additions & 0 deletions docs/docs/api/optimizers/KNNFewShot.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# dspy.KNNFewShot

!!! note "Requires numpy"
`dspy.KNNFewShot` requires numpy. Install it with `pip install dspy[numpy]`.

<!-- START_API_REF -->
::: dspy.KNNFewShot
handler: python
Expand Down
3 changes: 3 additions & 0 deletions docs/docs/api/optimizers/SIMBA.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# dspy.SIMBA

!!! note "Requires numpy"
`dspy.SIMBA` requires numpy. Install it with `pip install dspy[numpy]`.

<!-- START_API_REF -->
::: dspy.SIMBA
handler: python
Expand Down
3 changes: 3 additions & 0 deletions docs/docs/api/tools/Embeddings.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# dspy.retrievers.Embeddings

!!! note "Requires numpy"
`dspy.retrievers.Embeddings` requires numpy. Install it with `pip install dspy[numpy]`.

<!-- START_API_REF -->
::: dspy.Embeddings
handler: python
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/tutorials/rag/index.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"\n",
"Let's walk through a quick example of **basic question answering** with and without **retrieval-augmented generation** (RAG) in DSPy. Specifically, let's build **a system for answering Tech questions**, e.g. about Linux or iPhone apps.\n",
"\n",
"Install the latest DSPy via `pip install -U dspy` and follow along. If you're looking instead for a conceptual overview of DSPy, this [recent lecture](https://www.youtube.com/live/JEMYuzrKLUw) is a good place to start. You also need to run `pip install datasets`."
"Install the latest DSPy via `pip install -U dspy` and follow along. If you're looking instead for a conceptual overview of DSPy, this [recent lecture](https://www.youtube.com/live/JEMYuzrKLUw) is a good place to start. You also need to run `pip install datasets`. This tutorial uses `dspy.Embedder` and `dspy.retrievers.Embeddings`, which require numpy: `pip install dspy[numpy]`."
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/tutorials/tool_use/index.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"\n",
"Let's walk through a quick example of building and prompt-optimizing a DSPy agent for advanced tool use. We'll do this for the challenging task [ToolHop](https://arxiv.org/abs/2501.02506) but with an even stricter evaluation criteria.\n",
"\n",
"Install the latest DSPy via `pip install -U dspy` and follow along. You will also need to `pip install func_timeout datasets`."
"Install the latest DSPy via `pip install -U dspy` and follow along. You will also need to `pip install func_timeout datasets`. This tutorial uses `dspy.SIMBA`, which requires numpy: `pip install dspy[numpy]`."
]
},
{
Expand Down
5 changes: 4 additions & 1 deletion dspy/clients/embedding.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from __future__ import annotations

from typing import Any, Callable

import litellm
import numpy as np

from dspy.clients.cache import request_cache
from dspy.utils.lazy_import import require

np = require("numpy")

class Embedder:
"""DSPy embedding class.
Expand Down
5 changes: 3 additions & 2 deletions dspy/predict/knn.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import numpy as np

from dspy.clients import Embedder
from dspy.primitives import Example
from dspy.utils.lazy_import import require

np = require("numpy")


class KNN:
Expand Down
7 changes: 5 additions & 2 deletions dspy/retrievers/embeddings.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from __future__ import annotations

import json
import os
from typing import Any

import numpy as np

from dspy.utils.lazy_import import require
from dspy.utils.unbatchify import Unbatchify

np = require("numpy")


class Embeddings:
"""DSPy Embeddings retriever.
Expand Down
10 changes: 4 additions & 6 deletions dspy/teleprompt/copro_optimizer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import statistics
from collections import defaultdict

import dspy
Expand Down Expand Up @@ -142,9 +143,6 @@ def compile(self, student, *, trainset, eval_kwargs):
id(p): {"depth": [], "max": [], "average": [], "min": [], "std": []} for p in module.predictors()
}

if self.track_stats:
import numpy as np

candidates = {}
evaluated_candidates = defaultdict(dict)

Expand Down Expand Up @@ -254,7 +252,7 @@ def compile(self, student, *, trainset, eval_kwargs):
results_latest[id(p_old)]["max"].append(max(latest_scores))
results_latest[id(p_old)]["average"].append(sum(latest_scores) / len(latest_scores))
results_latest[id(p_old)]["min"].append(min(latest_scores))
results_latest[id(p_old)]["std"].append(np.std(latest_scores))
results_latest[id(p_old)]["std"].append(statistics.pstdev(latest_scores))

# Now that we've evaluated the candidates, set this predictor to the best performing version
# to ensure the next round of scores reflect the best possible version
Expand Down Expand Up @@ -296,7 +294,7 @@ def compile(self, student, *, trainset, eval_kwargs):
results_best[id(p_base)]["max"].append(max(scores))
results_best[id(p_base)]["average"].append(sum(scores) / len(scores))
results_best[id(p_base)]["min"].append(min(scores))
results_best[id(p_base)]["std"].append(np.std(scores))
results_best[id(p_base)]["std"].append(statistics.pstdev(scores))

for i in range(shortest_len - 1, -1, -1):
# breakpoint()
Expand Down Expand Up @@ -341,7 +339,7 @@ def compile(self, student, *, trainset, eval_kwargs):
results_best[id(predictor)]["max"].append(max(scores))
results_best[id(predictor)]["average"].append(sum(scores) / len(scores))
results_best[id(predictor)]["min"].append(min(scores))
results_best[id(predictor)]["std"].append(np.std(scores))
results_best[id(predictor)]["std"].append(statistics.pstdev(scores))

candidates.sort(key=lambda x: x["score"], reverse=True)

Expand Down
5 changes: 2 additions & 3 deletions dspy/teleprompt/gepa/gepa.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import inspect
import logging
import math
import random
from dataclasses import dataclass
from typing import Any, Literal, Optional, Protocol, Union
Expand Down Expand Up @@ -443,9 +444,7 @@ def __init__(
def auto_budget(
self, num_preds, num_candidates, valset_size: int, minibatch_size: int = 35, full_eval_steps: int = 5
) -> int:
import numpy as np

num_trials = int(max(2 * (num_preds * 2) * np.log2(num_candidates), 1.5 * num_candidates))
num_trials = int(max(2 * (num_preds * 2) * math.log2(num_candidates), 1.5 * num_candidates))
if num_trials < 0 or valset_size < 0 or minibatch_size < 0:
raise ValueError("num_trials, valset_size, and minibatch_size must be >= 0.")
if full_eval_steps < 1:
Expand Down
5 changes: 2 additions & 3 deletions dspy/teleprompt/infer_rules.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import logging
import math
import random

import numpy as np

import dspy
from dspy.evaluate.evaluate import Evaluate
from dspy.teleprompt import BootstrapFewShot
Expand Down Expand Up @@ -32,7 +31,7 @@ def compile(self, student, *, teacher=None, trainset, valset=None):
all_predictors = [p for p in original_program.predictors() if hasattr(p, "signature")]
instructions_list = [p.signature.instructions for p in all_predictors]

best_score = -np.inf
best_score = -math.inf
best_program = None

for candidate_idx in range(self.num_candidates):
Expand Down
6 changes: 2 additions & 4 deletions dspy/teleprompt/mipro_optimizer_v2.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import logging
import math
import random
from collections import defaultdict
from typing import TYPE_CHECKING, Any, Callable, Literal

import numpy as np

import dspy
from dspy.evaluate.evaluate import Evaluate
from dspy.propose import GroundedProposer
Expand Down Expand Up @@ -277,14 +276,13 @@ def compile(

def _set_random_seeds(self, seed):
self.rng = random.Random(seed)
np.random.seed(seed)

Comment thread
isaacbmiller marked this conversation as resolved.
def _set_num_trials_from_num_candidates(self, program, zeroshot_opt, num_candidates):
num_vars = len(program.predictors())
if not zeroshot_opt:
num_vars *= 2 # Account for few-shot examples + instruction variables
# Trials = MAX(c*M*log(N), c=2, 3/2*N)
num_trials = int(max(2 * num_vars * np.log2(num_candidates), 1.5 * num_candidates))
num_trials = int(max(2 * num_vars * math.log2(num_candidates), 1.5 * num_candidates))

return num_trials

Expand Down
5 changes: 3 additions & 2 deletions dspy/teleprompt/simba.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
import random
from typing import Any, Callable

import numpy as np

import dspy
from dspy.teleprompt.simba_utils import append_a_demo, append_a_rule, prepare_models_for_resampling, wrap_program
from dspy.teleprompt.teleprompt import Teleprompter
from dspy.utils.lazy_import import require

np = require("numpy")

logger = logging.getLogger(__name__)

Expand Down
11 changes: 4 additions & 7 deletions dspy/teleprompt/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
import shutil
import sys

import numpy as np

try:
from IPython.core.magics.code import extract_symbols
except ImportError:
Expand Down Expand Up @@ -121,8 +119,8 @@ def get_program_with_highest_avg_score(param_score_dict, fully_evaled_param_comb
# Calculate the mean for each combination of categorical parameters, based on past trials
results = []
for key, values in param_score_dict.items():
scores = np.array([v[0] for v in values])
mean = np.average(scores)
scores = [v[0] for v in values]
mean = sum(scores) / len(scores)
program = values[0][1]
params = values[0][2]
results.append((key, mean, program, params))
Expand Down Expand Up @@ -284,9 +282,8 @@ def get_token_usage(model) -> tuple[int, int]:
input_tokens.append(_input_tokens)
output_tokens.append(_output_tokens)

total_input_tokens = int(np.sum(input_tokens))
total_output_tokens = int(np.sum(output_tokens))

total_input_tokens = sum(input_tokens)
total_output_tokens = sum(output_tokens)
return total_input_tokens, total_output_tokens


Expand Down
7 changes: 5 additions & 2 deletions dspy/utils/dummies.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
from __future__ import annotations

import random
from collections import defaultdict
from typing import Any

import numpy as np

from dspy.adapters.chat_adapter import FieldInfoWithName, field_header_pattern
from dspy.clients.base_lm import BaseLM
from dspy.dsp.utils.utils import dotdict
from dspy.signatures.field import OutputField
from dspy.utils.lazy_import import require

np = require("numpy")


class DummyLM(BaseLM):
Expand Down
Loading