You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
With system-one-adapter 0.2.1 (also 0.2.0) in llm_answer_mode="probabilities", a model
output whose Choice probabilities are all 0 comes back as a normal, successful answer: choice
is the first criterion, confidence is 0.0, and with normalize_probabilities=True every option
reads 1/n. Nothing is raised and no corrective retry is spent. The Score twin returns the middle
level. The only trace is response.debug["probability_errors"][<question id>] == 1.0.
This happens on real inputs. Claude Haiku 4.5 through provider="anthropic" with structured_outputs=True returns all-zero maps. One raw content[0].text (stop reason end_turn, 10 options):
{"answers":{"intent":{"activate my card":0,"age limit":0,"apple pay or google pay":0,"atm support":0,"automatic top up":0,"balance not updated after bank transfer":0,"balance not updated after cheque or cash deposit":0,"beneficiary not allowed":0,"cancel transfer":0,"card about to expire":0}}}
Measured with one 10-option Choice question over Banking77 test messages: 58 of 70 calls came back
all-zero on 14 messages that had come back uniform in an earlier run (5 calls each), against 1 of
70 on 14 other messages with the same labels. All 59 answers were the first option at confidence 0.
Repro
No key and no model call. bash, macOS arm64, Python 3.14 (resolved by uv 0.9):
model returned all zeros normalize=True calls=1 choice='refund' confidence=0.0 probabilities={'refund': 0.333, 'shipping': 0.333, 'billing': 0.333} probability_errors={'topic': 1.0}
model returned all zeros normalize=False calls=1 choice='refund' confidence=0.0 probabilities={'refund': 0.0, 'shipping': 0.0, 'billing': 0.0} probability_errors={'topic': 1.0}
model returned 1/3 each normalize=True calls=1 choice='refund' confidence=0.0 probabilities={'refund': 0.333, 'shipping': 0.333, 'billing': 0.333} probability_errors={}
Expected vs observed
- expected: a model output that assigns no probability to any option is not returned as an- answer (it is surfaced as an error, or counts against n_retry_malformed_structure)+ observed: choice='refund' (option #1), confidence=0.0, calls=1, no exception, in both+ normalize_probabilities modes; answers identical to a model-asserted uniform
Workarounds tried
normalize_probabilities=False: still choice='refund', confidence=0.0, no exception
(repro line 2); only the probabilities stay 0.0.
n_retry_malformed_structure=2: one provider call (calls=1). Each value is inside [0, 1],
so the output validates and no retry happens.
Reading response.answers only: identical to a model-asserted uniform (repro lines 1 and 3).
Only debug["probability_errors"] or debug["original_probabilities"] tell them apart.
Score has the same shape: an all-zero map over 3 levels returns score=1.0 (the middle level)
at confidence=0.0 with normalize_probabilitiesTrue or False, because the score is
always computed from a rescaled copy.
Root cause
All citations at commit e1d4cc9 (tag v0.2.1), src/system_one_adapter/:
_utils/probability_normalization.py:100-106: the model's values are read as-is; a zero total
gives error = 1.0, and with normalization on, rescale_probabilities is called.
_utils/probability_normalization.py:70-72: rescale_probabilities has a zero-total branch that
returns 1/len(probabilities) for every option. Its docstring (:58) names this fallback; the
README describes the option as "Rescale invalid LLM probability distributions to sum to 1",
and a zero vector has no scale to preserve.
_client.py:159: max(answers, key=probabilities.__getitem__) returns the first option on a
tie, so an all-zero or uniform vector always answers option Review fixes and cassette-backed provider tests #1.
_utils/confidence_metrics.py:22-24 with :27-32: confidence normalizes a zero total to uniform
and reports 0.0.
_client.py:141-142: the Score path always rescales (rescale_probabilities) before taking the
expected value, so the substituted uniform becomes the middle level regardless of the option.
_schema.py:25: Probability = Annotated[float, Field(ge=0, le=1)] bounds each value but not
the total, so _client.py:222-231 (_decode_or_correct) accepts the output and the corrective
retry never triggers.
I see why the uniform fallback exists: it keeps the arithmetic total-free. The trouble is where
it lands: "the model gave no option any probability" becomes "the model picked option #1, weakly".
Independent confirmation
Live numbers: one model and provider, macOS arm64. The repro is provider-free, on the published
wheel.
Why this matters
Choice is the routing primitive. A caller reading choice routes every such message to whichever
option is listed first, and accuracy computed from choice counts it as that option. Score reports
a mid-scale value the model never gave. A confidence gate does catch these rows, but cannot tell
them from a model-asserted uniform without reading debug.
This is the same shape as #38, where an incomplete provider output was returned as a successful
evaluation and v0.2.1 now rejects it.
Constructive ask
Make the zero-total case distinguishable outside debug. One option that reuses existing
machinery: treat a probability map with no mass as malformed output, so it raises TypeSafeAPIResponseValidationError by default and a caller who sets n_retry_malformed_structure gets a corrective retry. You may prefer a different shape; the
ask is only that choice not name an option the model gave zero probability.
Out of scope
Rescaling of non-zero invalid totals (e.g. 0.6 to 1.0); that behaviour looks right.
The model's own tendency to return all zeros; that is provider behaviour and outside this repo.
llm_answer_mode="discrete"; not exercised here.
Duplicate check
Searched this repo with gh issue list --repo typesafe-ai/system-one-adapter-python --state all --search for uniform, zero probabilities, normalize, all zero, rescale, confidence 0, and probability sum, and read the full issue and PR lists (8 issues and
32 PRs as of 2026-09-24). No duplicate found; the nearest is #38 (truncated output accepted as success), a
different mechanism in the provider layer.
What happened
With
system-one-adapter0.2.1 (also 0.2.0) inllm_answer_mode="probabilities", a modeloutput whose Choice probabilities are all
0comes back as a normal, successful answer:choiceis the first criterion,
confidenceis0.0, and withnormalize_probabilities=Trueevery optionreads
1/n. Nothing is raised and no corrective retry is spent. The Score twin returns the middlelevel. The only trace is
response.debug["probability_errors"][<question id>] == 1.0.This happens on real inputs. Claude Haiku 4.5 through
provider="anthropic"withstructured_outputs=Truereturns all-zero maps. One rawcontent[0].text(stop reasonend_turn, 10 options):{"answers":{"intent":{"activate my card":0,"age limit":0,"apple pay or google pay":0,"atm support":0,"automatic top up":0,"balance not updated after bank transfer":0,"balance not updated after cheque or cash deposit":0,"beneficiary not allowed":0,"cancel transfer":0,"card about to expire":0}}}Measured with one 10-option Choice question over Banking77 test messages: 58 of 70 calls came back
all-zero on 14 messages that had come back uniform in an earlier run (5 calls each), against 1 of
70 on 14 other messages with the same labels. All 59 answers were the first option at confidence 0.
Repro
No key and no model call.
bash, macOS arm64, Python 3.14 (resolved byuv0.9):Output (
typesafe-sdk0.7.1 resolved):Expected vs observed
Workarounds tried
normalize_probabilities=False: stillchoice='refund',confidence=0.0, no exception(repro line 2); only the probabilities stay
0.0.n_retry_malformed_structure=2: one provider call (calls=1). Each value is inside[0, 1],so the output validates and no retry happens.
response.answersonly: identical to a model-asserted uniform (repro lines 1 and 3).Only
debug["probability_errors"]ordebug["original_probabilities"]tell them apart.score=1.0(the middle level)at
confidence=0.0withnormalize_probabilitiesTrueorFalse, because the score isalways computed from a rescaled copy.
Root cause
All citations at commit
e1d4cc9(tag v0.2.1),src/system_one_adapter/:_utils/probability_normalization.py:100-106: the model's values are read as-is; a zero totalgives
error = 1.0, and with normalization on,rescale_probabilitiesis called._utils/probability_normalization.py:70-72:rescale_probabilitieshas a zero-total branch thatreturns
1/len(probabilities)for every option. Its docstring (:58) names this fallback; theREADME describes the option as "Rescale invalid LLM probability distributions to sum to 1",
and a zero vector has no scale to preserve.
_client.py:159:max(answers, key=probabilities.__getitem__)returns the first option on atie, so an all-zero or uniform vector always answers option Review fixes and cassette-backed provider tests #1.
_utils/confidence_metrics.py:22-24with:27-32: confidence normalizes a zero total to uniformand reports
0.0._client.py:141-142: the Score path always rescales (rescale_probabilities) before taking theexpected value, so the substituted uniform becomes the middle level regardless of the option.
_schema.py:25:Probability = Annotated[float, Field(ge=0, le=1)]bounds each value but notthe total, so
_client.py:222-231(_decode_or_correct) accepts the output and the correctiveretry never triggers.
I see why the uniform fallback exists: it keeps the arithmetic total-free. The trouble is where
it lands: "the model gave no option any probability" becomes "the model picked option #1, weakly".
Independent confirmation
Live numbers: one model and provider, macOS arm64. The repro is provider-free, on the published
wheel.
Why this matters
Choice is the routing primitive. A caller reading
choiceroutes every such message to whicheveroption is listed first, and accuracy computed from
choicecounts it as that option. Score reportsa mid-scale value the model never gave. A
confidencegate does catch these rows, but cannot tellthem from a model-asserted uniform without reading
debug.This is the same shape as #38, where an incomplete provider output was returned as a successful
evaluation and v0.2.1 now rejects it.
Constructive ask
Make the zero-total case distinguishable outside
debug. One option that reuses existingmachinery: treat a probability map with no mass as malformed output, so it raises
TypeSafeAPIResponseValidationErrorby default and a caller who setsn_retry_malformed_structuregets a corrective retry. You may prefer a different shape; theask is only that
choicenot name an option the model gave zero probability.Out of scope
llm_answer_mode="discrete"; not exercised here.Duplicate check
Searched this repo with
gh issue list --repo typesafe-ai/system-one-adapter-python --state all --searchforuniform,zero probabilities,normalize,all zero,rescale,confidence 0, andprobability sum, and read the full issue and PR lists (8 issues and32 PRs as of 2026-09-24). No duplicate found; the nearest is #38 (truncated output accepted as success), a
different mechanism in the provider layer.