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

Aperta
#862 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
bug
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.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.