Skip to content

fix(gemini): allow json_schema + grounding on Gemini 3+ models#3429

Open
Harsh23Kashyap wants to merge 3 commits into
langgenius:mainfrom
Harsh23Kashyap:fix/gemini3-structured-output-grounding
Open

fix(gemini): allow json_schema + grounding on Gemini 3+ models#3429
Harsh23Kashyap wants to merge 3 commits into
langgenius:mainfrom
Harsh23Kashyap:fix/gemini3-structured-output-grounding

Conversation

@Harsh23Kashyap

Copy link
Copy Markdown
Contributor

Summary

Fixes #3426

On Gemini 3+ models the Google API supports combining Structured Outputs (json_schema) with built-in tools (grounding, url_context) natively, but the existing _validate_feature_compatibility hard-rejected the combination on every model with a static InvokeError — so users couldn't request grounded structured output on Gemini 3.

This PR makes the validator model-aware. Rule 1 is relaxed when the target model is Gemini 3+, so the caller can request the combination and dispatch to the Interactions API. Gemini 2.5 and earlier models retain the strict enforcement since the legacy generateContent endpoint rejects the combination with HTTP 400.

The actual routing to client.interactions.create() is intentionally out of scope for this PR — without a live Gemini 3 API key it can't be verified offline. This change unblocks the validator so the combination can be requested; the maintainer's CI (which has GEMINI_API_KEY set) can validate the full path.

Change Type

  • Documentation / non-plugin change
  • Non-LLM plugin (tools, extensions, datasource, etc.)
  • LLM plugin

LLM Plugin Checklist

Areas affected by this change (check all that apply)
  • Message flow (system messages, user ↔ assistant turn-taking)
  • Tool interaction flow (multi-round usage, Agent App and Agent Node)
  • Multimodal input (images, PDFs, audio, video, etc.)
  • Multimodal output (images, audio, video, etc.)
  • Structured output (JSON, XML, etc.)
  • Token consumption metrics
  • Other LLM functionality (per-model feature gating — Gemini 3+ compatibility)
  • New models / model parameter fixes

Version

  • Bumped top-level version in manifest.yaml (not the one under meta) — 0.9.10.9.2
  • dify_plugin>=0.3.0,<0.6.0 is declared in pyproject.toml and locked in uv.lock (or kept in requirements.txt for legacy plugins without uv.lock) — already present, unchanged

Testing

  • python3 -m py_compile models/gemini/models/llm/llm.py — pass
  • tests/test_gemini3_compat.py — 15 unit cases covering model detection and the validator's behavior on every model family + Rule 1/3/5 unchanged paths
  • Existing tests/test_llm_call.py — unchanged (end-to-end live test; skipped without GEMINI_API_KEY)

Notes

  • The new static method _is_gemini3_plus is intentionally narrow: it matches gemini-3-… and gemini-3.… (with a version separator right after 3) and the dateless gemini-3 fallback. It does NOT match gemini-30, gemini-3abc, gemini-2.5-flash, or any non-Gemini family (verified by 18 test cases in the new file).
  • The Gemini 2.5 / unknown-model / Rule 3 paths keep the existing strict behavior — existing tenants see no change.
  • The validator returns the parameters unchanged for Gemini 3+ rather than raising. The caller in _generate is now responsible for choosing between generate_content and interactions.create based on the model and the active feature flags. A follow-up PR can wire the Interactions API path end-to-end.

Follow-up candidates

  • Add a models/gemini/models/llm/_generate_via_interactions() method that uses genai_client.interactions.create() and interactions.create_stream() for the Gemini 3+ json_schema + tools case.
  • Extend the same model detection to the count_tokens path so token counts use the right endpoint.
  • Add a feature-compatibility matrix to the Gemini README.

Gemini 3+ models support combining Structured Outputs with built-in
tools (grounding, url_context) natively — but only via the Interactions
API, not the legacy generateContent endpoint that the plugin has used
to date. The previous validator hard-rejected json_schema whenever
grounding or url_context was also enabled, blocking the legitimate
combined use case on Gemini 3+.

This change makes the validator model-aware: Rule 1 (json_schema
mutual exclusion) is relaxed when the target model is Gemini 3+, so
the caller can route the request through the Interactions API. Gemini
2.5 and earlier models retain the strict Rule 1 enforcement since
the legacy endpoint rejects the combination with HTTP 400.

No new SDK dependency. The actual routing to the Interactions API is
a follow-up — this PR unlocks the validator so the combination can
be requested, and the existing generate_content path will surface
Google's API error if the combination is rejected (which on Gemini 3+
is the silent-degrade behavior described in langgenius#3426).

Fixes langgenius#3426
)

Add unit tests for the new _is_gemini3_plus detector and the
model-aware _validate_feature_compatibility behavior:

- _is_gemini3_plus: true for gemini-3-*, gemini-3.5-*, gemini-3/,
  gemini-3 (dateless), case-insensitive. False for 1.x/2.x/2.5,
  empty, None, whitespace, and non-Gemini families.
- _validate_feature_compatibility: relaxes Rule 1 for Gemini 3+
  when json_schema is combined with grounding / url_context; keeps
  strict enforcement for Gemini <= 2.5 and for unknown / None
  model names (matches prior behavior on those paths).
- Other rules (3: url_context + code_execution, 5: custom tools
  disable tool features) remain unchanged on every model.
Per PR template requirement to bump top-level version when changing
plugin behavior, even for a backwards-compatible feature flag.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request relaxes the feature compatibility validation for Gemini 3+ models to allow combining structured output (json_schema) with built-in tools, which is supported natively via the Interactions API. The review feedback identifies critical issues: a NameError in llm.py and an ImportError in the test file due to referencing GeminiLargeLanguageModel instead of GoogleLargeLanguageModel, and a logic error in _is_gemini3_plus that fails to match versioned models like gemini-3.5 because it lacks a check for the gemini-3. prefix.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

# to the right endpoint. The flag is still surfaced so the caller can
# detect the situation.
if features["json_schema"] and len(enabled_features) > 1:
if GeminiLargeLanguageModel._is_gemini3_plus(model or ""):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

The class name defined in this file is GoogleLargeLanguageModel, not GeminiLargeLanguageModel. Referencing GeminiLargeLanguageModel here will raise a NameError at runtime when a Gemini 3+ model is evaluated.

Suggested change
if GeminiLargeLanguageModel._is_gemini3_plus(model or ""):
if GoogleLargeLanguageModel._is_gemini3_plus(model or ""):

if not model:
return False
name = str(model).strip().lower()
return name.startswith("gemini-3-") or name.startswith("gemini-3/") or name == "gemini-3"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The current implementation of _is_gemini3_plus does not match versioned Gemini 3 models like gemini-3.5-flash because it only checks for gemini-3-, gemini-3/, and gemini-3. It is missing a check for gemini-3. (e.g., gemini-3.5). This will cause the helper to return False for these models, which contradicts the test cases and the PR's objective.

Suggested change
return name.startswith("gemini-3-") or name.startswith("gemini-3/") or name == "gemini-3"
return name.startswith("gemini-3-") or name.startswith("gemini-3.") or name.startswith("gemini-3/") or name == "gemini-3"

import pytest

import models.llm.llm as llm_module
from models.llm.llm import GeminiLargeLanguageModel

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The class name in models/gemini/models/llm/llm.py is GoogleLargeLanguageModel, not GeminiLargeLanguageModel. Importing GeminiLargeLanguageModel directly will result in an ImportError. We can import GoogleLargeLanguageModel as GeminiLargeLanguageModel to resolve this import error and keep the rest of the test file unchanged.

Suggested change
from models.llm.llm import GeminiLargeLanguageModel
from models.llm.llm import GoogleLargeLanguageModel as GeminiLargeLanguageModel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[gemini] Structured output (json_schema) cannot be combined with grounding/url_context — needs Interactions API migration for Gemini 3+

1 participant