Skip to content

Fix: match adk-python in the Gemini 3.x Live model predicate - #715

Open
AmaadMartin wants to merge 2 commits into
mainfrom
fix/gemini-3x-live-predicate-parity
Open

Fix: match adk-python in the Gemini 3.x Live model predicate#715
AmaadMartin wants to merge 2 commits into
mainfrom
fix/gemini-3x-live-predicate-parity

Conversation

@AmaadMartin

Copy link
Copy Markdown
Owner

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):

None.

  1. Or, if no issue exists, describe the change:

Problem: isGemini3xFlashLive matches the literal -flash-live, so it returns false for gemini-3.5-flash-lite-live-preview. adk-python matches the substring -live and returns True for that id. adk-python also routes gemini-3.5-live-translate* out of the live predicate and into a separate public predicate, which adk-js does not have. The same model therefore drives a different live-session protocol depending on which SDK the user picked.

Solution: I renamed the predicate to isGemini3xLive, widened the match to -live, and added isGemini35LiveTranslate for the exclusion. The two parts must ship together: widening the substring alone would make gemini-3.5-live-translate match. Reference: adk-python src/google/adk/utils/model_name_utils.py, _is_gemini_3_x_live (line 187) and is_gemini_3_5_live_translate (line 206). The mirrored shape is kept, including passing the raw modelString to the second predicate.

BREAKING CHANGE: isGemini3xFlashLive is renamed to isGemini3xLive. Migration: replace isGemini3xFlashLive with isGemini3xLive. There is no deprecated alias. This repo renamed the same symbol the same way before, isGemini31FlashLive -> isGemini3xFlashLive in google#409, with no alias. The old name is now actively misleading, because the predicate matches non-flash live models.

Behaviour changes for gemini-3.5-flash-lite-live-preview (and any gemini-3.* live id without -flash-live), which flips false -> true:

  • sendHistory: turnComplete is now always true instead of "last content role is user".
  • sendContent: a single text part goes out through sendRealtimeInput({text}) instead of sendClientContent.
  • sendRealtime: audio/* goes to sendRealtimeInput({audio}) and image/* to sendRealtimeInput({video}) instead of sendRealtimeInput({media}). An unknown mime type is now warned about and dropped.
  • LiveResponseAggregator: tool calls are yielded on toolCall instead of buffered until turnComplete.

Nothing else changes. gemini-3.5-live-translate* is false before and after, and a new test pins that. gemini-3.1-flash-live, gemini-2.5-flash-live and every non-Gemini model keep their old value.

Parity note: adk-python also routes live-translate audio and video through send_realtime_input in gemini_llm_connection.py:172. That is a call-site change, not a predicate change, so it is out of scope here and is tracked separately.

Collision check: I listed all 710 pull requests on the fork and diffed every one that touches core/src/utils/model_name.ts (#372, #471, #615, #617, #618). None of them touches the live predicate; they change extractModelName, the Gemini-1 boundary and the EAP gate. git log --all -S finds no commit that introduces isGemini3xLive or isGemini35LiveTranslate.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.
npx vitest run --project unit:core \
  core/test/utils/model_name_test.ts \
  core/test/models/gemini_llm_connection_test.ts \
  core/test/utils/live_connection_utils_test.ts
# Test Files 3 passed (3) / Tests 80 passed (80)

New line and branch coverage on core/src/utils/model_name.ts lines 106-131 is 100%, measured with @vitest/coverage-v8.

The edits to the existing model_name_test.ts and gemini_llm_connection_test.ts blocks are a symbol rename and one title rename. No existing assertion or expected value changed. All new coverage is in new it blocks.

I proved the new tests can fail with two mutations of core/src/utils/model_name.ts.

Mutation 1, restore the old narrow substring (startsWith('gemini-3.') && includes('-flash-live')). Five tests failed:

× isGemini3xLive > should return true for non-flash Gemini 3.x Live models
  → expected false to be true
× LiveResponseAggregator > should yield tool calls immediately for non-flash Gemini 3.x Live
  → expected [] to deeply equal [ { content: { …(2) }, …(1) } ]
× sendHistory > should send history with turnComplete=true for non-flash Gemini 3.x Live
  → expected "spy" to be called with arguments: [ { …(2) } ]
× sendContent > should use sendRealtimeInput for non-flash Gemini 3.x Live single-part text
  → expected "spy" to be called with arguments: [ { text: 'hello' } ]
× sendRealtime > should use sendRealtimeInput with audio for non-flash Gemini 3.x Live audio
  → expected "spy" to be called with arguments: [ { audio: { …(2) } } ]

Mutation 2, delete only && !isGemini35LiveTranslate(modelString). Two tests failed:

× isGemini3xLive > should return false for Live Translate models
  → expected true to be false
× sendRealtime > should use sendRealtimeInput with media for Live Translate audio
  → expected "spy" to be called with arguments: [ { media: { …(2) } } ]

Mutation 2 is invisible to the old narrow code path. It only becomes reachable once the substring is widened, which is why both parts ship in one change.

Other local checks on the pushed commit:

npm run build          # pass
npm run docs:check     # pass (typedoc --treatWarningsAsErrors)
npx prettier --check   # pass, on all 7 changed files
npx eslint             # pass, on all 7 changed files
npx tsc --noEmit       # 280 errors, all pre-existing

npm run ts:check does not pass on main either. It reports 280 errors on this checkout before and after my change, so my change adds none. The cause is the @google/adk test imports resolving to dist.

Manual End-to-End (E2E) Tests:

Not applicable. Both functions are pure string predicates with no I/O. Exercising gemini-3.5-flash-lite-live-preview end to end needs live model access. tests/e2e/live_model_test.ts pins gemini-3.1-flash-live-preview-04-2026, whose classification this change does not alter.

Checklist

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

Amaad Martin added 2 commits August 6, 2026 04:01
isGemini3xFlashLive only matched the literal '-flash-live', so
gemini-3.5-flash-lite-live-preview was not classified as a Gemini 3.x Live
model. adk-python matches the substring '-live' and routes
gemini-3.5-live-translate* to a separate predicate.

Rename the predicate to isGemini3xLive, widen the match, and add
isGemini35LiveTranslate for the exclusion. Both changes must ship together:
widening the substring alone would make live-translate match.

Reference: adk-python src/google/adk/utils/model_name_utils.py,
_is_gemini_3_x_live and is_gemini_3_5_live_translate.

BREAKING CHANGE: isGemini3xFlashLive is renamed to isGemini3xLive.
…e exclusion

Add regression cases for gemini-3.5-flash-lite-live-preview at the predicate and
at all four call sites, and for the gemini-3.5-live-translate exclusion. Add a
describe block for isGemini35LiveTranslate mirroring adk-python
TestIsGemini35LiveTranslate.

All new coverage is in new it blocks; no existing assertion changed.
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.

1 participant