Skip to content

fix(arize): _set_usage_outputs handles raw OpenAI Pydantic CompletionUsage - #26506

Merged
krrish-berri-2 merged 5 commits into
BerriAI:litellm-oss-staging-04-25-2026from
alvinttang:fix/arize-langfuse-otel-pydantic-usage
Apr 25, 2026
Merged

fix(arize): _set_usage_outputs handles raw OpenAI Pydantic CompletionUsage#26506
krrish-berri-2 merged 5 commits into
BerriAI:litellm-oss-staging-04-25-2026from
alvinttang:fix/arize-langfuse-otel-pydantic-usage

Conversation

@alvinttang

Copy link
Copy Markdown
Contributor

Summary

Fixes #13672 (252 days old, 18 comments). _set_usage_outputs in litellm/integrations/arize/_utils.py calls usage.get("total_tokens") and usage.get("output_tokens_details", {}).get("reasoning_tokens"). When the logger is invoked via langfuse_otel (or arize on a Chat Completions response wrapped as raw OpenAI Pydantic), usage is openai.types.completion_usage.CompletionUsage — a Pydantic v2 model with no .get method — so the call raises AttributeError. Same for nested CompletionTokensDetails / OutputTokensDetails.

Latent secondary bug: reasoning_tokens for Chat Completions live in completion_tokens_details, not output_tokens_details, so they were silently dropped on that path.

Fix

_safe_get(obj, key, default) helper that prefers dict-style .get when callable and falls back to getattr otherwise — uniform for dicts, litellm Usage, and raw OpenAI Pydantic models. Used for total / completion / prompt / output tokens. For reasoning tokens, tries completion_tokens_details (Chat Completions) first, then output_tokens_details (Responses API). +42 LOC.

Test

tests/test_litellm/integrations/arize/test_arize_utils.py:

  • test_set_usage_outputs_pydantic_completion_usage — Chat Completions API path with raw CompletionUsage + CompletionTokensDetails.
  • test_set_usage_outputs_pydantic_response_api_usage — Responses API path with usage object lacking .get.

Both fail on main with AttributeError, pass after the fix. Wider pytest of arize + langfuse integrations: 52/52 (2 pre-existing proxy-import failures unrelated). ruff clean.

Risk notes

  • Backward-compatible: _safe_get returns identical values for dicts and litellm Usage (which has .get); only differs on Pydantic-without-.get, where it now succeeds instead of crashing.
  • The completion_tokens_details lookup change is additive — adds reasoning_tokens for Chat Completions that were previously silently dropped; does not regress Responses API.
  • Different subsystem from existing user PRs.

Refs #13672

yuneng-berri and others added 4 commits April 23, 2026 17:55
* feat(openai): day-0 support for GPT-5.5 and GPT-5.5 Pro

Add pricing + capability entries for the new GPT-5.5 family launched by
OpenAI on 2026-04-24:

- gpt-5.5 / gpt-5.5-2026-04-23 (chat): $5/$30/$0.50 per 1M
  input/output/cached input
- gpt-5.5-pro / gpt-5.5-pro-2026-04-23 (responses-only): $60/$360/$6
  per 1M input/output/cached input

Other fees (long-context >272k, flex, batches, priority, cache
discounts) follow the same ratios as GPT-5.4, with context window
retained at 1.05M input / 128K output.

No transformation / classifier code changes are required:
OpenAIGPT5Config.is_model_gpt_5_4_plus_model() already matches 5.5+ via
numeric version parsing, and model registration is driven from the
JSON. The existing responses-API bridge for tools + reasoning_effort
(litellm/main.py:970) already covers gpt-5.5-pro.

Tests:
- GPT5_MODELS regression list now covers gpt-5.5-pro and dated variants
- New test_generic_cost_per_token_gpt55_pro cost-calc test
- Updated test_generic_cost_per_token_gpt55 for long-context fields

* fix(openai): mirror reasoning_effort flags onto gpt-5.5 dated variants

gpt-5.5-2026-04-23 and gpt-5.5-pro-2026-04-23 were missing the
supports_none_reasoning_effort, supports_xhigh_reasoning_effort, and
supports_minimal_reasoning_effort flags that their non-dated
counterparts define. Reasoning-effort routing in OpenAIGPT5Config is
fully capability-driven from these JSON flags — since an absent flag
is treated as False for opt-in levels (xhigh), users pinning to a
dated snapshot would silently lose xhigh support and diverge from the
base alias on logprobs + flexible temperature handling.

Copy the flags onto both dated variants so every dated snapshot
inherits the base model's reasoning-effort capability profile.

Adds a parametrized regression test that asserts
supports_{none,minimal,xhigh}_reasoning_effort parity between each
dated variant and its non-dated counterpart, preventing future drift
when new snapshots are added.
…s) (BerriAI#26361)

* feat(azure): add azure/gpt-5.5 + azure/gpt-5.5-pro entries (+ dated variants)

Azure variants of OpenAI's GPT-5.5 family. Microsoft has not yet
shipped GPT-5.5 on Azure OpenAI (latest GA on the Foundry models page
is GPT-5.4 as of 2026-04-24), but adding the entries day-0 mirrors the
established precedent for azure/gpt-5.4* (which were in the cost map
before the Azure rollout) so cost tracking and capability flags work
the moment customers deploy.

Schema follows the existing azure/gpt-5.4* shape:
- Same base/long-context pricing as openai/gpt-5.5*: $5/$30 chat,
  $60/$360 pro per 1M, with priority tier 2x base
- Azure variants drop the flex/batches keys (Azure has no flex tier)
  but keep priority pricing, matching gpt-5.4* precedent
- mode=chat for the thinking model, mode=responses for pro

reasoning_effort capability flags mirror the OpenAI variants exactly
since Azure proxies the same API contract: minimal rejection on both
chat and pro, low/none rejection on pro. Once BerriAI#26456 (which sets
supports_low_reasoning_effort + minimal=false on openai/gpt-5.5*)
lands, OpenAI and Azure flag profiles align.

Tests pin entry presence + pricing for all four Azure variants and
verify the live-API-derived reasoning_effort flags.

* test: register supports_low_reasoning_effort in cost-map JSON schema

azure/gpt-5.5-pro and azure/gpt-5.5-pro-2026-04-23 added in this branch
carry supports_low_reasoning_effort=false. The strict
'additionalProperties: false' schema in
test_aaamodel_prices_and_context_window_json_is_valid rejected the new
key. Register it alongside the other supports_*_reasoning_effort
entries.

Note: the runtime side of this flag (code that reads it) lands in
BerriAI#26456. Until that PR merges the flag is inert for both Azure and
OpenAI pro entries, but having the schema accept it lets cost-map
tests pass on either merge order.
`_set_usage_outputs` called `usage.get(...)` and
`usage.get('output_tokens_details', {}).get('reasoning_tokens')`. These
crash with `AttributeError: 'CompletionUsage' object has no attribute
'get'` when `usage` (or the nested token-details object) is a raw OpenAI
Pydantic model rather than a dict / litellm `Usage` wrapper. Reproduces
on the langfuse_otel + arize Responses API logging paths.

Fixes BerriAI#13672.

Changes:
- Add `_safe_get(obj, key, default)` that prefers dict-style `.get` when
  available and otherwise falls back to `getattr`. Works uniformly for
  dicts, litellm's `Usage`, and plain Pydantic models like
  `openai.types.completion_usage.CompletionUsage` /
  `CompletionTokensDetails` / `OutputTokensDetails`.
- Use `_safe_get` for total / completion / prompt / output tokens.
- Look for reasoning tokens in `completion_tokens_details` (Chat
  Completions API) before falling back to `output_tokens_details`
  (Responses API). Previously reasoning tokens from the Chat Completions
  API were silently dropped.

Tests:
- `test_set_usage_outputs_pydantic_completion_usage` — covers the chat
  completions path with raw `CompletionUsage` + `CompletionTokensDetails`.
- `test_set_usage_outputs_pydantic_response_api_usage` — covers the
  Responses API path with a Pydantic usage object lacking `.get`.

Both tests fail on main before this commit and pass after.
@CLAassistant

CLAassistant commented Apr 25, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
2 out of 4 committers have signed the CLA.

✅ yuneng-berri
✅ mateo-berri
❌ alvinttang
❌ krrish-berri-2


alvinttang seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@veria-ai

veria-ai Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Low: No security issues

This PR adds a _safe_get helper to handle Pydantic models without .get() in the Arize/Langfuse OTel usage logger, plus new Azure model pricing entries. All changes are internal telemetry and static config — no security-relevant code paths are affected.


Status: 0 open
Risk: 1/10

Posted by Veria AI · 2026-04-25T21:08:53.488Z

@greptile-apps

greptile-apps Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a long-standing AttributeError in _set_usage_outputs when the usage object is a raw OpenAI Pydantic model (e.g. CompletionUsage) that lacks a .get() method. It also corrects the reasoning_tokens lookup path for Chat Completions (completion_tokens_details vs output_tokens_details), and adds azure/gpt-5.5 model family entries plus a new supports_low_reasoning_effort schema field. The fix is narrow, backward-compatible, and well-tested.

Confidence Score: 5/5

Safe to merge — the fix is backward-compatible, narrowly scoped, and fully covered by new regression tests.

No P0 or P1 findings. The _safe_get helper is a clean, defensively-written shim; both dict and pydantic paths return identical values to the previous code for objects that already had .get. The or-based truthiness concern on the token_details lookup and the fragile precondition assertion were already flagged in prior review threads. JSON additions follow established schema conventions.

No files require special attention.

Important Files Changed

Filename Overview
litellm/integrations/arize/_utils.py Adds _safe_get helper to unify dict/pydantic attribute access and updates _set_usage_outputs to use it; also fixes reasoning_tokens lookup order for Chat vs Responses API.
tests/test_litellm/integrations/arize/test_arize_utils.py Adds two regression tests covering the Pydantic CompletionUsage and Responses API paths; no real network calls made.
tests/test_litellm/test_utils.py Extends the JSON schema validator to allow supports_low_reasoning_effort as a valid boolean field.
litellm/model_prices_and_context_window_backup.json Adds azure/gpt-5.5, azure/gpt-5.5-2026-04-23, and azure/gpt-5.5-pro model entries with pricing, context window, and capability flags.
model_prices_and_context_window.json Mirror of backup JSON: adds same azure/gpt-5.5 model family entries.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["_set_usage_outputs(span, response_obj, span_attrs)"] --> B["usage = response_obj.get('usage')"]
    B --> C{usage is falsy?}
    C -- yes --> Z[return]
    C -- no --> D["_safe_get(usage, 'total_tokens')"]
    D --> E["set LLM_TOKEN_COUNT_TOTAL"]
    E --> F["_safe_get(usage, 'completion_tokens')\n OR _safe_get(usage, 'output_tokens')"]
    F --> G["set LLM_TOKEN_COUNT_COMPLETION"]
    G --> H["_safe_get(usage, 'prompt_tokens')\n OR _safe_get(usage, 'input_tokens')"]
    H --> I["set LLM_TOKEN_COUNT_PROMPT"]
    I --> J["token_details = _safe_get(usage, 'completion_tokens_details')\n OR _safe_get(usage, 'output_tokens_details')"]
    J --> K["_safe_get(token_details, 'reasoning_tokens')"]
    K --> L["set LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING"]

    subgraph _safe_get["_safe_get(obj, key, default)"]
        S1{obj is None?} -- yes --> S2[return default]
        S1 -- no --> S3{obj has callable .get?}
        S3 -- yes --> S4["obj.get(key, default)"]
        S3 -- no --> S5["getattr(obj, key, default)"]
    end
Loading

Reviews (2): Last reviewed commit: "Merge branch 'litellm-oss-staging-04-25-..." | Re-trigger Greptile

@codspeed-hq

codspeed-hq Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing alvinttang:fix/arize-langfuse-otel-pydantic-usage (a74772c) with main (0beec45)

Open in CodSpeed

Comment on lines +270 to +272
token_details = _safe_get(usage, "completion_tokens_details") or _safe_get(
usage, "output_tokens_details"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 or truthiness check may bypass completion_tokens_details unintentionally

If a CompletionTokensDetails Pydantic model instance defines a custom __bool__ or __len__ that returns falsy (e.g. all fields are zero/None), _safe_get(usage, "completion_tokens_details") or _safe_get(usage, "output_tokens_details") would fall through to output_tokens_details even though completion_tokens_details was explicitly present. Using an explicit None check would be safer:

token_details = _safe_get(usage, "completion_tokens_details")
if token_details is None:
    token_details = _safe_get(usage, "output_tokens_details")
Suggested change
token_details = _safe_get(usage, "completion_tokens_details") or _safe_get(
usage, "output_tokens_details"
)
token_details = _safe_get(usage, "completion_tokens_details")
if token_details is None:
token_details = _safe_get(usage, "output_tokens_details")

completion_tokens_details=CompletionTokensDetails(reasoning_tokens=25),
)
assert not hasattr(usage, "get"), "precondition: CompletionUsage must lack .get"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Precondition assertion is fragile against library updates

assert not hasattr(usage, "get") hard-codes an assumption about the openai SDK's internal Pydantic model structure. If a future version of openai or Pydantic v2 adds a .get() method (e.g. for dict-compatibility), this assertion will fail and block CI even though the fix itself still works correctly via the callable(getter) branch of _safe_get. Consider removing this precondition or rewriting it as a pytest.skip with a note, so the test degrades gracefully.

@codecov

codecov Bot commented Apr 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.71429% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/integrations/arize/_utils.py 85.71% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@krrish-berri-2
krrish-berri-2 changed the base branch from main to litellm-oss-staging-04-25-2026 April 25, 2026 21:07
@krrish-berri-2
krrish-berri-2 merged commit 98a9005 into BerriAI:litellm-oss-staging-04-25-2026 Apr 25, 2026
3 checks passed
restato added a commit to restato/litellm that referenced this pull request Apr 27, 2026
`_set_usage_outputs` emits only total / completion / prompt / reasoning
tokens, leaving `LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ` and
`LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE` defined-but-unused. As a
result, observability backends (Langfuse, Arize Phoenix) cannot display
the prompt-cache breakdown nor apply correct cost calculation for
Anthropic / Bedrock prompt caching (cache reads at 0.1x, cache writes
at 1.25x of standard input).

Read from `usage.prompt_tokens_details` so the change is provider-agnostic.
LiteLLM's `Usage.__init__` already normalizes provider-specific cache
fields onto this object:

- Anthropic `cache_read_input_tokens` -> prompt_tokens_details.cached_tokens
- DeepSeek `prompt_cache_hit_tokens`  -> prompt_tokens_details.cached_tokens
- OpenAI native cached_tokens -> prompt_tokens_details.cached_tokens
- Anthropic `cache_creation_input_tokens` -> prompt_tokens_details.cache_creation_tokens

Mapping:
- prompt_tokens_details.cached_tokens
    -> llm.token_count.prompt_details.cache_read
- prompt_tokens_details.cache_creation_tokens
    -> llm.token_count.prompt_details.cache_write

Note: PR BerriAI#24112 introduced a similar `cached_tokens` block in
litellm_oss_staging_03_21_2026 but has not yet landed on main; PR BerriAI#26506
on litellm-oss-staging-04-25-2026 refactored the function without
including it. This change brings the emission directly to main with
extended cache_creation coverage.

Tests:
- Anthropic: both cache_read and cache_write emitted
- OpenAI: only cache_read emitted (no cache_write concept)
- DeepSeek: prompt_cache_hit_tokens normalized -> cache_read emitted
- No-cache: neither attribute emitted
yugborana pushed a commit to yugborana/litellm that referenced this pull request Jun 2, 2026
…Usage (BerriAI#26506)

* [Feat] Day-0 support for GPT-5.5 and GPT-5.5 Pro (BerriAI#26449)

* feat(openai): day-0 support for GPT-5.5 and GPT-5.5 Pro

Add pricing + capability entries for the new GPT-5.5 family launched by
OpenAI on 2026-04-24:

- gpt-5.5 / gpt-5.5-2026-04-23 (chat): $5/$30/$0.50 per 1M
  input/output/cached input
- gpt-5.5-pro / gpt-5.5-pro-2026-04-23 (responses-only): $60/$360/$6
  per 1M input/output/cached input

Other fees (long-context >272k, flex, batches, priority, cache
discounts) follow the same ratios as GPT-5.4, with context window
retained at 1.05M input / 128K output.

No transformation / classifier code changes are required:
OpenAIGPT5Config.is_model_gpt_5_4_plus_model() already matches 5.5+ via
numeric version parsing, and model registration is driven from the
JSON. The existing responses-API bridge for tools + reasoning_effort
(litellm/main.py:970) already covers gpt-5.5-pro.

Tests:
- GPT5_MODELS regression list now covers gpt-5.5-pro and dated variants
- New test_generic_cost_per_token_gpt55_pro cost-calc test
- Updated test_generic_cost_per_token_gpt55 for long-context fields

* fix(openai): mirror reasoning_effort flags onto gpt-5.5 dated variants

gpt-5.5-2026-04-23 and gpt-5.5-pro-2026-04-23 were missing the
supports_none_reasoning_effort, supports_xhigh_reasoning_effort, and
supports_minimal_reasoning_effort flags that their non-dated
counterparts define. Reasoning-effort routing in OpenAIGPT5Config is
fully capability-driven from these JSON flags — since an absent flag
is treated as False for opt-in levels (xhigh), users pinning to a
dated snapshot would silently lose xhigh support and diverge from the
base alias on logprobs + flexible temperature handling.

Copy the flags onto both dated variants so every dated snapshot
inherits the base model's reasoning-effort capability profile.

Adds a parametrized regression test that asserts
supports_{none,minimal,xhigh}_reasoning_effort parity between each
dated variant and its non-dated counterpart, preventing future drift
when new snapshots are added.

* [Feat] Add azure/gpt-5.5 + azure/gpt-5.5-pro entries (+ dated variants) (BerriAI#26361)

* feat(azure): add azure/gpt-5.5 + azure/gpt-5.5-pro entries (+ dated variants)

Azure variants of OpenAI's GPT-5.5 family. Microsoft has not yet
shipped GPT-5.5 on Azure OpenAI (latest GA on the Foundry models page
is GPT-5.4 as of 2026-04-24), but adding the entries day-0 mirrors the
established precedent for azure/gpt-5.4* (which were in the cost map
before the Azure rollout) so cost tracking and capability flags work
the moment customers deploy.

Schema follows the existing azure/gpt-5.4* shape:
- Same base/long-context pricing as openai/gpt-5.5*: $5/$30 chat,
  $60/$360 pro per 1M, with priority tier 2x base
- Azure variants drop the flex/batches keys (Azure has no flex tier)
  but keep priority pricing, matching gpt-5.4* precedent
- mode=chat for the thinking model, mode=responses for pro

reasoning_effort capability flags mirror the OpenAI variants exactly
since Azure proxies the same API contract: minimal rejection on both
chat and pro, low/none rejection on pro. Once BerriAI#26456 (which sets
supports_low_reasoning_effort + minimal=false on openai/gpt-5.5*)
lands, OpenAI and Azure flag profiles align.

Tests pin entry presence + pricing for all four Azure variants and
verify the live-API-derived reasoning_effort flags.

* test: register supports_low_reasoning_effort in cost-map JSON schema

azure/gpt-5.5-pro and azure/gpt-5.5-pro-2026-04-23 added in this branch
carry supports_low_reasoning_effort=false. The strict
'additionalProperties: false' schema in
test_aaamodel_prices_and_context_window_json_is_valid rejected the new
key. Register it alongside the other supports_*_reasoning_effort
entries.

Note: the runtime side of this flag (code that reads it) lands in
OpenAI pro entries, but having the schema accept it lets cost-map
tests pass on either merge order.

* fix(arize/langfuse_otel): handle Pydantic usage objects without `.get`

`_set_usage_outputs` called `usage.get(...)` and
`usage.get('output_tokens_details', {}).get('reasoning_tokens')`. These
crash with `AttributeError: 'CompletionUsage' object has no attribute
'get'` when `usage` (or the nested token-details object) is a raw OpenAI
Pydantic model rather than a dict / litellm `Usage` wrapper. Reproduces
on the langfuse_otel + arize Responses API logging paths.

Fixes BerriAI#13672.

Changes:
- Add `_safe_get(obj, key, default)` that prefers dict-style `.get` when
  available and otherwise falls back to `getattr`. Works uniformly for
  dicts, litellm's `Usage`, and plain Pydantic models like
  `openai.types.completion_usage.CompletionUsage` /
  `CompletionTokensDetails` / `OutputTokensDetails`.
- Use `_safe_get` for total / completion / prompt / output tokens.
- Look for reasoning tokens in `completion_tokens_details` (Chat
  Completions API) before falling back to `output_tokens_details`
  (Responses API). Previously reasoning tokens from the Chat Completions
  API were silently dropped.

Tests:
- `test_set_usage_outputs_pydantic_completion_usage` — covers the chat
  completions path with raw `CompletionUsage` + `CompletionTokensDetails`.
- `test_set_usage_outputs_pydantic_response_api_usage` — covers the
  Responses API path with a Pydantic usage object lacking `.get`.

Both tests fail on main before this commit and pass after.

---------

Co-authored-by: yuneng-jiang <yuneng@berri.ai>
Co-authored-by: Mateo Wang <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: alvinttang <alvin@pm.me>
Co-authored-by: Krrish Dholakia <krrish+github@berri.ai>
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…Usage (BerriAI#26506)

* [Feat] Day-0 support for GPT-5.5 and GPT-5.5 Pro (BerriAI#26449)

* feat(openai): day-0 support for GPT-5.5 and GPT-5.5 Pro

Add pricing + capability entries for the new GPT-5.5 family launched by
OpenAI on 2026-04-24:

- gpt-5.5 / gpt-5.5-2026-04-23 (chat): $5/$30/$0.50 per 1M
  input/output/cached input
- gpt-5.5-pro / gpt-5.5-pro-2026-04-23 (responses-only): $60/$360/$6
  per 1M input/output/cached input

Other fees (long-context >272k, flex, batches, priority, cache
discounts) follow the same ratios as GPT-5.4, with context window
retained at 1.05M input / 128K output.

No transformation / classifier code changes are required:
OpenAIGPT5Config.is_model_gpt_5_4_plus_model() already matches 5.5+ via
numeric version parsing, and model registration is driven from the
JSON. The existing responses-API bridge for tools + reasoning_effort
(litellm/main.py:970) already covers gpt-5.5-pro.

Tests:
- GPT5_MODELS regression list now covers gpt-5.5-pro and dated variants
- New test_generic_cost_per_token_gpt55_pro cost-calc test
- Updated test_generic_cost_per_token_gpt55 for long-context fields

* fix(openai): mirror reasoning_effort flags onto gpt-5.5 dated variants

gpt-5.5-2026-04-23 and gpt-5.5-pro-2026-04-23 were missing the
supports_none_reasoning_effort, supports_xhigh_reasoning_effort, and
supports_minimal_reasoning_effort flags that their non-dated
counterparts define. Reasoning-effort routing in OpenAIGPT5Config is
fully capability-driven from these JSON flags — since an absent flag
is treated as False for opt-in levels (xhigh), users pinning to a
dated snapshot would silently lose xhigh support and diverge from the
base alias on logprobs + flexible temperature handling.

Copy the flags onto both dated variants so every dated snapshot
inherits the base model's reasoning-effort capability profile.

Adds a parametrized regression test that asserts
supports_{none,minimal,xhigh}_reasoning_effort parity between each
dated variant and its non-dated counterpart, preventing future drift
when new snapshots are added.

* [Feat] Add azure/gpt-5.5 + azure/gpt-5.5-pro entries (+ dated variants) (BerriAI#26361)

* feat(azure): add azure/gpt-5.5 + azure/gpt-5.5-pro entries (+ dated variants)

Azure variants of OpenAI's GPT-5.5 family. Microsoft has not yet
shipped GPT-5.5 on Azure OpenAI (latest GA on the Foundry models page
is GPT-5.4 as of 2026-04-24), but adding the entries day-0 mirrors the
established precedent for azure/gpt-5.4* (which were in the cost map
before the Azure rollout) so cost tracking and capability flags work
the moment customers deploy.

Schema follows the existing azure/gpt-5.4* shape:
- Same base/long-context pricing as openai/gpt-5.5*: $5/$30 chat,
  $60/$360 pro per 1M, with priority tier 2x base
- Azure variants drop the flex/batches keys (Azure has no flex tier)
  but keep priority pricing, matching gpt-5.4* precedent
- mode=chat for the thinking model, mode=responses for pro

reasoning_effort capability flags mirror the OpenAI variants exactly
since Azure proxies the same API contract: minimal rejection on both
chat and pro, low/none rejection on pro. Once BerriAI#26456 (which sets
supports_low_reasoning_effort + minimal=false on openai/gpt-5.5*)
lands, OpenAI and Azure flag profiles align.

Tests pin entry presence + pricing for all four Azure variants and
verify the live-API-derived reasoning_effort flags.

* test: register supports_low_reasoning_effort in cost-map JSON schema

azure/gpt-5.5-pro and azure/gpt-5.5-pro-2026-04-23 added in this branch
carry supports_low_reasoning_effort=false. The strict
'additionalProperties: false' schema in
test_aaamodel_prices_and_context_window_json_is_valid rejected the new
key. Register it alongside the other supports_*_reasoning_effort
entries.

Note: the runtime side of this flag (code that reads it) lands in
BerriAI#26456. Until that PR merges the flag is inert for both Azure and
OpenAI pro entries, but having the schema accept it lets cost-map
tests pass on either merge order.

* fix(arize/langfuse_otel): handle Pydantic usage objects without `.get`

`_set_usage_outputs` called `usage.get(...)` and
`usage.get('output_tokens_details', {}).get('reasoning_tokens')`. These
crash with `AttributeError: 'CompletionUsage' object has no attribute
'get'` when `usage` (or the nested token-details object) is a raw OpenAI
Pydantic model rather than a dict / litellm `Usage` wrapper. Reproduces
on the langfuse_otel + arize Responses API logging paths.

Fixes BerriAI#13672.

Changes:
- Add `_safe_get(obj, key, default)` that prefers dict-style `.get` when
  available and otherwise falls back to `getattr`. Works uniformly for
  dicts, litellm's `Usage`, and plain Pydantic models like
  `openai.types.completion_usage.CompletionUsage` /
  `CompletionTokensDetails` / `OutputTokensDetails`.
- Use `_safe_get` for total / completion / prompt / output tokens.
- Look for reasoning tokens in `completion_tokens_details` (Chat
  Completions API) before falling back to `output_tokens_details`
  (Responses API). Previously reasoning tokens from the Chat Completions
  API were silently dropped.

Tests:
- `test_set_usage_outputs_pydantic_completion_usage` — covers the chat
  completions path with raw `CompletionUsage` + `CompletionTokensDetails`.
- `test_set_usage_outputs_pydantic_response_api_usage` — covers the
  Responses API path with a Pydantic usage object lacking `.get`.

Both tests fail on main before this commit and pass after.

---------

Co-authored-by: yuneng-jiang <yuneng@berri.ai>
Co-authored-by: Mateo Wang <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: alvinttang <alvin@pm.me>
Co-authored-by: Krrish Dholakia <krrish+github@berri.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants