Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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.",
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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}")
17 changes: 17 additions & 0 deletions python/tests/adapters/openai/test_responses_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading