A Scala 3 library for building language model programs with signatures. Inputs and outputs are ordinary Scala types, so the compiler checks them for you.
dspy4s is a port of DSPy to Scala 3. It keeps DSPy's structure (signatures, modules, adapters, optimizers) and replaces the dynamic Python surface with a compile-time-checked Scala API.
Status: pre-release. The API is still moving and the artifacts are not yet published to Maven Central. For now, build from source.
A signature declares inputs and outputs. A program (here Predict)
runs it against a language model.
import dspy4s.programs.strategies.Predict
import dspy4s.signatures.Signature
// Inputs and outputs are named tuples, so `sentence` and `sentiment` are real
// fields. A typo is a compile error, and `_.output.sentiment` is a Boolean.
val classify = Predict(Signature.fromType[(sentence: String) => (sentiment: Boolean)])
classify.apply((sentence = "it's a charming and often affecting journey."))
.map(_.output.sentiment)
// Either[DspyError, Boolean]Swapping Predict for ChainOfThought prepends reasoning: String to
the output, with no signature changes.
- Compile-time field checks. Signatures are ordinary Scala types. A wrong
field name is a compile error, not a runtime lookup failure, and output access
(
_.output.sentiment) is checked by the compiler. - One codec spine. Field values flow through a single
DynamicValue.Recordintermediate (fromzio-blocks-schema) shared by adapters, programs, evaluation, and the program API. Decode failures surface at the call boundary asEither[DspyError, _], not via lazy field access. - Composable programs.
Predict,ChainOfThought,ReAct,CodeAct,ProgramOfThought,BestOfN, andRefineare plain values you compose like any other Scala code.RLM(Recursive Language Model) is an experimental program for reasoning over long contexts without placing them in the prompt: inputs become variables in a sandboxed REPL that the model explores with generated code. - Compiler-verified examples. Scala samples extracted into the docs site
from the
examplesmodule build under strict flags (-Werror,-Wunused:all). An extracted snippet that would not compile fails the build.
dspy4s is split into focused modules so you can depend only on what you need.
| Module | Artifact | What it gives you |
|---|---|---|
algebra |
dspy4s-algebra |
Categories, functors, monads, optics, and executable laws. |
core |
dspy4s-core |
Contract layer: Example, DynamicValue spine, runtime. |
signatures |
dspy4s-signatures |
Static Signature / Spec surface and Shape codecs. |
lm |
dspy4s-lm |
Provider-agnostic LM API and the OpenAI client. |
adapters |
dspy4s-adapters |
Chat / JSON / XML adapters and native function-calling. |
programs |
dspy4s-programs |
Predict, ChainOfThought, ReAct, refinement programs. |
evaluate |
dspy4s-evaluate |
Evaluate runner, metrics, LLM-as-judge. |
optimize |
dspy4s-optimize |
Bootstrap few-shot, COPRO, MIPROv2, KNN, ensembles. |
gepa |
dspy4s-gepa |
The reflective Genetic-Pareto prompt optimizer. |
streaming |
dspy4s-streaming |
Synchronous streaming of predictions. |
- Scala 3 (the build pins
3.8.4) - JDK 21 (the repo pins
openjdk-21.0.1in.tool-versions) - sbt 1.x
- An
OPENAI_API_KEYfor examples that make live LM calls
git clone https://github.com/jpablo/dspy4s.git
cd dspy4s
sbt compileRun the test suite:
sbt testFormat all Scala and sbt sources, or check formatting without changing files:
sbt fmt
sbt fmtCheckRun one of the bundled examples (reads OPENAI_API_KEY from the environment):
OPENAI_API_KEY=sk-... sbt "examples/runMain dspy4s.examples.learn.programming.main"The docs site is built with MkDocs Material and lives under site/.
It is published at https://jpablo.github.io/dspy4s/.
- Quickstart: write your first signature and run it end to end.
- Signatures: the full set of ways to declare inputs and outputs (inline, traits, enums, custom types).
- Architecture: the design choices, module graph, and the static and runtime-defined stacks.
To preview the docs locally:
cd site
uv run mkdocs servedspy4s follows the upstream DSPy decomposition (the data spine, composite
programs, adapter contracts, and optimizer pattern), so concepts carry over.
The main differences are in the surface: signatures are produced by macros and
typeclasses rather than Python metaclasses, inputs and outputs are checked by
the compiler, and errors are a structured DspyError ADT. The
docs/port/ directory tracks the per-symbol mapping and the
behavioral deltas.
dspy4s is released under the MIT License, the same license used by DSPy. The upstream Stanford Future Data Systems copyright notice is retained in the license file.