// lesson: robustness
Robustness โ Retries and Streaming
Two upgrades separate a demo from a tool you actually use: surviving flaky network calls, and streaming replies instead of staring at a frozen prompt.
Retries. API calls fail for boring reasons โ a rate limit (HTTP 429), a
momentary overload (529), a dropped connection. These are transient: the
same request usually succeeds a second later. The fix is a tiny wrapper that
retries a callable a bounded number of times and re-raises the last error if
every attempt fails. Add it to chatbot.py:
def call_with_retries(fn, attempts):
for attempt in range(attempts):
try:
return fn()
except Exception:
if attempt == attempts - 1:
raise
Bounded is the important word: retry forever and a real outage turns your app
into a busy-loop. Notice the wrapper catches bare Exception โ deliberately
broad, since the tests below only care about attempt counting. A real app
should usually narrow that to the transient errors described above (the
Anthropic SDK raises typed exceptions โ RateLimitError, APIConnectionError,
InternalServerError โ for exactly this), so a bug in your own code, like a
TypeError from a bad dict key, fails immediately instead of quietly burning
through your retry budget.
Use it in the chat loop by wrapping the client call in a zero-argument lambda:
reply = call_with_retries(
lambda: chat_turn(history, user_input, anthropic_client),
attempts=3,
)
print(reply)
Two honest footnotes for your real app. First, the official SDKs already
retry rate limits and server errors internally (the Anthropic SDK defaults to
two retries with backoff) โ the wrapper still earns its keep for errors the
SDK re-raises and as the pattern you'll reuse around any flaky call. Second,
production retry loops sleep between attempts (exponential backoff) so a
struggling server isn't hammered; in your app, add time.sleep(2 ** attempt)
before retrying. The unit-tested version keeps sleeping out of scope โ the
logic under test is the attempt counting.
Streaming. Long replies feel broken when they arrive all at once after ten silent seconds. The APIs can stream text as it's generated; printing chunks as they land makes the bot feel alive. A streaming Anthropic client for your app:
def anthropic_streaming_client(messages):
system = ""
if messages and messages[0]["role"] == "system":
system = messages[0]["content"]
messages = messages[1:]
parts = []
with _anthropic.messages.stream(
model="claude-opus-4-8",
max_tokens=1024,
system=system,
messages=messages,
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
parts.append(text)
print()
return "".join(parts)
Notice it still returns the full reply string โ so it drops into chat_turn
unchanged, and history stays correct. Seams pay rent again: printing moved
into the client; the loop logic didn't change at all.
The challenge unit-tests the retry wrapper against a deliberately flaky fake "client" that fails a set number of times before succeeding โ the sandbox version of a bad network day.
โบ Retry Flaky Calls
15 ptsImplement call_with_retries(fn, attempts):
- Call
fn()(no arguments). If it returns without raising, return its result immediately โ no further calls. - If
fn()raises, try again, up toattemptstotal calls. - If the last allowed attempt also raises, let that exception propagate.
- Do not sleep between attempts. You may assume
attempts >= 1.
Log in to submit a solution and earn points.