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: 1 addition & 1 deletion CHANGELOG_V1.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Date: 2026-01-15
- File updated: `README.md`.

## Release Prep
- Set package version to `0.9.0` for the public v1 beta.
- Set package version to `0.9.1` for the public v1 beta.
- Updated development status classifier to Beta.
- Removed notebook/scraping-only runtime deps from install requirements (ipykernel, matplotlib, beautifulsoup4).
- File updated: `pyproject.toml`.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "celeste-ai"
version = "0.9.0"
version = "0.9.1"
description = "Open source, type-safe primitives for multi-modal AI. All capabilities, all providers, one interface"
authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}]
readme = "README.md"
Expand Down
5 changes: 4 additions & 1 deletion src/celeste/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
StrictJsonSchemaGenerator,
StrictRefResolvingJsonSchemaGenerator,
)
from celeste.types import JsonValue
from celeste.types import Content, JsonValue, Message, Role
from celeste.websocket import WebSocketClient, WebSocketConnection, close_all_ws_clients

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -244,10 +244,12 @@ def create_client(
"Capability",
"ClientNotFoundError",
"ConstraintViolationError",
"Content",
"Error",
"HTTPClient",
"Input",
"JsonValue",
"Message",
"MissingCredentialsError",
"Modality",
"ModalityClient",
Expand All @@ -259,6 +261,7 @@ def create_client(
"Parameters",
"Provider",
"RefResolvingJsonSchemaGenerator",
"Role",
"StreamEmptyError",
"StreamNotExhaustedError",
"StreamingNotSupportedError",
Expand Down
17 changes: 16 additions & 1 deletion src/celeste/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ def get_headers(self) -> dict[str, str]:
return {self.header: f"{self.prefix}{self.secret.get_secret_value()}"}


class NoAuth(Authentication):
"""Authentication that returns no headers (local providers)."""

def get_headers(self) -> dict[str, str]:
"""Return empty headers for unauthenticated requests."""
return {}


# Backwards compatibility alias
APIKey = AuthHeader

Expand Down Expand Up @@ -78,4 +86,11 @@ def get_auth_class(auth_type: str) -> type[Authentication]:
return _auth_classes[auth_type]


__all__ = ["APIKey", "AuthHeader", "Authentication", "get_auth_class", "register_auth"]
__all__ = [
"APIKey",
"AuthHeader",
"Authentication",
"NoAuth",
"get_auth_class",
"register_auth",
]
7 changes: 6 additions & 1 deletion src/celeste/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ def _stream(
self,
inputs: In,
stream_class: type[Stream[Out, Params, Chunk]],
*,
endpoint: str | None = None,
base_url: str | None = None,
extra_body: dict[str, Any] | None = None,
**parameters: Unpack[Params], # type: ignore[misc]
) -> Stream[Out, Params, Chunk]:
Expand Down Expand Up @@ -207,7 +210,9 @@ def _stream(
request_body = self._build_request(
inputs, extra_body=extra_body, streaming=True, **parameters
)
sse_iterator = self._make_stream_request(request_body, **parameters)
sse_iterator = self._make_stream_request(
request_body, endpoint=endpoint, base_url=base_url, **parameters
)
return stream_class(
sse_iterator,
transform_output=self._transform_output,
Expand Down
2 changes: 2 additions & 0 deletions src/celeste/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class Provider(StrEnum):
ELEVENLABS = "elevenlabs"
GROQ = "groq"
GRADIUM = "gradium"
OPENRESPONSES = "openresponses"
OLLAMA = "ollama"


class Modality(StrEnum):
Expand Down
84 changes: 66 additions & 18 deletions src/celeste/modalities/text/client.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Text modality client."""

from typing import Unpack
from typing import Any, Unpack

from asgiref.sync import async_to_sync

from celeste.client import ModalityClient
from celeste.core import InputType, Modality
from celeste.types import AudioContent, ImageContent, TextContent, VideoContent
from celeste.types import AudioContent, ImageContent, Message, TextContent, VideoContent

from .io import TextInput, TextOutput
from .parameters import TextParameters
Expand Down Expand Up @@ -69,7 +69,11 @@ def __init__(self, client: TextClient) -> None:

def generate(
self,
prompt: str,
prompt: str | None = None,
*,
messages: list[Message] | None = None,
base_url: str | None = None,
extra_body: dict[str, Any] | None = None,
**parameters: Unpack[TextParameters],
) -> TextStream:
"""Stream text generation.
Expand All @@ -78,20 +82,25 @@ def generate(
async for chunk in client.stream.generate("Hello"):
print(chunk.content)
"""
inputs = TextInput(prompt=prompt)
inputs = TextInput(prompt=prompt, messages=messages)
return self._client._stream(
inputs,
stream_class=self._client._stream_class(),
base_url=base_url,
extra_body=extra_body,
**parameters,
)

def analyze(
self,
prompt: str,
prompt: str | None = None,
*,
messages: list[Message] | None = None,
image: ImageContent | None = None,
video: VideoContent | None = None,
audio: AudioContent | None = None,
base_url: str | None = None,
extra_body: dict[str, Any] | None = None,
**parameters: Unpack[TextParameters],
) -> TextStream:
"""Stream media analysis (image, video, or audio).
Expand All @@ -106,11 +115,16 @@ def analyze(
async for chunk in client.stream.analyze("Transcribe", audio=aud):
print(chunk.content)
"""
self._client._check_media_support(image=image, video=video, audio=audio)
inputs = TextInput(prompt=prompt, image=image, video=video, audio=audio)
if messages is None:
self._client._check_media_support(image=image, video=video, audio=audio)
inputs = TextInput(
prompt=prompt, messages=messages, image=image, video=video, audio=audio
)
return self._client._stream(
inputs,
stream_class=self._client._stream_class(),
base_url=base_url,
extra_body=extra_body,
**parameters,
)

Expand All @@ -126,7 +140,11 @@ def __init__(self, client: TextClient) -> None:

def generate(
self,
prompt: str,
prompt: str | None = None,
*,
messages: list[Message] | None = None,
base_url: str | None = None,
extra_body: dict[str, Any] | None = None,
**parameters: Unpack[TextParameters],
) -> TextOutput:
"""Blocking text generation.
Expand All @@ -135,16 +153,21 @@ def generate(
result = client.sync.generate("Hello")
print(result.content)
"""
inputs = TextInput(prompt=prompt)
return async_to_sync(self._client._predict)(inputs, **parameters)
inputs = TextInput(prompt=prompt, messages=messages)
return async_to_sync(self._client._predict)(
inputs, base_url=base_url, extra_body=extra_body, **parameters
)

def analyze(
self,
prompt: str,
prompt: str | None = None,
*,
messages: list[Message] | None = None,
image: ImageContent | None = None,
video: VideoContent | None = None,
audio: AudioContent | None = None,
base_url: str | None = None,
extra_body: dict[str, Any] | None = None,
**parameters: Unpack[TextParameters],
) -> TextOutput:
"""Blocking media analysis (image, video, or audio).
Expand All @@ -159,9 +182,14 @@ def analyze(
result = client.sync.analyze("Transcribe", audio=aud)
print(result.content)
"""
self._client._check_media_support(image=image, video=video, audio=audio)
inputs = TextInput(prompt=prompt, image=image, video=video, audio=audio)
return async_to_sync(self._client._predict)(inputs, **parameters)
if messages is None:
self._client._check_media_support(image=image, video=video, audio=audio)
inputs = TextInput(
prompt=prompt, messages=messages, image=image, video=video, audio=audio
)
return async_to_sync(self._client._predict)(
inputs, base_url=base_url, extra_body=extra_body, **parameters
)

@property
def stream(self) -> "TextSyncStreamNamespace":
Expand All @@ -177,7 +205,11 @@ def __init__(self, client: TextClient) -> None:

def generate(
self,
prompt: str,
prompt: str | None = None,
*,
messages: list[Message] | None = None,
base_url: str | None = None,
extra_body: dict[str, Any] | None = None,
**parameters: Unpack[TextParameters],
) -> TextStream:
"""Sync streaming text generation.
Expand All @@ -191,15 +223,24 @@ def generate(
print(stream.output.usage)
"""
# Return same stream as async version - __iter__/__next__ handle sync iteration
return self._client.stream.generate(prompt, **parameters)
return self._client.stream.generate(
prompt,
messages=messages,
base_url=base_url,
extra_body=extra_body,
**parameters,
)

def analyze(
self,
prompt: str,
prompt: str | None = None,
*,
messages: list[Message] | None = None,
image: ImageContent | None = None,
video: VideoContent | None = None,
audio: AudioContent | None = None,
base_url: str | None = None,
extra_body: dict[str, Any] | None = None,
**parameters: Unpack[TextParameters],
) -> TextStream:
"""Sync streaming media analysis (image, video, or audio).
Expand All @@ -224,7 +265,14 @@ def analyze(
"""
# Return same stream as async version - __iter__/__next__ handle sync iteration
return self._client.stream.analyze(
prompt, image=image, video=video, audio=audio, **parameters
prompt,
messages=messages,
image=image,
video=video,
audio=audio,
base_url=base_url,
extra_body=extra_body,
**parameters,
)


Expand Down
5 changes: 3 additions & 2 deletions src/celeste/modalities/text/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
from pydantic import Field

from celeste.io import Chunk, FinishReason, Input, Output, Usage
from celeste.types import AudioContent, ImageContent, TextContent, VideoContent
from celeste.types import AudioContent, ImageContent, Message, TextContent, VideoContent


class TextInput(Input):
"""Input for text operations."""

prompt: str
prompt: str | None = None
messages: list[Message] | None = None
text: str | list[str] | None = None
image: ImageContent | None = None
video: VideoContent | None = None
Expand Down
2 changes: 2 additions & 0 deletions src/celeste/modalities/text/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .providers.groq.models import MODELS as GROQ_MODELS
from .providers.mistral.models import MODELS as MISTRAL_MODELS
from .providers.moonshot.models import MODELS as MOONSHOT_MODELS
from .providers.ollama.models import MODELS as OLLAMA_MODELS
from .providers.openai.models import MODELS as OPENAI_MODELS
from .providers.xai.models import MODELS as XAI_MODELS

Expand All @@ -18,6 +19,7 @@
*DEEPSEEK_MODELS,
*GOOGLE_MODELS,
*GROQ_MODELS,
*OLLAMA_MODELS,
*MISTRAL_MODELS,
*MOONSHOT_MODELS,
*OPENAI_MODELS,
Expand Down
4 changes: 4 additions & 0 deletions src/celeste/modalities/text/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
from .groq import GroqTextClient
from .mistral import MistralTextClient
from .moonshot import MoonshotTextClient
from .ollama import OllamaTextClient
from .openai import OpenAITextClient
from .openresponses import OpenResponsesTextClient
from .xai import XAITextClient

PROVIDERS: dict[Provider, type[TextClient]] = {
Expand All @@ -19,6 +21,8 @@
Provider.DEEPSEEK: DeepSeekTextClient,
Provider.GOOGLE: GoogleTextClient,
Provider.GROQ: GroqTextClient,
Provider.OLLAMA: OllamaTextClient,
Provider.OPENRESPONSES: OpenResponsesTextClient,
Provider.MISTRAL: MistralTextClient,
Provider.MOONSHOT: MoonshotTextClient,
Provider.OPENAI: OpenAITextClient,
Expand Down
Loading
Loading