litellm - 💡(How to fix) Fix [Bug]: Streaming /v1/responses success logger crashes with "'dict' object has no attribute 'usage'" → no spend log written, request goes uncharged [1 pull requests]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

LiteLLM.LoggingError: [Non-Blocking] ... 'dict' object has no attribute 'usage'

Root Cause

litellm/litellm_core_utils/litellm_logging.py, in Logging._get_assembled_streaming_response:

elif isinstance(result, (ResponseCompletedEvent, ResponseIncompleteEvent, ResponseFailedEvent)):
    ## return unified Usage object
    if isinstance(result.response.usage, ResponseAPIUsage):   # <-- crashes here
        ...

(Line 3410 on the v1.86.2 tag; the logically-identical line is 3486 on current main.)

result is a ResponseCompletedEvent. Its .response field is typed ResponsesAPIResponse, but the streaming-assembly path stores a plain dict there at runtime (the base models use model_config = {"extra": "allow"}, so a dict is accepted into the field). Accessing result.response.usage on a dict raises AttributeError: 'dict' object has no attribute 'usage'.

The exception propagates out of async_success_handler before cost is calculated or the spend log is written, and is re-raised as a [Non-Blocking] LoggingError — so the stream completes 200 to the client but no LiteLLM_SpendLogs row lands.

Fix Action

Fix / Workaround

A sibling fix, PR #13654 (stream=True + background=True, 'dict' object has no attribute 'id'), already established that result.response can be a dict in this path — but it patched the .id access, not .usage, and not in this function.

Code Example

LiteLLM.LoggingError: [Non-Blocking] ... 'dict' object has no attribute 'usage'

---

elif isinstance(result, (ResponseCompletedEvent, ResponseIncompleteEvent, ResponseFailedEvent)):
    ## return unified Usage object
    if isinstance(result.response.usage, ResponseAPIUsage):   # <-- crashes here
        ...

---

responses/streaming_iterator.py
  → logging_obj.async_success_handler(result=ResponseCompletedEvent)
Logging.async_success_handler            (litellm_logging.py)
Logging._get_assembled_streaming_response
isinstance(result.response.usage, ResponseAPIUsage)   # AttributeError: 'dict' object has no attribute 'usage'

---

elif isinstance(result, (ResponseCompletedEvent, ResponseIncompleteEvent, ResponseFailedEvent)):
    resp = result.response
    usage = resp.get("usage") if isinstance(resp, dict) else getattr(resp, "usage", None)
    if isinstance(usage, ResponseAPIUsage):
        ...
    elif isinstance(usage, dict) and ResponseAPILoggingUtils._is_response_api_usage(usage):
        ...

---

curl -sN http://localhost:4000/v1/responses \
     -H "Authorization: Bearer $KEY" \
     -H "Content-Type: application/json" \
     -d '{"model": "<any-model>", "input": "hello", "stream": true}'

---

LiteLLM.LoggingError: [Non-Blocking] Exception occurred while logging ...
Traceback (most recent call last):
  File ".../litellm/litellm_core_utils/litellm_logging.py", line 3410, in _get_assembled_streaming_response
    if isinstance(result.response.usage, ResponseAPIUsage):
                  ^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'dict' object has no attribute 'usage'
RAW_BUFFERClick to expand / collapse

Check for existing issues

  • I have searched the existing issues and checked that my issue is not a duplicate.

What happened?

On the proxy, streaming requests to /v1/responses (OpenAI Responses API surface, stream=True) never get a LiteLLM_SpendLogs row written, so they are not charged. The client still receives a correct 200 streamed body, but the success-logging callback crashes non-blockingly with:

LiteLLM.LoggingError: [Non-Blocking] ... 'dict' object has no attribute 'usage'

The crash happens only on the streaming Responses path:

PathSpend log written?
/v1/responses, stream=Truecrashes — no row
/v1/responses, stream=False (blocking)✅ fine
/v1/chat/completions, stream=True✅ fine

Because cost is never computed for the streamed responses request, the gateway silently under-bills that surface. The x-litellm-response-cost response header is also unreliable here (it's a pre-stream estimate flushed before the body), so the missing spend row is the real signal.

Root cause

litellm/litellm_core_utils/litellm_logging.py, in Logging._get_assembled_streaming_response:

elif isinstance(result, (ResponseCompletedEvent, ResponseIncompleteEvent, ResponseFailedEvent)):
    ## return unified Usage object
    if isinstance(result.response.usage, ResponseAPIUsage):   # <-- crashes here
        ...

(Line 3410 on the v1.86.2 tag; the logically-identical line is 3486 on current main.)

result is a ResponseCompletedEvent. Its .response field is typed ResponsesAPIResponse, but the streaming-assembly path stores a plain dict there at runtime (the base models use model_config = {"extra": "allow"}, so a dict is accepted into the field). Accessing result.response.usage on a dict raises AttributeError: 'dict' object has no attribute 'usage'.

The exception propagates out of async_success_handler before cost is calculated or the spend log is written, and is re-raised as a [Non-Blocking] LoggingError — so the stream completes 200 to the client but no LiteLLM_SpendLogs row lands.

Call path (async streaming /v1/responses)

responses/streaming_iterator.py
  → logging_obj.async_success_handler(result=ResponseCompletedEvent)
    → Logging.async_success_handler            (litellm_logging.py)
      → Logging._get_assembled_streaming_response
        → isinstance(result.response.usage, ResponseAPIUsage)   # AttributeError: 'dict' object has no attribute 'usage'

Blocking responses (aresponses) and streaming chat-completions (acompletion) take different branches that don't hit this access, which is why only streaming /v1/responses loses the spend log.

A sibling fix, PR #13654 (stream=True + background=True, 'dict' object has no attribute 'id'), already established that result.response can be a dict in this path — but it patched the .id access, not .usage, and not in this function.

What LiteLLM version?

Reproduced on 1.86.2. Verified the crashing line is unchanged (no isinstance(dict) / .get("usage") guard) in 1.88.0, 1.89.0rc1, and main HEAD 9608dd51 (2026-06-07).

Proposed fix

Guard result.response being a dict before the .usage access — the same shape get_usage_as_dict / other helpers in this file already handle elsewhere:

elif isinstance(result, (ResponseCompletedEvent, ResponseIncompleteEvent, ResponseFailedEvent)):
    resp = result.response
    usage = resp.get("usage") if isinstance(resp, dict) else getattr(resp, "usage", None)
    if isinstance(usage, ResponseAPIUsage):
        ...
    elif isinstance(usage, dict) and ResponseAPILoggingUtils._is_response_api_usage(usage):
        ...

Related issues / PRs (distinct from this one)

  • #28943 (closed) / PR #29394 — "Anthropic messages failed to handle spend log when stream is true." Same family (streaming Responses spend log silently dropped) but a different path (/v1/messages → anthropic_messages bridge) and a different exception (Pydantic ValidationError on ResponseCompletedEvent, not AttributeError). Its fix merged only to a litellm_oss_staging_050626 staging branch and is not in a release tag. Does not cover native /v1/responses ingress.
  • #28595 (open) — anthropic_messages logging silently drops spend rows on cross-route to OpenAI Responses backend (same mechanism family).
  • PR #28985, #28628, #29413 (open) — ResponseCompletedEvent passthrough / translate fixes for the /v1/messages bridge.
  • PR #13654 — fixed the analogous 'dict' object has no attribute 'id' in the background=True Responses path; proves the dict shape but doesn't cover .usage in _get_assembled_streaming_response.

Steps to Reproduce

  1. Run the litellm proxy with a database / spend logging configured.
  2. Send a streaming request to /v1/responses:
    curl -sN http://localhost:4000/v1/responses \
      -H "Authorization: Bearer $KEY" \
      -H "Content-Type: application/json" \
      -d '{"model": "<any-model>", "input": "hello", "stream": true}'
  3. Stream returns 200 with a correct body.
  4. Check the proxy logs → the [Non-Blocking] ... 'dict' object has no attribute 'usage' LoggingError appears.
  5. Query LiteLLM_SpendLogsno row for that request. Repeat with "stream": false → a row is written.

Relevant log output

LiteLLM.LoggingError: [Non-Blocking] Exception occurred while logging ...
Traceback (most recent call last):
  File ".../litellm/litellm_core_utils/litellm_logging.py", line 3410, in _get_assembled_streaming_response
    if isinstance(result.response.usage, ResponseAPIUsage):
                  ^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'dict' object has no attribute 'usage'

What part of LiteLLM is this about?

Proxy

What LiteLLM version are you on ?

1.86.2

Twitter / LinkedIn details

No response

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING