Skip to content

Conversation

@TejasGhatte
Copy link
Collaborator

@TejasGhatte TejasGhatte commented Dec 20, 2025

Support for Gemini 3 Pro thought signatures and structured output improvements

This PR adds support for Gemini 3 Pro's thought signatures in tool calls and improves handling of structured outputs across providers.

Changes

  • Added support for Gemini 3 Pro's thought signatures in tool calls and responses
  • Fixed JSON schema handling in Bedrock's structured output tools
  • Improved handling of thought signatures in multi-turn conversations
  • Enhanced conversion between Bifrost and Gemini message formats
  • Added integration test for Gemini 3 Pro thought signature handling with tool calling

Type of change

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

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (Next.js)
  • Docs

How to test

Test the Gemini 3 Pro thought signature handling:

# Run the integration test for Gemini 3 Pro thought signatures
cd tests/integrations
python -m pytest tests/test_google.py::TestGoogleGenAI::test_28_gemini_3_pro_thought_signatures_multi_turn -v

Test the Bedrock structured output improvements:

# Run Bedrock provider tests
go test ./core/providers/bedrock/...

Breaking changes

  • Yes
  • No

Related issues

Adds support for Gemini 3 Pro thought signatures which are needed for advanced reasoning capabilities.

Security considerations

The PR handles encrypted thought signatures which are used by Google's Gemini models for advanced reasoning.

Checklist

  • I added/updated tests where appropriate
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 20, 2025

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Fixed thought-signature handling in Gemini multi-turn conversations for more reliable reasoning display and tool-calling.
  • New Features

    • Enhanced propagation and decoding of thought signatures across responses, streaming deltas, and function-call/response messages.
  • Tests

    • Added Gemini 3 Pro Preview multi-turn thought-signature tests with tool-calling scenarios.
  • Chores

    • Bumped versions and updated changelogs across core, framework, and plugins.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds Gemini thought-signature handling across ingestion, conversion, and streaming: base64 encode/decode paths, custom Part JSON unmarshaling, expanded content→message conversions with consumed-index tracking and signature propagation, a Bedrock schema extraction tweak, test additions/renumbering for Google/Gemini, and version/changelog bumps.

Changes

Cohort / File(s) Summary
Gemini: Part deserialization
core/providers/gemini/types.go
Adds Part.UnmarshalJSON to decode URL-safe/base64 thought_signature into Part.ThoughtSignature bytes and return decoding errors.
Gemini: Response conversion & streaming
core/providers/gemini/responses.go
Reworks content→message conversion and streaming: tracks consumed indices for thought signatures, emits separate reasoning messages for encrypted signatures, propagates signatures into blocks and stream deltas, and expands handling for FunctionCall, FunctionResponse, InlineData, and FileData.
Gemini: Chat/tool integration & utils
core/providers/gemini/chat.go, core/providers/gemini/utils.go
Builds tool calls locally, base64-encodes ThoughtSignature into toolCall.ExtraContent["google.thought_signature"] when present, and adds base64 decoding when ingesting thought_signature.
Bedrock: schema extraction tweak
core/providers/bedrock/utils.go
Stops checking format.JSONSchema.Schema explicitly and uses format.JSONSchema directly when extracting schema.
Tests: Google/Gemini integration
tests/integrations/tests/test_google.py
Adds Gemini multi-turn thought-signature test, renumbers many file/batch/structured-output tests, increases HTTP timeout handling, and adds helper extract_google_function_calls.
Meta: changelogs & versions
core/changelog.md, core/version, framework/changelog.md, framework/version, plugins/*/changelog.md, plugins/*/version, transports/changelog.md, transports/version
Adds changelog entries and bumps version strings across core, framework, transports, and multiple plugins.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client
participant Provider as GeminiProvider
participant Stream
participant Tool
Client->>Provider: send request (may include FunctionCall)
Provider->>Provider: convert contents -> Parts; if Part.ThoughtSignature present decode base64
Provider->>Tool: emit FunctionCall (attach base64-encoded thought_signature in ExtraContent)
Tool-->>Provider: FunctionResponse (may include Signature)
Provider->>Stream: emit deltas (include Signature field when present)
Stream-->>Client: stream deltas (client receives content + thought_signature)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Focus review on core/providers/gemini/responses.go (consumed-index logic, reasoning-message emission, signature propagation).
  • Verify base64 URL-safe padding/decoding and error handling in core/providers/gemini/types.go and core/providers/gemini/utils.go.
  • Check core/providers/gemini/chat.go for correct embedding of signature into tool-call ExtraContent and downstream consumers.
  • Confirm tests in tests/integrations/tests/test_google.py were renumbered consistently and new multi-turn tests validate signature usage.

Poem

🐇 I nibbled at signatures, bits and bloom,

unwrapped the whispers hidden in the room,
stitched calls and thoughts in tidy rows,
hopped through deltas where the magic flows,
— a rabbit's byte of tidy tomorrows.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: gemini thought signature handling' clearly and concisely describes the main purpose of the PR, matching the primary changes related to Gemini thought signature handling improvements.
Description check ✅ Passed The PR description comprehensively covers the template requirements with clear summary, detailed changes, type selection, affected areas, test instructions, breaking changes declaration, and completed checklist items.
Docstring Coverage ✅ Passed Docstring coverage is 86.36% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 12-20-fix_gemini_thought_signature_handling

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

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

Copy link
Collaborator Author

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

@github-actions
Copy link
Contributor

🧪 Test Suite Available

This PR can be tested by a repository admin.

Run tests for PR #1142

@TejasGhatte TejasGhatte marked this pull request as ready for review December 20, 2025 15:15
Copy link
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

🧹 Nitpick comments (3)
tests/integrations/tests/test_google.py (3)

117-117: Consider handling missing API key explicitly.

os.environ.get("GEMINI_API_KEY") returns None if the environment variable is not set. This could lead to unclear errors downstream when the client attempts authentication.

Proposed fix
-    api_key = os.environ.get("GEMINI_API_KEY")
+    api_key = os.environ.get("GEMINI_API_KEY")
+    if not api_key:
+        raise ValueError("GEMINI_API_KEY environment variable is required")

132-133: Hardcoded timeout ignores the configured value.

The code checks if a timeout is configured but then uses a hardcoded 30000 instead of the actual config value. This seems unintentional.

Proposed fix
     if api_config.get("timeout"):
-        http_options_kwargs["timeout"] = 30000
+        http_options_kwargs["timeout"] = api_config.get("timeout")

1447-1447: f-string without placeholders.

This f-string has no interpolation and can be a regular string.

Proposed fix
-                print(f"\n=== Token Usage ===")
+                print("\n=== Token Usage ===")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47fe4ed and ff6b422.

📒 Files selected for processing (6)
  • core/providers/bedrock/utils.go (1 hunks)
  • core/providers/gemini/chat.go (1 hunks)
  • core/providers/gemini/responses.go (8 hunks)
  • core/providers/gemini/types.go (1 hunks)
  • core/providers/gemini/utils.go (2 hunks)
  • tests/integrations/tests/test_google.py (14 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • core/providers/gemini/utils.go
  • core/providers/gemini/types.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • tests/integrations/tests/test_google.py
  • core/providers/bedrock/utils.go
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/providers/gemini/utils.go
  • core/providers/gemini/types.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/bedrock/utils.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/gemini/utils.go
  • core/providers/gemini/types.go
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/bedrock/utils.go
🧬 Code graph analysis (3)
core/providers/gemini/chat.go (1)
core/schemas/chatcompletions.go (2)
  • ChatAssistantMessageToolCall (710-716)
  • ChatToolChoiceTypeFunction (382-382)
core/providers/gemini/responses.go (3)
core/providers/gemini/types.go (5)
  • Type (782-782)
  • Role (17-17)
  • FunctionCall (1197-1207)
  • Content (977-985)
  • Part (991-1015)
core/schemas/responses.go (6)
  • ResponsesReasoning (741-744)
  • ResponsesMessage (321-334)
  • ResponsesToolMessage (472-492)
  • ResponsesToolMessageOutputStruct (541-545)
  • ResponsesMessageContent (346-351)
  • ResponsesMessageContentBlock (406-421)
core/schemas/utils.go (1)
  • Ptr (16-18)
tests/integrations/tests/test_google.py (3)
tests/integrations/tests/utils/common.py (1)
  • skip_if_no_api_key (1813-1824)
core/providers/gemini/types.go (5)
  • Tool (604-631)
  • Content (977-985)
  • Part (991-1015)
  • FunctionCall (1197-1207)
  • FunctionResponse (1210-1232)
tests/integrations/tests/utils/parametrize.py (1)
  • get_cross_provider_params_for_scenario (12-47)
🪛 Ruff (0.14.8)
tests/integrations/tests/test_google.py

1238-1238: Unused method argument: test_config

(ARG002)


1447-1447: f-string without any placeholders

Remove extraneous f prefix

(F541)


1455-1455: Unused method argument: test_config

(ARG002)


1537-1537: Unused method argument: test_config

(ARG002)


1591-1591: Unused method argument: test_config

(ARG002)


1638-1638: Unused method argument: test_config

(ARG002)


1682-1682: Unused method argument: test_config

(ARG002)


1727-1727: Unused method argument: test_config

(ARG002)


1787-1787: Unused method argument: test_config

(ARG002)


1821-1821: Unused method argument: test_config

(ARG002)


1841-1841: Unused method argument: test_config

(ARG002)


1881-1881: Unused method argument: test_config

(ARG002)


1924-1924: Unused method argument: test_config

(ARG002)

🔇 Additional comments (12)
core/providers/gemini/chat.go (1)

114-130: LGTM! Clean extraction of tool call construction with thought signature handling.

The refactoring to build the tool call in a temporary variable before appending allows for clean conditional attachment of the ThoughtSignature. The base64 encoding and nested structure under google.thought_signature is consistent with the decoding logic in utils.go.

core/providers/gemini/utils.go (1)

613-617: LGTM! Proper base64 decoding of thought signatures.

The decoding logic correctly reverses the encoding performed in chat.go. Silently skipping on decode failure is appropriate here since a malformed signature shouldn't break the entire request. The structure navigation path google.thought_signature is consistent with the encoding side.

core/providers/gemini/types.go (1)

1017-1066: LGTM! Robust URL-safe base64 handling for thought signatures.

The custom UnmarshalJSON correctly handles thought signatures that may arrive as URL-safe base64 from the Google GenAI SDK. The conversion from URL-safe to standard base64 (replacing - with + and _ with /) and the padding logic are both correct. Returning an error on decode failure is appropriate for unmarshaling since it indicates malformed input.

core/providers/gemini/responses.go (5)

174-176: LGTM! Well-structured look-ahead pattern for thought signature consumption.

The consumedIndices map elegantly solves the problem of pairing function call messages with their subsequent reasoning messages containing thought signatures. The bounds checking at line 240 is correct, and the skip logic at lines 288-290 properly prevents double-processing.

Also applies to: 238-251, 287-290


829-842: LGTM! Properly propagates thought signatures in streaming responses.

The streaming path correctly base64-encodes the ThoughtSignature and attaches it to the stream response's Signature field, maintaining consistency with the non-streaming conversion paths.


1370-1538: LGTM! Comprehensive restructuring with proper thought signature propagation.

The rewritten convertGeminiContentsToResponsesMessages correctly:

  1. Tracks function call IDs by name for correlation with responses
  2. Emits separate reasoning messages for thought signatures attached to function calls (lines 1412-1424)
  3. Attaches signatures directly to text content blocks when present (lines 1502-1506)
  4. Handles the new InlineData and FileData part types

The pattern of emitting a reasoning message immediately after a function call message aligns with the consumption logic in ToGeminiResponsesResponse.


1728-1732: LGTM! Consistent signature attachment on output text blocks.

Properly attaches the base64-encoded thought signature to the text content block that was just created, mirroring the pattern used in convertGeminiContentsToResponsesMessages.


2387-2396: LGTM! Correctly decodes signatures when converting content blocks to Gemini parts.

The signature decoding in convertContentBlockToGeminiPart properly reverses the encoding performed elsewhere, and the silent failure on decode error is appropriate since the part can still be used without the signature.

tests/integrations/tests/test_google.py (4)

1291-1301: LGTM on the multi-turn thought signature test structure.

The test comprehensively validates Gemini 3 Pro thought signature handling across multiple turns:

  • Turn 1: Basic thinking with thought signatures
  • Turn 2: Tool calls with thought signatures
  • Turn 3: Multi-turn conversation preserving thought signatures
  • Turn 4: Parallel tool calls

The conversation construction at lines 1358-1386 correctly propagates the thought_signature from the previous response to maintain context.


1536-1589: Test renumbering looks correct.

The file API tests have been correctly renumbered from test_26-29 to test_30-33 to accommodate the new test cases. The test logic and cleanup handling remain proper.


1726-1789: Batch API test renumbering is correct.

The batch API tests have been correctly renumbered to maintain sequential ordering after the new test cases were inserted.


1923-1925: E2E batch test renumbering is correct.

The end-to-end batch workflow test has been renumbered appropriately and maintains its comprehensive workflow validation: upload → create batch → poll → verify in list → cleanup.

@TejasGhatte TejasGhatte force-pushed the 12-20-fix_gemini_thought_signature_handling branch from ff6b422 to 12987f9 Compare December 20, 2025 15:54
@coderabbitai coderabbitai bot requested a review from danpiths December 20, 2025 15:56
Copy link
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/providers/gemini/responses.go (1)

444-448: Potential double-encoding of thought signatures in streaming reverse path

In ToGeminiResponsesStreamResponse, the OutputItemAdded case currently does:

if bifrostResp.Item != nil && bifrostResp.Item.ResponsesReasoning != nil && bifrostResp.Item.EncryptedContent != nil {
    candidate.Content.Parts = append(candidate.Content.Parts, &Part{
        ThoughtSignature: []byte(*bifrostResp.Item.ResponsesReasoning.EncryptedContent),
    })
}

But EncryptedContent is already a base64 string produced earlier (e.g., via base64.StdEncoding.EncodeToString(part.ThoughtSignature)). Treating that string as raw bytes means that when this GenerateContentResponse is later marshaled, ThoughtSignature will be base64‑encoded again, effectively double-encoding the signature.

To keep the streaming reverse path consistent with the non-streaming conversions and with convertContentBlockToGeminiPart, you likely want to base64‑decode here instead:

-        ThoughtSignature: []byte(*bifrostResp.Item.ResponsesReasoning.EncryptedContent),
+        ThoughtSignature: decoded,

after a base64.StdEncoding.DecodeString with appropriate error handling.

This isn’t introduced by the new changes, but it becomes more visible now that thought signatures are first-class citizens in the streaming APIs.

🧹 Nitpick comments (12)
plugins/maxim/changelog.md (1)

1-1: Changelog entry should describe the actual improvements, not just version bumps.

The changelog entry currently only documents version increments with a "chore:" prefix. However, the PR is classified as a "Bug fix and feature" and includes significant improvements (Gemini 3 Pro thought signature support, multi-turn conversation handling, Bedrock JSON schema fixes). Consider updating the entry to briefly describe what was actually fixed/added, and verify the prefix aligns with the PR type. For reference, the PR objectives mention:

  • Adds support for Gemini 3 Pro thought signatures in tool calls and responses
  • Improves handling of structured outputs across providers
  • Fixes JSON schema handling in Bedrock structured output tools
  • Improves handling of thought signatures in multi-turn conversations
tests/integrations/tests/test_google.py (3)

1445-1449: Resolve Ruff F541: f-string without placeholders

print(f"\n=== Token Usage ===") is flagged by Ruff (F541) because it doesn’t interpolate anything. You can silence this by dropping the f prefix:

-        print(f"\n=== Token Usage ===")
+        print("\n=== Token Usage ===")

1537-1995: Renumbered Files/Batch tests: behavior unchanged, but test_config is unused

The Files API and Batch tests (now test_30_file_upload through test_39_batch_e2e_file_api) appear to be pure renames/renumbering plus docstring updates; the core logic (upload/list/retrieve/delete/batch create/list/retrieve/cancel/E2E) is unchanged and still looks correct, including cleanup in finally blocks.

Static analysis (Ruff ARG002) correctly points out that test_config is unused in these parametrized tests. If you don’t plan to use it, consider dropping the parameter or renaming it to _test_config to keep the fixture wiring explicit while silencing the warning:

-    def test_30_file_upload(self, test_config, provider, model):
+    def test_30_file_upload(self, _test_config, provider, model):

Same applies to the other file/batch tests where test_config is unused.


1238-1924: Unused test_config fixture across several tests (Ruff ARG002)

Beyond the Files/Batch tests, test_config is also injected but unused in:

  • test_28_gemini_3_pro_thought_signatures_multi_turn
  • test_29_structured_output_with_thinking
  • And other parametrized tests highlighted by Ruff (ARG002) in this file.

If these are only present for future expansion or interface consistency, you can either:

  • Remove the parameter where not needed, or
  • Rename it to _test_config to make the intent explicit and silence the linter.

This is cosmetic but will keep the test module clean of ARG002 noise.

core/providers/gemini/chat.go (1)

96-131: Thought signature propagation on tool calls looks correct

Building the toolCall struct first and then attaching a base64-encoded ThoughtSignature into ExtraContent["google"]["thought_signature"] cleanly exposes Gemini’s thought signatures on tool calls while keeping existing fields (Index, Type, ID, Function) intact. This aligns with how ReasoningDetails.Signature is encoded below and with ExtraContent’s purpose as a provider-specific extension field.

Minor note: here you use ChatToolChoiceTypeFunction for Type, while the streaming path uses ChatToolTypeFunction; both stringify to "function", so behavior is consistent.

core/providers/gemini/types.go (1)

1017-1066: Custom Part.UnmarshalJSON correctly decodes URL-safe base64 thought signatures

The alias-based Unmarshal:

  • Preserves all existing fields (VideoMetadata, Thought, InlineData, FileData, CodeExecutionResult, ExecutableCode, FunctionCall, FunctionResponse, Text).
  • Treats thoughtSignature as a string, converts from URL-safe to standard base64 with padding, and decodes into the []byte ThoughtSignature field.
  • Fails fast with a clear error if decoding breaks, which is appropriate for malformed provider payloads.

Given Blob.UnmarshalJSON performs the same URL-safe→standard base64 conversion, you might consider extracting a small helper for the “urlsafe+padding → []byte” path and using %w in the error to keep stack context, but that’s purely a readability/DRY improvement.

core/providers/gemini/responses.go (6)

174-311: Pairing reasoning messages with tool calls to recover thought signatures looks correct

The additions in ToGeminiResponsesResponse:

  • Track which ResponsesMessage indices have been “consumed” as carriers of encrypted thought signatures.
  • When converting a FunctionCall message back into a Gemini Part, look ahead one message for a Reasoning message with EncryptedContent, base64‑decode it into Part.ThoughtSignature, and mark that index as consumed.
  • Skip reasoning messages whose indices are in consumedIndices to avoid producing duplicate thought-signature-only parts.
  • Still handle standalone reasoning messages (summary text and encrypted_content) when not associated with a tool call.

This gives you a clean round-trip for Gemini 3 Pro tool-call thought signatures while preserving backward compatibility for other reasoning messages.


772-846: Streaming text deltas now correctly surface thought signatures via Signature

In processGeminiTextPart, emitting output_text.delta events now:

  • Reuses a single text item and content index (0) via TextOutputIndex and TextBuffer.
  • Attaches a Signature field when part.ThoughtSignature is non-empty, encoding it as standard base64.

This is a good way to stream encrypted reasoning signatures alongside text without changing the existing delta structure. Downstream consumers of BifrostResponsesStreamResponse can now associate a signature with each text span.

Just be aware that signatures are only on the delta events, not on the final output_text.done; make sure any consumers expecting signatures read them from the delta path.


1369-1537: Content→Responses conversion handles per-part messages and thought signatures coherently

The reworked convertGeminiContentsToResponsesMessages now:

  • Normalizes Gemini roles to Responses roles ("model"→assistant, "user"→user, defaulting unknown roles to user).
  • Emits one ResponsesMessage per Part, making function calls, function responses, reasoning text, regular text, inline data, and file data each first-class items.
  • For function calls:
    • Serializes Args to JSON, derives a call_id (ID or name), and tracks it in functionCallIDs for later matching with responses.
    • Emits a FunctionCall message with CallID, Name, and Arguments.
    • If ThoughtSignature is present on the part, also emits a sibling Reasoning message whose EncryptedContent is the base64-encoded signature (so it can be carried in the Responses schema and reattached later).
  • For function responses:
    • Reconstructs CallID by preferring FunctionResponse.ID, falling back to the last-seen call ID for the same function name, then finally to the function name itself.
    • Serializes the Response map via the output key (or full JSON) into ResponsesToolMessage.Output.
  • For normal text:
    • Uses output_text vs input_text block types based on content.Role.
    • If a ThoughtSignature exists, pushes it into the block’s Signature field (to be decoded back into Part.ThoughtSignature later).

This design gives you enough structure to rehydrate full Content objects on the Gemini side, including encrypted thoughts, while keeping the Responses representation granular and tool-friendly. The functionCallIDs map is only used as a fallback when response IDs are absent; if you ever have multiple concurrent tool calls with the same name and missing IDs, their responses could be mis-associated, but that’s a niche edge case.


1685-1777: Candidate→Responses conversion now preserves thought signatures for both text and tool calls

In convertGeminiCandidatesToResponsesOutput:

  • Regular assistant text (part.Text with no part.Thought) is converted into an output_text content block, and when part.ThoughtSignature is present, its base64 encoding is stored in block.Signature. This aligns with the reverse path in convertContentBlockToGeminiPart, which decodes Signature back into Part.ThoughtSignature.
  • For FunctionCall parts, you:
    • Serialize Args to JSON for ResponsesToolMessage.Arguments.
    • Derive a CallID (preferring ID, falling back to Name).
    • Emit a FunctionCall message with CallID and Name.
    • If ThoughtSignature exists, emit a separate Reasoning message whose EncryptedContent carries the base64-encoded signature.

Together with the lookahead logic in ToGeminiResponsesResponse, this lets you:

  • Represent tool-call signatures cleanly in the Responses API.
  • Reconstruct them on the Gemini side without inventing a new top-level field on the tool-call message itself.

The switch structure ensures that the “catch-all case part.ThoughtSignature != nil” is only used for signature-only parts, not for function calls or text that already handle signatures explicitly.


2324-2368: Function-call output conversion now correctly prefers Output and falls back to ContentStr

The ResponsesMessageTypeFunctionCallOutput branch in convertResponsesMessagesToGeminiContents:

  • Builds responseMap from msg.ResponsesToolMessage.Output.ResponsesToolCallOutputStr when present.
  • Falls back to msg.Content.ContentStr for backward compatibility when the structured Output wrapper isn’t populated.
  • Derives the Gemini function name by preferring ResponsesToolMessage.Name and falling back to CallID when the name is missing, which matches how Gemini associates call outputs with tools.
  • Keeps FunctionResponse.ID in sync with CallID.

This should make the Responses↔Gemini conversion more robust across older and newer payload shapes without changing behavior for existing clients.


2387-2510: Decoding Signature into Part.ThoughtSignature completes the round-trip

In convertContentBlockToGeminiPart, for text blocks:

  • You now construct a Part with Text and, when block.Signature is present, base64-decode it into ThoughtSignature.
  • Decode failures are deliberately ignored (you just omit the signature), which is a reasonable fallback for malformed clients.

Combined with the earlier logic that populates Signature when converting from Gemini to Responses, this gives you a clean, symmetric path for encrypted thought signatures on text content. Other block types (reasoning, refusal, image/audio/file) remain unaffected and continue to behave as before.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff6b422 and 12987f9.

📒 Files selected for processing (28)
  • core/changelog.md (1 hunks)
  • core/providers/bedrock/utils.go (1 hunks)
  • core/providers/gemini/chat.go (1 hunks)
  • core/providers/gemini/responses.go (8 hunks)
  • core/providers/gemini/types.go (1 hunks)
  • core/providers/gemini/utils.go (2 hunks)
  • core/version (1 hunks)
  • framework/changelog.md (1 hunks)
  • framework/version (1 hunks)
  • plugins/governance/changelog.md (1 hunks)
  • plugins/governance/version (1 hunks)
  • plugins/jsonparser/changelog.md (1 hunks)
  • plugins/jsonparser/version (1 hunks)
  • plugins/logging/changelog.md (1 hunks)
  • plugins/logging/version (1 hunks)
  • plugins/maxim/changelog.md (1 hunks)
  • plugins/maxim/version (1 hunks)
  • plugins/mocker/changelog.md (1 hunks)
  • plugins/mocker/version (1 hunks)
  • plugins/otel/changelog.md (1 hunks)
  • plugins/otel/version (1 hunks)
  • plugins/semanticcache/changelog.md (1 hunks)
  • plugins/semanticcache/version (1 hunks)
  • plugins/telemetry/changelog.md (1 hunks)
  • plugins/telemetry/version (1 hunks)
  • tests/integrations/tests/test_google.py (14 hunks)
  • transports/changelog.md (1 hunks)
  • transports/version (1 hunks)
✅ Files skipped from review due to trivial changes (19)
  • plugins/otel/changelog.md
  • transports/changelog.md
  • core/changelog.md
  • framework/changelog.md
  • plugins/governance/version
  • plugins/semanticcache/version
  • plugins/semanticcache/changelog.md
  • plugins/jsonparser/changelog.md
  • plugins/logging/version
  • core/version
  • plugins/logging/changelog.md
  • plugins/jsonparser/version
  • transports/version
  • framework/version
  • plugins/mocker/version
  • plugins/governance/changelog.md
  • plugins/telemetry/version
  • plugins/telemetry/changelog.md
  • plugins/otel/version
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/providers/bedrock/utils.go
  • core/providers/gemini/utils.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • plugins/mocker/changelog.md
  • plugins/maxim/changelog.md
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
  • plugins/maxim/version
  • tests/integrations/tests/test_google.py
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
🧬 Code graph analysis (2)
core/providers/gemini/chat.go (1)
core/schemas/chatcompletions.go (2)
  • ChatAssistantMessageToolCall (710-716)
  • ChatToolChoiceTypeFunction (382-382)
core/providers/gemini/responses.go (3)
core/providers/gemini/types.go (7)
  • Type (782-782)
  • Role (17-17)
  • FunctionCall (1197-1207)
  • FunctionResponse (1210-1232)
  • Content (977-985)
  • FileData (1186-1194)
  • Part (991-1015)
core/schemas/responses.go (9)
  • ResponsesReasoning (741-744)
  • ResponsesMessageRoleType (336-336)
  • ResponsesMessage (321-334)
  • ResponsesToolMessage (472-492)
  • ResponsesReasoningSummary (755-758)
  • ResponsesToolMessageOutputStruct (541-545)
  • ResponsesMessageContent (346-351)
  • ResponsesMessageContentBlock (406-421)
  • ResponsesMessageContentBlockType (392-392)
core/schemas/utils.go (1)
  • Ptr (16-18)
🪛 Ruff (0.14.8)
tests/integrations/tests/test_google.py

1238-1238: Unused method argument: test_config

(ARG002)


1447-1447: f-string without any placeholders

Remove extraneous f prefix

(F541)


1455-1455: Unused method argument: test_config

(ARG002)


1537-1537: Unused method argument: test_config

(ARG002)


1591-1591: Unused method argument: test_config

(ARG002)


1638-1638: Unused method argument: test_config

(ARG002)


1682-1682: Unused method argument: test_config

(ARG002)


1727-1727: Unused method argument: test_config

(ARG002)


1787-1787: Unused method argument: test_config

(ARG002)


1821-1821: Unused method argument: test_config

(ARG002)


1841-1841: Unused method argument: test_config

(ARG002)


1881-1881: Unused method argument: test_config

(ARG002)


1924-1924: Unused method argument: test_config

(ARG002)

🔇 Additional comments (6)
plugins/maxim/version (1)

1-1: LGTM!

The patch version bump (1.4.51 → 1.4.52) is appropriate for the bug fix and feature enhancements related to Gemini thought-signature handling introduced in this PR.

plugins/mocker/changelog.md (1)

1-1: Changelog entry is consistent with version bumps

The note clearly documents the core/framework version upgrades and aligns with the PR scope; no issues here.

tests/integrations/tests/test_google.py (4)

36-49: Scenario list update matches new tests

The docstring enumeration now reflects the new Gemini 3 Pro thought-signature test (28) and the renumbered structured output and Files/Batch API tests (29–39); numbering and descriptions look consistent with the test names below.


1455-1530: Structured-output + thinking test looks solid

The test_29_structured_output_with_thinking test thoroughly validates:

  • That response.parsed is populated and is a MathProblem instance.
  • Field presence and types, including reasoning_steps content.
  • Thinking token accounting for Gemini via usage_metadata.thoughts_token_count.

This should give good coverage for the structured-output + thinking path; no changes needed.


2017-2038: Helper extract_google_function_calls is defensive and appropriate

The helper correctly:

  • Short-circuits when response.function_calls is missing/empty.
  • Checks for name and args on each function-call object.
  • Safely converts args into a dict, catching AttributeError/TypeError and logging a warning without failing the whole extraction.

This is a reasonable, defensive adapter for Google GenAI function-call responses.


113-135: Verify GEMINI_API_KEY usage and timeout configuration inconsistencies

The function has two alignment issues with other provider implementations:

  1. API key retrieval: Uses os.environ.get("GEMINI_API_KEY") directly instead of delegating to get_api_key() like get_provider_openai_client and get_provider_anthropic_client do. This creates fragile coupling with skip_if_no_api_key("google"), which expects the same mapping.

  2. Timeout handling: Hard-codes timeout=30000 when api_config.get("timeout") is truthy, ignoring the actual configured value. The OpenAI implementation correctly uses timeout=api_config.get("timeout", 300) to respect configuration. This prevents respecting per-test timeout customization.

Consider using get_api_key("google") and api_config.get("timeout", 30000) to align with existing patterns.

@TejasGhatte TejasGhatte force-pushed the 12-20-fix_gemini_thought_signature_handling branch from 12987f9 to d516b42 Compare December 20, 2025 16:24
Copy link
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/providers/gemini/responses.go (1)

443-449: Streaming: OutputItemAdded reasoning branch likely never fires due to EncryptedContent check

In ToGeminiResponsesStreamResponse’s ResponsesStreamResponseTypeOutputItemAdded case:

if bifrostResp.Item != nil && bifrostResp.Item.ResponsesReasoning != nil && bifrostResp.Item.EncryptedContent != nil {
    candidate.Content.Parts = append(candidate.Content.Parts, &Part{
        ThoughtSignature: []byte(*bifrostResp.Item.ResponsesReasoning.EncryptedContent),
    })
}

But elsewhere in this file (e.g. processGeminiThoughtSignaturePart and the non‑streaming conversions), encrypted reasoning content is stored only under ResponsesReasoning.EncryptedContent, not on the ResponsesMessage itself.

That means bifrostResp.Item.EncryptedContent is almost certainly always nil, so this branch never runs and thought signatures encoded as reasoning items won’t be surfaced back into Gemini streaming responses.

You probably intended:

if bifrostResp.Item != nil &&
   bifrostResp.Item.ResponsesReasoning != nil &&
   bifrostResp.Item.ResponsesReasoning.EncryptedContent != nil {
    candidate.Content.Parts = append(candidate.Content.Parts, &Part{
        ThoughtSignature: []byte(*bifrostResp.Item.ResponsesReasoning.EncryptedContent),
    })
}

Fixing this would ensure that reasoning items carrying signatures from the Bifrost stream are correctly converted back into Part.ThoughtSignature for streaming clients using the Gemini format.

🧹 Nitpick comments (3)
core/providers/gemini/types.go (1)

1017-1066: Custom Part.UnmarshalJSON correctly handles URL-safe base64, but is brittle to future field additions

The decoding logic and URL-safe→standard base64 normalization look solid and will handle Gemini’s URL-safe, unpadded signatures correctly. The only real concern is maintainability:

  • PartAlias re-lists every field of Part with a different type for ThoughtSignature. Any future field added to Part must also be added here or it won’t be deserialized.
  • The URL-safe base64 normalization is now duplicated with Blob.UnmarshalJSON.

Consider a small refactor (non-blocking):

  • Use an embedded alias to automatically keep all other fields in sync, e.g.:

    type rawPart Part
    aux := struct {
        ThoughtSignature string `json:"thoughtSignature,omitempty"`
        *rawPart
    }{rawPart: (*rawPart)(p)}

    Then only post-process aux.ThoughtSignature into p.ThoughtSignature.

  • Optionally extract a tiny helper like decodeURLSafeBase64(s string) ([]byte, error) and reuse it here and in Blob.UnmarshalJSON.

core/providers/gemini/responses.go (1)

1369-1537: Expanded convertGeminiContentsToResponsesMessages correctly covers function calls, outputs, thoughts, signatures, and rich media

The new per‑Part handling here is doing quite a bit, and overall it’s coherent:

  • Maps role once per Content (model→assistant, user→user, otherwise defaults to user).
  • For FunctionCall parts:
    • Marshals Args to JSON.
    • Derives a callID (preferring ID, falling back to Name) and tracks it in functionCallIDs for later response correlation.
    • Emits a ResponsesMessageTypeFunctionCall with ResponsesToolMessage.
    • Optionally emits a separate Reasoning message carrying the base64‑encoded ThoughtSignature.
  • For FunctionResponse parts:
    • Derives responseID from FunctionResponse.ID, falling back to the tracked call ID or name.
    • Extracts "output" from the response map (string if present, otherwise marshalled JSON).
    • Produces a FunctionCallOutput message with CallID, Output, and Name (if non‑blank).
  • For thought text (Thought && Text), regular text (with optional Signature), inline data, and file data it constructs the appropriate ResponsesMessage + content block forms.

Two minor notes (non‑blocking):

  • The function call and function response messages don’t set Role; elsewhere in this file, function-call output is typically tagged with assistant. If downstream assumes a role here, you might want to set Role: role (or explicitly assistant for responses) for consistency.
  • In scenarios with multiple concurrent calls using the same function name, the functionCallIDs[name] map may be ambiguous. Relying on FunctionResponse.ID when present is correct; the name‑based fallback is best‑effort and probably acceptable given Gemini semantics but worth keeping in mind.

Overall, the mapping and ThoughtSignature propagation here look correct.

tests/integrations/tests/test_google.py (1)

147-149: test_config fixture is unused in many tests (Ruff ARG002)

Ruff correctly notes that test_config is accepted but never used in a number of tests (e.g., test_28_gemini_3_pro_thought_signatures_multi_turn, test_29_structured_output_with_thinking, and the file/batch tests 30–39).

If you want to quiet ARG002 without changing behavior, you can either:

  • Rename the parameter to _test_config in tests where it’s not used, or
  • Drop the parameter entirely from those tests if you don’t plan to use the fixture there.

Both options are safe and keep the signatures aligned with how the fixtures are actually consumed.

Also applies to: 1237-1239, 1468-1469, 1550-1552, 1604-1606, 1651-1653, 1695-1697, 1740-1742, 1800-1802, 1834-1836, 1854-1856, 1894-1896, 1937-1939

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12987f9 and d516b42.

📒 Files selected for processing (28)
  • core/changelog.md (1 hunks)
  • core/providers/bedrock/utils.go (1 hunks)
  • core/providers/gemini/chat.go (1 hunks)
  • core/providers/gemini/responses.go (8 hunks)
  • core/providers/gemini/types.go (1 hunks)
  • core/providers/gemini/utils.go (2 hunks)
  • core/version (1 hunks)
  • framework/changelog.md (1 hunks)
  • framework/version (1 hunks)
  • plugins/governance/changelog.md (1 hunks)
  • plugins/governance/version (1 hunks)
  • plugins/jsonparser/changelog.md (1 hunks)
  • plugins/jsonparser/version (1 hunks)
  • plugins/logging/changelog.md (1 hunks)
  • plugins/logging/version (1 hunks)
  • plugins/maxim/changelog.md (1 hunks)
  • plugins/maxim/version (1 hunks)
  • plugins/mocker/changelog.md (1 hunks)
  • plugins/mocker/version (1 hunks)
  • plugins/otel/changelog.md (1 hunks)
  • plugins/otel/version (1 hunks)
  • plugins/semanticcache/changelog.md (1 hunks)
  • plugins/semanticcache/version (1 hunks)
  • plugins/telemetry/changelog.md (1 hunks)
  • plugins/telemetry/version (1 hunks)
  • tests/integrations/tests/test_google.py (14 hunks)
  • transports/changelog.md (1 hunks)
  • transports/version (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • plugins/maxim/changelog.md
  • plugins/maxim/version
  • plugins/governance/changelog.md
🚧 Files skipped from review as they are similar to previous changes (21)
  • core/providers/bedrock/utils.go
  • core/changelog.md
  • plugins/logging/changelog.md
  • plugins/jsonparser/version
  • plugins/semanticcache/changelog.md
  • core/providers/gemini/chat.go
  • transports/changelog.md
  • plugins/jsonparser/changelog.md
  • plugins/mocker/version
  • plugins/governance/version
  • core/providers/gemini/utils.go
  • plugins/otel/changelog.md
  • plugins/semanticcache/version
  • plugins/otel/version
  • framework/version
  • transports/version
  • plugins/telemetry/changelog.md
  • plugins/logging/version
  • plugins/telemetry/version
  • framework/changelog.md
  • plugins/mocker/changelog.md
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • core/providers/gemini/types.go
  • core/providers/gemini/responses.go
  • tests/integrations/tests/test_google.py
  • core/version
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/providers/gemini/types.go
  • core/providers/gemini/responses.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/gemini/types.go
  • core/providers/gemini/responses.go
🧬 Code graph analysis (1)
tests/integrations/tests/test_google.py (3)
tests/integrations/tests/utils/common.py (1)
  • skip_if_no_api_key (1813-1824)
core/providers/gemini/types.go (3)
  • Part (991-1015)
  • FunctionCall (1197-1207)
  • FunctionResponse (1210-1232)
tests/integrations/tests/utils/parametrize.py (1)
  • get_cross_provider_params_for_scenario (12-47)
🪛 Ruff (0.14.8)
tests/integrations/tests/test_google.py

1238-1238: Unused method argument: test_config

(ARG002)


1460-1460: f-string without any placeholders

Remove extraneous f prefix

(F541)


1468-1468: Unused method argument: test_config

(ARG002)


1550-1550: Unused method argument: test_config

(ARG002)


1604-1604: Unused method argument: test_config

(ARG002)


1651-1651: Unused method argument: test_config

(ARG002)


1695-1695: Unused method argument: test_config

(ARG002)


1740-1740: Unused method argument: test_config

(ARG002)


1800-1800: Unused method argument: test_config

(ARG002)


1834-1834: Unused method argument: test_config

(ARG002)


1854-1854: Unused method argument: test_config

(ARG002)


1894-1894: Unused method argument: test_config

(ARG002)


1937-1937: Unused method argument: test_config

(ARG002)

🔇 Additional comments (9)
core/version (1)

1-1: LGTM! Version bump aligns with the coordinated release.

The patch increment from 1.2.40 to 1.2.41 is appropriate for the Gemini thought signature handling fixes and improvements. This version bump is consistent with the documented changelog entries and the framework's upgrade to core 1.2.41.

core/providers/gemini/responses.go (6)

175-312: Reasoning→thought-signature pairing in ToGeminiResponsesResponse looks correct

The consumedIndices approach that:

  • Looks ahead from a FunctionCall message to a following Reasoning message with EncryptedContent,
  • Decodes the base64 signature into Part.ThoughtSignature, and
  • Marks that reasoning index as consumed so it isn't also turned into a separate thought/ThoughtSignature part

is a clean way to align Bifrost’s reasoning messages with Gemini function-call parts. Failure to decode just falls back to the original reasoning path, which is safe.

No issues from a correctness perspective here.


773-846: Streaming: attaching ThoughtSignature to output_text.delta via Signature field is sound

processGeminiTextPart now:

  • Accumulates text in TextBuffer as before.
  • When emitting ResponsesStreamResponseTypeOutputTextDelta, base64-encodes part.ThoughtSignature and wires it into streamResponse.Signature.

This matches the new responses schema and allows downstream consumers to see signatures inline with text deltas. No behavioral regressions visible.


1545-1624: InlineData/FileData → content-block helpers are consistent with the rest of the adapter

convertGeminiInlineDataToContentBlock and convertGeminiFileDataToContentBlock:

  • Respect MIME type, using isImageMimeType for images.
  • Encode inline data as:
    • data:<mime>;base64,... URLs for images.
    • {format, data} blocks for audio, stripping the "audio/" prefix.
    • File blocks with base64 payload + filename for other blobs.
  • Map file URIs to image/file blocks, defaulting MIME type to application/octet-stream when missing.

These helpers align with the other conversions in this file and make the media handling symmetric across directions. No issues to flag.


1685-1923: Candidate→Responses output: ThoughtSignature propagation and richer part handling look correct

In convertGeminiCandidatesToResponsesOutput the new logic:

  • For text parts, attaches ThoughtSignature as a base64 Signature on the last text content block, mirroring what convertGeminiContentsToResponsesMessages does.
  • For FunctionCall parts, continues to emit a FunctionCall message and now, if ThoughtSignature is present, emits a separate Reasoning message with EncryptedContent for better scalability.
  • Adds a final case part.ThoughtSignature != nil to emit a pure‑signature Reasoning message when the part has no text/call/etc.
  • Leaves the existing handling for thoughts, function responses, inline data, file data, code execution results, and executable code intact.

Control flow is safe (the switch is first‑match, so FunctionCall+signature won’t double‑emit via the trailing ThoughtSignature case), and the encoding is consistent (standard base64 everywhere).

This matches the PR’s goal of surfacing thought signatures in both tool calls and plain text responses.


2225-2379: Responses→Gemini contents: function outputs and thought signatures are wired appropriately

The updates in convertResponsesMessagesToGeminiContents for tool messages:

  • For FunctionCall:
    • Reconstruct FunctionCall with Name, Args decoded from JSON.
    • Preserve CallID as FunctionCall.ID.
    • Look ahead to messages[i+1] for a Reasoning message with EncryptedContent, base64‑decode it, and attach as Part.ThoughtSignature. This mirrors the forward path where a function call plus separate reasoning message are emitted.
  • For FunctionCallOutput:
    • Prefer ResponsesToolMessage.Output.ResponsesToolCallOutputStr as "output", with a fallback to ContentStr for compatibility.
    • Derive funcName from Name if present, otherwise fall back to CallID.
    • Set FunctionResponse.Name and FunctionResponse.ID (from CallID) and attach the responseMap.

Error handling returns contextual errors if argument or content‑block decoding fails, which is helpful.

This should allow multi‑turn and tool‑calling flows (including thought signatures) to round‑trip cleanly.


2381-2510: Text-block SignaturePart.ThoughtSignature mapping closes the round-trip

In convertContentBlockToGeminiPart for text blocks:

  • You now construct Part{Text: *block.Text} and, if block.Signature is present, base64‑decode it and set Part.ThoughtSignature.
  • Decode failures are silently ignored, leaving a plain text part, which is reasonable for robustness.
  • Other content types (reasoning, refusal, image, audio, file) are unchanged and consistent with the rest of the adapter.

Together with the earlier changes that write Signature when going Gemini→Responses, this gives you a full round-trip path for thought signatures on text content.

No issues here.

tests/integrations/tests/test_google.py (2)

113-137: GEMINI client configuration: direct env key + fixed timeout look reasonable

Using GEMINI_API_KEY directly and wiring timeout=30000 into HttpOptions when a timeout is configured keeps the Google client setup simple and isolates cross-provider routing to the x-model-provider header. As long as your config loader and CI set GEMINI_API_KEY consistently, this looks fine.

No functional issues spotted here.


1237-1465: Multi-turn Gemini 3 Pro thought-signature test is thorough and avoids prior pitfalls

The new test_28_gemini_3_pro_thought_signatures_multi_turn does a good job of validating the end-to-end behavior you just implemented:

  • Turn 1 asserts presence of either thought or text content and thought_signature_count > 0, while guarding all debug prints with getattr(part, "text", None) before slicing.
  • Turn 2 asserts at least one function call and at least one thought_signature attached to a tool call.
  • Turn 3 reconstructs a multi-turn conversation, reusing the captured tool_thought_signatures[0], and asserts that the model produces non-empty text, again guarding thought/text prints.
  • Turn 4 drives multiple tools in one request and, if any function calls exist, asserts that at least one has a thought signature even at thinking_level="low".
  • Finally, it checks usage_metadata.thoughts_token_count > 0 when available.

This turns the scenario into a real regression test for thought-signature propagation, not just logging, and aligns tightly with the adapter changes in core/providers/gemini.

Copy link
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: 2

🧹 Nitpick comments (4)
tests/integrations/tests/test_google.py (2)

1238-1238: Optional: Remove unused test_config fixture parameter.

The test_config parameter is injected as a pytest fixture but not used in multiple test functions. Consider removing it to clean up the signature, or add a comment if it's kept for future use or consistency.

This is flagged by static analysis (Ruff ARG002) but is a minor code quality issue.

Also applies to: 1468-1468, 1550-1550, 1604-1604, 1651-1651, 1695-1695, 1740-1740, 1800-1800, 1834-1834, 1854-1854, 1894-1894, 1937-1937


1460-1460: Optional: Remove unnecessary f-string prefix.

Line 1460 uses an f-string without any placeholders. You can use a regular string instead.

-                print(f"\n=== Token Usage ===")
+                print("\n=== Token Usage ===")
core/providers/gemini/responses.go (2)

238-251: Document the Gemini API convention for thought signatures following function calls.

The look-ahead logic assumes that a reasoning message with encrypted content immediately follows a function call message. While the bounds check is correct, adding a comment would clarify this Gemini API convention and help future maintainers understand why this specific pattern is expected.

🔎 Suggested documentation
 					part := &Part{
 						FunctionCall: functionCall,
 					}
 
+					// Gemini API convention: encrypted thought signatures for function calls
+					// appear as a separate reasoning message immediately following the function call
 					// Look ahead to see if the next message is a reasoning message with encrypted content
 					// (thought signature for this function call)
 					if i+1 < len(bifrostResp.Output) {

2343-2350: Consider handling the case when both output paths are nil.

The extraction logic tries two paths but doesn't explicitly handle when both are absent. While an empty responseMap may be acceptable, consider adding a comment or validation to clarify the expected behavior in this edge case.

🔎 Suggested enhancement
 				// Extract output from ResponsesToolMessage.Output
 				if msg.ResponsesToolMessage.Output != nil && msg.ResponsesToolMessage.Output.ResponsesToolCallOutputStr != nil {
 					responseMap["output"] = *msg.ResponsesToolMessage.Output.ResponsesToolCallOutputStr
 				} else if msg.Content != nil && msg.Content.ContentStr != nil {
 					// Fallback to Content.ContentStr for backward compatibility
 					responseMap["output"] = *msg.Content.ContentStr
+				} else {
+					// No output available - empty responseMap is acceptable for Gemini
 				}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12987f9 and d516b42.

📒 Files selected for processing (28)
  • core/changelog.md (1 hunks)
  • core/providers/bedrock/utils.go (1 hunks)
  • core/providers/gemini/chat.go (1 hunks)
  • core/providers/gemini/responses.go (8 hunks)
  • core/providers/gemini/types.go (1 hunks)
  • core/providers/gemini/utils.go (2 hunks)
  • core/version (1 hunks)
  • framework/changelog.md (1 hunks)
  • framework/version (1 hunks)
  • plugins/governance/changelog.md (1 hunks)
  • plugins/governance/version (1 hunks)
  • plugins/jsonparser/changelog.md (1 hunks)
  • plugins/jsonparser/version (1 hunks)
  • plugins/logging/changelog.md (1 hunks)
  • plugins/logging/version (1 hunks)
  • plugins/maxim/changelog.md (1 hunks)
  • plugins/maxim/version (1 hunks)
  • plugins/mocker/changelog.md (1 hunks)
  • plugins/mocker/version (1 hunks)
  • plugins/otel/changelog.md (1 hunks)
  • plugins/otel/version (1 hunks)
  • plugins/semanticcache/changelog.md (1 hunks)
  • plugins/semanticcache/version (1 hunks)
  • plugins/telemetry/changelog.md (1 hunks)
  • plugins/telemetry/version (1 hunks)
  • tests/integrations/tests/test_google.py (14 hunks)
  • transports/changelog.md (1 hunks)
  • transports/version (1 hunks)
✅ Files skipped from review due to trivial changes (4)
  • plugins/otel/changelog.md
  • framework/version
  • framework/changelog.md
  • plugins/mocker/changelog.md
🚧 Files skipped from review as they are similar to previous changes (15)
  • core/providers/gemini/utils.go
  • plugins/telemetry/version
  • plugins/maxim/version
  • plugins/semanticcache/version
  • plugins/maxim/changelog.md
  • core/changelog.md
  • core/providers/bedrock/utils.go
  • plugins/otel/version
  • plugins/jsonparser/changelog.md
  • transports/version
  • core/version
  • plugins/governance/changelog.md
  • plugins/logging/version
  • plugins/mocker/version
  • plugins/telemetry/changelog.md
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • transports/changelog.md
  • core/providers/gemini/chat.go
  • plugins/governance/version
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
  • plugins/semanticcache/changelog.md
  • plugins/jsonparser/version
  • tests/integrations/tests/test_google.py
  • plugins/logging/changelog.md
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
🧬 Code graph analysis (2)
core/providers/gemini/chat.go (3)
core/schemas/chatcompletions.go (2)
  • ChatAssistantMessageToolCall (710-716)
  • ChatToolChoiceTypeFunction (382-382)
core/providers/gemini/types.go (1)
  • Type (782-782)
core/schemas/utils.go (1)
  • Ptr (16-18)
tests/integrations/tests/test_google.py (3)
tests/integrations/tests/utils/common.py (1)
  • skip_if_no_api_key (1813-1824)
core/providers/gemini/types.go (5)
  • Tool (604-631)
  • Content (977-985)
  • Part (991-1015)
  • FunctionCall (1197-1207)
  • FunctionResponse (1210-1232)
tests/integrations/tests/utils/parametrize.py (1)
  • get_cross_provider_params_for_scenario (12-47)
🪛 Ruff (0.14.8)
tests/integrations/tests/test_google.py

1238-1238: Unused method argument: test_config

(ARG002)


1460-1460: f-string without any placeholders

Remove extraneous f prefix

(F541)


1468-1468: Unused method argument: test_config

(ARG002)


1550-1550: Unused method argument: test_config

(ARG002)


1604-1604: Unused method argument: test_config

(ARG002)


1651-1651: Unused method argument: test_config

(ARG002)


1695-1695: Unused method argument: test_config

(ARG002)


1740-1740: Unused method argument: test_config

(ARG002)


1800-1800: Unused method argument: test_config

(ARG002)


1834-1834: Unused method argument: test_config

(ARG002)


1854-1854: Unused method argument: test_config

(ARG002)


1894-1894: Unused method argument: test_config

(ARG002)


1937-1937: Unused method argument: test_config

(ARG002)

🔇 Additional comments (15)
tests/integrations/tests/test_google.py (3)

1237-1465: Well-structured multi-turn thought signature test.

The new test_28_gemini_3_pro_thought_signatures_multi_turn test provides comprehensive coverage of thought signature handling across multiple scenarios:

  • Turn 1: Initial thinking validation
  • Turn 2: Tool calls with thought signatures
  • Turn 3: Multi-turn conversation with tool results
  • Turn 4: Multiple parallel tool calls

The assertions added (lines 1328, 1359, 1456) properly validate the presence of thought signatures, addressing previous review feedback. Good work on the comprehensive test coverage!


1415-1416: Guard against None when slicing part.text (duplicate pattern).

Same issue as Line 1316: part_text might be None, causing a TypeError when slicing.

🔎 Proposed fix
                     part_text = getattr(part, 'text', None)
-                    print(f"  [THOUGHT] {part_text[:100] if part_text else '(no text)'}...")
+                    preview = part_text[:100] if part_text else '(no text)'
+                    print(f"  [THOUGHT] {preview}...")

Likely an incorrect or invalid review comment.


1315-1316: Guard against None when slicing part.text.

The code uses getattr(part, 'text', None) to safely retrieve the text attribute, but then immediately slices it with part_text[:100]. If part_text is None, this will raise a TypeError.

🔎 Proposed fix to add None check
                 part_text = getattr(part, 'text', None)
-                print(f"  [THOUGHT] {part_text[:100] if part_text else '(no text)'}...")
+                print(f"  [THOUGHT] {part_text[:100] if part_text and len(part_text) > 0 else '(no text)'}...")

Or more simply:

                 part_text = getattr(part, 'text', None)
-                print(f"  [THOUGHT] {part_text[:100] if part_text else '(no text)'}...")
+                preview = part_text[:100] if part_text else '(no text)'
+                print(f"  [THOUGHT] {preview}...")

Likely an incorrect or invalid review comment.

core/providers/gemini/chat.go (1)

114-130: LGTM: Thought signature correctly attached to tool calls.

The implementation properly:

  1. Builds the tool call with standard fields (Index, Type, ID, Function)
  2. Base64-encodes the binary ThoughtSignature for safe transmission
  3. Attaches it to ExtraContent under the google.thought_signature key, following the provider-specific extension pattern
  4. Maintains clean separation between standard and provider-specific fields

The nested structure in ExtraContent is consistent with the field documentation in core/schemas/chatcompletions.go (line 714) which describes it as "Provider-specific fields (e.g., thought_signature for Gemini)".

core/providers/gemini/types.go (1)

1017-1066: LGTM: Correct URL-safe base64 decoding for thought signatures.

The custom UnmarshalJSON implementation correctly handles the thoughtSignature field sent as a base64-encoded string from the Google GenAI SDK:

  1. URL-safe to standard conversion: Properly replaces URL-safe characters (_/, -+)
  2. Padding logic: Correctly adds padding based on length modulo 4
  3. Error handling: Returns descriptive errors if decoding fails
  4. Consistency: Mirrors the pattern used in Blob.UnmarshalJSON (lines 1079-1114)

This implementation enables proper round-tripping of encrypted thought signatures through JSON serialization/deserialization.

core/providers/gemini/responses.go (10)

174-176: LGTM! Well-designed state tracking for consumed messages.

The consumedIndices map cleanly tracks which reasoning messages have been consumed as thought signatures, preventing duplicate processing later in the loop. This pairs well with the look-ahead logic.


287-290: LGTM! Proper skip logic for consumed messages.

The skip logic correctly prevents double-processing of reasoning messages that were already consumed as thought signatures attached to function calls.


829-842: LGTM! Proper signature attachment in streaming deltas.

The logic correctly encodes the ThoughtSignature to base64 and attaches it to the stream response delta, maintaining consistency with the signature handling pattern throughout the file.


1728-1732: LGTM! Symmetric signature handling across conversion paths.

The signature handling uses two patterns consistently:

  • Text content: signature embedded in the content block
  • Function calls: separate reasoning message with encrypted content

This mirrors the decoding logic in ToGeminiResponsesResponse (lines 238-251), ensuring proper round-trip conversion.

Also applies to: 1763-1776


2387-2396: LGTM! Proper signature decoding with graceful error handling.

The signature decoding correctly handles base64 decoding errors by gracefully degrading (text is preserved even if signature decode fails). This is consistent with the error handling pattern used throughout the file.


244-244: LGTM! Consistent base64 encoding/decoding throughout the file.

All ThoughtSignature encoding/decoding operations consistently use base64.StdEncoding with proper error handling. The symmetric encode-to-string and decode-from-string pattern ensures correct round-trip conversion.

Also applies to: 304-304, 838-838, 1414-1414, 1504-1504, 1730-1730, 1766-1766, 1908-1908, 2330-2330, 2391-2391


303-310: LGTM! Proper handling of standalone reasoning signatures.

This correctly processes standalone reasoning messages with encrypted content, which are distinct from thought signatures attached to function calls (those are skipped at lines 287-290).


244-246: LGTM! Robust error handling with graceful degradation.

The error handling consistently treats thought signatures as optional enhancements. Base64 decode failures and JSON marshal errors are handled gracefully without disrupting the core message conversion flow. This approach ensures system resilience even when signature data is malformed.

Also applies to: 304-306, 2330-2333


174-176: Verify integration test coverage for thought signature handling includes all multi-turn patterns.

Thought signatures appear in content parts (text or functionCall), are returned with all function call responses, appear on the first part of parallel function calls, appear on each part of sequential function calls, and appear in the last part of responses without function calls. The integration test should confirm coverage for:

  • Function calls with attached thought signatures (look-ahead pattern)
  • Text messages with inline signatures
  • Streaming responses with signatures in deltas
  • Round-trip conversion (Bifrost → Gemini → Bifrost)

1370-1542: The multiple-message-per-Content refactor is architecturally sound.

The change generates multiple ResponsesMessage entries per Content (one per Part, plus additional reasoning messages when signatures are present). This is handled properly throughout the stack:

  • bifrostReq.Input is a slice ([]ResponsesMessage), naturally accommodating variable message counts
  • The reverse conversion convertResponsesMessagesToGeminiContents correctly reconstructs individual Content objects from the message stream using lookahead to match reasoning messages with function calls (line 2232 skips orphan reasoning messages; lines 2101-2109 match them with preceding function calls)
  • No callers assume 1:1 Content-to-Message mapping

@TejasGhatte TejasGhatte force-pushed the 12-20-fix_gemini_thought_signature_handling branch from d516b42 to 31f1ad5 Compare December 20, 2025 16:51
Copy link
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/providers/gemini/responses.go (1)

738-771: Streaming: FunctionCall parts with ThoughtSignature are dropping the signature and will cause Gemini 3 Pro validation failures.

The switch statement in processGeminiPart (lines 742-767) processes parts as mutually exclusive cases. When a part contains both FunctionCall and ThoughtSignature (which Gemini 3 Pro always includes on the first function call part, and it is mandatory to return that part), only the FunctionCall case executes.

processGeminiFunctionCallPart (lines 978-1044) does not extract or emit the ThoughtSignature. This means Gemini 3 Pro requires thoughtSignature to be passed back during function calling, otherwise validation will fail with a 400 error.

Fix by extracting part.ThoughtSignature within processGeminiFunctionCallPart and emitting a parallel reasoning item (similar to how processGeminiThoughtSignaturePart does), or restructure the switch logic to handle parts with multiple content types.

♻️ Duplicate comments (2)
tests/integrations/tests/test_google.py (2)

113-135: Don’t ignore the configured timeout in get_provider_google_client.

The block

if api_config.get("timeout"):
    http_options_kwargs["timeout"] = 30000

drops the configured timeout and hardcodes 30000, reducing flexibility and regressing prior behavior. Prefer honoring the config value with a sensible default, e.g.:

Proposed fix
-    if api_config.get("timeout"):
-        http_options_kwargs["timeout"] = 30000
+    timeout = api_config.get("timeout")
+    if timeout:
+        http_options_kwargs["timeout"] = timeout

1458-1462: Minor: remove unnecessary f-string around static log line.

print(f"\n=== Token Usage ===") is an f-string without interpolation (Ruff F541). It can be a plain string:

-        if hasattr(response_1.usage_metadata, 'thoughts_token_count'):
-                print(f"\n=== Token Usage ===")
+        if hasattr(response_1.usage_metadata, 'thoughts_token_count'):
+                print("\n=== Token Usage ===")
🧹 Nitpick comments (4)
core/providers/gemini/types.go (1)

1017-1066: Part.UnmarshalJSON correctly normalizes URL-safe thought signatures; consider a small shared helper.

The custom unmarshal cleanly handles URL-safe base64 (replacing -/_ and padding) and populates ThoughtSignature as bytes. This aligns with Blob.UnmarshalJSON and should interoperate correctly with the rest of the pipeline.

To avoid duplication and keep future changes in one place, you could extract the URL-safe base64 → []byte logic into a small helper reused by both Part and Blob unmarshaling.

tests/integrations/tests/test_google.py (2)

1238-1238: Clean up unused test_config parameters to satisfy Ruff (ARG002).

Several tests accept test_config but never use it (Ruff ARG002 on these definitions). If you only need the fixture for consistency, renaming to _test_config is enough; otherwise, you can drop the parameter.

Example:

Example signature tweak
-    def test_28_gemini_3_pro_thought_signatures_multi_turn(self, test_config):
+    def test_28_gemini_3_pro_thought_signatures_multi_turn(self, _test_config):

Apply the same pattern to the other flagged tests to keep the suite lint-clean.

Also applies to: 1468-1468, 1550-1550, 1604-1604, 1651-1651, 1695-1695, 1740-1740, 1800-1800, 1834-1834, 1854-1854, 1894-1894, 1937-1937


2030-2051: extract_google_function_calls helper is currently unused in this file.

If you don’t end up wiring this into any tests, consider removing it or adding a narrow unit test that exercises it to avoid dead code in the integration suite.

core/providers/gemini/chat.go (1)

114-131: Tool-call ExtraContent correctly carries ThoughtSignature; keep merge semantics in mind for future fields.

Encoding part.ThoughtSignature and attaching it under toolCall.ExtraContent["google"]["thought_signature"] preserves the encrypted signature alongside the tool call without disturbing the core schema, which is exactly what you want here.

Right now toolCall is freshly constructed so assigning ExtraContent = map[...] is safe; if you later add other metadata into ExtraContent before this block, you’ll want to merge into an existing map rather than overwrite it.

Also applies to: 145-152

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d516b42 and 31f1ad5.

📒 Files selected for processing (28)
  • core/changelog.md (1 hunks)
  • core/providers/bedrock/utils.go (1 hunks)
  • core/providers/gemini/chat.go (1 hunks)
  • core/providers/gemini/responses.go (8 hunks)
  • core/providers/gemini/types.go (1 hunks)
  • core/providers/gemini/utils.go (2 hunks)
  • core/version (1 hunks)
  • framework/changelog.md (1 hunks)
  • framework/version (1 hunks)
  • plugins/governance/changelog.md (1 hunks)
  • plugins/governance/version (1 hunks)
  • plugins/jsonparser/changelog.md (1 hunks)
  • plugins/jsonparser/version (1 hunks)
  • plugins/logging/changelog.md (1 hunks)
  • plugins/logging/version (1 hunks)
  • plugins/maxim/changelog.md (1 hunks)
  • plugins/maxim/version (1 hunks)
  • plugins/mocker/changelog.md (1 hunks)
  • plugins/mocker/version (1 hunks)
  • plugins/otel/changelog.md (1 hunks)
  • plugins/otel/version (1 hunks)
  • plugins/semanticcache/changelog.md (1 hunks)
  • plugins/semanticcache/version (1 hunks)
  • plugins/telemetry/changelog.md (1 hunks)
  • plugins/telemetry/version (1 hunks)
  • tests/integrations/tests/test_google.py (13 hunks)
  • transports/changelog.md (1 hunks)
  • transports/version (1 hunks)
✅ Files skipped from review due to trivial changes (6)
  • plugins/jsonparser/changelog.md
  • plugins/telemetry/changelog.md
  • plugins/governance/changelog.md
  • plugins/otel/version
  • plugins/telemetry/version
  • transports/changelog.md
🚧 Files skipped from review as they are similar to previous changes (15)
  • plugins/logging/version
  • core/providers/bedrock/utils.go
  • plugins/semanticcache/version
  • plugins/jsonparser/version
  • framework/version
  • core/version
  • plugins/maxim/version
  • framework/changelog.md
  • plugins/mocker/version
  • plugins/governance/version
  • core/providers/gemini/utils.go
  • plugins/mocker/changelog.md
  • plugins/otel/changelog.md
  • plugins/logging/changelog.md
  • transports/version
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • core/changelog.md
  • plugins/maxim/changelog.md
  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
  • tests/integrations/tests/test_google.py
  • plugins/semanticcache/changelog.md
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/gemini/chat.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
🧬 Code graph analysis (1)
tests/integrations/tests/test_google.py (3)
tests/integrations/tests/utils/common.py (1)
  • skip_if_no_api_key (1813-1824)
core/providers/gemini/types.go (5)
  • Tool (604-631)
  • Content (977-985)
  • Part (991-1015)
  • FunctionCall (1197-1207)
  • FunctionResponse (1210-1232)
tests/integrations/tests/utils/parametrize.py (1)
  • get_cross_provider_params_for_scenario (12-47)
🪛 Ruff (0.14.8)
tests/integrations/tests/test_google.py

1238-1238: Unused method argument: test_config

(ARG002)


1460-1460: f-string without any placeholders

Remove extraneous f prefix

(F541)


1468-1468: Unused method argument: test_config

(ARG002)


1550-1550: Unused method argument: test_config

(ARG002)


1604-1604: Unused method argument: test_config

(ARG002)


1651-1651: Unused method argument: test_config

(ARG002)


1695-1695: Unused method argument: test_config

(ARG002)


1740-1740: Unused method argument: test_config

(ARG002)


1800-1800: Unused method argument: test_config

(ARG002)


1834-1834: Unused method argument: test_config

(ARG002)


1854-1854: Unused method argument: test_config

(ARG002)


1894-1894: Unused method argument: test_config

(ARG002)


1937-1937: Unused method argument: test_config

(ARG002)

🔇 Additional comments (4)
plugins/maxim/changelog.md (1)

1-1: Changelog entry is accurate and properly formatted.

The version upgrades (core to 1.2.41 and framework to 1.1.51) align with the PR objectives and are appropriately documented for the plugin's dependency changes.

core/changelog.md (1)

1-1: Changelog entry accurately reflects the Gemini thought-signature fix.

Documentation-only change; content is clear and consistent with the code updates elsewhere.

tests/integrations/tests/test_google.py (1)

1237-1465: Gemini 3 Pro thought-signature test exercises the right multi-turn/tool-call paths.

The new test_28_gemini_3_pro_thought_signatures_multi_turn now:

  • Positively asserts presence of thought signatures (not just logs) across turns.
  • Reuses tool-call thought signatures in Turn 3 safely (tool_thought_signatures is asserted non-empty before indexing).
  • Guards part.text slicing via getattr, avoiding TypeError on thought-only parts.

Given this leans on specific Gemini 3 Pro behavior, just be aware that future model changes around when signatures are emitted could make this test brittle; if that starts happening, you may want to gate some assertions on explicit model metadata or relax counts slightly rather than disabling the test.

core/providers/gemini/responses.go (1)

174-257: Non-stream Responses ↔ Gemini thought-signature round-tripping looks consistent.

The updated conversions correctly wire encrypted thought signatures through the Responses API:

  • ToGeminiResponsesResponse and convertResponsesMessagesToGeminiContents look ahead to a following reasoning message, decode its EncryptedContent, and attach it as Part.ThoughtSignature on the associated function call, skipping the consumed reasoning message.
  • convertGeminiContentsToResponsesMessages / convertGeminiCandidatesToResponsesOutput emit:
    • Function-call messages plus a separate reasoning message when Part.ThoughtSignature is present.
    • Text blocks with Signature set when a text part carries ThoughtSignature.
  • convertContentBlockToGeminiPart decodes block.Signature back into Part.ThoughtSignature, closing the loop.

This gives you a coherent, lossless mapping for thought signatures across the non-streaming Responses surface.

Also applies to: 285-311, 1364-1542, 1685-1923, 2382-2511

Copy link
Contributor

akshaydeo commented Dec 22, 2025

Merge activity

  • Dec 22, 4:25 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Dec 22, 4:26 AM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo akshaydeo merged commit 4e0d43a into main Dec 22, 2025
9 checks passed
@akshaydeo akshaydeo deleted the 12-20-fix_gemini_thought_signature_handling branch December 22, 2025 04:26
@coderabbitai coderabbitai bot mentioned this pull request Jan 19, 2026
8 tasks
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.

3 participants