Python: DSPy adapter backed by DSRs (dsrs-dspy, PyO3) - #89
Open
darinkishore wants to merge 7 commits into
Open
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LLMs routinely emit Pythonish JSON ({'age': None, 'ok': True}). Map the
Python spellings onto null/true/false so such completions parse instead
of degrading to strings. Ported from the dsrs_dspy adapter's vendored
jsonish, where this ran in production.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dspy-py compiles JSON Schema (from DSPy/pydantic signatures) into BAML classes/enums directly, without facet shapes, so it needs the registry that schema_builder uses internally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A PyO3 extension exposing DSRs BAML-style schema rendering and JSONish
parsing to Python, plus a pure-Python DSRSBAMLAdapter that drops into
DSPy as a ChatAdapter replacement.
Install (no PyPI needed; builds from source via maturin):
uv add "dsrs-dspy @ git+https://github.com/krypticmouse/DSRs@main#subdirectory=crates/dspy-py"
- abi3-py39 wheel: one build covers CPython >= 3.9
- Rust tests: unit + property (schema fuzzing, JSONish noise, a matrix
driven by real pydantic.TypeAdapter(...).json_schema() output)
- Python tests: render/parse end-to-end against a real DSPy install
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
miri cannot interpret an embedded CPython, so the PyO3 crate is excluded there; it is covered by the build, test, clippy, and new python-adapter jobs instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collaborator
Author
|
CI status note: MIRI red is pre-existing on main, not from this PR. The failure is Meanwhile Clippy Lint was also red on main (fmt drift in |
- Drop the Build job: cargo test --all-features builds a strict superset. - Drop the MIRI job: red on main since at least February (fastant's cpuid ctor aborts every dspy-rs test binary before anything runs), so it has produced no signal for months while costing ~30 minutes per push. Restore it scoped to specific UB-relevant crates if wanted. - Stop double-running: push triggers only on main, PRs use the pull_request event, and in-flight runs for the same ref are cancelled. - Add Swatinem/rust-cache to the remaining jobs; the Test job was ~90% cold compilation. - Clippy now also gates PRs (it previously only ran on push events) and every job gets a timeout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI's randomized run found the flake: a generated Literal["0"] next to an int in the same union. JSONish deliberately coerces quoted numbers, so for '"0"' against Literal["0"] | int either parse is defensible and a round-trip oracle cannot call a winner. Generate only unambiguous literal strings and enum values (leading letter, no JSONish keywords); the union-scoring behavior itself is unchanged. Verified with ~86k randomized cases across 6 runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
crates/dspy-py— a PyO3 extension + pure-Python shim that makes DSRs's BAML-style schema rendering and JSONish parsing available to DSPy as a drop-inChatAdapterreplacement (dsrs_dspy.DSRSBAMLAdapter).Since DSRs isn't published, the package installs straight from git — no PyPI involved, wheel builds from source via maturin:
uv add "dsrs-dspy @ git+https://github.com/krypticmouse/DSRs@main#subdirectory=crates/dspy-py"Why
ChatAdapterformatting on structured outputs (this adapter ran in production on a financial-workbook classification pipeline).[[ ## field ##]]marker whitespace variants. No silent fallback re-call — failures surface asAdapterParseError.Commits
fmt --checkwas red on main (one hunk intest_predict_lm_override.rs).True/False/Nonemap totrue/false/nullin the fixing parser (LLMs emit Pythonish JSON constantly; includes test).schema_builderuses internally.module-name = "dsrs_dspy._dsrs_dspy"so one wheel ships both the Python package and the native module.python-adapterjob builds the wheel and runs the Python tests against a real DSPy install; miri excludes dspy-py (can't interpret embedded CPython); README section.Testing
cargo test --all-features— full workspace green (73 test binaries), including dspy-py's 8 Rust tests: unit + property (random schema fuzzing with JSONish noise, description propagation, and a matrix driven by realpydantic.TypeAdapter(...).json_schema()output).crates/dspy-py/tests/python/— 5 end-to-end tests against released DSPy 3.2.1: render, noisy parse, pydantic default restoration, missing-field error, chat message formatting. No network, no LM calls.git+…#subdirectory=crates/dspy-py) from the committed state, then imported and exercised the adapter.cargo clippy -p dspy-py --all-targets --all-features— clean (remaining warnings are pre-existing in vendored crates).Notes for reviewers
cargo buildat the workspace root now compiles dspy-py, which needs apython3on PATH for pyo3's build script (present on GH runners and dev machines; if it ever annoys,default-memberscan gate it out of default builds).ChatAdapter,dspy.adapters.utils.parse_value); thepython-adapterCI job exists precisely to catch DSPy-side drift.🤖 Generated with Claude Code