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
2 changes: 1 addition & 1 deletion packages/gen/gen_ai_hub/orchestration_v2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@

# message
"SystemMessage", "UserMessage", "AssistantMessage", "ToolChatMessage", "DeveloperChatMessage",
"ChatMessage", "ResponseChatMessage", "FunctionCall", "MessageToolCall",
"ChatMessage", "ResponseChatMessage", "FunctionCall", "MessageToolCall", "ReasoningBlock",

# multimodal_items
"ImageDetailLevel", "TextPart", "ImageUrl", "ImagePart", "ContentPart", "ImageItem",
Expand Down
4 changes: 2 additions & 2 deletions packages/gen/gen_ai_hub/orchestration_v2/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from .llama_guard_3_filter import LlamaGuard38bFilter
from .llm_model_details import LLMModelDetails
from .message import (SystemMessage, UserMessage, AssistantMessage, ToolChatMessage, DeveloperChatMessage, ChatMessage,
ResponseChatMessage, FunctionCall, MessageToolCall)
ResponseChatMessage, FunctionCall, MessageToolCall, ReasoningBlock)
from .multimodal_items import ImageDetailLevel, TextPart, ImageUrl, ImagePart, ContentPart, ImageItem
from .response import (PromptTokensDetails, CompletionTokensDetails, TokenUsage, GenericModuleResult, TopLogprob,
ChatCompletionTokenLogprob, ChoiceLogprobs, LLMChoice, StreamFunctionObject, StreamToolCall,
Expand Down Expand Up @@ -73,7 +73,7 @@

# message
"SystemMessage", "UserMessage", "AssistantMessage", "ToolChatMessage", "DeveloperChatMessage",
"ChatMessage", "ResponseChatMessage", "FunctionCall", "MessageToolCall",
"ChatMessage", "ResponseChatMessage", "FunctionCall", "MessageToolCall", "ReasoningBlock",

# multimodal_items
"ImageDetailLevel", "TextPart", "ImageUrl", "ImagePart", "ContentPart", "ImageItem",
Expand Down
13 changes: 13 additions & 0 deletions packages/gen/gen_ai_hub/orchestration_v2/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,16 @@ def model_dump(self, **kwargs):
kwargs.setdefault("by_alias", True)
kwargs.setdefault("exclude_none", True)
return super().model_dump(**kwargs)

class ResponseBaseModel(BaseModel):
"""
Base model for API response models.

- `extra="allow"` allows unexpected fields in responses to be accepted,
since the external API might introduce new attributes in the response.
"""

model_config = ConfigDict(
extra="allow",
frozen=False,
)
10 changes: 5 additions & 5 deletions packages/gen/gen_ai_hub/orchestration_v2/models/embeddings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from pydantic import Field

from gen_ai_hub.orchestration_v2.models.base import ABCBaseModel as BaseModel
from gen_ai_hub.orchestration_v2.models.base import ABCBaseModel as BaseModel, ResponseBaseModel
from gen_ai_hub.orchestration_v2.models.data_masking import MaskingModuleConfig


Expand Down Expand Up @@ -117,7 +117,7 @@ class EmbeddingsInput(BaseModel):
type_: Optional[EmbeddingsInputType] = Field(default=None, alias="type")


class EmbeddingsUsage(BaseModel):
class EmbeddingsUsage(ResponseBaseModel):
"""
Token usage information for the embeddings request.

Expand All @@ -129,7 +129,7 @@ class EmbeddingsUsage(BaseModel):
total_tokens: int


class EmbeddingResult(BaseModel):
class EmbeddingResult(ResponseBaseModel):
"""
A single embedding result.

Expand All @@ -143,7 +143,7 @@ class EmbeddingResult(BaseModel):
index: int


class EmbeddingsResponse(BaseModel):
class EmbeddingsResponse(ResponseBaseModel):
"""
The response from the embedding model, following OpenAI specification.

Expand All @@ -159,7 +159,7 @@ class EmbeddingsResponse(BaseModel):
usage: EmbeddingsUsage


class EmbeddingsPostResponse(BaseModel):
class EmbeddingsPostResponse(ResponseBaseModel):
"""
Response for an embeddings POST request.

Expand Down
25 changes: 20 additions & 5 deletions packages/gen/gen_ai_hub/orchestration_v2/models/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@

from pydantic import field_validator, ValidationError

from gen_ai_hub.orchestration_v2.models.base import ABCBaseModel as BaseModel
from gen_ai_hub.orchestration_v2.models.base import ABCBaseModel as BaseModel, ResponseBaseModel
from gen_ai_hub.orchestration_v2.models.multimodal_items import ContentPart, ImageItem, TextPart, ImageUrl, ImagePart


class FunctionCall(BaseModel):
class FunctionCall(ResponseBaseModel):
"""
Represents a function call with its name and arguments.

Expand Down Expand Up @@ -44,7 +44,7 @@ def parse_arguments(self) -> dict:
return json.loads(self.arguments)


class MessageToolCall(BaseModel):
class MessageToolCall(ResponseBaseModel):
"""
The tool calls generated by the model, such as function calls.

Expand All @@ -59,6 +59,17 @@ class MessageToolCall(BaseModel):
type: typing.Literal["function"] = "function"
function: FunctionCall

class ReasoningBlock(ResponseBaseModel):
"""
The reasoning content returned by the model.

Attributes:
content: The reasoning content.
signature: Signature string for hidden reasoning content.
"""
content: str = ""
signature: str = ""


class Role(str, Enum):
"""
Expand Down Expand Up @@ -157,6 +168,7 @@ class AssistantMessage(BaseModel):
content: Optional[Union[str, List[TextPart]]] = None
refusal: Optional[str] = None
tool_calls: Optional[List[MessageToolCall]] = None
reasoning_content: Optional[List[ReasoningBlock]] = None


class ToolChatMessage(BaseModel):
Expand All @@ -169,7 +181,7 @@ class DeveloperChatMessage(BaseModel):
role: Role = Role.DEVELOPER
content: Union[str, List[TextPart]]

class ResponseChatMessage(BaseModel):
class ResponseChatMessage(ResponseBaseModel):
Comment thread
yamaceay marked this conversation as resolved.
Comment thread
yamaceay marked this conversation as resolved.
"""
Represents a response message in a conversation.

Expand All @@ -182,14 +194,17 @@ class ResponseChatMessage(BaseModel):
refusal: A string indicating refusal reason.

tool_calls: A list of tool call objects.

reasoning_content: A list of reasoning content blocks.
"""
role: Role = Role.ASSISTANT
content: str
refusal: Optional[str] = None
tool_calls: Optional[List[MessageToolCall]] = None
reasoning_content: Optional[List[ReasoningBlock]] = None

ChatMessage = Union[SystemMessage, UserMessage, AssistantMessage, ToolChatMessage, DeveloperChatMessage,
ResponseChatMessage]

__all__ = ["Role", "SystemMessage", "UserMessage", "AssistantMessage", "ToolChatMessage", "DeveloperChatMessage",
"ChatMessage", "ResponseChatMessage", "FunctionCall", "MessageToolCall"]
"ChatMessage", "ResponseChatMessage", "FunctionCall", "MessageToolCall", "ReasoningBlock"]
22 changes: 3 additions & 19 deletions packages/gen/gen_ai_hub/orchestration_v2/models/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,8 @@
from pydantic import ConfigDict, Field

from gen_ai_hub.orchestration.models.response import ModuleResultsStreaming
from gen_ai_hub.orchestration_v2.models.base import ABCBaseModel as BaseModel
from gen_ai_hub.orchestration_v2.models.message import ChatMessage, FunctionCall, ResponseChatMessage


class ResponseBaseModel(BaseModel):
"""
Abstract base model that extends Pydantic's BaseModel and ABC.

- `extra="allow"` allows unexpected fields in responses to be accepted,
since the external API might introduce new attributes in the response.

This enforces consistent and safe serialization behavior across all
derived models.
"""

model_config = ConfigDict(
extra="allow",
frozen=False,
)
from gen_ai_hub.orchestration_v2.models.base import ResponseBaseModel
from gen_ai_hub.orchestration_v2.models.message import ChatMessage, FunctionCall, ResponseChatMessage, ReasoningBlock


class PromptTokensDetails(ResponseBaseModel):
Expand Down Expand Up @@ -186,6 +169,7 @@ class StreamDelta(ResponseBaseModel):
role: Optional[str] = None
content: str
tool_calls: Optional[List[StreamToolCall]] = None
reasoning_content: Optional[List[ReasoningBlock]] = None


class StreamLLMChoice(ResponseBaseModel):
Expand Down
2 changes: 1 addition & 1 deletion packages/gen/tests/orchestration_v2/test_flat_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

# message
"SystemMessage", "UserMessage", "AssistantMessage", "ToolChatMessage", "DeveloperChatMessage",
"ChatMessage", "ResponseChatMessage", "FunctionCall", "MessageToolCall",
"ChatMessage", "ResponseChatMessage", "FunctionCall", "MessageToolCall", "ReasoningBlock",

# multimodal_items
"ImageDetailLevel", "TextPart", "ImageUrl", "ImagePart", "ContentPart", "ImageItem",
Expand Down
42 changes: 42 additions & 0 deletions packages/gen/tests/orchestration_v2/test_message_v2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import unittest

from gen_ai_hub.orchestration_v2.models.message import (
AssistantMessage,
ReasoningBlock,
ResponseChatMessage,
)


class TestResponseChatMessageValidation(unittest.TestCase):

def test_reasoning_content_deserialized_from_dict(self):
msg = ResponseChatMessage.model_validate({
"role": "assistant",
"content": "Hello",
"reasoning_content": [{"content": "I think...", "signature": "sig123"}],
})
self.assertIsNotNone(msg.reasoning_content)
self.assertIsInstance(msg.reasoning_content[0], ReasoningBlock)

def test_reasoning_content_optional(self):
msg = ResponseChatMessage.model_validate({"role": "assistant", "content": "Hello"})
self.assertIsNone(msg.reasoning_content)


class TestAssistantMessageValidation(unittest.TestCase):

def test_reasoning_content_deserialized_from_dict(self):
msg = AssistantMessage.model_validate({
"role": "assistant",
"content": "Paris",
"reasoning_content": [{"content": "France's capital is Paris.", "signature": "sig"}],
})
self.assertIsNotNone(msg.reasoning_content)
self.assertIsInstance(msg.reasoning_content[0], ReasoningBlock)
self.assertEqual(msg.reasoning_content[0].content, "France's capital is Paris.")
self.assertEqual(msg.reasoning_content[0].signature, "sig")

def test_reasoning_content_optional(self):
msg = AssistantMessage.model_validate({"role": "assistant", "content": "Hello"})
self.assertIsNone(msg.reasoning_content)

21 changes: 21 additions & 0 deletions packages/gen/tests/orchestration_v2/test_response_v2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import unittest

from gen_ai_hub.orchestration_v2.models.message import ReasoningBlock
from gen_ai_hub.orchestration_v2.models.response import StreamDelta


class TestStreamDeltaValidation(unittest.TestCase):

def test_reasoning_content_deserialized_from_dict(self):
delta = StreamDelta.model_validate({
"content": "",
"reasoning_content": [{"content": "I should respond politely.", "signature": ""}],
})
self.assertIsNotNone(delta.reasoning_content)
self.assertIsInstance(delta.reasoning_content[0], ReasoningBlock)
self.assertEqual(delta.reasoning_content[0].content, "I should respond politely.")

def test_reasoning_content_optional(self):
delta = StreamDelta.model_validate({"content": "Hello"})
self.assertIsNone(delta.reasoning_content)