Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion integrations/google_genai/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ classifiers = [
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = ["haystack-ai>=2.22.0", "google-genai[aiohttp]>=1.18.0", "jsonref>=1.0.0"]
dependencies = ["haystack-ai>=2.22.0", "google-genai[aiohttp]>=1.51.0", "jsonref>=1.0.0"]

[project.urls]
Documentation = "https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/google_genai#readme"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,15 @@ def __init__(
- `-1`: Dynamic (default for most models)
- `0`: Disable thinking (Flash/Flash-Lite only)
- Positive integer: Set explicit budget
For Gemini 3 series and newer, supports `thinking_level` to configure thinking depth:
- `thinking_level`: str, controls thinking (https://ai.google.dev/gemini-api/docs/thinking#levels-budgets)
- `minimal`: Matches the "no thinking" setting for most queries. The model may think very minimally for
complex coding tasks. Minimizes latency for chat or high throughput applications.
- `low`: Minimizes latency and cost. Best for simple instruction following, chat, or high-throughput
applications.
- `medium`: Balanced thinking for most tasks.
- `high`: (Default, dynamic): Maximizes reasoning depth. The model may take significantly longer to reach
a first token, but the output will be more carefully reasoned.
:param safety_settings: Safety settings for content filtering
:param streaming_callback: A callback function that is called when a new token is received from the stream.
:param tools: A list of Tool and/or Toolset objects, or a single Toolset for which the model can prepare calls.
Expand Down Expand Up @@ -789,12 +798,44 @@ def _process_thinking_config(self, generation_kwargs: dict[str, Any]) -> dict[st
logger.warning(
f"Invalid thinking_budget type: {type(thinking_budget)}. Expected int, using dynamic allocation."
)
# fall back to default: dynamic thinking budget allocation
thinking_budget = -1

# Create thinking config
thinking_config = types.ThinkingConfig(thinking_budget=thinking_budget, include_thoughts=True)
generation_kwargs["thinking_config"] = thinking_config

if "thinking_level" in generation_kwargs:
thinking_level = generation_kwargs.pop("thinking_level")

# Basic type validation
if not isinstance(thinking_level, str):
logger.warning(
f"Invalid thinking_level type: {type(thinking_level).__name__}. Expected str, "
f"falling back to THINKING_LEVEL_UNSPECIFIED."
)
thinking_level = types.ThinkingLevel.THINKING_LEVEL_UNSPECIFIED
else:
# Convert to uppercase for case-insensitive matching
thinking_level_upper = thinking_level.upper()

# Check if the uppercase value is a valid ThinkingLevel enum member
valid_levels = [level.value for level in types.ThinkingLevel]
if thinking_level_upper not in valid_levels:
logger.warning(
f"Invalid thinking_level value: '{thinking_level}'. "
f"Must be one of: {valid_levels} (case-insensitive). "
"Falling back to THINKING_LEVEL_UNSPECIFIED."
)
thinking_level = types.ThinkingLevel.THINKING_LEVEL_UNSPECIFIED
else:
# Parse valid string to ThinkingLevel enum
thinking_level = types.ThinkingLevel(thinking_level_upper)

# Create thinking config with level
thinking_config = types.ThinkingConfig(thinking_level=thinking_level, include_thoughts=True)
generation_kwargs["thinking_config"] = thinking_config

return generation_kwargs

@component.output_types(replies=list[ChatMessage])
Expand Down
40 changes: 39 additions & 1 deletion integrations/google_genai/tests/test_chat_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -1337,7 +1337,7 @@ def test_aggregate_streaming_chunks_with_reasoning(monkeypatch):
assert result.meta["model"] == "gemini-2.5-pro"


def test_process_thinking_config(monkeypatch):
def test_process_thinking_budget(monkeypatch):
"""Test the _process_thinking_config method with different thinking_budget values."""
monkeypatch.setenv("GOOGLE_API_KEY", "test-api-key")
component = GoogleGenAIChatGenerator()
Expand Down Expand Up @@ -1378,3 +1378,41 @@ def test_process_thinking_config(monkeypatch):
result = component._process_thinking_config(generation_kwargs.copy())
assert result["thinking_config"].thinking_budget == -1 # Dynamic allocation
assert result["temperature"] == 0.5


def test_process_thinking_level(monkeypatch):
"""Test the _process_thinking_config method with different thinking_level values."""
monkeypatch.setenv("GOOGLE_API_KEY", "test-api-key")
component = GoogleGenAIChatGenerator()

# Test valid thinking_level values
generation_kwargs = {"thinking_level": "high", "temperature": 0.7}
result = component._process_thinking_config(generation_kwargs.copy())

# thinking_level should be moved to thinking_config
assert "thinking_level" not in result
assert "thinking_config" in result
assert result["thinking_config"].thinking_level == types.ThinkingLevel.HIGH
# Other kwargs should be preserved
assert result["temperature"] == 0.7

# Test THINKING_LEVEL_LOW in upper case
generation_kwargs = {"thinking_level": "LOW"}
result = component._process_thinking_config(generation_kwargs.copy())
assert result["thinking_config"].thinking_level == types.ThinkingLevel.LOW

# Test THINKING_LEVEL_UNSPECIFIED
generation_kwargs = {"thinking_level": "test"}
result = component._process_thinking_config(generation_kwargs.copy())
assert result["thinking_config"].thinking_level == types.ThinkingLevel.THINKING_LEVEL_UNSPECIFIED

# Test when thinking_level is not present
generation_kwargs = {"temperature": 0.5}
result = component._process_thinking_config(generation_kwargs.copy())
assert result == generation_kwargs # No changes

# Test invalid type (should fall back to THINKING_LEVEL_UNSPECIFIED)
generation_kwargs = {"thinking_level": 123, "temperature": 0.5}
result = component._process_thinking_config(generation_kwargs.copy())
assert result["thinking_config"].thinking_level == types.ThinkingLevel.THINKING_LEVEL_UNSPECIFIED
assert result["temperature"] == 0.5
Loading