Saving an optimized program¶
This example walks the full optimize-then-reuse cycle: build a question -> answer predictor, compile it into demonstrations with an optimizer, write that state to disk, then recreate the program and load the state back. It demonstrates that a program's learned state survives a round-trip through JSON.
Build the program¶
The example uses a single dynamic question -> answer predictor for brevity. Optimizers and ProgramPersistence
are generic over any program with OptimizableStructure evidence, including Predict and ChainOfThought programs.
Compile it¶
def compile(metric: Metric, student: DynamicPredict, trainset: Vector[Example])(using
RuntimeContext
)
: Either[DspyError, DynamicPredict] =
new BootstrapFewShot[DynamicPredict](BootstrapFewShotConfig(
metric = Some(metric),
maxBootstrappedDemos = DemoCount(4),
maxLabeledDemos = DemoCount(4),
maxRounds = RoundCount(5)
)).compile(student, trainset).map(_.bestProgram)
BootstrapFewShot runs the program over an LM against a trainset to collect demonstration traces, and returns the best program it found. The result is the same predictor with demos attached. This step needs OPENAI_API_KEY because it calls the LM. Bring your own Examples for the trainset.
Save the state¶
def save(program: DynamicPredict, path: String): Either[DspyError, Unit] =
ProgramPersistence.save(program, path)
ProgramPersistence.save writes each leaf's OptimizableParameters: instructions, demos, and module-level config. It
does not write signature field structure, module names, runtime bindings, tools, or program code.
Recreate and load¶
def load(fresh: DynamicPredict, path: String): Either[DspyError, DynamicPredict] =
ProgramPersistence.load(fresh, path)
load takes a freshly built program with the same optimizable structure and leaf order, then returns a new immutable program
with the saved instructions, demos, and config written into it. The fresh value keeps its signature structure,
name, runtime, output schema, bound LM, and tools. For the complete contract and ordinal-ID caveat, see
Saving and loading.
Running it¶
The runnable savingMain does the round trip with no model. It attaches two demos by hand in place of compile, saves to a temporary file, loads into a fresh program, and asserts that the loaded demos equal the saved demos.
Notes¶
The whole-program save form (Python's save_program=True, which serializes the program's architecture into a
directory) is out of scope. There is no code or pickle serialization, hence no .pkl variant and no
modules_to_serialize option. The former layout-bearing predictor-state format is unsupported; regenerate saved
artifacts with the current version.
Full source: Saving.scala