diff --git a/python/beeai_framework/adapters/openai/serve/responses/_types.py b/python/beeai_framework/adapters/openai/serve/responses/_types.py index f6597b1fe..6224fa924 100644 --- a/python/beeai_framework/adapters/openai/serve/responses/_types.py +++ b/python/beeai_framework/adapters/openai/serve/responses/_types.py @@ -1,6 +1,6 @@ # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 -from typing import Any, TypeAlias +from typing import Any, Literal, TypeAlias from pydantic import BaseModel, Field @@ -12,13 +12,18 @@ class BaseEvent(BaseModel): # request +class ResponsesRequestInputTextContent(BaseModel): + type: Literal["input_text"] = Field("input_text", description="The type of the content part.") + text: str = Field(..., description="The text content.") + + class ResponsesRequestInputMessage(BaseEvent): role: str = Field( ..., description="The role of the message input. One of 'user', 'assistant', 'system', or 'developer'.", pattern="^(user|assistant|developer|system)$", ) - content: str | None = Field( + content: str | list[ResponsesRequestInputTextContent] | None = Field( None, description="Input to the model, used to generate a response. Can also contain previous assistant responses.", ) diff --git a/python/beeai_framework/adapters/openai/serve/responses/_utils.py b/python/beeai_framework/adapters/openai/serve/responses/_utils.py index 2f81a78f5..28fa7fb7c 100644 --- a/python/beeai_framework/adapters/openai/serve/responses/_utils.py +++ b/python/beeai_framework/adapters/openai/serve/responses/_utils.py @@ -1,24 +1,38 @@ # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 +from typing import cast + import beeai_framework.adapters.openai.serve.responses._types as openai_api from beeai_framework.backend import AssistantMessage, SystemMessage from beeai_framework.backend.message import ( AnyMessage, + AssistantMessageContent, + MessageTextContent, UserMessage, + UserMessageContent, ) from beeai_framework.logger import Logger logger = Logger(__name__) +def _message_content(message: openai_api.ResponsesRequestInputMessage) -> str | list[MessageTextContent]: + if isinstance(message.content, str) or message.content is None: + return message.content or "" + + return [MessageTextContent(text=part.text) for part in message.content] + + def openai_input_to_beeai_message(message: openai_api.ResponsesRequestInputMessage) -> AnyMessage: + content = _message_content(message) + match message.role: case "user": - return UserMessage(message.content or "") + return UserMessage(cast(str | list[UserMessageContent], content)) case "system" | "developer": - return SystemMessage(message.content or "") + return SystemMessage(content) case "assistant": - return AssistantMessage(message.content or "") + return AssistantMessage(cast(str | list[AssistantMessageContent], content)) case _: raise ValueError(f"Invalid role: {message.role}") diff --git a/python/tests/adapters/openai/test_responses_utils.py b/python/tests/adapters/openai/test_responses_utils.py index 5d9659584..8dafa9df9 100644 --- a/python/tests/adapters/openai/test_responses_utils.py +++ b/python/tests/adapters/openai/test_responses_utils.py @@ -46,6 +46,23 @@ def test_none_content_defaults_to_empty_string() -> None: assert result.content[0].text == "" +@pytest.mark.unit +def test_input_text_content_parts_are_converted_to_message_text_content() -> None: + msg = ResponsesRequestInputMessage( + role="user", + content=[ + {"type": "input_text", "text": "hello"}, + {"type": "input_text", "text": " world"}, + ], + ) + result = openai_input_to_beeai_message(msg) + + assert isinstance(result, UserMessage) + text_parts = [part for part in result.content if isinstance(part, MessageTextContent)] + assert [part.text for part in text_parts] == ["hello", " world"] + assert result.text == "hello world" + + @pytest.mark.unit def test_openai_input_to_beeai_message_invalid_role() -> None: msg = ResponsesRequestInputMessage.model_construct(role="unknown", content="x")