Skip to content

fix(gemini): normalize JSON Schema union types for Vertex compatibility#3150

Open
Vaibhav701161 wants to merge 1 commit intomainfrom
fix/vertex-union-type-schema
Open

fix(gemini): normalize JSON Schema union types for Vertex compatibility#3150
Vaibhav701161 wants to merge 1 commit intomainfrom
fix/vertex-union-type-schema

Conversation

@Vaibhav701161
Copy link
Copy Markdown
Contributor

Summary

This change fixes an incompatibility between Bifrost and Vertex AI when handling JSON Schema union types in tool and function parameters.

Previously, schemas containing union types such as:

"type": ["integer", "null"]

were not correctly normalized before being sent to the Gemini or Vertex provider. This resulted in invalid schemas where the type field was effectively missing, causing Vertex to reject requests with a 400 error.

This PR introduces proper normalization of union types to ensure compatibility with Gemini and Vertex AI.


Previous Behavior

Bifrost assumed that the JSON Schema type field was always a string. When an array was provided:

"type": ["integer", "null"]

the conversion logic failed silently because:

  • The type assertion for string did not match
  • No fallback handling existed for arrays
  • The resulting schema had an empty type field

This led to requests being rejected by Vertex with errors such as:

parameters. schema didn't specify the schema type field

As a result:

  • Tool-based requests using nullable or union types failed
  • Compatibility with OpenAPI-style schemas and external tools like Goose was broken

Current Behavior

The schema conversion logic now correctly handles JSON Schema union types.

Supported transformations:

  • "type": "integer"
    -> unchanged

  • "type": ["integer", "null"]
    -> type: "integer", nullable: true

  • "type": ["string", "null"]
    -> type: "string", nullable: true

  • "type": ["integer", "string"]
    -> anyOf: [{type: "integer"}, {type: "string"}]

  • "type": ["integer", "string", "null"]
    -> anyOf: [...], nullable: true

  • "type": ["null"]
    -> type: "null"

Additional improvements:

  • Duplicate types are deduplicated
  • Order of types does not affect output
  • Invalid non-string entries are ignored safely

Validation

The fix has been validated through both unit tests and real end-to-end requests.

Unit Tests

  • Coverage for all union type cases
  • Edge cases including ordering and duplicates
  • End-to-end tool schema conversion validation

End-to-End Verification

A real request using:

"type": ["integer", "null"]

was successfully processed through Bifrost and accepted by Gemini (gemini-2.5-flash), resulting in a valid tool call:

"tool_calls": [
{
"function": {
"name": "get_process",
"arguments": {
"pid": 42,
"timeout_secs": null
}
}
}
]

This confirms:

  • Schema is correctly normalized
  • Vertex accepts the transformed schema
  • Tool execution works as expected

(See attached screenshot)

image

Impact

  • Fixes a compatibility issue with Vertex AI
  • Enables support for OpenAPI-style schemas with nullable and union types
  • Improves interoperability with external tooling that emits JSON Schema arrays
  • No breaking changes to existing behavior
  • No observable impact on performance

Type of change

  • Bug fix

Affected areas

  • Core (Go)
  • Providers/Integrations

How to test

execute:

curl -s -X POST http://localhost:9090/v1/chat/completions
-H "Content-Type: application/json"
-d '{
"model": "gemini/gemini-2.5-flash",
"messages": [
{
"role": "user",
"content": "Call get_process with pid 42 and timeout_secs null"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_process",
"parameters": {
"type": "object",
"properties": {
"pid": { "type": "integer" },
"timeout_secs": { "type": ["integer", "null"] }
},
"required": ["pid"]
}
}
}
],
"tool_choice": "auto"
}'

Expected:

  • Response contains tool_calls
  • No schema validation error
  • timeout_secs handled as null

Breaking changes

  • No

Related issues

Fixes: #3141


Security considerations

No changes to authentication, data handling, or external interfaces. Schema normalization is internal.


Checklist

  • Tests added and passing
  • No regressions observed
  • Verified with real provider
  • Builds succeed

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e09badba-3844-46d1-b388-bc0cbe445583

📥 Commits

Reviewing files that changed from the base of the PR and between 5d9577d and fdd2c76.

📒 Files selected for processing (2)
  • core/providers/gemini/uniontype_test.go
  • core/providers/gemini/utils.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/providers/gemini/utils.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added support for JSON Schema union types in Gemini conversion: single non-null + null becomes a nullable primitive, multi-type unions use anyOf, null-only maps to a null type, and redundant types are deduplicated.
  • Tests

    • Added comprehensive tests covering conversion, serialization, nullable handling, deduplication, and edge cases.

Walkthrough

Adds JSON Schema union-type handling to the Gemini provider by updating convertPropertyToSchema to accept array type values and normalize them into Gemini schema fields; includes a comprehensive test suite validating conversions and final wire-level JSON serialization.

Changes

Cohort / File(s) Summary
Union Type Handling Logic
core/providers/gemini/utils.go
convertPropertyToSchema now accepts type as arrays ([]interface{} / []string), deduplicates non-"null" members, tracks "null" presence, maps single non-null + null to schema.Type + schema.Nullable=true, maps multiple non-null types to schema.AnyOf (with optional Nullable), and treats ["null"] as TypeNULL. Non-string elements are skipped.
Union Type Test Suite
core/providers/gemini/uniontype_test.go
New tests covering: single non-null + null conversion, null ordering invariance, multi-type unions producing anyOf, deduplication of union members, handling of invalid/unusable union elements, Go caller []string unions, end-to-end tool conversion, and wire-level JSON asserting "type":"<primitive>" + "nullable":true (never array "type") or "anyOf":[...] for multi-type unions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibble types both small and wide,
Turn arrays to strings with nullable pride,
AnyOf streams where many paths meet,
Nulls tucked gently — the schema's complete,
Hoppity hop, the wire JSON is neat.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly describes the main change: normalizing JSON Schema union types for Vertex compatibility, which is directly supported by both the code changes and the PR description.
Description check ✅ Passed The PR description is comprehensive and follows the template structure, including summary, changes, type of change, affected areas, testing instructions, related issues, and security considerations.
Linked Issues check ✅ Passed The PR directly addresses issue #3141 by implementing schema normalization for union types (arrays in the 'type' field), enabling transparent interoperability with Goose and other tools using OpenAPI-style schemas.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the union type normalization problem: core logic in utils.go and comprehensive test coverage in uniontype_test.go, with no unrelated modifications.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/vertex-union-type-schema

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.11.4)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Suite Available

This PR can be tested by a repository admin.

Run tests for PR #3150

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 29, 2026

Confidence Score: 5/5

Safe to merge — the change is well-scoped to the union-type normalization path with no impact on existing string-typed schemas.

No P0/P1 findings. The type-switch pattern (case []interface{}, []string: with v as interface{}) correctly delegates to extractUnionTypes which has its own type switch. Recursive property/items/anyOf paths all call convertPropertyToSchema, so the fix propagates automatically. Tests cover all stated edge cases including wire-payload serialization.

No files require special attention.

Important Files Changed

Filename Overview
core/providers/gemini/utils.go Adds extractUnionTypes and applyUnionType helpers, and updates convertPropertyToSchema to handle array-typed type fields (JSON Schema union types) for Vertex AI compatibility. Logic is correct, recursive property/items/anyOf paths all flow through the same function so the fix is transitively applied.
core/providers/gemini/uniontype_test.go New test file with comprehensive coverage: unit tests for extractUnionTypes, convertPropertyToSchema, and an end-to-end wire-payload test using MarshalSorted. All documented cases (nullable, anyOf, null-only, deduplicated types, all-invalid elements) are covered.

Reviews (2): Last reviewed commit: "fix(gemini): normalize JSON Schema union..." | Re-trigger Greptile

Comment thread core/providers/gemini/utils.go Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/providers/gemini/utils.go`:
- Around line 1265-1322: The union-type handling in the propType switch should
not assume propType is only []interface{} nor treat arrays of all-unparseable
elements as null; update the case to reuse extractTypesFromValue to parse both
[]interface{} and []string (or any slice of strings) into nonNullTypes and
hasNull, deduplicate properly, and only set schema.Type = TypeNULL when hasNull
is true (instead of when len(nonNullTypes)==0 regardless of hasNull); for
multiple non-null types set schema.AnyOf as before and preserve Nullable when
hasNull is true—modify the code around the propType branch that currently builds
nonNullTypes/hasNull (and references schema.Type, schema.Nullable, schema.AnyOf)
to call extractTypesFromValue and apply these rules.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 57e4be28-1a49-49b1-b01f-df1d18a29915

📥 Commits

Reviewing files that changed from the base of the PR and between 36443c5 and 5d9577d.

📒 Files selected for processing (2)
  • core/providers/gemini/uniontype_test.go
  • core/providers/gemini/utils.go

Comment thread core/providers/gemini/utils.go Outdated
- Handle ["T", "null"] -> Type + Nullable
- Convert multi-type unions -> anyOf
- Deduplicate type arrays
- Prevent empty Type field causing Vertex 400 error
- Support both []interface{} and []string union type inputs
- Fix: only emit TypeNULL when "null" is explicitly present (not on all-invalid arrays)
- Refactor type extraction into reusable extractUnionTypes + applyUnionType helpers
- Add comprehensive unit tests + e2e validation

Addresses review feedback from Greptile and CodeRabbit.
Fixes issue: Vertex rejects tool schemas with union type arrays
@Vaibhav701161 Vaibhav701161 force-pushed the fix/vertex-union-type-schema branch from 5d9577d to fdd2c76 Compare April 29, 2026 20:17
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.

[Bug]: Using Goose with Bifrost fails due timeout_secs being an array

1 participant