Guide · Google

Migrate google-generativeai to google-genai (Python)

Google replaced the google-generativeai package with the Google Gen AI SDK, google-genai. Module-level configuration and GenerativeModel objects give way to one genai.Client whose services take the model name on every call.

The old repository is now named deprecated-generative-ai-python, and its README says all support ended permanently on November 30, 2025. The Gemini API docs only show the new SDK, and Google's migration guide maps each old call to its replacement. Both packages are imported as genai in most code (import google.generativeai as genai before, from google import genai after), which makes half-migrated files easy to miss. This guide follows Google's migration guide and the google-genai README, linked at the end.

What changed

  • Install google-genai instead of google-generativeai, and import from google import genai.
  • genai.configure(api_key=...) is replaced by client = genai.Client(), which reads GEMINI_API_KEY or GOOGLE_API_KEY.
  • genai.GenerativeModel(name) is gone. Pass model= on each call to client.models.generate_content().
  • generation_config, safety_settings and system_instruction move into one config=types.GenerateContentConfig(...) per request.
  • stream=True is replaced by a separate generate_content_stream() method.
  • Chats are created with client.chats.create(model=...) instead of model.start_chat().
  • Files, token counting and embeddings move onto the client: client.files, client.models.count_tokens, client.models.embed_content.

Migration steps

  1. Swap the package

    pip uninstall google-generativeai
    pip install -U google-genai

    Remove the old package from your requirements in the same change, so nothing keeps importing it by accident. The new imports are from google import genai and, for typed configuration, from google.genai import types.

  2. Create a client instead of configuring the module

    Before

    import google.generativeai as genai
    
    genai.configure(api_key=os.environ["GEMINI_API_KEY"])
    model = genai.GenerativeModel("gemini-flash-latest")
    response = model.generate_content("Tell me a story in 300 words")
    print(response.text)

    After

    from google import genai
    
    client = genai.Client()  # reads GEMINI_API_KEY or GOOGLE_API_KEY
    response = client.models.generate_content(
        model="gemini-flash-latest",
        contents="Tell me a story in 300 words",
    )
    print(response.text)

    genai.Client(api_key=...) also works. If both environment variables are set, GOOGLE_API_KEY takes precedence, so set only one. Code that created one GenerativeModel per model name now passes model= on each call, which usually means storing the model name in configuration rather than a model object. response.text stays a property in Python.

  3. Move settings into GenerateContentConfig

    Before

    model = genai.GenerativeModel(
        "gemini-flash-latest",
        system_instruction="you are a story teller for kids under 5 years old",
        generation_config=genai.GenerationConfig(
            max_output_tokens=400,
            temperature=0.5,
            response_mime_type="application/json",
        ),
    )
    response = model.generate_content("tell me a story in 100 words")

    After

    from google.genai import types
    
    response = client.models.generate_content(
        model="gemini-flash-latest",
        contents="tell me a story in 100 words",
        config=types.GenerateContentConfig(
            system_instruction="you are a story teller for kids under 5 years old",
            max_output_tokens=400,
            temperature=0.5,
            response_mime_type="application/json",
        ),
    )

    Safety settings move into the same object, as a list of types.SafetySetting. The old shorthand category names do not carry over: {"HATE": "BLOCK_ONLY_HIGH"} becomes types.SafetySetting(category="HARM_CATEGORY_HATE_SPEECH", threshold="BLOCK_ONLY_HIGH"). The README notes that every method also accepts plain dicts in place of the typed objects.

  4. Use the streaming method

    Before

    response = model.generate_content("Write a cute story about cats.", stream=True)
    for chunk in response:
        print(chunk.text)

    After

    for chunk in client.models.generate_content_stream(
        model="gemini-flash-latest",
        contents="Write a cute story about cats.",
    ):
        print(chunk.text)
  5. Create chats from the client

    Before

    model = genai.GenerativeModel("gemini-flash-latest")
    chat = model.start_chat()
    response = chat.send_message("Tell me a story in 100 words")
    response = chat.send_message("What happened after that?")

    After

    chat = client.chats.create(model="gemini-flash-latest")
    response = chat.send_message(message="Tell me a story in 100 words")
    response = chat.send_message(message="What happened after that?")
    
    for chunk in chat.send_message_stream("Now make it shorter"):
        print(chunk.text)
  6. Move files, tokens and embeddings onto the client

    • genai.upload_file(path="a11.txt") to client.files.upload(file="a11.txt")
    • genai.list_files() and genai.get_file(name=...) to client.files.list() and client.files.get(name=...)
    • genai.delete_file(name=...) to client.files.delete(name=...)
    • model.count_tokens(text) to client.models.count_tokens(model=..., contents=text)
    • genai.embed_content(model="models/gemini-embedding-001", content=text) to client.models.embed_content(model="gemini-embedding-001", contents=text)

    Note the renamed arguments: path= becomes file= and content= becomes contents=. Uploaded file objects can be passed straight into contents alongside text.

  7. Switch async code to client.aio, and update error handling

    Every service has an async twin under client.aio: await client.aio.models.generate_content(...), client.aio.models.generate_content_stream(...) and client.aio.chats.create(...). Errors from the service are raised as google.genai.errors.APIError, with the HTTP status in code:

    from google.genai import errors
    
    try:
        client.models.generate_content(model="invalid-model-name", contents="Hi")
    except errors.APIError as e:
        log.warning("Gemini error %s: %s", e.code, e.message)

How to find every affected call

Start from the imports, then look for each old entry point. These ast-grep patterns match the syntax tree, so multi-line calls are found too:

sg run --pattern 'import google.generativeai as $ALIAS' --lang python
sg run --pattern '$GENAI.configure($$$ARGS)' --lang python
sg run --pattern '$GENAI.GenerativeModel($$$ARGS)' --lang python
sg run --pattern '$MODEL.start_chat($$$ARGS)' --lang python
sg run --pattern '$GENAI.GenerationConfig($$$ARGS)' --lang python
sg run --pattern '$GENAI.upload_file($$$ARGS)' --lang python

Also search for generate_content($$$ARGS) calls that pass stream=True. Because both SDKs are commonly imported as genai, $GENAI.configure can match unrelated modules; confirm the import in each file before rewriting. On Linux, sg may be a system command; ast-grep run is the same tool.

Testing the change

  • Check that google-generativeai is gone from the lock file, not only from requirements.txt, so nothing imports it transitively.
  • Replace mocks of genai.GenerativeModel with a fake client passed into your code. Mocks of the old class keep passing against code that no longer calls it.
  • Run one real request per feature you use (plain, streamed, chat, JSON output) and one with safety settings, since the category names changed.
  • Force an error with an invalid model name and confirm your handler catches errors.APIError with code 404.

Automating it

Google does not publish a codemod for this migration. Its migration guide is a side-by-side table of old and new calls, and the google-genai README recommends loading Gemini API Skills into AI coding assistants so they generate code for the new SDK instead of the deprecated one.

Versyka finds the old call sites deterministically with ast-grep patterns like the ones above, rewrites them along with the code around them (config objects, chat handling, mocks), then runs your repository's own type check and pytest in an isolated virtualenv inside a disposable sandbox. It opens a pull request only if those pass.

Was this guide helpful?

Check your own repository

Paste your package.json, requirements.txt or pyproject.toml into the free upgrade check to see which API SDKs are behind, with no account. Or let Versyka run this migration on one repository and open a pull request that has already passed your tests.

Sources