Back to Bytes

Add safety rating handling and role normalization logic — lab audio overview

2026-04-21

Add safety block detection by checking candidate finish_reason against FinishReason.SAFETY, implement role normalization from Gemini 'model' to 'assistant', and emit StreamError objects when content is blocked.

GenAI Application Engineering › Full-Stack GenAI Applications › Chapter 1 · Chat Completion API with Streaming › Implement a Gemini 2.5 Flash streaming adapter with thinking budget

6:11
Add safety block detection by checking candidate finish_reason against FinishReason.SAFETY, implement role normalization from Gemini 'model' to 'assistant', and emit StreamError objects when content is blocked.
Share

More from this chapter

Transcript
Host: Welcome back to Full-Stack GenAI Applications. You're in the chapter on Chat Completion APIs with Streaming — it's about building web endpoints that send an AI's answer to the user one word at a time, instead of making them wait for the whole response. Streaming is what makes chat feel alive, and today's skill is what keeps it safe. Expert: Picture this. You're on a team of maybe eight engineers at a healthcare education company. You've shipped a chatbot that answers patient questions, and it streams beautifully — words appear as they're generated, just like the big consumer chat products. Then on a Tuesday morning, a user asks a sensitive question, and Google's model — the one you're using under the hood — decides the response crosses a safety line and refuses to complete it. What does your app do? If you haven't handled this case, the stream just… stops. Mid-sentence. No explanation. The user sees half an answer and a spinner that never resolves. Your support inbox fills up. Worse, your frontend code might crash because it expected a clean ending that never came. Every production AI application has to gracefully handle the moment when the model says "I won't answer that" — and today's exercise is exactly about that moment. Host: So this is the third and final exercise in this objective. In the previous one, you built the core streaming machinery for Google's Gemini models, including a toggle for their extended reasoning feature. Now you're hardening it. So what exactly are we building? Expert: You're adding two pieces of polish that turn a working prototype into something you'd actually put in front of real users. The first piece is safety handling. When Google's model streams back a response, each chunk carries a little signal called a finish reason — basically, a label that says "here's why I stopped talking." Most of the time that label means "I finished normally." But sometimes it says "I stopped because the content tripped a safety filter." Your job is to watch for that specific label and, when you see it, send a clean, structured error message down the stream instead of letting it die silently. The second piece is what's called role normalization. Different AI providers use different words for the same thing. When Google's model speaks, it labels itself with the word "model." But most of the industry — including OpenAI, Anthropic, and the frontend code your team has already written — uses the word "assistant." So you need to translate. Every time a chunk comes back labeled "model," you rewrite the label to say "assistant" before passing it along. Host: Okay, that second piece sounds almost trivial — just swap one word for another. Is there a deeper idea here? Expert: There is, and it's the whole point of this exercise. The deeper idea is called an adapter pattern. Think of it like a universal power plug. Your application speaks one standard language internally — one vocabulary for roles, one shape for errors, one way of signaling that something went wrong. But every AI provider speaks its own dialect. Google says "model," OpenAI says "assistant," Anthropic has its own conventions. The adapter is the thin translation layer that sits between the wild world of provider-specific quirks and the clean, consistent world your frontend expects. By normalizing the role name and by converting a provider-specific safety block into a standard error object, you're making a promise to the rest of your application: no matter which provider I'm talking to today, you'll always get data in the same shape. That promise is what lets your team swap providers, run experiments, or add a fourth model next quarter without rewriting the frontend. Host: Before folks start, what's the one thing that trips people up here? Expert: The trap is assuming that a safety block is the same thing as an error. It's not. When the model refuses on safety grounds, the stream itself is working correctly — the network connection is fine, the provider responded, nothing crashed. So if you treat it like a crash and throw an exception, you'll take down the whole request. Instead, you want to let the stream continue normally, but insert a well-formed error message into it — a little structured packet that says "content was blocked for safety reasons, here's the reason code." The frontend can then display a friendly message to the user. The tip: read carefully through the finish reason values the provider defines, identify the specific one that means "safety block," and handle that case as a normal event in your stream, not an exception. Also, when you're normalizing the role, make sure you only rewrite when the incoming label is the Google-specific one — don't blindly overwrite every role, or you'll corrupt the user's own messages on the way back through. Host: Great. Bring it home — what will someone walk away able to do, and where does this leave the objective? Expert: After this exercise, you'll be able to take a raw stream from any AI provider and turn it into something safe, consistent, and production-ready — with graceful handling of refusals and a normalized vocabulary your whole stack can rely on. For your team, this is the final brick in a reusable adapter they can point at any provider and trust to behave the same way. And because this is the last exercise in the objective, you now have the complete picture: a streaming chat system that spans multiple AI providers, handles extended reasoning, and degrades gracefully when content gets blocked. That's a real piece of production plumbing — the kind of work you can bring into your team's next architecture review and say, here's how we should structure every provider integration going forward. Thanks for listening, and good luck.

Want to go deeper? Explore disciplines with hands-on labs, quizzes, and chapter podcasts.