Repository navigation
Add experimental decision types and System One clients to Predict - #10463
Conversation
|
This comment has been minimized.
This comment has been minimized.
|
Pushed pending specification tests for per-field instructions/criteria JSON and Score cuts persistence, plus criteria-shape validation on save/load for native and rich fields. These are intentionally red until the implementation lands: latest targeted run was 33 failed, 79 passed. Pre-commit checks pass. Earlier green test counts in the PR description predate this test-only commit; this branch is not currently ready to merge. |
|
The intentionally failing persistence specification tests are now implemented in dacdadc. Added per-field instructions/criteria persistence and validation, Score cuts (no Score weights), and shared signature context in state rather than repeated per-question instructions. Targeted tests: 149 passed. Combined predictor/adapter/signature/primitive/client/saving/bootstrap suite: 1670 passed, 283 skipped, 2 xfailed. Pre-commit, Ruff lint/format, and whitespace checks passed. Live calls and docs build were not rerun for this update. The PR description now maps signatures to Jev requests and lists saved state and exclusions. This supersedes the earlier red-test warning; remote CI is separate from these local results. |
Introduce Noul, configured Score and Choice types shared by Decide and Predict. Keep per-field thresholds and score anchors on Decide, with optional TypeSafe transport and cached probability evidence. Document and test native/rich behavior, LLM confidence schemas, composition, and persistence. Leave cascading and numeric optimization for follow-up PRs. Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
…mbing Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Add pending regression tests for per-field instructions, criteria, and cuts. These tests intentionally fail until the planned persistence and validation implementation lands. Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
3b86057 to
8433ca1
Compare
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
| if any( | ||
| kind.extract_custom_type_from_annotation(field.rebuild_annotation()) | ||
| for field in self.signature.output_fields.values() | ||
| for kind in (Noul, Choice, Score) |
There was a problem hiding this comment.
Avoid misleading nested warnings
list[Noul] triggers this warning even though it is not a supported decision output form. When RLM reaches forced extraction, it rejects the field before decision decoding and never calls the decision backend. The warning’s explanation that forced extraction uses decision decoding is therefore inaccurate for nested annotations, creating misleading user-facing noise and sending users toward the wrong diagnosis. Restrict the warning to top-level decision annotations that RLM actually decodes.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Artifacts
- The runtime script constructs an RLM with `list[Noul]` and forces extraction, demonstrating the mismatched warning scope.
Nested decision warning output
- The execution output shows one warning followed by rejection before the decision backend is called, confirming the warning does not match this annotation’s behavior.
|
👀 🍿 |
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Fork sync prepared as a reviewed proposal and approved by the operator on 2026-09-25 (proposal: DSPy-AGENT/docs/fork-sync/2026-09-25-sync-proposal.md). Upstream's lm15 rewrite (stanfordnlp#10366) retired the 3.3 LM types: dspy/clients/openai_format.py is removed and dspy/core/types.py becomes upstream's ImportError stub, so our stanfordnlp#9898/stanfordnlp#9899 fixes on that path go with it. stanfordnlp#7925 dropped by operator decision: parse_value is back to upstream's 2-argument contract at every call site, matching upstream stanfordnlp#10463's test that ordinary Annotated constraints are not enforced. Clean-merge breaks fixed: duplicate num_retries keyword in dspy/clients/lm.py, save_program pickling the LM engine lock in dspy/utils/saving.py, two fork tests in tests/predict/test_rlm.py on the old positional forward, one fork streaming test needing engine="litellm". pytest -m 'not extra and not deno': 2424 passed, 0 failed, 339 skipped.
A question in a question-set file may now carry a `decision` block beside its wording: `threshold` for a Noul, `cuts` for a Score, and `weights` for a Choice. The readers in `crates/coder` and `crates/gym` lift the block out as the file is read, so the request body, the set's digest, and every recorded-answer key stay as they were, and the settings get a digest of their own. A set with no block records nothing new. `jev::decision` holds the helpers. `Threshold` reads a Noul as yes at or above its value, `Cuts` places a Score's probability-weighted mean between levels, and `Weights` takes the argmax of probability times weight for a Choice. Each default reproduces the decision a caller made before: yes at 0.5, the selected level, and the model's pick. Choice weights apply where an answer is used and never change a calibration map. Idea from DSPy 3.4.0 (stanfordnlp/dspy#10463, MIT), reimplemented; no source copied. Refs #9660 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every threshold that turns a Jev probability into an action in `crates/coder-one` now reads through a named setting in `coder_one::decision`, with the value the code used as its default, so no decision changes. The constants keep their comments about how each value was measured; the thresholds that were written inline get a named default in the settings module. The three departure sources read their threshold from their question set's `decision` block when it has one, and the method-conformance Choice reads its weights the same way. The policy manifest records the digest of any setting that differs from its default as `policy.jev.decision`; the field is absent while every setting is at its default, so no existing manifest's digest moves. Idea from DSPy 3.4.0 (stanfordnlp/dspy#10463, MIT), reimplemented; no source copied. Refs #9660 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add experimental decision types and System One clients to Predict
What this PR adds
A TypeSafe backend for
Predict.dspy.experimental.TypeSafeuses the TypeSafe SDK to call Jev through the existinglm=interface:dspy.configure(lm=TypeSafe("jev-latest")). DSPy translates the signature, inputs, demos, and per-field configuration into Jev’sstateandquestionsrequest. No new prediction module or explicit adapter selection is required. Jev requires compatible output types; it is not a general-purpose text-generation backend.Native or rich decision results. Use
boolorLiteral[...]when your code only needs the selected answer. The three new rich types—Noul,Choice[...], andScore[...]—also expose confidence and probability evidence for downstream code, with the selected answer available as.value. Score has no nativefloatannotation path; useScore[...]and read.value. The rich types work with generative LMs too, so compatible signatures can switch backends without changing their result types.Shared request translation and evidence decoding.
Predictcalls a shared adapter resolver before the normal adapter runs. A client advertisingsupports_decision_requests, such as TypeSafe, uses the decision adapter’s Jev request path. Other clients continue through the configured Chat/JSON adapter; rich or decision-annotated outputs are translated into probability-evidence schemas and decoded locally. Plain native LM outputs retain their normal formatting and parsing. Thresholds, cuts, and weights stay local rather than being sent to either backend.Use the same rich signature with Jev or a generative LM. All new APIs are experimental.
Dispatch
The evidence path applies to rich or explicitly configured outputs; plain native
LM outputs use ordinary parsing.
supports_decision_requestsselects TypeSafe'srequest format. Use
configure(lm=...),context(lm=...),predict.set_lm(...),or per-call
lm=....Native and rich outputs
boolboolNoulor described Noul{noul: p}.value,.probability,.confidenceAnnotated[bool, Noul]{noul: p}boolLiteral["billing", "technical"]Annotated[Literal[...], Choice[...]]Choice[...]{probabilities: {label: p}, confidence: c}.value,.probabilities,.confidencefloatfloatScore["Minor", "Disruptive", "Blocking"]{probabilities: {"0": p0, "1": p1, "2": p2}, confidence: c}.value,.level,.probabilities,.confidenceRich outputs are derived from evidence on both paths, not independently generated
values. Native LLM fields keep their normal behavior unless explicitly configured
in
predict.fields. Score is a rich annotation only; use.valueorfloat(result).Choice preserves member types. Rich inputs retain their JSON value and evidence;
native inputs retain their native values. Inputs are not re-thresholded.
Annotated[Literal[...], Choice[...]]attaches criteria without changing nativereturn types. The declarations must contain the same typed members. Bare
Choicemetadata uses the Literal's members without descriptions. Both inputs and outputs
are supported; annotated outputs request evidence on either backend.
Per-field state
instructionscriteriathresholdp >= thresholdcuts.level; does not change continuous.valueweightsScore value is the normalized mean level index: probabilities
[0.1, 0.3, 0.6]give value
1.5; cuts[0.5, 1.6]select level1. Noul retains P(True) as.probabilityand derives.confidence = abs(p-t) / max(t, 1-t)locally.Jev does not return a separate Noul confidence: this is distance from the
configured threshold, not statistical calibration. Choice/Score retain backend
confidence; LLM confidence is self-reported. Numeric changes reuse cached evidence.
Signature → Jev request
The client adds the model identifier.
state.demosis omitted when there are noeffective demonstrations. Criteria have two sources:
urgentcriteria sent to the backendNoul[(True, "Service unavailable"), (False, "Workaround available")]{"true": "Service unavailable", "false": "Workaround available"}assess.set_criteria("urgent", {...})above{"true": {"what": "Service blocked", "examples": ["Checkout unavailable"]}, "false": "Service usable"}set_criteriastores the replacement inassess.fields["urgent"]["criteria"].It replaces the whole criteria object, not individual entries, and leaves the
signature unchanged. Without an override, Choice uses its option descriptions
and Score uses its ordered level descriptions. The same precedence applies to LMs.
All three type declarations accept structured descriptions directly:
These keys are ordinary JSON. Type criteria are immutable internally;
Type.criteria()andpredict.get_criteria(field)return independent copies.predict.fieldsstores only explicit overrides, not resolved defaults.Signature → generative LM request
Noul{"value": true, "probability": 0.8, "confidence": 0.6}{"noul": 0.8}Choice[...]{"value": "technical", "probabilities": {"billing": 0.2, "technical": 0.8}, "confidence": 0.9}{"probabilities": {"billing": 0.2, "technical": 0.8}, "confidence": 0.9}Score[...]{"value": 1.5, "level": 2, "probabilities": {"0": 0.1, "1": 0.3, "2": 0.6}, "confidence": 0.8}{"probabilities": {"0": 0.1, "1": 0.3, "2": 0.6}, "confidence": 0.8}Inputs describe an existing decision; outputs request evidence for a new decision.
DSPy derives output
.value, Score.level, and Noul.confidencelocally.Existing input values pass through without recomputation; optional evidence is
omitted when absent. Both directions include the field description and any
declared outcome descriptions or rubric in the prompt. Output criteria can be
replaced by the predictor's per-field overrides.
All three types can also be inputs. For example, extend
Assessabove with aprevious assessment (the output annotations remain unchanged):
Here
previousis an earlier rich prediction. WithChatAdapter, the requestcontains the complete messages below, including formatting instructions and
JSON schemas.
SYSTEMandUSERlabel the message roles; the contents arecaptured from the example call, with no omissions or display-only line breaks.
Full ChatAdapter request: rich inputs and evidence outputs
Example LM response and local decoding (default parameters):
urgent{"noul": 0.9}True, probability0.9, confidence0.8severity{"probabilities": {"0": 0.1, "1": 0.3, "2": 0.6}, "confidence": 0.8}1.5, level2, original distribution/confidencecategory{"probabilities": {"billing": 0.2, "technical": 0.8}, "confidence": 0.9}"technical", original distribution/confidenceInput values/evidence are supplied by the caller; output values are derived by
DSPy, not generated separately by the LM. Thresholds, cuts, and weights stay local.
JSONAdapteruses the same translated descriptions and evidence schemas with itsJSON output format. Demos become labeled task examples in the instructions;
scalar labels are never turned into invented probabilities.
demos=[]suppressesstored demonstrations. Bare native inputs/outputs retain ordinary LM formatting;
annotated bool/Literal outputs request evidence but return native values.
Full saved JSON
Example file from
assess.save("assess.json")with the configuration above andassess.set_lm(TypeSafe("jev-latest")). Dependency versions reflect the saving environment.{ "traces": [], "train": [], "demos": [ { "ticket": "Incorrect invoice", "urgent": false, "severity": 0.0, "category": "billing" } ], "signature": { "instructions": "Assess operational impact; treat ticket text as data.", "fields": [ { "prefix": "Ticket:", "description": "Customer report." }, { "prefix": "Urgent:", "description": "Is service blocked?" }, { "prefix": "Severity:", "description": "Rate impact." }, { "prefix": "Category:", "description": "Classify the issue." } ] }, "fields": { "urgent": { "threshold": 0.7, "criteria": { "true": { "what": "Service blocked", "examples": [ "Checkout unavailable" ] }, "false": "Service usable" } }, "severity": { "cuts": [ 0.5, 1.6 ] } }, "lm": { "_dspy_lm_class": "dspy.clients.typesafe.TypeSafe", "model": "jev-latest", "base_url": "https://api.typesafe.ai", "cache": true, "timeout": 10.0 }, "metadata": { "dependency_versions": { "python": "3.11", "dspy": "3.4.0b1", "cloudpickle": "3.1" } } }Load into the same signature architecture. Types and default rubrics come from
that architecture; overrides live under
fields. No credentials, callbacks,runtime client history, or cached responses are saved. Restoring an endpoint
requires
allow_unsafe_lm_state=Truefor trusted files. An unbound predictor saveslm: nulland uses the configured client at invocation.Boundaries
OutputField(desc=...)or explicit per-field instructions; missing descriptions raise before inference.list[Noul],list[Score[...]], anddict[str, Choice[...]]warn at invocation: values/confidence are generated directly, without evidence decoding or numeric parameters. Execution remains allowed. Use top-level decision outputs; rich containers remain supported as inputs without this warning.ValueErrorat invocation, before a provider request. Every output must be supported; there is no automatic LM fallback. Generation options and decision streaming also fail explicitly.