Skip to content

Commit 4e896f8

Browse files
authored
Merge branch 'main' into automation/metadata-litellm-1.105.0-20261011
2 parents 3850741 + 2151e3a commit 4e896f8

20 files changed

Lines changed: 617 additions & 180 deletions

File tree

‎.github/workflows/run_tests.yml‎

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,31 @@ jobs:
7070
test:
7171
name: Run Tests
7272
runs-on: ubuntu-latest
73+
# 3.15 is an allowed failure (see the matrix note). Tolerating it stops a
74+
# failure from failing the workflow and, under the default fail-fast, from
75+
# cancelling the 3.10-3.14 legs.
76+
continue-on-error: ${{ matrix.python-version == '3.15' }}
77+
env:
78+
# fastuuid has no cp315 wheel and its pyo3 rejects 3.15; build it against
79+
# the stable ABI on the 3.15 leg. pyo3 reads only the value "1".
80+
PYO3_USE_ABI3_FORWARD_COMPATIBILITY: ${{ matrix.python-version == '3.15' && '1' || '0' }}
7381
permissions:
7482
contents: read
7583
strategy:
7684
matrix:
77-
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
85+
# 3.15 is an allowed failure (continue-on-error above) while Python 3.15
86+
# is a release candidate. Its deps install from cp315 wheels except
87+
# fastuuid (a litellm dep), whose pyo3 rejects a Python past 3.14; the
88+
# job env builds it against the stable ABI on the 3.15 leg only.
89+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
7890
steps:
7991
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
8092
with:
8193
persist-credentials: false
8294
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
8395
with:
8496
python-version: ${{ matrix.python-version }}
97+
allow-prereleases: true
8598
- name: Install uv with caching
8699
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
87100
with:
@@ -103,18 +116,27 @@ jobs:
103116
extra_test:
104117
name: Run Extra and Deno Tests
105118
runs-on: ubuntu-latest
119+
# 3.15 is an allowed failure (see the note in the `test` job); tolerated so
120+
# it does not fail the workflow or, under fail-fast, cancel the 3.10-3.14 legs.
121+
continue-on-error: ${{ matrix.python-version == '3.15' }}
122+
env:
123+
# fastuuid has no cp315 wheel and its pyo3 rejects 3.15; build it against
124+
# the stable ABI on the 3.15 leg. pyo3 reads only the value "1".
125+
PYO3_USE_ABI3_FORWARD_COMPATIBILITY: ${{ matrix.python-version == '3.15' && '1' || '0' }}
106126
permissions:
107127
contents: read
108128
strategy:
109129
matrix:
110-
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
130+
# 3.15 is an allowed failure (see the note in the `test` job above).
131+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
111132
steps:
112133
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
113134
with:
114135
persist-credentials: false
115136
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
116137
with:
117138
python-version: ${{ matrix.python-version }}
139+
allow-prereleases: true
118140
- name: Install uv with caching
119141
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
120142
with:
@@ -256,18 +278,27 @@ jobs:
256278
build_package:
257279
name: Build Package
258280
runs-on: ubuntu-latest
281+
# 3.15 is an allowed failure (see the note in the `test` job); tolerated so
282+
# it does not fail the workflow or, under fail-fast, cancel the 3.10-3.14 legs.
283+
continue-on-error: ${{ matrix.python-version == '3.15' }}
284+
env:
285+
# fastuuid has no cp315 wheel and its pyo3 rejects 3.15; build it against
286+
# the stable ABI on the 3.15 leg. pyo3 reads only the value "1".
287+
PYO3_USE_ABI3_FORWARD_COMPATIBILITY: ${{ matrix.python-version == '3.15' && '1' || '0' }}
259288
permissions:
260289
contents: read
261290
strategy:
262291
matrix:
263-
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
292+
# 3.15 is an allowed failure (see the note in the `test` job above).
293+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
264294
steps:
265295
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
266296
with:
267297
persist-credentials: false
268298
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
269299
with:
270300
python-version: ${{ matrix.python-version }}
301+
allow-prereleases: true
271302
- name: Install uv with caching
272303
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
273304
with:

‎CONTRIBUTING.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ AI-assisted contributions are welcome under these conditions:
165165

166166
- If you can't explain your changes without consulting an AI, don't submit the PR.
167167
- Disclose what AI tool you used and how in your PR description.
168+
- Agents must disclose in the PR description whether the contribution comes from a sweep for issues, a problem encountered during normal usage, or another origin.
169+
State which applies, or provide a non-sensitive description of another origin. Do not share private or sensitive information about the use case.
168170
- Share your prompts. It helps reviewers and shows you thought through the problem.
169171
- Verify AI-found bugs yourself. Reproduce and confirm before filing. Write the report in
170172
your own words.

‎docs/docs/api/modules/Flex.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ The `program_trace` parameter is opt-in *by declaration*: only metrics that name
8484

8585
## Interpreter Execution
8686

87-
`Flex` always runs its generated code through a `CodeInterpreter`. `interpreter_factory` defaults to `dspy.PythonInterpreter` (Deno/Pyodide) and must be a **zero-argument factory** returning a fresh interpreter; a bare instance is not accepted, so parallel evaluations receive isolated sessions. `dspy.configure(interpreter_factory=...)` replaces that default. A factory passed to `Flex` wins, unless it is `PythonInterpreter`. The factory is called once per interpreter session, including separate sessions requested by nested code-executing modules. With the default interpreter, optimizer-authored control flow, string work, arithmetic, and supported imports run inside the sandbox, and only provided-tool calls, predictor construction, and predictor calls bridge back to the host, which makes the real LM calls. Custom factories define their own trust boundary: `dspy.LocalInterpreter`, for example, separates process memory and stdout but retains the host user's filesystem, environment, credentials, network, and process authority.
87+
`Flex` always runs its generated code through a `CodeInterpreter`. `interpreter_factory` defaults to `dspy.PythonInterpreter` (Deno/Pyodide) and must be a **zero-argument factory** returning a fresh interpreter; a bare instance is not accepted, so parallel evaluations receive isolated sessions. `dspy.configure(interpreter_factory=...)` replaces that default. A factory passed to `Flex` wins, unless it is `PythonInterpreter`. The factory is called once per interpreter session, including separate sessions requested by nested code-executing modules. With the default interpreter, optimizer-authored control flow, string work, arithmetic, and supported imports run inside the sandbox, and only provided-tool calls, predictor construction, and predictor calls bridge back to the host, which makes the real LM calls. Custom factories define their own trust boundary: `dspy.LocalInterpreter`, for example, separates process memory and stdout but retains the host user's filesystem, environment, credentials, network, and process authority. An in-process interpreter that runs code with `exec()` in the host works, but isolates nothing; use one only for trusted code.
8888

8989
Because the default builds a `PythonInterpreter`, *running* a `Flex` needs [Deno](https://deno.land/) installed; without it, the call raises.
9090

‎docs/docs/diving-deeper/flex.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ The reflection model authors code, and code can be wrong. A candidate that doesn
3434

3535
### 7. Generated code always runs through an interpreter
3636

37-
`Flex` takes its interpreter from `interpreter_factory`, else from `dspy.settings.interpreter_factory`, else from `dspy.PythonInterpreter` (Deno/Pyodide). Each of those must be a *zero-argument factory* — a bare instance or `None` is rejected. With the default, everything stays in the sandbox except provided-tool calls, predictor construction, and predictor calls, which bridge back to the host. Custom factories define their own trust boundary: `LocalInterpreter` separates process memory and stdout but retains the host user's filesystem, environment, credentials, network, and process authority. The factory creates a fresh interpreter for each session; a forward owns an outer session and nested code-executing modules may request separate sessions. Source portability between custom interpreters is not guaranteed. `max_predictor_calls` caps how many predictor calls the generated code can make in one `forward`.
37+
`Flex` takes its interpreter from `interpreter_factory`, else from `dspy.settings.interpreter_factory`, else from `dspy.PythonInterpreter` (Deno/Pyodide). Each of those must be a *zero-argument factory* — a bare instance or `None` is rejected. With the default, everything stays in the sandbox except provided-tool calls, predictor construction, and predictor calls, which bridge back to the host. Custom factories define their own trust boundary: `LocalInterpreter` separates process memory and stdout but retains the host user's filesystem, environment, credentials, network, and process authority. An in-process interpreter (one that runs code with `exec()` in the host, such as `dspy_interpreters.InProcessInterpreter`) works but isolates nothing: generated code shares the host's memory, so `max_predictor_calls` and the bridged LM-option allowlist bind only well-behaved code. Use one only for code you trust. The facade binds `dspy` in that interpreter's namespace and leaves the host's `dspy` module in place. The factory creates a fresh interpreter for each session; a forward owns an outer session and nested code-executing modules may request separate sessions. Source portability between custom interpreters is not guaranteed. `max_predictor_calls` caps how many predictor calls the generated code can make in one `forward`.
3838

3939
### 8. The declared output types are enforced at the sandbox boundary
4040

‎docs/requirements.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ beautifulsoup4==4.15.0
22
markdownify==1.2.3
33
mdformat==0.7.22
44
mdformat-tables==1.0.0
5-
mkdocstrings==1.0.6
5+
mkdocstrings==1.0.7
66
mkdocstrings-python==2.0.9
77
nbconvert==7.17.1
88
pillow==12.3.0

‎dspy/adapters/two_step_adapter.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,10 @@ def format_user_message_content(
194194
inputs: dict[str, Any],
195195
prefix: str = "",
196196
suffix: str = "",
197+
main_request: bool = False,
197198
) -> str:
199+
# `main_request` is accepted for compatibility with the base Adapter contract; the first-stage prompt has no
200+
# output-format reminder to add.
198201
parts = [prefix]
199202

200203
for name in signature.input_fields.keys():

‎dspy/adapters/utils.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ def serialize_for_json(value: Any) -> Any:
7171
Returns:
7272
The formatted value, which is serializable as a JSON string.
7373
"""
74-
# Attempt to format the value as a JSON-compatible object using pydantic, falling back to
75-
# a string representation of the value if that fails (e.g. if the value contains an object
76-
# that pydantic doesn't recognize or can't serialize)
74+
# Format the value as a JSON-compatible object using pydantic. A nested value pydantic can't
75+
# serialize (e.g. a numpy scalar) becomes its string form without affecting its siblings; the
76+
# whole value falls back to its string form only if pydantic can't handle it at all.
7777
try:
78-
return TypeAdapter(type(value)).dump_python(value, mode="json")
78+
return TypeAdapter(type(value)).dump_python(value, mode="json", fallback=str)
7979
except Exception:
8080
return str(value)
8181

‎dspy/predict/rlm.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -588,8 +588,9 @@ def _setup_facade(
588588
)
589589
try:
590590
invocation.install(repl)
591-
except CodeExecutionError as e:
592-
# The runtime cannot host the shim (e.g. it runs code in the host's memory): no sub-agents this call.
591+
except (CodeExecutionError, SyntaxError) as e:
592+
# The runtime cannot host the shim (e.g. a restricted compiler rejects the shim's `_dspy_*` names):
593+
# no sub-agents this call.
593594
for name in (CONSTRUCT_TOOL, CALL_TOOL):
594595
repl.tools.pop(name, None)
595596
logger.warning("RLM sub-agents are unavailable on %s: %s", type(repl).__name__, e)

‎dspy/primitives/_facade_shim.py‎

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,24 @@ def _dspy_tool(func, **_kw):
149149
dspy = _dspy
150150

151151
# Register as the importable ``dspy`` only inside the sandbox, where the registered host tools are
152-
# present in globals(). A sandbox whose ``dspy`` is the host's own module image (the host process or a
153-
# fork of it) would hand generated code the host's memory, so the facade refuses it.
152+
# present in globals(). An interpreter that runs code in the host's memory (the host process or a fork
153+
# of it) shares the host's sys.modules, so there ``import dspy`` resolves to the shim through this
154+
# namespace's own __import__ and the host's ``dspy`` module stays in place.
154155
if "__dspy_construct__" in globals():
155156
_dspy_host_facade = getattr(_dspy_sys, "modules", {}).get("dspy.primitives.facade")
156157
if getattr(_dspy_host_facade, "_HOST_PROCESS_TOKEN", None) == __dspy_host_token: # noqa: F821 - host-injected
157-
raise RuntimeError(
158-
"This interpreter runs code in the host's memory (the host process or a fork of it); "
159-
"the dspy facade needs an isolated interpreter."
160-
)
161-
_dspy_sys.modules["dspy"] = _dspy
158+
_dspy_builtins = globals()["__builtins__"]
159+
_dspy_builtins = dict(_dspy_builtins if isinstance(_dspy_builtins, dict) else vars(_dspy_builtins))
160+
_dspy_import_orig = _dspy_builtins["__import__"]
161+
162+
def _dspy_import(name, globals=None, locals=None, fromlist=(), level=0):
163+
if level == 0 and name.split(".")[0] == "dspy":
164+
if name != "dspy":
165+
raise ModuleNotFoundError(f"No module named {name!r}; 'dspy' is not a package", name=name)
166+
return _dspy
167+
return _dspy_import_orig(name, globals, locals, fromlist, level)
168+
169+
_dspy_builtins["__import__"] = _dspy_import
170+
globals()["__builtins__"] = _dspy_builtins
171+
else:
172+
_dspy_sys.modules["dspy"] = _dspy

‎dspy/primitives/code_interpreter.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,9 @@ class CodeInterpreter(Protocol):
8484
Sandbox dspy facade:
8585
dspy.Flex and dspy.RLM install a ``dspy`` shim whose predictors are built and
8686
run on the host, so registered tools must be callable as globals in executed
87-
code and state must persist across execute() calls. The shim refuses an
88-
interpreter that runs code in the host's memory (the host process or a fork).
87+
code and state must persist across execute() calls. In an interpreter that runs
88+
code in the host's memory, the shim binds ``dspy`` in that interpreter's namespace
89+
and leaves the host's ``sys.modules`` alone.
8990
"""
9091

9192
@property

0 commit comments

Comments
 (0)