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
10 changes: 9 additions & 1 deletion src/celeste/artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import base64
from typing import Any

from pydantic import BaseModel, Field, field_serializer
from pydantic import BaseModel, Field, field_serializer, field_validator

from celeste.mime_types import (
AudioMimeType,
Expand All @@ -30,6 +30,14 @@ class Artifact(BaseModel):
mime_type: MimeType | None = None
metadata: dict[str, Any] = Field(default_factory=dict)

@field_validator("data", mode="before")
@classmethod
def decode_base64_data(cls, v: bytes | str | None) -> bytes | None:
"""Decode base64 string to bytes when constructing from JSON."""
if isinstance(v, str):
return base64.b64decode(v)
return v

@field_serializer("data", when_used="json")
def serialize_data(self, value: bytes | None) -> str | None:
"""Serialize bytes as base64 string for JSON compatibility."""
Expand Down
4 changes: 1 addition & 3 deletions src/celeste/modalities/audio/providers/google/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Google audio client."""

import base64
from typing import Any, Unpack

from celeste.artifacts import AudioArtifact
Expand Down Expand Up @@ -62,12 +61,11 @@ def _parse_content(
) -> AudioArtifact:
"""Extract audio bytes from response."""
audio_b64 = super()._parse_content(response_data)
audio_bytes = base64.b64decode(audio_b64)

output_format = parameters.get(AudioParameter.OUTPUT_FORMAT)
mime_type = AudioMimeType(output_format) if output_format else AudioMimeType.MP3

return AudioArtifact(data=audio_bytes, mime_type=mime_type)
return AudioArtifact(data=audio_b64, mime_type=mime_type)

def _parse_finish_reason(self, response_data: dict[str, Any]) -> AudioFinishReason:
"""Parse finish reason from response."""
Expand Down
7 changes: 2 additions & 5 deletions src/celeste/modalities/images/providers/byteplus/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""BytePlus images client."""

import base64
from typing import Any, Unpack

from celeste.artifacts import ImageArtifact
Expand Down Expand Up @@ -80,8 +79,7 @@ def _parse_chunk(self, event_data: dict[str, Any]) -> ImageChunk | None:
if content_type == "url":
artifact = ImageArtifact(url=content, mime_type=ImageMimeType.PNG)
else: # b64_json
image_data = base64.b64decode(content)
artifact = ImageArtifact(data=image_data)
artifact = ImageArtifact(data=content)

return ImageChunk(
content=artifact,
Expand Down Expand Up @@ -162,9 +160,8 @@ def _parse_content(
mime_type=ImageMimeType.PNG,
)
if image_data.get("b64_json"):
image_bytes = base64.b64decode(image_data["b64_json"])
return ImageArtifact(
data=image_bytes,
data=image_data["b64_json"],
mime_type=ImageMimeType.PNG,
)

Expand Down
7 changes: 2 additions & 5 deletions src/celeste/modalities/images/providers/ollama/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Ollama images client."""

import base64
from typing import Any, Unpack

from celeste.artifacts import ImageArtifact
Expand Down Expand Up @@ -53,9 +52,8 @@ def _parse_chunk(self, event_data: dict[str, Any]) -> ImageChunk | None:
metadata=self._parse_chunk_metadata(event_data),
)

image_bytes = base64.b64decode(b64_image)
return ImageChunk(
content=ImageArtifact(data=image_bytes),
content=ImageArtifact(data=b64_image),
finish_reason=self._parse_chunk_finish_reason(event_data),
usage=self._parse_chunk_usage(event_data),
metadata=self._parse_chunk_metadata(event_data),
Expand Down Expand Up @@ -115,8 +113,7 @@ def _parse_content(
Ollama returns base64-encoded image in the 'image' field.
"""
image_b64 = super()._parse_content(response_data)
image_bytes = base64.b64decode(image_b64)
return ImageArtifact(data=image_bytes)
return ImageArtifact(data=image_b64)

def _parse_finish_reason(self, response_data: dict[str, Any]) -> ImageFinishReason:
"""Parse finish reason from response."""
Expand Down
7 changes: 2 additions & 5 deletions src/celeste/modalities/images/providers/openai/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""OpenAI images client."""

import base64
from typing import Any, Unpack

from celeste.artifacts import ImageArtifact
Expand Down Expand Up @@ -61,8 +60,7 @@ def _parse_chunk(self, event_data: dict[str, Any]) -> ImageChunk | None:
metadata={"event_data": event_data},
)

image_data = base64.b64decode(b64_json)
artifact = ImageArtifact(data=image_data)
artifact = ImageArtifact(data=b64_json)

return ImageChunk(
content=artifact,
Expand Down Expand Up @@ -143,8 +141,7 @@ def _parse_content(

b64_json = image_data.get("b64_json")
if b64_json:
image_bytes = base64.b64decode(b64_json)
return ImageArtifact(data=image_bytes)
return ImageArtifact(data=b64_json)

url = image_data.get("url")
if url:
Expand Down
5 changes: 1 addition & 4 deletions src/celeste/modalities/images/providers/xai/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,7 @@ def _parse_content(
# xAI returns either b64_json or url
b64_json = image_data.get("b64_json")
if b64_json:
import base64

image_bytes = base64.b64decode(b64_json)
return ImageArtifact(data=image_bytes)
return ImageArtifact(data=b64_json)

url = image_data.get("url")
if url:
Expand Down
4 changes: 1 addition & 3 deletions src/celeste/modalities/videos/providers/openai/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""OpenAI videos client."""

import base64
from typing import Any, Unpack

from celeste.artifacts import VideoArtifact
Expand Down Expand Up @@ -55,9 +54,8 @@ def _parse_content(
) -> VideoArtifact:
"""Parse content from response."""
video_data_b64 = super()._parse_content(response_data)
video_data = base64.b64decode(video_data_b64)
return VideoArtifact(
data=video_data,
data=video_data_b64,
mime_type=VideoMimeType.MP4,
)

Expand Down
Loading