Lesson 16 of 30
Every provider streams differently, errors differently, and shapes messages differently — an AI SDK exists to hide those differences behind one interface so your UI code doesn't have to know which model is answering.
11 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
Your app currently calls one AI provider directly, hand-parsing its streaming response format in your chat component. What's the main cost of that approach as the app grows?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Calling a raw provider API for a chat feature means handling a lot yourself: opening a streaming connection, parsing whatever chunk format that specific provider uses, tracking loading/error/streaming state in your own useState calls, and reshaping the provider's response into whatever your UI expects. Do that for one provider and it's manageable. Do it for two, or swap providers six months later, and you're rewriting UI-adjacent plumbing that has nothing to do with your actual product.
An AI SDK (the pattern, not any one specific library) is an abstraction layer that solves this two ways:
useChat/useCompletion) manage the message list, the streaming/loading/error state, and the send/retry actions for you, so a chat UI component consumes state instead of building a state machine from scratch.The value isn't "less typing." It's that the streaming-response parsing, the error-shape normalization, and the message-array bookkeeping are the exact same problem for every team building a chat feature — solving it once in a shared layer means your component code stays about your product's UI, not about re-deriving provider plumbing.
Provider A stream chunk: data: {"choices":[{"delta":{"content":"Hi"}}]}
Provider B stream chunk: event: content_block_delta
data: {"delta":{"text":"Hi"}}
Two providers, two different wire formats, for the same underlying idea: "here's the next piece of text." Without an abstraction layer, code that renders a streaming response has to know which shape it's looking at. With one, both get parsed into the same internal event type before your UI ever sees them — the component that renders streaming text writes one handler, not one per provider.
Praxis's own backend takes a related but narrower approach: lib/ai/chat/handler.ts's handleChat is the single entry point every AI call site goes through, and lib/ai/provider/resolveKey.ts's resolveProviderConfig resolves which key/provider config to use before the call happens. It's a hand-rolled version of the same underlying idea an SDK's provider-agnostic interface solves — one seam that call sites depend on, so the provider-specific details (which key, which fallback ladder) stay out of every route handler that needs to talk to a model.
handleChat is exactly this kind of deliberate, narrower version."Why would you reach for an AI SDK instead of calling the provider's API directly?" tests whether a candidate can name the real cost being avoided (re-solving streaming/state/error-shape plumbing per provider) rather than giving a vague "it's easier" answer.