Skip to content

handle gemini thought signatures#3

Open
DhirenMhatre wants to merge 1 commit into
masterfrom
cc/openai_gemini_thought_signatures
Open

handle gemini thought signatures#3
DhirenMhatre wants to merge 1 commit into
masterfrom
cc/openai_gemini_thought_signatures

Conversation

@DhirenMhatre
Copy link
Copy Markdown

Fixes #


Read the full contributing guidelines: https://docs.langchain.com/oss/python/contributing/overview

All contributions must be in English. See the language policy.

If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!

Thank you for contributing to LangChain! Follow these steps to have your pull request considered as ready for review.

  1. PR title: Should follow the format: TYPE(SCOPE): DESCRIPTION
  1. PR description:
  • Write 1-2 sentences summarizing the change.
  • The Fixes #xx line at the top is required for external contributions — update the issue number and keep the keyword. This links your PR to the approved issue and auto-closes it on merge.
  • If there are any breaking changes, please clearly describe them.
  • If this PR depends on another PR being merged first, please include "Depends on #PR_NUMBER" in the description.
  1. Run make format, make lint and make test from the root of the package(s) you've modified.
  • We will not consider a PR unless these three are passing in CI.
  1. How did you verify your code works?

Additional guidelines:

  • All external PRs must link to an issue or discussion where a solution has been approved by a maintainer, and you must be assigned to that issue. PRs without prior approval will be closed.
  • PRs should not touch more than one package unless absolutely necessary.
  • Do not update the uv.lock files or add dependencies to pyproject.toml files (even optional ones) unless you have explicit permission to do so by a maintainer.

Social handles (optional)

Twitter: @
LinkedIn: https://linkedin.com/in/

@DhirenMhatre
Copy link
Copy Markdown
Author

@codity review

1 similar comment
@DhirenMhatre
Copy link
Copy Markdown
Author

@codity review

@codity-dm
Copy link
Copy Markdown

codity-dm Bot commented May 18, 2026

Policy Check Failed

✗ 3/3 policy checks failed:

• Need 2 more approval(s) (0/2) — comment LGTM or approve via review
• Missing ticket reference (expected: JIRA-, ENG-, #*)
• 2 code file(s) changed but no test files added


To merge this PR:

  1. Address the failed checks listed above
  2. Ensure branch protection requires the codity/policy-check status

Configure policies in your dashboard

@codity-dm
Copy link
Copy Markdown

codity-dm Bot commented May 18, 2026

PR Summary

What Changed

  • Added support for preserving Gemini thought signatures in OpenAI-compatible tool calls. Google's endpoint requires these signatures to be echoed back in subsequent turns.
  • Extracts signatures from extra_content.google.thought_signature and stores them in additional_kwargs, then re-injects them on outgoing calls.

Key Changes by Area

Tool Call Handling: Extracts and stores Gemini thought signatures from incoming tool calls in a {tool_call_id: signature} map.

Response Generation: Re-injects stored signatures into outgoing tool calls via extra_content.google.thought_signature for both streaming and non-streaming modes.

Files Changed

File Changes Summary
libs/partners/openai/langchain_openai/chat_models/base.py Added extraction and re-injection of Gemini thought signatures in tool calls
libs/partners/openai/tests/unit_tests/chat_models/test_base.py Added roundtrip test for signature preservation in streaming and non-streaming modes

Review Focus Areas

  • Signature extraction logic in base.py handles both streaming and non-streaming response formats correctly.
  • The _GEMINI_THOUGHT_SIGNATURES_MAP_KEY constant usage is consistent across extraction and re-injection paths.

Architecture

Design Decisions: Uses additional_kwargs as the storage mechanism for cross-turn state. This keeps the change minimal and avoids schema changes to core message types. The {tool_call_id: signature} map structure allows efficient lookup during re-injection without scanning full message history.

Risks: This is a provider-specific workaround (Gemini via OpenAI-compatible endpoint) embedded in the generic OpenAI partner package. If Google changes their API shape, this code needs updating. The risk is intentional and acceptable given the current lack of a provider-agnostic way to handle tool call metadata persistence.

Merge Status

MERGEABLE — PR Score 73/100, above threshold (50). All gates passed.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 18, 2026

Greptile Summary

This PR adds support for Google's Gemini thought signatures that are returned on tool calls via the OpenAI-compatible endpoint (extra_content.google.thought_signature). The signatures are extracted on inbound messages and re-injected on outbound tool calls so Gemini can correlate them across turns.

  • Adds _extract_gemini_thought_signature helper and collects a {id → signature} map into additional_kwargs in both _convert_dict_to_message (non-streaming) and _convert_delta_to_message_chunk (streaming).
  • In _convert_message_to_dict, re-attaches each signature as extra_content.google.thought_signature on the matching tool call before the request is sent.
  • Adds a parametrized round-trip unit test covering both streaming and non-streaming paths.

Confidence Score: 4/5

The change is narrowly scoped to Gemini thought-signature handling and does not affect standard OpenAI or Azure paths; it is safe to merge for general use.

The extraction and re-injection logic is correct for the happy path covered by the tests. The only issue found is a truthiness guard (if signature and …) that would silently discard an empty-string thought signature rather than treating it as a valid value — a scenario that is unlikely today but semantically incorrect. Both the streaming and non-streaming paths share this pattern.

libs/partners/openai/langchain_openai/chat_models/base.py — the two signature-collection loops in _convert_dict_to_message and _convert_delta_to_message_chunk.

Important Files Changed

Filename Overview
libs/partners/openai/langchain_openai/chat_models/base.py Adds Gemini thought-signature extraction on inbound messages (both streaming and non-streaming) and re-injection on outbound tool calls; the truthiness guard on the extracted signature silently discards empty-string values in two places.
libs/partners/openai/tests/unit_tests/chat_models/test_base.py Adds a parametrized round-trip test covering both streaming and non-streaming Gemini thought-signature handling; coverage is solid for the happy path.

Sequence Diagram

sequenceDiagram
    participant User
    participant LangChain
    participant GeminiAPI

    User->>LangChain: invoke / stream (Turn 1)
    LangChain->>GeminiAPI: POST /chat/completions
    GeminiAPI-->>LangChain: "tool_calls[{id, function, extra_content.google.thought_signature}]"
    LangChain->>LangChain: extract thought_signatures → additional_kwargs[__gemini_function_call_thought_signatures__]
    LangChain-->>User: AIMessage (tool_calls + additional_kwargs with signatures)

    User->>LangChain: invoke / stream (Turn 2 with history + ToolMessage)
    LangChain->>LangChain: re-inject extra_content.google.thought_signature onto each tool call
    LangChain->>GeminiAPI: POST /chat/completions (tool_calls with thought_signature echoed back)
    GeminiAPI-->>LangChain: assistant reply
    LangChain-->>User: final AIMessage
Loading

Reviews (1): Last reviewed commit: "handle gemini thought signatures" | Re-trigger Greptile

Comment on lines +247 to +251
if (signature := _extract_gemini_thought_signature(raw_tool_call)) and (
tool_call_id := raw_tool_call.get("id")
):
thought_signatures[tool_call_id] = signature
if thought_signatures:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 The walrus-operator condition uses truthiness to guard the signature, so an empty-string thought_signature (falsy) would be silently dropped instead of being stored and echoed back. The same pattern appears a second time in _convert_delta_to_message_chunk. Prefer an explicit is not None check to match the intent of the extractor, which already returns None for absent/wrong-type values.

Suggested change
if (signature := _extract_gemini_thought_signature(raw_tool_call)) and (
tool_call_id := raw_tool_call.get("id")
):
thought_signatures[tool_call_id] = signature
if thought_signatures:
if (signature := _extract_gemini_thought_signature(raw_tool_call)) is not None and (
tool_call_id := raw_tool_call.get("id")
):
thought_signatures[tool_call_id] = signature
if thought_signatures:

Comment on lines +492 to +497
for rtc in raw_tool_calls:
if (signature := _extract_gemini_thought_signature(rtc)) and (
tool_call_id := rtc.get("id")
):
thought_signatures[tool_call_id] = signature
if thought_signatures:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Same empty-string silent-drop issue in the streaming (_convert_delta_to_message_chunk) path as in the non-streaming path above — is not None is the correct guard here.

Suggested change
for rtc in raw_tool_calls:
if (signature := _extract_gemini_thought_signature(rtc)) and (
tool_call_id := rtc.get("id")
):
thought_signatures[tool_call_id] = signature
if thought_signatures:
for rtc in raw_tool_calls:
if (signature := _extract_gemini_thought_signature(rtc)) is not None and (
tool_call_id := rtc.get("id")
):
thought_signatures[tool_call_id] = signature
if thought_signatures:

@codity-dm
Copy link
Copy Markdown

codity-dm Bot commented May 18, 2026

Security Scan Summary

Metric Value
Vulnerabilities Critical: 0
Overall Risk Clean
Files Scanned 2

No critical security issues detected

Scan completed in 26.9s

Security scan powered by Codity.ai

@codity-dm
Copy link
Copy Markdown

codity-dm Bot commented May 18, 2026

License Compliance Scan

Metric Value
Packages Scanned 0
High Risk (Strong Copyleft) 0
Medium Risk (Weak Copyleft) 0
Low Risk (Permissive) 0
Unknown License 0

All licenses are low-risk and compliant

Powered by Codity.ai · Docs

@codity-dm
Copy link
Copy Markdown

codity-dm Bot commented May 18, 2026

Code Quality Report — test-org-codity/langchain · PR #3

Scanned: 2026-05-18 12:40 UTC | Score: 55/100 | Provider: github

Executive Summary

Severity Count
Critical 0
High 0
Medium 3
Low 21
Top Findings

[CQ-LLM-001] libs/partners/openai/langchain_openai/chat_models/base.py:195 (Complexity · MEDIUM)

Issue: The function _extract_gemini_thought_signature has multiple nested if statements, which increases cyclomatic complexity.
Suggestion: Consider flattening the logic or using early returns to reduce nesting.

if not isinstance(extra_content, Mapping):
    return None
google = extra_content.get("google")
if not isinstance(google, Mapping):
    return None
signature = google.get("thought_signature")
return signature if isinstance(signature, str) else None

[CQ-LLM-003] libs/partners/openai/langchain_openai/chat_models/base.py:195 (Documentation · MEDIUM)

Issue: The function _extract_gemini_thought_signature lacks a detailed docstring explaining its parameters and return value.
Suggestion: Add a docstring that describes the function's purpose, parameters, and return value.

def _extract_gemini_thought_signature(raw_tool_call: Mapping[str, Any]) -> str | None:

[CQ-LLM-004] libs/partners/openai/tests/unit_tests/chat_models/test_base.py:788 (Testability · MEDIUM)

Issue: The test function test_gemini_thought_signature_roundtrip uses hard-coded values for tool calls, which may reduce test flexibility.
Suggestion: Consider using constants or configuration for tool call values to improve test maintainability.

tool_call = {
    "id": "tc_1",
    "type": "function",
    "function": {"name": "greet", "arguments": "{}"},
    "extra_content": {"google": {"thought_signature": "SIG_A"}},
}

[CQ-LLM-005] libs/partners/openai/langchain_openai/chat_models/base.py:194 (Maintainability · LOW)

Issue: The constant _GEMINI_THOUGHT_SIGNATURES_MAP_KEY is not clearly named, which may lead to confusion about its purpose.
Suggestion: Consider renaming the constant to something more descriptive, such as GEMINI_THOUGHT_SIGNATURES_KEY.

_GEMINI_THOUGHT_SIGNATURES_MAP_KEY = "__gemini_function_call_thought_signatures__"

[CQ-LLM-002] libs/partners/openai/langchain_openai/chat_models/base.py:226 (Error_Handling · LOW)

Issue: The exception in the try block is caught but not logged or handled, which may lead to silent failures.
Suggestion: Log the exception or handle it appropriately to avoid silent failures.

try:
    # some code that may raise an exception
except Exception as e:
    invalid_tool_calls.append(
        make_invalid_tool_call(raw_tool_call, str(e))
    )

[CQ-002] libs/partners/openai/langchain_openai/chat_models/base.py:248 (Complexity · LOW)

Issue: Deep nesting detected (depth ~5)
Suggestion: Extract nested blocks into helper functions

tool_call_id := raw_tool_call.get("id")

[CQ-002] libs/partners/openai/langchain_openai/chat_models/base.py:250 (Complexity · LOW)

Issue: Deep nesting detected (depth ~5)
Suggestion: Extract nested blocks into helper functions

thought_signatures[tool_call_id] = signature

[CQ-002] libs/partners/openai/langchain_openai/chat_models/base.py:415 (Complexity · LOW)

Issue: Deep nesting detected (depth ~5)
Suggestion: Extract nested blocks into helper functions

signature := thought_signatures.get(tool_call.get("id"))

[CQ-002] libs/partners/openai/langchain_openai/chat_models/base.py:417 (Complexity · LOW)

Issue: Deep nesting detected (depth ~5)
Suggestion: Extract nested blocks into helper functions

tool_call["extra_content"] = {

[CQ-002] libs/partners/openai/langchain_openai/chat_models/base.py:418 (Complexity · LOW)

Issue: Deep nesting detected (depth ~6)
Suggestion: Extract nested blocks into helper functions

"google": {"thought_signature": signature}

Per-File Breakdown

File Critical High Medium Low Total
libs/partners/openai/langchain_openai/chat_models/base.py 0 0 2 8 10
libs/partners/openai/tests/unit_tests/chat_models/test_base.py 0 0 1 13 14

Recommendations

  • Run automated tests after applying fixes to verify no regressions.

@DhirenMhatre
Copy link
Copy Markdown
Author

@codity review

@codity-dm
Copy link
Copy Markdown

codity-dm Bot commented May 18, 2026

Policy Check Failed

✗ 3/3 policy checks failed:

• Need 2 more approval(s) (0/2) — comment LGTM or approve via review
• Missing ticket reference (expected: JIRA-, ENG-, #*)
• 2 code file(s) changed but no test files added


To merge this PR:

  1. Address the failed checks listed above
  2. Ensure branch protection requires the codity/policy-check status

Configure policies in your dashboard

@codity-dm
Copy link
Copy Markdown

codity-dm Bot commented May 18, 2026

PR Summary

What Changed

  • Added support for Google's Gemini thought signatures in OpenAI-compatible endpoints to maintain round-trip compatibility with Google's reasoning models.
  • Extracts extra_content.google.thought_signature from incoming tool calls and stores it in additional_kwargs for re-injection on outgoing requests.

Key Changes by Area

OpenAI Chat Models: Added thought signature extraction and re-attachment in message conversion methods for both streaming and non-streaming scenarios.

Testing: Added parametrized roundtrip test covering streaming and non-streaming tool call scenarios.

Files Changed

File Changes Summary
libs/partners/openai/langchain_openai/chat_models/base.py Added _GEMINI_THOUGHT_SIGNATURES_MAP_KEY constant, _extract_gemini_thought_signature() helper, and modified conversion methods to handle thought signatures
libs/partners/openai/tests/unit_tests/chat_models/test_base.py Added test_gemini_thought_signature_roundtrip() parametrized test

Review Focus Areas

  • Verify thought signature extraction logic handles nested extra_content.google structure correctly.
  • Check streaming delta conversion preserves partial thought signatures across chunks.
  • Confirm additional_kwargs storage doesn't conflict with existing OpenAI metadata.

Architecture

Design Decisions: Uses a dedicated constant and helper function to isolate Gemini-specific logic, keeping changes localized to conversion layer rather than message schema. Storage in additional_kwargs follows LangChain's pattern for provider-specific extensions without breaking OpenAI API compatibility.

Risks: Thought signatures are provider-specific metadata that may not be portable to other model providers. This is intentional and acceptable for Gemini compatibility, but creates a subtle dependency on Google's internal format that could change.

Merge Status

MERGEABLE — PR Score 73/100, above threshold (50). All gates passed.

@codity-dm
Copy link
Copy Markdown

codity-dm Bot commented May 18, 2026

Security Scan Summary

Metric Value
Vulnerabilities Critical: 0
Overall Risk Clean
Files Scanned 2

No critical security issues detected

Scan completed in 26.5s

Security scan powered by Codity.ai

@codity-dm
Copy link
Copy Markdown

codity-dm Bot commented May 18, 2026

License Compliance Scan

Metric Value
Packages Scanned 0
High Risk (Strong Copyleft) 0
Medium Risk (Weak Copyleft) 0
Low Risk (Permissive) 0
Unknown License 0

All licenses are low-risk and compliant

Powered by Codity.ai · Docs

@codity-dm
Copy link
Copy Markdown

codity-dm Bot commented May 18, 2026

Code Quality Report — test-org-codity/langchain · PR #3

Scanned: 2026-05-18 16:43 UTC | Score: 55/100 | Provider: github

Executive Summary

Severity Count
Critical 0
High 0
Medium 3
Low 21
Top Findings

[CQ-LLM-003] libs/partners/openai/langchain_openai/chat_models/base.py:194 (Documentation · MEDIUM)

Issue: The new function _extract_gemini_thought_signature lacks a detailed docstring explaining its parameters and return value.
Suggestion: Add a docstring that describes the function's purpose, parameters, and return type.

def _extract_gemini_thought_signature(raw_tool_call: Mapping[str, Any]) -> str | None:

[CQ-LLM-001] libs/partners/openai/langchain_openai/chat_models/base.py:195 (Complexity · MEDIUM)

Issue: The function _extract_gemini_thought_signature has multiple nested if statements which can increase cyclomatic complexity.
Suggestion: Consider flattening the logic or using early returns to reduce nesting.

if not isinstance(extra_content, Mapping):
    return None
google = extra_content.get("google")
if not isinstance(google, Mapping):
    return None
signature = google.get("thought_signature")
return signature if isinstance(signature, str) else None

[CQ-LLM-004] libs/partners/openai/tests/unit_tests/chat_models/test_base.py:788 (Testability · MEDIUM)

Issue: The test function test_gemini_thought_signature_roundtrip uses hard-coded values which can make it less flexible and harder to maintain.
Suggestion: Consider using constants or configuration for values like 'tc_1' and 'SIG_A' to improve maintainability.

tool_call = {
    "id": "tc_1",
    "extra_content": {"google": {"thought_signature": "SIG_A"}},
}

[CQ-LLM-002] libs/partners/openai/langchain_openai/chat_models/base.py:226 (Error_Handling · LOW)

Issue: The code does not handle potential exceptions when accessing dictionary keys, which could lead to KeyError.
Suggestion: Consider using .get() method for safer access to dictionary keys.

tool_call_id := raw_tool_call.get("id")

[CQ-LLM-005] libs/partners/openai/langchain_openai/chat_models/base.py:235 (Maintainability · LOW)

Issue: The variable name 'thought_signatures' could be more descriptive to improve code readability.
Suggestion: Consider renaming 'thought_signatures' to something more descriptive, like 'gemini_thought_signatures'.

thought_signatures: dict[str, str] = {}

[CQ-002] libs/partners/openai/langchain_openai/chat_models/base.py:248 (Complexity · LOW)

Issue: Deep nesting detected (depth ~5)
Suggestion: Extract nested blocks into helper functions

tool_call_id := raw_tool_call.get("id")

[CQ-002] libs/partners/openai/langchain_openai/chat_models/base.py:250 (Complexity · LOW)

Issue: Deep nesting detected (depth ~5)
Suggestion: Extract nested blocks into helper functions

thought_signatures[tool_call_id] = signature

[CQ-002] libs/partners/openai/langchain_openai/chat_models/base.py:415 (Complexity · LOW)

Issue: Deep nesting detected (depth ~5)
Suggestion: Extract nested blocks into helper functions

signature := thought_signatures.get(tool_call.get("id"))

[CQ-002] libs/partners/openai/langchain_openai/chat_models/base.py:417 (Complexity · LOW)

Issue: Deep nesting detected (depth ~5)
Suggestion: Extract nested blocks into helper functions

tool_call["extra_content"] = {

[CQ-002] libs/partners/openai/langchain_openai/chat_models/base.py:418 (Complexity · LOW)

Issue: Deep nesting detected (depth ~6)
Suggestion: Extract nested blocks into helper functions

"google": {"thought_signature": signature}

Per-File Breakdown

File Critical High Medium Low Total
libs/partners/openai/langchain_openai/chat_models/base.py 0 0 2 8 10
libs/partners/openai/tests/unit_tests/chat_models/test_base.py 0 0 1 13 14

Recommendations

  • Run automated tests after applying fixes to verify no regressions.

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