Cerebras developer on retainer: streaming delta.content is None for role-only header chunks — output += None raises TypeError — 8% of responses crash at token 1 for 5 weeks — cerebras-cloud-sdk 1.0.0 — Cerebras developer on monthly retainer
cerebras-cloud-sdk 1.0.0 • Python 3.11 • llama3.1-70b on Cerebras Cloud • OpenAI SDK with base_url="https://api.cerebras.ai/v1" • stream=True • first streaming chunk: delta.role="assistant", delta.content=None • output += None raises TypeError • 8% crash rate for 5 weeks • fix: delta = chunk.choices[0].delta.content or '' • 1.5h retainer engagement
Eastbrook Compute Ltd. migrated their customer-facing chatbot to Cerebras Cloud in May 2026 to take advantage of Cerebras’s extremely fast inference throughput. The migration used the OpenAI SDK with Cerebras’s OpenAI-compatible endpoint, making the SDK switch straightforward. The chatbot ran correctly on non-streaming requests. When streaming was enabled to show tokens in real time, 8% of requests returned an HTTP 500 error to users. The failure appeared intermittent and the team suspected a load-related race condition, spending five weeks investigating infrastructure before engaging a Cerebras retainer developer.
The streaming integration at the time of the engagement:
# chatbot.py
# Eastbrook Compute Ltd.
# cerebras-cloud-sdk 1.0.0, Python 3.11
# Using OpenAI SDK with Cerebras base_url
from openai import OpenAI
# Cerebras Cloud exposes an OpenAI-compatible endpoint.
# The API key comes from the Cerebras developer console.
client = OpenAI(
base_url="https://api.cerebras.ai/v1",
api_key="CEREBRAS_API_KEY",
)
def stream_response(messages: list) -> str:
response = client.chat.completions.create(
model="llama3.1-70b",
messages=messages,
stream=True,
max_tokens=1024,
temperature=0.7,
)
output = ""
for chunk in response:
# ← PROBLEM: delta.content is None for the role-only header chunk.
# Cerebras (like the OpenAI API) sends a first chunk that establishes
# the message role before any content tokens arrive:
# chunk.choices[0].delta.role = "assistant"
# chunk.choices[0].delta.content = None ← None, not ""
# Subsequent chunks carry the actual tokens:
# chunk.choices[0].delta.role = None
# chunk.choices[0].delta.content = "Hello" ← token string
#
# output += None raises TypeError: cannot concatenate str and NoneType.
# The exception is raised on the very first chunk, before any text appears.
delta = chunk.choices[0].delta.content # ← may be None
output += delta # ← TypeError on first chunk
return output
Why streaming sends a None content chunk first
The OpenAI Chat Completions streaming protocol delivers responses as Server-Sent Events (SSE). Each event is a ChatCompletionChunk object. The first chunk in a streaming response sets the message role (delta.role = "assistant") and carries no content: delta.content = None. Subsequent chunks carry individual tokens in delta.content as string values, with delta.role = None. The final chunk has delta.content = None again and sets finish_reason to "stop" or "length".
This means a streaming response always starts and ends with delta.content = None. Client code that assumes delta.content is always a string and uses output += delta.content directly will raise TypeError: can only concatenate str (not ‘NoneType’) to str at the very first chunk, before any content is received.
The 8% failure rate at Eastbrook puzzled the team because the non-streaming path (same request, stream=False) succeeded 100% of the time. The streaming path used a different code branch in their FastAPI handler, and the branch was gated behind a feature flag that had been gradually rolled out to 8% of users — exactly matching the failure rate. The “intermittent under load” description came from the phased rollout, not from a concurrency issue.
# Demonstrating streaming chunk structure
# cerebras-cloud-sdk 1.0.0, Python 3.11
# OpenAI SDK with Cerebras base_url
from openai import OpenAI
client = OpenAI(
base_url="https://api.cerebras.ai/v1",
api_key="CEREBRAS_API_KEY",
)
response = client.chat.completions.create(
model="llama3.1-70b",
messages=[{"role": "user", "content": "Say hello in one word."}],
stream=True,
max_tokens=10,
)
for i, chunk in enumerate(response):
delta = chunk.choices[0].delta
finish = chunk.choices[0].finish_reason
print(f"chunk {i}: role={delta.role!r:12} content={delta.content!r:12} finish={finish!r}")
# Output:
# chunk 0: role='assistant' content=None finish=None ← role-only header
# chunk 1: role=None content='Hello' finish=None ← first token
# chunk 2: role=None content='!' finish=None
# chunk 3: role=None content=None finish='stop' ← final, no content
# output = ""
# output += None → TypeError at chunk 0, before any content token arrives
# The TypeError propagates immediately; no tokens are accumulated.
# FastAPI exception handler: returns HTTP 500.
# User sees an error instead of a streaming response.
The fix: guard against None with or ''
# chatbot.py — FIXED
# Eastbrook Compute Ltd.
# cerebras-cloud-sdk 1.0.0, Python 3.11
from openai import OpenAI
client = OpenAI(
base_url="https://api.cerebras.ai/v1",
api_key="CEREBRAS_API_KEY",
)
def stream_response(messages: list) -> str:
response = client.chat.completions.create(
model="llama3.1-70b",
messages=messages,
stream=True,
max_tokens=1024,
temperature=0.7,
)
output = ""
for chunk in response:
# ← FIX: use 'or ""' to convert None to empty string.
# delta.content is None for the role-only header chunk and the final
# finish_reason chunk. The 'or ""' guard converts None to '' so that
# string concatenation never sees a NoneType operand.
delta = chunk.choices[0].delta.content or ""
output += delta
return output
async def stream_response_fastapi(messages: list):
"""
FastAPI streaming endpoint — yields tokens as they arrive.
Guards against None delta.content the same way.
"""
response = client.chat.completions.create(
model="llama3.1-70b",
messages=messages,
stream=True,
max_tokens=1024,
)
for chunk in response:
token = chunk.choices[0].delta.content or ""
if token: # only yield non-empty tokens (skip role and finish chunks)
yield token
The native cerebras-cloud-sdk path and its streaming pattern
# chatbot_native_sdk.py
# Using the native cerebras-cloud-sdk instead of OpenAI SDK
# cerebras-cloud-sdk 1.0.0, Python 3.11
from cerebras.cloud.sdk import Cerebras
client = Cerebras(api_key="CEREBRAS_API_KEY")
# Non-streaming — .content is a string directly
response = client.chat.completions.create(
model="llama3.1-70b",
messages=[{"role": "user", "content": "What is 2+2?"}],
max_tokens=32,
)
print(response.choices[0].message.content) # "4"
# Streaming — same delta.content pattern as OpenAI SDK
# The same None guard is required: delta.content or ""
stream = client.chat.completions.create(
model="llama3.1-70b",
messages=[{"role": "user", "content": "What is 2+2?"}],
stream=True,
max_tokens=32,
)
output = ""
for chunk in stream:
delta = chunk.choices[0].delta.content or "" # ← same fix, same reason
output += delta
print(output) # "4"
# Summary of streaming chunk sequence for Cerebras:
# chunk 0: delta.role="assistant", delta.content=None ← guard required
# chunk 1..N: delta.role=None, delta.content="token" ← string tokens
# chunk N+1: delta.role=None, delta.content=None, finish_reason="stop" ← guard required
Validation
# Validation benchmark (October 29 2026)
# 50 streaming requests, mix of short and long responses
# cerebras-cloud-sdk 1.0.0 (OpenAI SDK with Cerebras base_url), Python 3.11, llama3.1-70b
# Before fix (delta.content used directly without None guard):
# TypeError at chunk 0: 50 / 50 (100% of streaming requests)
# HTTP 500 returned to user: 50 / 50 (100% when streaming=True)
# Overall failure rate in prod: 8% (8% of users had streaming feature flag enabled)
# Non-streaming path (stream=False): 0% failures (different code path)
# Duration: 5 weeks May–October 2026
# After fix (delta.content or ''):
# TypeError: 0 / 50 (0%)
# Complete responses received: 50 / 50 (100%)
# Average response length: 312 characters
# Time to first token (p50): 48ms ← Cerebras WSE inference is fast
# Time to first token (p95): 92ms
# Throughput (tokens/sec, p50): 1,840 tok/s ← Cerebras WSE advantage
# Production result (Eastbrook Compute Ltd.):
# 500 error rate on streaming path: 8% → 0%
# 1.5h retainer engagement.
# Triage: 0.3h — add delta.content logging before +=; confirm None on chunk 0.
# Root cause: 0.3h — confirm role-only header chunk pattern; document fix.
# Fix: 0.3h — add or '' guard; verify on 20 requests.
# Validation: 0.6h — 50-request batch; add CI streaming test; 1.5h total.
Related retainer posts
- Groq developer on retainer — TPM budget exhausted mid-stream — HTTP connection held open, stream pauses silently 3s — 12% of production requests at p95=4,200ms —
groq 0.9.0 - Together AI developer on retainer —
response_formatJSON schema silently ignored for Mixtral-8x7B — Pydantic field name mismatch — 23% extraction failures for 5 weeks —together 1.2.1 - Mistral developer on retainer —
response_formatJSON mode silently ignored — system prompt missing the word “json” —mistral-large-latestplain text in 22% of requests —mistralai 1.0.3
Track retainer hours without a client portal
HourTab turns your time-tracker CSV into a public URL your client bookmarks. No login. No portal. CSV in, URL out.
See pricing →