anthropics / anthropics/claude-cookbooks
[BUG] fable_5_fallback_billing/guide.ipynb, cell 14: `betas=betas or None` raises TypeError when there's no fallback credit token
- Lingua principale
- Jupyter Notebook
- Stelle
- 52.7k
- Fork
- 6.3k
- Merge medio
- 25m
- PR unite (30g)
- 6
Descrizione
### 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
```shell
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
Guida per i contributori
Apri la guida per i contributori
Valutazione
Questa issue non è ancora stata valutata.