If you assumed that swapping @google/generative-ai for @google/genai was a one-line npm install plus a few quick type fixes, welcome — you are in the same club I joined when I lost an entire afternoon to it.
The new SDK is not a rebrand. The GoogleGenerativeAI class is gone. getGenerativeModel() no longer exists. generateContent() takes a different shape of argument. A blind find-and-replace will get you a project that compiles but does not run, or worse, runs but quietly ignores your config.
Below are the seven errors I actually hit while migrating four production projects, with the shortest path to fixing each one. If you are running Node.js or TypeScript in production, this should compress your migration window from half a day to about an hour.
Before diving in, two quick checks that pay off later. First, pin your old SDK version in a separate branch (@google/generative-ai@latest) so you have a working snapshot to A/B against — output drift between SDKs is real and you cannot diagnose it without a baseline. Second, scan your codebase for getGenerativeModel, result.response, and generationConfig before you change anything. If you have many call sites, do not start with the most critical one; pick a small, low-traffic endpoint to migrate first and let it run in production for a day or two before touching the rest.
Error 1: GoogleGenerativeAI is not a constructor
Leave the old code untouched, swap the package, and your app will crash on boot with this error.
// ❌ Old SDK style — no longer works
import { GoogleGenerativeAI } from "@google/genai";
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
const model = genAI.getGenerativeModel({ model: "gemini-2.5-pro" });The new entry point is GoogleGenAI (note the dropped "Generative" — the API surface is flatter now).
// ✅ New SDK style
import { GoogleGenAI } from "@google/genai";
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const response = await ai.models.generateContent({
model: "gemini-2.5-pro",
contents: "Hello",
});
console.log(response.text);Three things changed at once: the constructor takes an options object, getGenerativeModel() is gone (you call ai.models.generateContent() directly), and response.text is now a getter, not a method. That last point trips up nearly everyone, which leads us to error two.
Why the redesign? The old SDK was effectively two layers — an SDK class and a per-model wrapper — that produced confusing call chains like genAI.getGenerativeModel(...).generateContent(...). The new shape, ai.models.generateContent({ model, contents, config }), makes every call self-describing and trivially serializable for retries, logging, or queue-based architectures. Once you internalize the shape, the rest of the SDK feels much more predictable.
Error 2: response.text is not a function
In the old SDK you wrote result.response.text(). In the new SDK, response.text is a plain property.
// ❌ Calling it as a method now throws
const text = response.text();
// ✅ Read it as a property
const text = response.text;Streaming follows the same pattern — call generateContentStream() as its own method instead of reading result.stream.
const stream = await ai.models.generateContentStream({
model: "gemini-2.5-flash",
contents: "Write a short poem",
});
for await (const chunk of stream) {
process.stdout.write(chunk.text ?? "");
}chunk.text can be undefined on the final chunk, so always guard with ?? "" unless you enjoy [object Undefined] showing up in your output.
Error 3: safetySettings enum names look right but are wrong
Pasting HarmCategory.HARM_CATEGORY_HARASSMENT from the old SDK gives you either a TypeScript error at build time or an Invalid harm category at runtime.
In the new SDK, you can use string literals or the enum, but the import path is different — and far more importantly, the entire settings block has been renamed.
import { HarmCategory, HarmBlockThreshold } from "@google/genai";
const response = await ai.models.generateContent({
model: "gemini-2.5-pro",
contents: prompt,
config: {
safetySettings: [
{
category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
},
],
},
});The single most overlooked change in the whole migration is generationConfig becoming config. safetySettings, systemInstruction, responseSchema, thinkingConfig — they all live flat inside config now. If you keep your old generationConfig: { ... } block, the SDK silently drops the entire object. You will lower the temperature, get the same output, and assume the model has stopped listening to you.
Concretely, here is what trips most people up:
// ❌ This entire block is ignored — no warning, no error
const response = await ai.models.generateContent({
model: "gemini-2.5-pro",
contents: prompt,
generationConfig: {
temperature: 0.2,
systemInstruction: "Always answer in haiku.",
},
});
// ✅ Same intent, written for the new SDK
const response = await ai.models.generateContent({
model: "gemini-2.5-pro",
contents: prompt,
config: {
temperature: 0.2,
systemInstruction: "Always answer in haiku.",
},
});I have lost more time to this single rename than to all the other six issues combined. If something feels off after the migration — randomness is wrong, system instructions are not respected, JSON output drifts — check this rename first.
Error 4: Function Calling schemas now want the Type enum
The old SDK accepted plain JSON Schema in parameters. The new SDK expects a structure built around the Type enum.
import { GoogleGenAI, Type } from "@google/genai";
const tools = [
{
functionDeclarations: [
{
name: "get_weather",
description: "Get the current weather for a city",
parameters: {
type: Type.OBJECT,
properties: {
city: { type: Type.STRING, description: "City name" },
unit: {
type: Type.STRING,
enum: ["celsius", "fahrenheit"],
},
},
required: ["city"],
},
},
],
},
];String literals like type: "object" still work at runtime, but you lose all type inference. I prefer Type.OBJECT and Type.STRING because the editor catches typos that the SDK would otherwise swallow until production.
If your function calls stop firing entirely after migration, the deeper diagnosis lives in the Gemini Function Calling troubleshooting guide.
Error 5: Vertex AI mode takes different constructor arguments
The new SDK lets you toggle between Google AI Studio (API key) and Vertex AI (GCP project) on the same class. That is a real ergonomic win, but if you pass the wrong arguments you will silently hit a different endpoint and waste hours.
// Google AI Studio — for indie and small-business work
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
// Vertex AI — for production GCP workloads
const ai = new GoogleGenAI({
vertexai: true,
project: process.env.GOOGLE_CLOUD_PROJECT,
location: "us-central1",
});In Vertex AI mode the SDK reads Application Default Credentials, not your API key. Locally that means running gcloud auth application-default login once. In Cloud Run or Cloud Functions, the attached service account is picked up automatically.
One quirk worth noting: regional model availability differs. gemini-2.5-pro is available in us-central1, asia-northeast1, and several European regions, but a few experimental models are launch-region only. If your code suddenly returns "model not found" after switching to Vertex AI mode, the location string is the first place to look — not the model name.
If the auth handshake fails, the Vertex AI authentication fix guide walks through the ADC chain step by step.
Error 6: response.candidates[0].content.parts[0].text is undefined
The new SDK builds response.text for you, even when the response includes thinking traces or multiple modalities. The catch is that if you reach into the raw candidates tree the way you used to, you may grab a part flagged thought: true and pull an empty string.
// ❌ May land on a thinking-trace part
const text = response.candidates[0].content.parts[0].text;
// ✅ Use the SDK-formatted text
const text = response.text;
// To inspect just the thinking trace
const thoughts = response.candidates?.[0]?.content?.parts
?.filter((p) => (p as any).thought)
?.map((p) => p.text)
?.join("\n");If you have thinkingConfig enabled on a Gemini 2.5 model, this is the bug you are most likely to file as "the SDK is broken" before realizing it is the cleanest API behavior in the release.
A practical detail when logging: do not dump the full response object to your structured logs. Thinking traces can balloon to thousands of tokens, and shipping them to a log aggregator like Datadog or Cloud Logging gets expensive fast. Log response.text and a token-count summary, and store the full response.candidates only when you are actively debugging.
Error 7: Edge runtimes (Cloudflare Workers) refuse to build
The new SDK trims down its Node dependencies, but a few internal helpers still touch node:crypto and node:stream. Deploy to Cloudflare Workers and you may see Module not found: node:stream at build time.
The quickest unblock is enabling nodejs_compat in wrangler.toml.
compatibility_flags = ["nodejs_compat"]
compatibility_date = "2026-03-01"If that still fails, drop down to a thin REST wrapper — call https://generativelanguage.googleapis.com/v1beta/models/...:generateContent with fetch() directly. On the edge, that is what I usually ship; the SDK overhead rarely pays off there.
A pattern I have settled on for hybrid stacks: keep the SDK in your Node-based backend (where you get type safety and ergonomic helpers), and use a hand-rolled fetch wrapper at the edge (where the runtime is tighter). Sharing a single SDK across both ends sounds neat in theory but tends to leak Node-specific dependencies into the edge bundle.
For deeper edge-specific issues like subrequest limits, see the Gemini API edge runtime subrequest limit troubleshooting article.
Three small habits that make migrations boring (in a good way)
A few things I started doing after the second messy migration. First, keep one snapshot test that hits the API with a fixed prompt before and after the migration. The new SDK handles system instructions slightly differently, and outputs can drift even when nothing visibly changed. A single golden file catches it.
Second, silence type errors with // @ts-expect-error first, then run the code. It separates "the types are wrong" from "the runtime is wrong" — and most of the time the runtime path is shorter to fix than the type maze.
Third, migrate one entry point at a time, behind a feature flag. If you have multiple call sites — chat, embeddings, function calling, batch — flip them one by one. The new SDK is generally more strict, so failures show up at the first call site you migrate. Migrating in a single sweeping PR makes it almost impossible to tell which call site is the actual culprit when something breaks in staging.
If you also hit install or version-pinning trouble during the upgrade, the Gemini SDK install and version error guide covers the npm side of the story.
The single fix that resolves most cascading errors
If your migration is sliding into a multi-hour fight, check one thing first: are you putting safetySettings, systemInstruction, and friends inside a config object at the top level of your request? If they are still nested in a generationConfig block, the SDK is silently dropping every one of them. Fix that, and most of the strange "the model isn't listening to me" symptoms tend to disappear at once.