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
Proposal 0093 gives OA one model for "the provider reported no usage"
across both retrieval responses and both typed events: no usage yields
usage = null, never a fabricated record, zero, or estimate.
The response types and both observers were already correct. The gap was
OpenAIEmbeddingProvider, which recorded EmbeddingUsage(input_tokens=0)
when the usage block was absent or malformed. A zero asserts "zero
tokens billed", a claim the provider never made. It now surfaces
usage = null; a reported zero still yields a record carrying 0.
Reviewing that diff surfaced a second, unrelated defect. Proposal 0092
requires every capped embedding mapping to chunk-and-stitch and names
OpenAI's 2048-input cap, but the 0092 implementation adopted the shared
helper for TEI and Cohere only. An over-2048 embed call sent one
over-cap request the wire rejects, breaking the arbitrary-length input
contract. OpenAI now chunks at 2048. Conformance stayed green because
the spec ships no over-cap fixture for that mapping; tracked. The shared
helper now takes the response identity (id and model) from the first
chunk, so OpenAI keeps reporting the provider-returned model while the
wires that carry none fall back to the bound identifier.
A missing usage figure and a corrupt one were both swallowed silently,
so a gateway sending garbage looked identical to a healthy no-usage
call. The shared nonneg_int reader now logs a warning when a figure is
present but not a non-negative int, while an absent figure stays quiet.
nonneg_int lifts into _wire.py and is adopted by OpenAI, Cohere, and
Jina.
Un-defers observability fixtures 139 and 140. Fixture 143 stays
deferred behind the unimplemented section 11 embedding-metrics path,
not behind anything in 0093.
Copy file name to clipboardExpand all lines: conformance.toml
+6-6Lines changed: 6 additions & 6 deletions
Original file line number
Diff line number
Diff line change
@@ -887,15 +887,15 @@ note = "Retrieval-provider Cohere /v2/embed wire mapping (§8.4) -- the embed ha
887
887
[proposals."0092"]
888
888
status = "implemented"
889
889
since = "0.16.0"
890
-
note = "Retrieval-provider general §8 *Batch chunking* rule for the embedding side + fixture 038 (TEI /embed chunk-and-stitch by the construction chunk_size). The shared chunk_and_stitch_embed helper in _wire.py realizes the rule mapping-agnostically: split the inputs into consecutive <=cap slices preserving order, issue one request per slice via a per-mapping closure (every field other than the chunked input list identical across slices), concatenate the per-chunk vectors in input order (so §4's one-vector-per-input + input-order invariants hold across the whole call, enforced against the stitched result), combine the per-chunk usage record-aware (sum EmbeddingUsage.input_tokens when the provider reports usage, else usage = null -- never fabricated), take response_id from the first chunk, and set raw to the single response for a single-request call or the list of per-chunk responses in request order (proposal 0096). TeiEmbeddingProvider.embed() adopts it with cap = its construction chunk_size (TEI's max-client-batch-size, default 32): an over-cap embed call chunk-and-stitches (fixture 038: chunk_size 2 over 5 inputs -> 3 /embed requests sized 2/2/1 with identical per-call params), and since TEI /embed reports no usage and no id the stitched usage is null and response_id is null; the len(input) <= chunk_size path issues a single /embed request (the pre-0092 behavior preserved). CohereEmbeddingProvider migrates its existing 96-input chunk path (0091, fixture 037) onto the same helper (cap = 96, closure POSTs /v2/embed) with identical behavior. The per-chunk vector count is validated before stitching so a positional misalignment cannot pass on a compensating total. Conformance fixtures 037 (Cohere, unchanged) + 038 (TEI) pass via the general wire-capture harness; no retrieval fixtures remain deferred."
890
+
note = "Retrieval-provider general §8 *Batch chunking* rule for the embedding side + fixture 038 (TEI /embed chunk-and-stitch by the construction chunk_size). The shared chunk_and_stitch_embed helper in _wire.py realizes the rule mapping-agnostically: split the inputs into consecutive <=cap slices preserving order, issue one request per slice via a per-mapping closure (every field other than the chunked input list identical across slices), concatenate the per-chunk vectors in input order (so §4's one-vector-per-input + input-order invariants hold across the whole call, enforced against the stitched result), combine the per-chunk usage record-aware (sum EmbeddingUsage.input_tokens when the provider reports usage, else usage = null -- never fabricated), take response_id from the first chunk, and set raw to the single response for a single-request call or the list of per-chunk responses in request order (proposal 0096). TeiEmbeddingProvider.embed() adopts it with cap = its construction chunk_size (TEI's max-client-batch-size, default 32): an over-cap embed call chunk-and-stitches (fixture 038: chunk_size 2 over 5 inputs -> 3 /embed requests sized 2/2/1 with identical per-call params), and since TEI /embed reports no usage and no id the stitched usage is null and response_id is null; the len(input) <= chunk_size path issues a single /embed request (the pre-0092 behavior preserved). CohereEmbeddingProvider migrates its existing 96-input chunk path (0091, fixture 037) onto the same helper (cap = 96, closure POSTs /v2/embed) with identical behavior. OpenAIEmbeddingProvider adopts it at §8.3's fixed 2048-input cap (closure POSTs /v1/embeddings): an over-2048 embed call now chunk-and-stitches instead of sending one over-cap request the wire rejects. OpenAI's summed-token ceiling is NOT addressed by the count-based rule -- an over-token chunk still fails loud as provider_invalid_request. Jina is the cap-free mapping the rule exempts (server-side batching), so it does not chunk. The per-chunk vector count is validated before stitching so a positional misalignment cannot pass on a compensating total, and the response identity (response_id + response_model) is taken from the FIRST chunk -- the model falls back to the bound identifier for the wires that carry none (TEI's bare array, Cohere /v2/embed), while OpenAI reports the body's model per §4. Conformance fixtures 037 (Cohere, unchanged) + 038 (TEI) pass via the general wire-capture harness; no retrieval fixtures remain deferred. The spec ships NO §8.3 over-cap fixture, so the OpenAI chunking is covered by unit tests only (2049 inputs -> 2 requests sized 2048/1) -- flagged to spec as a fixture-set gap."
# EmbeddingResponse.usage / RerankResponse.usage become record|null and
894
894
# the embedding usage emission (OTel §5.5.8 gen_ai.usage.input_tokens,
895
-
# Langfuse §8.4.5 usageDetails.input) is conditional. Partial: the
896
-
#response-type widening ships; the no-usage observability fixtures
897
-
#139-143 stay deferred as the 0093-last PR.
895
+
# Langfuse §8.4.5 usageDetails.input) is conditional. One uniform model
896
+
#for "the provider reported nousage" across both responses and both
897
+
#typed events: no usage => usage = null, never fabricated.
898
898
[proposals."0093"]
899
-
status = "partial"
899
+
status = "implemented"
900
900
since = "0.16.0"
901
-
note = "The response-type nullable widening ships: RerankResponse.usage is record | null from the start (0060a), and EmbeddingResponse.usage is widened to EmbeddingUsage | null here with 0077 (the OpenAIEmbeddingProvider still builds an EmbeddingUsage record; the TeiEmbeddingProvider surfaces usage = null since TEI /embed returns no usage object, never fabricated -- §4). Both bundled observers already emit the embedding usage conditionally (OTel §5.5.8 gen_ai.usage.input_tokens guarded by `event.usage is not None`; Langfuse §8.4.5 usageDetails.input built only when event.usage is not None), landed with the embedding observability in 0059b, so a null-usage EmbeddingEvent renders without a usage attribute rather than crashing. partial because the no-usage OBSERVABILITY fixtures (139 / 140 / 143, asserting the conditional-emission contract end-to-end) stay deferred as the 0093-last PR."
901
+
note = "One uniform model across all four surfaces: a provider that reports no usage yields usage = null, never a fabricated record / zero / client-side estimate (§4 / §6). The response types were already widened (RerankResponse.usage is record | null from 0060a; EmbeddingResponse.usage from 0077), and both bundled observers already emitted the usage conditionally (OTel §5.5.8 gen_ai.usage.input_tokens guarded by `event.usage is not None`; Langfuse §8.4.5 usageDetails.input built only when a record is present), landed with the embedding observability in 0059b. Completed here: the OpenAIEmbeddingProvider no longer FABRICATES an EmbeddingUsage(input_tokens=0) when the OpenAI usage block is absent or malformed -- it surfaces usage = null (a *reported* zero still yields a record carrying 0; a malformed figure reads as not-reported rather than failing the call, since the vectors are sound and usage is secondary accounting -- the posture the Cohere and Jina mappings already took, and deliberately unlike the §6 document echo of 0097, where the corrupt field IS the payload). The other three embedding mappings were already conformant: TEI /embed and /rerank surface usage = null (§8.1 -- neither endpoint returns a usage object), and Cohere / Jina return null when the body reports no figure. The shared nonneg_int helper (one figure out of a vendor usage block: a non-negative int is reported, anything else is not) lifts into _wire.py and is adopted by all three of Cohere, Jina, and OpenAI. Conformance: the no-usage rendering fixtures 139 (OTel embedding span omits gen_ai.usage.input_tokens) + 140 (Langfuse embedding usageDetails carries no `input` key) are un-deferred and pass, joining the rerank pair 141 / 142 (wired with 0060b); retrieval fixtures 017 / 038 already assert usage: null for TEI /embed. Fixture 143 (embedding no-usage token METRIC not observed) stays deferred, and NOT for a 0093 reason: the OTelObserver records §11 GenAI metrics from the LLM per-attempt event only, so the embedding metric path does not exist yet -- 143 defers behind the same gate as 089, whose implementation (proposal 0067) realizes it. 0093 itself changes nothing in §11 or graph-engine §6 (both were already null-usage-aware), so no part of THIS proposal is left unimplemented by that carve-out; cf. 0060, marked implemented with the sibling metric fixture 109 deferred on the same grounds."
0 commit comments