#862

[BUG] fable_5_fallback_billing/guide.ipynb, cell 14: `betas=betas or None` raises TypeError when there's no fallback credit token

OpenHetCreepopened 1mo ago

Affected Notebook/File

fable_5_fallback_billing/guide.ipynb

Bug Description

Cell 14 defines redeem_credit_after_block, which builds the retry call like this:

extra = {}
betas = []
if credit is not None:  # present only when the blocked request had a cached prefix
    betas.append(FALLBACK_CREDIT_BETA)
    extra["fallback_credit_token"] = credit

return client.beta.messages.create(
    model=FALLBACK_MODEL,
    max_tokens=max_tokens,
    messages=messages,
    betas=betas or None,
    extra_body=extra or None,
)

When credit is None — which the function's own docstring says is the case whenever the blocked request didn't have a billable cached prefix, i.e. every example call in this notebook, since none of them sets cache_control — betas stays [], and betas or None evaluates to None.

The SDK's betas parameter is typed List[AnthropicBetaParam] | Omit, never Optional, and its own "is this given" check treats None as a real value:

# anthropic/_utils/_utils.py (current main, and unchanged since this notebook's own
# pin, anthropic>=0.108.0)
def is_given(obj):
    return not isinstance(obj, NotGiven) and not isinstance(obj, Omit)

is_given(None) is True, so the header-building line in messages.py runs the join on None:

"anthropic-beta": ",".join(str(e) for e in betas) if is_given(betas) else not_given,

",".join(str(e) for e in None) raises TypeError: 'NoneType' object is not iterable — inside the SDK's own request-construction code, before any request is sent.

Expected: when there's no fallback-credit token, the retry either omits the beta header or sends it without the credit beta. Actual: a TypeError out of the SDK, on the exact branch the function's own if credit is not None: guard was written to handle.

Suggested fix — either works:

from anthropic import omit
...
betas=betas or omit,   # `omit` is what "not given" means to this SDK; `None` is not

or only pass betas at all when the list is non-empty.

Steps to Reproduce

No install and no API key needed — the crash happens inside the SDK's own header-construction logic before any network call, so the exact mechanism reproduces as a 6-line transcription:

class NotGiven: pass
class Omit: pass

def is_given(obj):                       # anthropic/_utils/_utils.py
    return not isinstance(obj, NotGiven) and not isinstance(obj, Omit)

betas = []                                # cell 14, when credit is None
betas = betas or None                     # the notebook's own expression
print(is_given(betas))                    # -> True
",".join(str(e) for e in betas)           # the header-build line in messages.py -> raises

To hit it through the real notebook instead: run cells 1-2, then call redeem_credit_after_block(blocked_response, messages) with any blocked_response whose stop_details.fallback_credit_token is None — i.e. any refusal without a billable cached prefix, which is every example call in this notebook.

Error Message

True
Traceback (most recent call last):
  ...
TypeError: 'NoneType' object is not iterable

Environment

  • Python version: any (the 6-line repro above needs nothing but the standard library)
  • OS: not version-sensitive — pure Python semantics
  • anthropic SDK version: reproduces at this notebook's own pinned floor (anthropic>=0.108.0) and at the current release (1.4.0) — same code path in both

Would you be willing to submit a PR to fix this?

No, I'm just reporting

Activity

0

No activity yet.