Skip to content

gemini tools + structured output fix - #3633

Merged
akshaydeo merged 2 commits into
devfrom
05-20-gemini_tools_structured_output_fix
May 20, 2026
Merged

gemini tools + structured output fix#3633
akshaydeo merged 2 commits into
devfrom
05-20-gemini_tools_structured_output_fix

Conversation

@akshaydeo

@akshaydeo akshaydeo commented May 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes two distinct issues: (1) Gemini 2.5 and earlier models reject requests that combine function-calling tools with responseMimeType: "application/json" (structured output / JSON mode). This PR drops the responseMimeType field for those models when tools are present, while still forwarding responseJsonSchema so schema-constrained output continues to work. Gemini 3.x supports the combination and is left unchanged. (2) Malformed JSON request bodies now return HTTP 400 with Connection: close, preventing the server from leaving keep-alive sockets open after a parse failure.

Changes

  • In convertParamsToGenerationConfig, clear ResponseMIMEType when tools are present and the model is older than Gemini 3.x, matching the Gemini API's documented constraint.
  • Extracted a parseJSONRequestBody helper that wraps sonic.Unmarshal with a descriptive error including the body length.
  • Both the custom RequestParser path and the default JSON parsing path now call ctx.SetConnectionClose() and return HTTP 400 (via newBifrostErrorWithCode) on parse failure, rather than leaving the status code unset and the connection open.
  • Added TestStructuredOutputWithToolsConflict covering four scenarios: Gemini 2.5 with tools + json_object, Gemini 2.5 with tools + json_schema, Gemini 3.x with tools + json_schema, and Gemini 2.5 with json_schema but no tools.
  • Added router-level tests verifying that parse failures set 400 and Connection: close, that valid keep-alive requests reuse the socket, and that a malformed request closes the socket and makes subsequent reads fail.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

go test ./core/providers/gemini/... -run TestStructuredOutputWithToolsConflict -v
go test ./transports/bifrost-http/integrations/... -run "TestCreateHandler_|TestOpenAIChatStructuredOutput" -v
go test ./...

Expected: all new and existing tests pass. Specifically:

  • Gemini2.5_ToolsWithJSONObject_DropsResponseMimeType and Gemini2.5_ToolsWithJSONSchema_DropsResponseMimeType assert ResponseMIMEType is empty.
  • Gemini3_ToolsWithJSONSchema_KeepsResponseFormat asserts ResponseMIMEType is "application/json".
  • TestCreateHandler_ParseFailureClosesKeepAliveSocket/malformed_request_closes_the_socket asserts resp.Close == true and that a subsequent read on the same connection returns an error.

Breaking changes

  • Yes
  • No

Related issues

Security considerations

None beyond the connection-close fix, which prevents a client from potentially reusing a connection that was left in an ambiguous state after a bad request.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 82789c71-6559-431a-9cf8-d35eccc674f1

📥 Commits

Reviewing files that changed from the base of the PR and between b8cf9f5 and b59ab55.

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

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved error handling for malformed requests with explicit bad-request status and connection closure
    • Fixed JSON structured-output compatibility when tools/functions are present on older Gemini models
  • Tests

    • Added tests for structured output behavior with active tools across Gemini versions
    • Added tests for request parsing failures and connection/keep-alive lifecycle handling

Walkthrough

Gemini conversion now avoids sending JSON structured-output hints for tool-calling on non-Gemini-3.0+ models. Bifrost HTTP router centralizes JSON parsing and marks connections to close and respond with HTTP 400 on request parse failures; tests cover parsing, conversion, and keep-alive socket closure.

Changes

Request Handling and Provider Compatibility

Layer / File(s) Summary
Gemini structured output with tools compatibility
core/providers/gemini/utils.go, core/providers/gemini/gemini_test.go
convertParamsToGenerationConfig clears ResponseMIMEType and nils ResponseJSONSchema for non-Gemini-3.0+ models when Tools are present. Tests (TestStructuredOutputWithToolsConflict) assert Gemini 2.5 drops MIME type with tools and forwards schema appropriately, while Gemini 3.x preserves MIME type.
Bifrost HTTP request parsing and error handling
transports/bifrost-http/integrations/router.go, transports/bifrost-http/integrations/router_test.go
Adds parseJSONRequestBody for default JSON unmarshaling (early-exit on empty body). createHandler now sets connection-close and returns a coded HTTP 400 on both custom parser failures and default JSON parse failures. Tests added for structured-output parsing, custom/default parser failures, and keep-alive socket closure on malformed requests.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Router
  participant RequestParser
  participant ResponseWriter
  Client->>Router: POST /v1/chat/completions (body)
  alt custom RequestParser set
    Router->>RequestParser: invoke custom parser
    RequestParser--XRouter: parse error
    Router->>Router: SetConnectionClose()
    Router->>ResponseWriter: newBifrostErrorWithCode(400, "failed to parse request")
  else default JSON parse
    Router->>Router: parseJSONRequestBody(body)
    Router--XRouter: sonic.Unmarshal fails
    Router->>Router: SetConnectionClose()
    Router->>ResponseWriter: newBifrostErrorWithCode(400, "Invalid JSON request body (len=...)")
  else success
    Router->>ResponseWriter: proceed to RequestConverter/handler
  end
  ResponseWriter->>Client: 400 + Connection: close (on errors) / normal response (on success)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • danpiths

Poem

🐰 I hopped through configs, MIME in tow,
Cleared the clutter where old models blow,
Routers now mind malformed lines,
Close the socket, tidy the signs,
A carrot-coded fix—soft and slow.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The linked issue #123 concerns Files API support, which is unrelated to the PR's focus on Gemini tools/structured output fixes and HTTP error handling. The PR appears to address different issues than those linked. Either link the correct issues or clarify the relationship to issue #123.
Out of Scope Changes check ❓ Inconclusive The PR contains changes across three domains: Gemini provider fixes, HTTP router error handling, and comprehensive tests. The HTTP router changes appear out of scope relative to the Gemini-focused title and primary issue. Clarify whether HTTP connection-close handling is part of the original scope or a separate fix bundled with this PR.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'gemini tools + structured output fix' clearly summarizes the main changes: fixes for Gemini model compatibility with tools and structured output features.
Description check ✅ Passed The PR description follows the template with well-populated sections including Summary, Changes, Type of change, Affected areas, How to test, and Breaking changes. All required information is present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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 05-20-gemini_tools_structured_output_fix

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.12.2)

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


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

@akshaydeo
akshaydeo marked this pull request as ready for review May 20, 2026 14:46

akshaydeo commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
coderabbitai Bot requested a review from danpiths May 20, 2026 14:47
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 20, 2026
@greptile-apps

greptile-apps Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 4/5

The connection-close and HTTP router changes are clean; the Gemini utils fix has a one-line discrepancy that will cause one of the four new tests to fail.

The config.ResponseJSONSchema = nil line in convertParamsToGenerationConfig is inconsistent with the stated intent and with the assert.NotNil assertion in Gemini2.5_ToolsWithJSONSchema_DropsResponseMimeType. Callers using json_schema format alongside tools on Gemini 2.x will receive a request with no schema.

core/providers/gemini/utils.go — the tools + structured output conflict guard at line 1251

Important Files Changed

Filename Overview
core/providers/gemini/utils.go Adds tools + structured output conflict guard, but incorrectly clears ResponseJSONSchema for the json_schema case, causing the Gemini2.5_ToolsWithJSONSchema_DropsResponseMimeType test to fail
core/providers/gemini/gemini_test.go Adds four well-structured test cases for the tools + structured output conflict; the json_schema case asserts NotNil on ResponseJSONSchema, which correctly exposes the implementation bug
transports/bifrost-http/integrations/router.go Extracts parseJSONRequestBody helper and adds ctx.SetConnectionClose() + HTTP 400 to both parse failure paths; logic is correct and consistent
transports/bifrost-http/integrations/router_test.go Adds comprehensive socket-level and unit tests for parse failures and keep-alive behaviour; coverage is thorough

Reviews (2): Last reviewed commit: "Apply suggestions from code review" | Re-trigger Greptile

Comment thread core/providers/gemini/utils.go

akshaydeo commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

Merge activity

  • May 20, 2:52 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 20, 2:52 PM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo
akshaydeo merged commit 8596f7a into dev May 20, 2026
12 of 13 checks passed
@akshaydeo
akshaydeo deleted the 05-20-gemini_tools_structured_output_fix branch May 20, 2026 14:52
Comment on lines +1247 to +1252
if len(params.Tools) > 0 &&
config.ResponseMIMEType == "application/json" &&
!isGemini3Plus(model) {
config.ResponseMIMEType = ""
config.ResponseJSONSchema = nil
}

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.

P1 Implementation contradicts test and PR description

config.ResponseJSONSchema = nil (line 1251) clears the schema for every model/tool combination, including json_schema format. But the test Gemini2.5_ToolsWithJSONSchema_DropsResponseMimeType asserts assert.NotNil(t, result.GenerationConfig.ResponseJSONSchema, "responseJsonSchema should still be forwarded"), and the PR description explicitly states it "still forwards responseJsonSchema". As a result, the json_schema + tools sub-test will fail: ResponseMIMEType is correctly cleared but ResponseJSONSchema is also wiped, causing the NotNil assertion to fail.

Suggested change
if len(params.Tools) > 0 &&
config.ResponseMIMEType == "application/json" &&
!isGemini3Plus(model) {
config.ResponseMIMEType = ""
config.ResponseJSONSchema = nil
}
if len(params.Tools) > 0 &&
config.ResponseMIMEType == "application/json" &&
!isGemini3Plus(model) {
config.ResponseMIMEType = ""
// ResponseJSONSchema is intentionally kept: Gemini 2.x rejects the pairing
// of responseMimeType + function declarations, but responseJsonSchema alone
// (without responseMimeType) is still forwarded for schema-constrained output.
}

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