Skip to content

Feat: Honor GOOGLE_GENAI_USE_ENTERPRISE in getExpressModeApiKey() (adk-python parity) - #569

Open
AmaadMartin wants to merge 5 commits into
google:mainfrom
AmaadMartin:feat/express-mode-enterprise-env-parity
Open

Feat: Honor GOOGLE_GENAI_USE_ENTERPRISE in getExpressModeApiKey() (adk-python parity)#569
AmaadMartin wants to merge 5 commits into
google:mainfrom
AmaadMartin:feat/express-mode-enterprise-env-parity

Conversation

@AmaadMartin

@AmaadMartin AmaadMartin commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Please ensure you have read the contribution guide before creating a pull request.

Link to Issue or Description of Change

  1. Link to an existing issue (if applicable):
    N/A — no existing issue.
  2. Or, if no issue exists, describe the change:
    Problem: ADK Python has moved the "am I running against the enterprise / Vertex AI surface?" decision off GOOGLE_GENAI_USE_VERTEXAI and onto GOOGLE_GENAI_USE_ENTERPRISE, keeping the old variable alive only as a deprecated fallback (src/google/adk/utils/env_utils.py::is_enterprise_mode_enabled()). ADK JS had no awareness of GOOGLE_GENAI_USE_ENTERPRISE at all: anyone who had already migrated their environment got undefined out of getExpressModeApiKey() and then hit Either (Project ID and Location) or an expressModeApiKey is required. from VertexAiSessionService / VertexAiMemoryBankService, with nothing pointing at the variable name as the cause.

Solution: Add isEnterpriseModeEnabled() next to getBooleanEnvVar in core/src/utils/env_aware_utils.ts, resolving the surface with the same presence-ordered algorithm as Python:

  1. If GOOGLE_GENAI_USE_ENTERPRISE is present, its boolean value decides — GOOGLE_GENAI_USE_VERTEXAI is never consulted and nothing is logged.
  2. Else, if GOOGLE_GENAI_USE_VERTEXAI is present, log a deprecation warning and use its boolean value.
  3. Else, enterprise mode is off.

Precedence is by presence, not truthiness: GOOGLE_GENAI_USE_ENTERPRISE=false means off and must not fall through to a stale GOOGLE_GENAI_USE_VERTEXAI=true. This mirrors Python's if 'GOOGLE_GENAI_USE_ENTERPRISE' in os.environ.

Every place that reads the variable now goes through that helper (review feedback). The first revision converted only getExpressModeApiKey, which left the repo inconsistent: GOOGLE_GENAI_USE_ENTERPRISE=true produced an express-mode key while getGoogleLlmVariant() still reported GEMINI_API and geminiInitParams() still set vertexai: false. Converted call sites:

  • core/src/utils/vertex_ai_utils.tsgetExpressModeApiKey()
  • core/src/utils/variant_utils.tsgetGoogleLlmVariant()
  • core/src/models/google_llm.tsgeminiInitParams(), which ApigeeLlm also routes through

After this change no GOOGLE_GENAI_USE_VERTEXAI read remains outside the helper.

Notes on the details:

  • The deprecation warning is logged once per process. getGoogleLlmVariant() is reached from BaseTool.apiVariant on every request, so warning unconditionally would emit a line per tool per request for everyone still on the legacy variable. Python's warnings.warn(..., DeprecationWarning) is deduplicated by the default filter, so once-per-process is the parity behaviour rather than a shortcut. It costs one module-level flag; the helper's tests reload the module (and the logger it warns to) per test so the flag cannot leak between them.
  • Two places that write the variable are deliberately left alone: dev/src/cli/cli_create.ts (the .env generated by adk create) and dev/src/cli/deploy/deploy_utils.ts (ENV GOOGLE_GENAI_USE_VERTEXAI=1 in the generated Dockerfile). They emit rather than read, so they cannot use the helper, and renaming what they emit is not backwards compatible: the generated image installs @google/adk-devtools@latest but takes @google/adk from the user's copied package.json / node_modules, so the container can run a core that only understands the legacy name — and since geminiInitParams forwards the resolved value to the SDK as an explicit vertexai flag, the SDK's own GOOGLE_GENAI_USE_ENTERPRISE support cannot cover for it. Both keep working unchanged (they take the deprecated path). ADK Python does emit the new name from its scaffolding; happy to follow suit here in a separate change once a release that reads the new variable is out, or to emit both names — let me know which you prefer.
  • Not a breaking change: GOOGLE_GENAI_USE_VERTEXAI still selects the enterprise surface everywhere it did before, it just also logs one deprecation warning. The single intentional behaviour change is that an environment setting both variables to conflicting values now resolves to GOOGLE_GENAI_USE_ENTERPRISE — the same precedence @google/genai (^2.9.0) applies internally.
  • No new module, no new exported package surface (isEnterpriseModeEnabled is package-internal, like getBooleanEnvVar), and no new dependency.

Testing Plan

Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.
Unit Tests:
[x] I have added or updated unit tests for my change.
[x] All unit tests pass locally.

New coverage, per call site:

  • core/test/utils/env_aware_utils_test.ts — the helper itself: neither variable set, the new variable enabled, both precedence directions, set-but-empty (proves precedence is by presence, not truthiness), the deprecated fallback when enabled and when disabled (both warn), and warn-once across repeated reads.
  • core/test/utils/variant_utils_test.tsgetGoogleLlmVariant() honours the new variable, and a disabled new variable beats an enabled legacy one.
  • core/test/models/google_llm_test.ts — the same two cases through geminiInitParams().
  • core/test/utils/vertex_ai_utils_test.ts — the same two cases through getExpressModeApiKey(), plus the set-but-empty case.

All 13 new cases fail against the unmodified core/src and pass with it (verified by reverting core/src and re-running). New lines and branches in env_aware_utils.ts are at 100% statements / branches / functions / lines.

Pre-existing tests were all kept, and edited only where this change makes them non-deterministic or where they were vacuous — disclosed explicitly:

  • variant_utils_test.ts, vertex_ai_utils_test.ts, google_llm_test.ts and apigee_llm_test.ts now clear GOOGLE_GENAI_USE_ENTERPRISE in setup/teardown. Without that, an ambient value of the new variable — including '' or false — silently flips those suites, because precedence is by presence. variant_utils_test.ts also moves from replacing process.env wholesale to per-key assignment for the same reason; no assertion changed.
  • Two vertex_ai_utils_test.ts cases gained a GOOGLE_API_KEY value, which strengthens assertions that previously passed for the wrong reason (they returned undefined because no key existed, not because the surface was off).
npx vitest run --project unit:core \
  core/test/utils/env_aware_utils_test.ts \
  core/test/utils/variant_utils_test.ts \
  core/test/utils/vertex_ai_utils_test.ts \
  core/test/models/google_llm_test.ts \
  core/test/models/apigee_llm_test.ts \
  core/test/sessions/vertex_ai_session_service_test.ts \
  core/test/memory/vertex_ai_memory_bank_service_test.ts
#   175 passed

npm run build          # ok
npm run lint           # ok
npm run format:check   # ok

Manual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.

The helper does no I/O, so the unit tests drive it with real environment variables and no mocks. To observe it by hand against the built package (npm run build first) — each command below was run on this branch and the output shown is the output produced:

RUN="node --input-type=module -e
  import {getExpressModeApiKey} from './core/dist/esm/utils/vertex_ai_utils.js';
  import {getGoogleLlmVariant} from './core/dist/esm/utils/variant_utils.js';
  console.log(getExpressModeApiKey(), getGoogleLlmVariant());"

# 1. New variable: express-mode key resolved, VERTEX_AI surface, no warning.
GOOGLE_GENAI_USE_ENTERPRISE=true GOOGLE_API_KEY=demo-key $RUN
# demo-key VERTEX_AI

# 2. Deprecated variable: same behaviour, plus ONE warning even though two
#    separate call sites read the variable in this process.
GOOGLE_GENAI_USE_VERTEXAI=true GOOGLE_API_KEY=demo-key $RUN
# WARN: [ADK] GOOGLE_GENAI_USE_VERTEXAI is deprecated, please use GOOGLE_GENAI_USE_ENTERPRISE instead
# demo-key VERTEX_AI

# 3. Explicit opt-out beats a stale deprecated variable, and stays silent.
GOOGLE_GENAI_USE_ENTERPRISE=false GOOGLE_GENAI_USE_VERTEXAI=true GOOGLE_API_KEY=demo-key $RUN
# undefined GEMINI_API

# 4. Neither variable set.
GOOGLE_API_KEY=demo-key $RUN
# undefined GEMINI_API

Checklist

[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas.
[x] I have added tests that prove my fix is effective or that my feature works.
[x] New and existing unit tests pass locally with my changes.

Amaad Martin added 2 commits July 29, 2026 11:04
Express mode key resolution only recognized GOOGLE_GENAI_USE_VERTEXAI, so
environments already migrated to GOOGLE_GENAI_USE_ENTERPRISE got undefined and
then failed with "Either (Project ID and Location) or an expressModeApiKey is
required."

Resolve enterprise mode by presence, matching adk-python's
is_enterprise_mode_enabled(): GOOGLE_GENAI_USE_ENTERPRISE decides whenever it is
set (even to a falsy value), GOOGLE_GENAI_USE_VERTEXAI is only consulted when
the new variable is absent and logs a deprecation warning.
Drop cases that re-tested getBooleanEnvVar's value parser (already covered by
env_aware_utils_test.ts) and fold the deprecation-warning assertions into the
existing GOOGLE_GENAI_USE_VERTEXAI tests instead of duplicating them. Coverage
of vertex_ai_utils.ts stays at 100% statements/branches/functions/lines.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GOOGLE_GENAI_USE_VERTEXAI used all over the code source, please update all the references to use isEnterpriseModeEnabled instead

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — every read of GOOGLE_GENAI_USE_VERTEXAI now goes through isEnterpriseModeEnabled.

I moved the helper out of vertex_ai_utils.ts and exported it from core/src/utils/env_aware_utils.ts, right next to getBooleanEnvVar (this mirrors adk-python, where is_enterprise_mode_enabled and is_env_enabled both live in utils/env_utils.py). Converted call sites:

  • core/src/utils/vertex_ai_utils.tsgetExpressModeApiKey()
  • core/src/utils/variant_utils.tsgetGoogleLlmVariant()
  • core/src/models/google_llm.tsgeminiInitParams(), which ApigeeLlm also routes through

No GOOGLE_GENAI_USE_VERTEXAI read remains outside the helper. You were right that the first revision was incoherent: with only getExpressModeApiKey converted, GOOGLE_GENAI_USE_ENTERPRISE=true returned an express-mode key while getGoogleLlmVariant() still reported GEMINI_API and geminiInitParams() still set vertexai: false.

Two things I did differently than a literal sweep — please push back if you disagree:

  1. The deprecation warning is now emitted once per process. getGoogleLlmVariant() is reached from BaseTool.apiVariant on every request, so warning on each read would print a line per tool per request for everyone still on the legacy variable. Python's warnings.warn(..., DeprecationWarning) is deduplicated by the default filter, so once-per-process is the parity behaviour; it costs one module-level flag, and the helper's tests reload the module (and its logger) per test so the flag cannot leak between them.

  2. I did not touch the two places that write the variabledev/src/cli/cli_create.ts (the .env from adk create) and dev/src/cli/deploy/deploy_utils.ts (ENV GOOGLE_GENAI_USE_VERTEXAI=1 in the generated Dockerfile). They emit rather than read, so they can't use the helper, and renaming what they emit is not backwards compatible: the generated image installs @google/adk-devtools@latest but takes @google/adk from the user's copied package.json/node_modules, so the container can run a core that only understands the old name — and because geminiInitParams forwards the resolved value to the SDK as an explicit vertexai flag, the SDK's own GOOGLE_GENAI_USE_ENTERPRISE support can't cover for it. adk-python does emit the new name from its scaffolding, so I'm happy to follow suit — either in this PR, or as a follow-up once a release that reads the new variable is out, or by emitting both names. Your call.

Tests: new cases per call site in env_aware_utils_test.ts, variant_utils_test.ts, google_llm_test.ts and vertex_ai_utils_test.ts (13 in total, each verified to fail against the unmodified core/src), including warn-once on repeated reads. The four suites that touch these variables now also clear GOOGLE_GENAI_USE_ENTERPRISE in setup — since precedence is by presence, an ambient value of any kind would otherwise flip them.

Amaad Martin added 3 commits July 30, 2026 22:44
…deEnabled

Reviewer feedback on the express-mode change: GOOGLE_GENAI_USE_VERTEXAI is read
in several places, so resolving it in getExpressModeApiKey() alone left the rest
of the repo on the deprecated variable — GOOGLE_GENAI_USE_ENTERPRISE=true would
hand back an express-mode key while getGoogleLlmVariant() still reported
GEMINI_API and geminiInitParams() still set vertexai=false.

Hoist isEnterpriseModeEnabled() next to getBooleanEnvVar in env_aware_utils.ts
and use it from every reader: vertex_ai_utils, variant_utils and google_llm. The
adk create / cloud run scaffolding now writes GOOGLE_GENAI_USE_ENTERPRISE too,
so generated projects no longer trip the deprecation warning; @google/genai
^2.9.0 resolves that variable with the same precedence, so the SDK's own
detection still works.
Revert the adk create / cloud run scaffolding to writing
GOOGLE_GENAI_USE_VERTEXAI. Those two sites emit the variable rather than read
it, and the generated Dockerfile installs the dev server fresh while taking
@google/adk from the user's copied node_modules, so a container can run a core
that only understands the legacy name. Since geminiInitParams forwards the
resolved value to the SDK as an explicit vertexai flag, the SDK's own
GOOGLE_GENAI_USE_ENTERPRISE support cannot cover for it, and the deploy would
fail at startup.

Also clear GOOGLE_GENAI_USE_ENTERPRISE in the apigee suite, which drives the
same env branch, and drop the deprecation-warning assertions from the
getExpressModeApiKey tests now that the helper owns that behavior and tests it
directly.
getGoogleLlmVariant() is read per request (BaseTool.apiVariant), so routing it
through isEnterpriseModeEnabled turned the deprecation notice into a log line
per tool per request for everyone still on GOOGLE_GENAI_USE_VERTEXAI. Guard it
with a module-level flag, matching Python's warnings.warn, which the default
filter emits once per location.

The helper's tests reload the module (and the logger it warns to) per test so
the flag cannot leak between them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants