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: 2 additions & 0 deletions haystack/components/generators/chat/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ class OpenAIChatGenerator:
```
"""

_HAYSTACK_TO_PROVIDER_GENERATION_KWARGS: ClassVar[dict[str, str]] = {"max_output_tokens": "max_completion_tokens"}

SUPPORTED_MODELS: ClassVar[list[str]] = [
"gpt-5-mini",
"gpt-5-nano",
Expand Down
2 changes: 2 additions & 0 deletions haystack/components/generators/chat/openai_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ class OpenAIResponsesChatGenerator:
```
"""

_HAYSTACK_TO_PROVIDER_GENERATION_KWARGS: ClassVar[dict[str, str]] = {"max_output_tokens": "max_output_tokens"}

SUPPORTED_MODELS: ClassVar[list[str]] = [
"gpt-5-mini",
"gpt-5-nano",
Expand Down
41 changes: 41 additions & 0 deletions haystack/components/generators/chat/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0

from typing import Any

from haystack.components.generators.chat.types import ChatGenerator

# The provider-neutral generation parameters that Haystack components can request from Chat Generators.
# The chosen name is based on OpenAI's Responses API.
_HAYSTACK_GENERATION_PARAMETERS = frozenset({"max_output_tokens"})


def _convert_haystack_generation_kwargs(
chat_generator: ChatGenerator, haystack_generation_kwargs: dict[str, Any]
) -> dict[str, Any]:
"""
Convert provider-neutral Haystack generation parameters for a Chat Generator.

Chat Generators advertise supported parameters through a private class-level mapping from the canonical Haystack
name to the provider-specific name. Parameters not advertised by the generator are omitted, allowing callers to
provide a fallback for generators that do not expose this optional capability.

:param chat_generator: The Chat Generator that will receive the converted parameters.
:param haystack_generation_kwargs: Generation parameters using Haystack's canonical names.
:returns: The supported parameters converted to their provider-specific names.
:raises ValueError: If a parameter is not part of Haystack's canonical vocabulary.
"""
unknown_parameters = haystack_generation_kwargs.keys() - _HAYSTACK_GENERATION_PARAMETERS
if unknown_parameters:
unknown = ", ".join(sorted(unknown_parameters))
msg = f"Unknown Haystack generation parameter(s): {unknown}"
raise ValueError(msg)

parameter_mapping = getattr(chat_generator, "_HAYSTACK_TO_PROVIDER_GENERATION_KWARGS", {})

return {
provider_name: haystack_generation_kwargs[haystack_name]
for haystack_name, provider_name in parameter_mapping.items()
if haystack_name in haystack_generation_kwargs
}
8 changes: 7 additions & 1 deletion test/components/generators/chat/test_azure.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import haystack.components.generators.chat.azure as azure_chat_module
from haystack import Pipeline, component
from haystack.components.generators.chat import AzureOpenAIChatGenerator
from haystack.components.generators.chat import AzureOpenAIChatGenerator, OpenAIChatGenerator
from haystack.components.generators.utils import print_streaming_chunk
from haystack.dataclasses import ChatMessage, ToolCall
from haystack.tools import ComponentTool, Tool
Expand Down Expand Up @@ -78,6 +78,12 @@ def tools():


class TestAzureOpenAIChatGenerator:
def test_haystack_to_provider_generation_kwargs(self) -> None:
assert (
AzureOpenAIChatGenerator._HAYSTACK_TO_PROVIDER_GENERATION_KWARGS
is OpenAIChatGenerator._HAYSTACK_TO_PROVIDER_GENERATION_KWARGS
)

def test_supported_models(self) -> None:
"""SUPPORTED_MODELS is a non-empty list of strings."""
models = AzureOpenAIChatGenerator.SUPPORTED_MODELS
Expand Down
8 changes: 7 additions & 1 deletion test/components/generators/chat/test_azure_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from pydantic import BaseModel

from haystack import Pipeline, component
from haystack.components.generators.chat import AzureOpenAIResponsesChatGenerator
from haystack.components.generators.chat import AzureOpenAIResponsesChatGenerator, OpenAIResponsesChatGenerator
from haystack.components.generators.utils import print_streaming_chunk
from haystack.dataclasses import ChatMessage, ToolCall
from haystack.tools import ComponentTool, Tool
Expand Down Expand Up @@ -75,6 +75,12 @@ def tools():


class TestInitialization:
def test_haystack_to_provider_generation_kwargs(self) -> None:
assert (
AzureOpenAIResponsesChatGenerator._HAYSTACK_TO_PROVIDER_GENERATION_KWARGS
is OpenAIResponsesChatGenerator._HAYSTACK_TO_PROVIDER_GENERATION_KWARGS
)

def test_supported_models(self) -> None:
"""SUPPORTED_MODELS is a non-empty list of strings."""
models = AzureOpenAIResponsesChatGenerator.SUPPORTED_MODELS
Expand Down
5 changes: 5 additions & 0 deletions test/components/generators/chat/test_openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ def tools():


class TestOpenAIChatGenerator:
def test_haystack_to_provider_generation_kwargs(self) -> None:
assert OpenAIChatGenerator._HAYSTACK_TO_PROVIDER_GENERATION_KWARGS == {
"max_output_tokens": "max_completion_tokens"
}

def test_supported_models(self) -> None:
"""SUPPORTED_MODELS is a non-empty list of strings."""
models = OpenAIChatGenerator.SUPPORTED_MODELS
Expand Down
5 changes: 5 additions & 0 deletions test/components/generators/chat/test_openai_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ def __call__(self, chunk: StreamingChunk) -> None:


class TestInitialization:
def test_haystack_to_provider_generation_kwargs(self) -> None:
assert OpenAIResponsesChatGenerator._HAYSTACK_TO_PROVIDER_GENERATION_KWARGS == {
"max_output_tokens": "max_output_tokens"
}

def test_supported_models(self) -> None:
"""SUPPORTED_MODELS is a non-empty list of strings."""
models = OpenAIResponsesChatGenerator.SUPPORTED_MODELS
Expand Down
33 changes: 33 additions & 0 deletions test/components/generators/chat/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0

from typing import ClassVar

import pytest

from haystack.components.generators.chat import MockChatGenerator
from haystack.components.generators.chat.utils import (
_HAYSTACK_GENERATION_PARAMETERS,
_convert_haystack_generation_kwargs,
)


class MappedMockChatGenerator(MockChatGenerator):
_HAYSTACK_TO_PROVIDER_GENERATION_KWARGS: ClassVar[dict[str, str]] = {"max_output_tokens": "provider_max_tokens"}


class TestConvertHaystackGenerationKwargs:
def test_haystack_generation_parameters(self) -> None:
assert {"max_output_tokens"} == _HAYSTACK_GENERATION_PARAMETERS

def test_conversion(self) -> None:
converted = _convert_haystack_generation_kwargs(MappedMockChatGenerator(), {"max_output_tokens": 100})
assert converted == {"provider_max_tokens": 100}

def test_no_mapping(self) -> None:
assert _convert_haystack_generation_kwargs(MockChatGenerator(), {"max_output_tokens": 100}) == {}
Comment thread
anakin87 marked this conversation as resolved.

def test_invalid_parameter(self) -> None:
with pytest.raises(ValueError, match="Unknown Haystack generation parameter\\(s\\): max_tokens"):
_convert_haystack_generation_kwargs(MockChatGenerator(), {"max_tokens": 100})
Loading