From f85432d78673438704ef1232f827cce66a658021 Mon Sep 17 00:00:00 2001 From: kamilbenkirane Date: Wed, 24 Dec 2025 14:16:45 +0100 Subject: [PATCH 1/3] feat: add input class registry and unregistered model fallback - Add register_input/get_input_class to celeste.io for capability-based Input class discovery - Register Input classes in all capability packages during initialization - Allow unregistered models when provider and capability are explicit, with warning about disabled parameter validation - Export new functions from celeste package - Update test to verify new warning behavior --- .../src/celeste_image_generation/__init__.py | 5 +++- .../src/celeste_speech_generation/__init__.py | 5 +++- .../src/celeste_text_generation/__init__.py | 5 +++- .../src/celeste_video_generation/__init__.py | 5 +++- src/celeste/__init__.py | 26 +++++++++++++++-- src/celeste/io.py | 28 ++++++++++++++++++- tests/unit_tests/test_init.py | 22 +++++++++++---- 7 files changed, 84 insertions(+), 12 deletions(-) diff --git a/packages/capabilities/image-generation/src/celeste_image_generation/__init__.py b/packages/capabilities/image-generation/src/celeste_image_generation/__init__.py index 8fcad5e4..517f1ba2 100644 --- a/packages/capabilities/image-generation/src/celeste_image_generation/__init__.py +++ b/packages/capabilities/image-generation/src/celeste_image_generation/__init__.py @@ -2,10 +2,12 @@ def register_package() -> None: - """Register image generation package (client and models).""" + """Register image generation package (client, models, and input).""" from celeste.client import register_client from celeste.core import Capability + from celeste.io import register_input from celeste.models import register_models + from celeste_image_generation.io import ImageGenerationInput from celeste_image_generation.models import MODELS from celeste_image_generation.providers import PROVIDERS @@ -13,6 +15,7 @@ def register_package() -> None: register_client(Capability.IMAGE_GENERATION, provider, client_class) register_models(MODELS, capability=Capability.IMAGE_GENERATION) + register_input(Capability.IMAGE_GENERATION, ImageGenerationInput) from celeste_image_generation.io import ( # noqa: E402 diff --git a/packages/capabilities/speech-generation/src/celeste_speech_generation/__init__.py b/packages/capabilities/speech-generation/src/celeste_speech_generation/__init__.py index c6dad719..325c9a8f 100644 --- a/packages/capabilities/speech-generation/src/celeste_speech_generation/__init__.py +++ b/packages/capabilities/speech-generation/src/celeste_speech_generation/__init__.py @@ -2,10 +2,12 @@ def register_package() -> None: - """Register speech generation package (client and models).""" + """Register speech generation package (client, models, and input).""" from celeste.client import register_client from celeste.core import Capability + from celeste.io import register_input from celeste.models import register_models + from celeste_speech_generation.io import SpeechGenerationInput from celeste_speech_generation.models import MODELS from celeste_speech_generation.providers import PROVIDERS @@ -13,6 +15,7 @@ def register_package() -> None: register_client(Capability.SPEECH_GENERATION, provider, client_class) register_models(MODELS, capability=Capability.SPEECH_GENERATION) + register_input(Capability.SPEECH_GENERATION, SpeechGenerationInput) from celeste_speech_generation.io import ( # noqa: E402 diff --git a/packages/capabilities/text-generation/src/celeste_text_generation/__init__.py b/packages/capabilities/text-generation/src/celeste_text_generation/__init__.py index c4d64474..89cd7f86 100644 --- a/packages/capabilities/text-generation/src/celeste_text_generation/__init__.py +++ b/packages/capabilities/text-generation/src/celeste_text_generation/__init__.py @@ -2,10 +2,12 @@ def register_package() -> None: - """Register text generation package (client and models).""" + """Register text generation package (client, models, and input).""" from celeste.client import register_client from celeste.core import Capability + from celeste.io import register_input from celeste.models import register_models + from celeste_text_generation.io import TextGenerationInput from celeste_text_generation.models import MODELS from celeste_text_generation.providers import PROVIDERS @@ -13,6 +15,7 @@ def register_package() -> None: register_client(Capability.TEXT_GENERATION, provider, client_class) register_models(MODELS, capability=Capability.TEXT_GENERATION) + register_input(Capability.TEXT_GENERATION, TextGenerationInput) from celeste_text_generation.io import ( # noqa: E402 diff --git a/packages/capabilities/video-generation/src/celeste_video_generation/__init__.py b/packages/capabilities/video-generation/src/celeste_video_generation/__init__.py index 4caa4a52..ae003bea 100644 --- a/packages/capabilities/video-generation/src/celeste_video_generation/__init__.py +++ b/packages/capabilities/video-generation/src/celeste_video_generation/__init__.py @@ -2,10 +2,12 @@ def register_package() -> None: - """Register video generation package (client and models).""" + """Register video generation package (client, models, and input).""" from celeste.client import register_client from celeste.core import Capability + from celeste.io import register_input from celeste.models import register_models + from celeste_video_generation.io import VideoGenerationInput from celeste_video_generation.models import MODELS from celeste_video_generation.providers import PROVIDERS @@ -13,6 +15,7 @@ def register_package() -> None: register_client(Capability.VIDEO_GENERATION, provider, client_class) register_models(MODELS, capability=Capability.VIDEO_GENERATION) + register_input(Capability.VIDEO_GENERATION, VideoGenerationInput) from celeste_video_generation.io import ( # noqa: E402 diff --git a/src/celeste/__init__.py b/src/celeste/__init__.py index 16790938..9f53fb05 100644 --- a/src/celeste/__init__.py +++ b/src/celeste/__init__.py @@ -1,4 +1,5 @@ import logging +import warnings from pydantic import SecretStr @@ -21,7 +22,7 @@ ValidationError, ) from celeste.http import HTTPClient, close_all_http_clients -from celeste.io import Input, Output, Usage +from celeste.io import Input, Output, Usage, get_input_class, register_input from celeste.models import Model, get_model, list_models, register_models from celeste.parameters import Parameters from celeste.registry import _load_from_entry_points @@ -71,7 +72,26 @@ def _resolve_model( if isinstance(model, str): found = get_model(model, provider) if not found: - raise ModelNotFoundError(model_id=model, provider=provider) + if provider is None: + raise ModelNotFoundError(model_id=model, provider=provider) + if capability is None: + msg = ( + f"Model '{model}' not registered. Specify 'capability' explicitly." + ) + raise ValueError(msg) + warnings.warn( + f"Model '{model}' not registered in Celeste for provider {provider.value}. " + "Parameter validation disabled.", + UserWarning, + stacklevel=3, + ) + return Model( + id=model, + provider=provider, + display_name=model, + capabilities={capability}, + streaming=True, + ) return found return model @@ -169,9 +189,11 @@ def create_client( "close_all_ws_clients", "create_client", "get_client_class", + "get_input_class", "get_model", "image_to_data_uri", "list_models", "register_client", + "register_input", "register_models", ] diff --git a/src/celeste/io.py b/src/celeste/io.py index e85f60a0..e74024de 100644 --- a/src/celeste/io.py +++ b/src/celeste/io.py @@ -4,6 +4,8 @@ from pydantic import BaseModel, Field +from celeste.core import Capability + class Input(BaseModel): """Base class for capability-specific input types.""" @@ -41,4 +43,28 @@ class Chunk[Content](BaseModel): metadata: dict[str, Any] = Field(default_factory=dict) -__all__ = ["Chunk", "FinishReason", "Input", "Output", "Usage"] +_inputs: dict[Capability, type[Input]] = {} + + +def register_input(capability: Capability, input_class: type[Input]) -> None: + """Register an Input class for a capability.""" + _inputs[capability] = input_class + + +def get_input_class(capability: Capability) -> type[Input]: + """Get the registered Input class for a capability.""" + if capability not in _inputs: + msg = f"No Input class registered for capability: {capability}" + raise KeyError(msg) + return _inputs[capability] + + +__all__ = [ + "Chunk", + "FinishReason", + "Input", + "Output", + "Usage", + "get_input_class", + "register_input", +] diff --git a/tests/unit_tests/test_init.py b/tests/unit_tests/test_init.py index 08e31e94..e0375998 100644 --- a/tests/unit_tests/test_init.py +++ b/tests/unit_tests/test_init.py @@ -50,14 +50,26 @@ def test_create_client_no_models_available_raises_error(self) -> None: ): create_client(capability=Capability.TEXT_GENERATION) - def test_create_client_specific_model_not_found_raises_error(self) -> None: - """Test error when specific model/provider combination doesn't exist.""" - with patch("celeste.get_model", autospec=True) as mock_get_model: + def test_create_client_unregistered_model_with_provider_emits_warning(self) -> None: + """Test that unregistered model with explicit provider emits warning and proceeds.""" + with ( + patch("celeste.get_model", autospec=True) as mock_get_model, + patch("celeste.get_client_class", autospec=True) as mock_get_client_class, + ): # Arrange mock_get_model.return_value = None + mock_get_client_class.side_effect = NotImplementedError( + "Client not registered" + ) - # Act & Assert - with pytest.raises(ModelNotFoundError, match=r"Model.*not found"): + # Act & Assert - should warn but not raise ModelNotFoundError + with ( + pytest.warns( + UserWarning, + match=r"Model.*not registered.*Parameter validation disabled", + ), + pytest.raises(NotImplementedError), + ): create_client( capability=Capability.TEXT_GENERATION, provider=Provider.OPENAI, From a301498c45231faa7b966bd6049a95c4eff59f5c Mon Sep 17 00:00:00 2001 From: kamilbenkirane Date: Wed, 24 Dec 2025 14:17:12 +0100 Subject: [PATCH 2/3] feat(byteplus): add Seedance 1.5 Pro video model - Add seedance-1-5-pro-251215 with 4-12s duration, 480p/720p resolution - Fix gradium py.typed marker file (should be empty) - Reorder google pyproject.toml sections - Update .gitignore for temporary files --- .gitignore | 5 +++++ .../providers/byteplus/models.py | 16 ++++++++++++++++ packages/providers/google/pyproject.toml | 6 +++--- .../gradium/src/celeste_gradium/py.typed | 1 - 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 4ad08e72..eb7dd786 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,8 @@ uv.lock # Security reports bandit-report.json mureka.md + +# Temporary/audit files +CHANGELOG_SINCE_RELEASE.md +MIGRATION_AUDIT_REPORT.md +scripts/ diff --git a/packages/capabilities/video-generation/src/celeste_video_generation/providers/byteplus/models.py b/packages/capabilities/video-generation/src/celeste_video_generation/providers/byteplus/models.py index 1a529d0a..732c1f9a 100644 --- a/packages/capabilities/video-generation/src/celeste_video_generation/providers/byteplus/models.py +++ b/packages/capabilities/video-generation/src/celeste_video_generation/providers/byteplus/models.py @@ -96,4 +96,20 @@ ), }, ), + Model( + id="seedance-1-5-pro-251215", + provider=Provider.BYTEPLUS, + capabilities={Capability.VIDEO_GENERATION}, + display_name="Seedance 1.5 Pro", + parameter_constraints={ + VideoGenerationParameter.DURATION: Range(min=4, max=12), + VideoGenerationParameter.RESOLUTION: Choice(options=["480p", "720p"]), + VideoGenerationParameter.FIRST_FRAME: ImageConstraint( + supported_mime_types=BYTEPLUS_SUPPORTED_MIME_TYPES, + ), + VideoGenerationParameter.LAST_FRAME: ImageConstraint( + supported_mime_types=BYTEPLUS_SUPPORTED_MIME_TYPES, + ), + }, + ), ] diff --git a/packages/providers/google/pyproject.toml b/packages/providers/google/pyproject.toml index b87adf27..0f711f54 100644 --- a/packages/providers/google/pyproject.toml +++ b/packages/providers/google/pyproject.toml @@ -10,12 +10,12 @@ dependencies = ["celeste-ai", "httpx", "google-auth", "requests"] [tool.uv.sources] celeste-ai = { workspace = true } +[project.entry-points."celeste.providers"] +google = "celeste_google:register_provider" + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["src/celeste_google"] - -[project.entry-points."celeste.providers"] -google = "celeste_google:register_provider" diff --git a/packages/providers/gradium/src/celeste_gradium/py.typed b/packages/providers/gradium/src/celeste_gradium/py.typed index e16c76df..e69de29b 100644 --- a/packages/providers/gradium/src/celeste_gradium/py.typed +++ b/packages/providers/gradium/src/celeste_gradium/py.typed @@ -1 +0,0 @@ -"" From eb09953f7a940dbe5988c376284e42c82f69bca3 Mon Sep 17 00:00:00 2001 From: kamilbenkirane Date: Wed, 24 Dec 2025 14:21:36 +0100 Subject: [PATCH 3/3] chore: bump all package versions to 0.3.6 --- .../image-generation/pyproject.toml | 2 +- .../speech-generation/pyproject.toml | 2 +- .../text-generation/pyproject.toml | 2 +- .../video-generation/pyproject.toml | 2 +- packages/providers/anthropic/pyproject.toml | 2 +- packages/providers/bfl/pyproject.toml | 2 +- packages/providers/byteplus/pyproject.toml | 2 +- packages/providers/cohere/pyproject.toml | 2 +- packages/providers/elevenlabs/pyproject.toml | 2 +- packages/providers/google/pyproject.toml | 2 +- packages/providers/gradium/pyproject.toml | 2 +- packages/providers/mistral/pyproject.toml | 2 +- packages/providers/openai/pyproject.toml | 2 +- packages/providers/xai/pyproject.toml | 2 +- pyproject.toml | 18 +++++++++--------- 15 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/capabilities/image-generation/pyproject.toml b/packages/capabilities/image-generation/pyproject.toml index 4583f72c..38eae47c 100644 --- a/packages/capabilities/image-generation/pyproject.toml +++ b/packages/capabilities/image-generation/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "celeste-image-generation" -version = "0.3.5" +version = "0.3.6" description = "Image generation package for Celeste AI. Unified interface for all providers" authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}] readme = "README.md" diff --git a/packages/capabilities/speech-generation/pyproject.toml b/packages/capabilities/speech-generation/pyproject.toml index 73ba29d0..09240919 100644 --- a/packages/capabilities/speech-generation/pyproject.toml +++ b/packages/capabilities/speech-generation/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "celeste-speech-generation" -version = "0.3.3" +version = "0.3.6" description = "Speech generation package for Celeste AI. Unified interface for all providers" authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}] readme = "README.md" diff --git a/packages/capabilities/text-generation/pyproject.toml b/packages/capabilities/text-generation/pyproject.toml index b4fbe01a..ee67a7b0 100644 --- a/packages/capabilities/text-generation/pyproject.toml +++ b/packages/capabilities/text-generation/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "celeste-text-generation" -version = "0.3.4" +version = "0.3.6" description = "Text generation package for Celeste AI. Unified interface for all providers" authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}] readme = "README.md" diff --git a/packages/capabilities/video-generation/pyproject.toml b/packages/capabilities/video-generation/pyproject.toml index 367722d8..d91610d6 100644 --- a/packages/capabilities/video-generation/pyproject.toml +++ b/packages/capabilities/video-generation/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "celeste-video-generation" -version = "0.3.3" +version = "0.3.6" description = "Video generation package for Celeste AI. Unified interface for all providers" authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}] readme = "README.md" diff --git a/packages/providers/anthropic/pyproject.toml b/packages/providers/anthropic/pyproject.toml index 0baedc52..2904a69f 100644 --- a/packages/providers/anthropic/pyproject.toml +++ b/packages/providers/anthropic/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "celeste-anthropic" -version = "0.3.3" +version = "0.3.6" description = "Anthropic provider package for Celeste AI" authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}] license = {text = "Apache-2.0"} diff --git a/packages/providers/bfl/pyproject.toml b/packages/providers/bfl/pyproject.toml index fbc53820..6fabd92c 100644 --- a/packages/providers/bfl/pyproject.toml +++ b/packages/providers/bfl/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "celeste-bfl" -version = "0.3.3" +version = "0.3.6" description = "BFL (Black Forest Labs) provider package for Celeste AI" authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}] license = {text = "Apache-2.0"} diff --git a/packages/providers/byteplus/pyproject.toml b/packages/providers/byteplus/pyproject.toml index 0de47773..3393a3bc 100644 --- a/packages/providers/byteplus/pyproject.toml +++ b/packages/providers/byteplus/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "celeste-byteplus" -version = "0.3.3" +version = "0.3.6" description = "BytePlus provider package for Celeste AI" authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}] license = {text = "Apache-2.0"} diff --git a/packages/providers/cohere/pyproject.toml b/packages/providers/cohere/pyproject.toml index a95580b0..d141744a 100644 --- a/packages/providers/cohere/pyproject.toml +++ b/packages/providers/cohere/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "celeste-cohere" -version = "0.3.3" +version = "0.3.6" description = "Cohere provider package for Celeste AI" authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}] license = {text = "Apache-2.0"} diff --git a/packages/providers/elevenlabs/pyproject.toml b/packages/providers/elevenlabs/pyproject.toml index ffe57577..c5cdb0a8 100644 --- a/packages/providers/elevenlabs/pyproject.toml +++ b/packages/providers/elevenlabs/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "celeste-elevenlabs" -version = "0.3.3" +version = "0.3.6" description = "ElevenLabs provider package for Celeste AI" authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}] license = {text = "Apache-2.0"} diff --git a/packages/providers/google/pyproject.toml b/packages/providers/google/pyproject.toml index 0f711f54..053aeaf9 100644 --- a/packages/providers/google/pyproject.toml +++ b/packages/providers/google/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "celeste-google" -version = "0.3.3" +version = "0.3.6" description = "Google (Gemini API) provider package for Celeste AI" authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}] license = {text = "Apache-2.0"} diff --git a/packages/providers/gradium/pyproject.toml b/packages/providers/gradium/pyproject.toml index 39532ac3..bd43ef45 100644 --- a/packages/providers/gradium/pyproject.toml +++ b/packages/providers/gradium/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "celeste-gradium" -version = "0.3.3" +version = "0.3.6" description = "Gradium provider package for Celeste AI" authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}] license = {text = "Apache-2.0"} diff --git a/packages/providers/mistral/pyproject.toml b/packages/providers/mistral/pyproject.toml index d3fb6b5a..0e12b504 100644 --- a/packages/providers/mistral/pyproject.toml +++ b/packages/providers/mistral/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "celeste-mistral" -version = "0.3.3" +version = "0.3.6" description = "Mistral provider package for Celeste AI" authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}] license = {text = "Apache-2.0"} diff --git a/packages/providers/openai/pyproject.toml b/packages/providers/openai/pyproject.toml index aba99708..2f4d888c 100644 --- a/packages/providers/openai/pyproject.toml +++ b/packages/providers/openai/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "celeste-openai" -version = "0.3.3" +version = "0.3.6" description = "OpenAI provider package for Celeste AI" authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}] license = {text = "Apache-2.0"} diff --git a/packages/providers/xai/pyproject.toml b/packages/providers/xai/pyproject.toml index 8a925a97..fd1d83e1 100644 --- a/packages/providers/xai/pyproject.toml +++ b/packages/providers/xai/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "celeste-xai" -version = "0.3.3" +version = "0.3.6" description = "xAI provider package for Celeste AI" authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}] license = {text = "Apache-2.0"} diff --git a/pyproject.toml b/pyproject.toml index 1270fe70..db297586 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "celeste-ai" -version = "0.3.5" +version = "0.3.6" 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" @@ -34,15 +34,15 @@ Repository = "https://github.com/withceleste/celeste-python" Issues = "https://github.com/withceleste/celeste-python/issues" [project.optional-dependencies] -text-generation = ["celeste-text-generation>=0.3.3"] -image-generation = ["celeste-image-generation>=0.3.3"] -video-generation = ["celeste-video-generation>=0.3.3"] -speech-generation = ["celeste-speech-generation>=0.3.3"] +text-generation = ["celeste-text-generation>=0.3.6"] +image-generation = ["celeste-image-generation>=0.3.6"] +video-generation = ["celeste-video-generation>=0.3.6"] +speech-generation = ["celeste-speech-generation>=0.3.6"] all = [ - "celeste-text-generation>=0.3.3", - "celeste-image-generation>=0.3.3", - "celeste-video-generation>=0.3.3", - "celeste-speech-generation>=0.3.3", + "celeste-text-generation>=0.3.6", + "celeste-image-generation>=0.3.6", + "celeste-video-generation>=0.3.6", + "celeste-speech-generation>=0.3.6", ] [dependency-groups]