dspy.Module per agent. Each lens wraps a signature. Where the app’s constraints matter (3–5 children, valid taxonomy types, non-empty labels), the lens is wrapped in dspy.Refine — the 3.x replacement for the removed Assert / Suggest. Refine retries with auto-generated feedback (hint_ field) until a deterministic reward function passes.
The lens set
Package:qlaw/lenses/.
Deterministic rewards for Refine
Reward functions live in qlaw/lenses/_validators.py and return 0.0 or 1.0. They enforce the app’s structural contract — item count, non-empty labels, allowed types — without an LM in the loop.
Example lens: SemanticInterpreter
Example lens: Decomposer (Refine-validated)
Signatures
Every lens ships with a typeddspy.Signature. Signatures live in qlaw/signatures.py and use pydantic fields for the output contract. The docstring is the instructions; the input and output fields are the schema. Output DTOs (SubNode, Concept, Trajectory, Risk, Intel) pin Literal[NodeType.…] so coercion enforces the type discipline at the boundary.
Why lenses are optimizable
Because each lens is adspy.Module with a Signature, it can be:
- Compiled with
dspy.MIPROv2ordspy.BootstrapFewShotfrom a per-lens trainset. - Evaluated with
dspy.Evaluateon the matching devset inqlaw/datasets.py. - Scored with the six metrics in
qlaw/metrics.py: taxonomy adherence, node validity, conciseness, novelty, coverage, and grounding. - Loaded back into the engine after compile, replacing the zero-shot module in-place.