Skip to content

fix(gemini): normalize nullable and literal tool schemas - #6777

Open
hedgeho9X wants to merge 2 commits into
QuantumNous:mainfrom
hedgeho9X:fix/gemini-tool-schema-normalization
Open

fix(gemini): normalize nullable and literal tool schemas#6777
hedgeho9X wants to merge 2 commits into
QuantumNous:mainfrom
hedgeho9X:fix/gemini-tool-schema-normalization

Conversation

@hedgeho9X

@hedgeho9X hedgeho9X commented Aug 11, 2026

Copy link
Copy Markdown

⚠️ 提交说明 / PR Notice

Important

  • 本 PR 的实现与测试由提交者使用 AI Coding Agent 协助完成,描述已根据实际代码路径整理。

📝 变更描述 / Description

修复 OpenAI Chat Completions / Responses 转 Gemini generateContent 时,tool function parameters 中的 JSON Schema union 不符合 Gemini OpenAPI Schema 约束的问题。

之前 string | null 会被转换成一个本身没有 typeanyOf,导致 Gemini / Vertex 在提交 function declaration 时返回 schema didn't specify the schema type field。TypeBox literal union 里的 const 也会被 allowlist 静默丢弃,使枚举约束失效。

现在共享 Gemini schema cleaner 会:

  • 将单一具体类型与 null 的 union 折叠为明确 type + nullable: true
  • 将同类型 literal union 折叠为明确 type + enum
  • 对其他同类型 anyOf 补充父层 type,同时保留原有分支约束。
  • 在发送上游前删除 Gemini 不支持的 const

修复位于 relaykit 共享转换层,因此同时覆盖 Gemini 渠道、Vertex Gemini 模式和使用相同 converter 的 Advanced Custom 路由。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • 暂无对应 Issue;错误由 Gemini / Vertex function declaration 校验稳定复现。

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 已搜索现有 Issues 与 PRs,未发现相同的 nullable/literal tool schema 修复。
  • Bug fix 说明: 当前未关联 Issue;PR 中已写明上游 400 的稳定触发条件。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已运行 relaykit 定向与全量测试、独立 build 及 vet。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

  • cd relaykit && GOWORK=off go test ./...
  • cd relaykit && GOWORK=off go build ./...
  • cd relaykit && GOWORK=off go vet ./...
  • 根仓除 main package 外的 Go packages 测试通过;main package 在本地因未生成 web/dist embed 产物而无法 setup,与本次 relaykit 改动无关。
  • 回归测试覆盖共享 cleaner、Chat Completions 接线和 Responses 接线。

Summary by CodeRabbit

  • Bug Fixes

    • Improved conversion of nullable and literal union tool parameters for Gemini.
    • Preserved string types, nullability, allowed values, and constant restrictions during schema conversion.
    • Normalized compatible unions while retaining distinct constraints when they cannot be safely combined.
  • Tests

    • Added regression coverage for nullable parameters, literal value unions, constants, and constrained schemas across supported request formats.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 7ba2d57c-7f43-404c-a8d7-4f6739612552

📥 Commits

Reviewing files that changed from the base of the PR and between 4cd11b7 and 8d6c0a1.

📒 Files selected for processing (2)
  • relaykit/relayconvert/internal/shared/gemini/schema.go
  • relaykit/relayconvert/internal/shared/gemini/schema_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • relaykit/relayconvert/internal/shared/gemini/schema.go

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


Walkthrough

The change adds Gemini schema normalization for const and eligible anyOf unions. It also adds regression tests for nullable, literal, const, and constrained tool-parameter unions from OpenAI Chat and Responses requests.

Changes

Gemini schema normalization

Layer / File(s) Summary
Schema cleaning normalization
relaykit/relayconvert/internal/shared/gemini/schema.go
Gemini schema cleaning converts const to single-value enum fields and normalizes eligible anyOf branches into shared type, nullable, and enum fields while preserving constrained unions.
Normalization regression coverage
relaykit/relayconvert/internal/shared/gemini/schema_test.go, relaykit/relayconvert/internal/oai_chat/to_gemini_chat_req_test.go, relaykit/relayconvert/internal/oai_responses/to_gemini_chat_req_test.go
Tests verify nullable and literal unions produce uppercase Gemini types, preserve nullable and enum values, remove anyOf and const when eligible, and preserve distinct pattern constraints.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OpenAI tool schema
  participant OpenAI request converter
  participant Gemini schema cleaner
  participant Gemini function declaration
  OpenAI tool schema->>OpenAI request converter: provide tool parameters
  OpenAI request converter->>Gemini schema cleaner: pass converted schema
  Gemini schema cleaner->>Gemini schema cleaner: normalize const and anyOf
  Gemini schema cleaner->>Gemini function declaration: return cleaned parameters
Loading

Merge Risk: ⚪ Minimal · up to 8d6c0

The change normalizes unsupported Gemini tool schemas while preserving nullable, literal, and constrained-value semantics. Current evidence shows no merge-blocking correctness or production-impact risk.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: normalizing nullable and literal tool schemas for Gemini conversion.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

A rabbit checked the schema bright,
And turned anyOf into right.
Nullable strings wore flags with care,
Literal values filled enums there.
Const met enums in a careful merge,
Tests now guard each schema edge.

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
relaykit/relayconvert/internal/shared/gemini/schema_test.go (1)

10-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for same-type non-enum anyOf branches.

Lines 160-168 preserve anyOf while adding a parent type. Add a test with two STRING branches that contain different constraints. Assert that the parent has type: "STRING" and that both branch constraints remain in anyOf.

As per coding guidelines: “Backend tests must protect real behavior, API contracts, billing/accounting invariants, compatibility, or regression paths.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relaykit/relayconvert/internal/shared/gemini/schema_test.go` around lines 10
- 53, Add a focused test for CleanFunctionParameters using an anyOf with two
string branches carrying different non-enum constraints, such as distinct
pattern or format values. Assert the normalized parent has type "STRING",
preserve both branch schemas in anyOf, and verify their constraints remain
unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@relaykit/relayconvert/internal/shared/gemini/schema.go`:
- Around line 123-131: Update normalizeGeminiSchemaConst to preserve the const
restriction when schema["enum"] already exists: replace the enum with the single
const value or intersect it so values other than constValue are removed before
const is later deleted.

---

Nitpick comments:
In `@relaykit/relayconvert/internal/shared/gemini/schema_test.go`:
- Around line 10-53: Add a focused test for CleanFunctionParameters using an
anyOf with two string branches carrying different non-enum constraints, such as
distinct pattern or format values. Assert the normalized parent has type
"STRING", preserve both branch schemas in anyOf, and verify their constraints
remain unchanged.
🪄 Autofix

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 Plus

Run ID: 5d1c8d67-4788-4e2e-ae5a-63256a40784e

📥 Commits

Reviewing files that changed from the base of the PR and between 3d5dc36 and 4cd11b7.

📒 Files selected for processing (4)
  • relaykit/relayconvert/internal/oai_chat/to_gemini_chat_req_test.go
  • relaykit/relayconvert/internal/oai_responses/to_gemini_chat_req_test.go
  • relaykit/relayconvert/internal/shared/gemini/schema.go
  • relaykit/relayconvert/internal/shared/gemini/schema_test.go

Comment thread relaykit/relayconvert/internal/shared/gemini/schema.go
@Calcium-Ion
Calcium-Ion force-pushed the main branch 2 times, most recently from 51fdfc5 to 2b6f1df Compare August 30, 2026 15:03
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