Skip to the solution
EasyFrontend12 min read

AI SDK 5: when to use streamText vs generateText vs generateObject?

141 views

The problem

Question presented to candidate: "In the Vercel AI SDK 5 you have generateText, streamText and generateObject. For each one, tell me what it returns, when you would reach for it, and one mistake people make with it."

What a strong answer should cover:

  • generateText awaits the whole result (text, toolCalls, finishReason, usage, steps): right for server jobs, evals, agents and anything no person watches token by token
  • streamText returns immediately with streams (textStream, fullStream) and promises that resolve at the end, plus helpers such as toUIMessageStreamResponse() for useChat; errors become part of the stream instead of being thrown, so you add onError
  • generateObject takes a schema (Zod or JSON Schema) and returns a validated object, throwing NoObjectGeneratedError (with the raw text) when the output does not fit; streamObject streams partial objects
  • Shared behaviour: the same model / prompt / messages options across providers, abortSignal, and multi-step tool use controlled by stopWhen (default stepCountIs(1) in v5, so a tool call ends the run unless you raise it)
  • Version awareness: AI SDK 6 deprecated generateObject / streamObject in favour of generateText / streamText with output: Output.object(...), and AI SDK 7 is the current major release (October 2026)

Code / implementation expected: Optional โ€” one call of each, or a short decision rule with the return shapes.

  • ai-sdk
  • streamText

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

12 min read ยท 9 sections

Read the code

The three call shapes on a fake clock
// The three AI SDK 5 call shapes, re-enacted with a stub model on a fake clock.
// This is NOT the real "ai" package: it mimics what generateText, streamText
// and generateObject hand back, so the behaviour around the model can run offline.

const TTFT = 500, PER_TOKEN = 25; // simulated ms

// Stub model: canned replies keyed by prompt; tokens are words for readability.
const REPLIES = {
  summary: "Refunds are allowed within 30 days with a receipt.",
  ticket_ok: '{"category":"billing","priority":"high"}',
  ticket_bad: '{"category":"billing","priority":"urgent!!"}',
};
const tokensOf = (key) => REPLIES[key].split(" ");
const doneAt = (n) => TTFT + (n - 1) * PER_TOKEN;

function generateText({ prompt }) {               // one awaited result
  const t = tokensOf(prompt);
  return { text: t.join(" "), finishReason: "stop", usage: { outputTokens: t.length }, resolvedAt: doneAt(t.length) };
}

function* streamText({ prompt }) {                // deltas as they are produced
  const t = tokensOf(prompt);
  for (let i = 0; i < t.length; i++) yield { at: TTFT + i * PER_TOKEN, delta: (i ? " " : "") + t[i] };
}

// Tiny schema check standing in for Zod: required keys, types, enums.
const ticketSchema = { category: { enum: ["billing", "bug", "other"] }, priority: { enum: ["low", "high"] } };
function validate(obj, schema) {
  for (const [k, rule] of Object.entries(schema)) {
    if (!(k in obj)) return "missing " + k;
    if (!rule.enum.includes(obj[k])) return k + " must be one of " + rule.enum.join("|") + ", got " + JSON.stringify(obj[k]);
  }
  return null;
}
function generateObject({ prompt, schema }) {     // typed object or a thrown error
  const r = generateText({ prompt });
  let obj;
  try { obj = JSON.parse(r.text); } catch { throw { name: "NoObjectGeneratedError", cause: "not JSON", text: r.text }; }
  const err = validate(obj, schema);
  if (err) throw { name: "NoObjectGeneratedError", cause: err, text: r.text };
  return { object: obj, resolvedAt: r.resolvedAt };
}

console.log("1) generateText: nothing until the whole answer exists");
const g = generateText({ prompt: "summary" });
console.log("   resolved at " + g.resolvedAt + " ms with " + g.usage.outputTokens + " tokens: " + g.text);

console.log("");
console.log("2) streamText: same answer, consumed as it is produced");
let shown = "", first = null, last = 0;
for (const part of streamText({ prompt: "summary" })) {
  if (first === null) first = part.at;
  last = part.at; shown += part.delta;
  if (part.at === TTFT || part.at === TTFT + 4 * PER_TOKEN) console.log("   t=" + part.at + " ms screen: " + shown);
}
console.log("   first text at " + first + " ms, finished at " + last + " ms");

console.log("");
console.log("3) generateObject: a validated object, or an error you must handle");
for (const prompt of ["ticket_ok", "ticket_bad"]) {
  try {
    const o = generateObject({ prompt, schema: ticketSchema });
    console.log("   " + prompt + ": object = " + JSON.stringify(o.object));
  } catch (e) {
    console.log("   " + prompt + ": " + e.name + " (" + e.cause + ")");
    console.log("     raw text kept for logging: " + e.text);
  }
}

// Multi-step tools: the v5 default stop condition is stepCountIs(1).
function toolLoop(maxSteps) {
  const steps = [{ toolCall: "lookupOrder", text: "" }, { toolCall: null, text: "Order 42 shipped on Monday." }];
  const done = [];
  for (const s of steps) { done.push(s); if (done.length >= maxSteps || !s.toolCall) break; }
  const lastStep = done[done.length - 1];
  return { steps: done.length, text: lastStep.text, finishReason: lastStep.toolCall ? "tool-calls" : "stop" };
}
console.log("");
console.log("4) tools with generateText or streamText");
for (const n of [1, 5]) {
  const r = toolLoop(n);
  console.log("   stopWhen: stepCountIs(" + n + ") -> steps " + r.steps + ", finishReason " + r.finishReason + ", text " + JSON.stringify(r.text));
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 13 of 83 in AI engineering

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

Up nextEasy

How do you choose the right model for a task?

Continue