From 3490a102fb94ccfacf7c56477c7d8ed492ba95e8 Mon Sep 17 00:00:00 2001 From: Elias TOURNEUX Date: Tue, 28 Oct 2025 09:10:00 -0400 Subject: [PATCH 1/5] Add OVHcloud AI Endpoints provider # Conflicts: # docs/source/en/guides/inference.md --- docs/source/en/guides/inference.md | 62 ++++---- src/huggingface_hub/inference/_client.py | 2 +- .../inference/_generated/_async_client.py | 2 +- .../inference/_providers/__init__.py | 8 + .../inference/_providers/_common.py | 1 + .../inference/_providers/ovhcloud.py | 84 +++++++++++ tests/test_inference_client.py | 6 + tests/test_inference_providers.py | 137 ++++++++++++++++++ 8 files changed, 269 insertions(+), 33 deletions(-) create mode 100644 src/huggingface_hub/inference/_providers/ovhcloud.py diff --git a/docs/source/en/guides/inference.md b/docs/source/en/guides/inference.md index 790cff4a6d..e4624881cc 100644 --- a/docs/source/en/guides/inference.md +++ b/docs/source/en/guides/inference.md @@ -192,37 +192,37 @@ For more details, refer to the [Inference Providers pricing documentation](https [`InferenceClient`]'s goal is to provide the easiest interface to run inference on Hugging Face models, on any provider. It has a simple API that supports the most common tasks. Here is a table showing which providers support which tasks: -| Task | Black Forest Labs | Cerebras | Clarifai | Cohere | fal-ai | Featherless AI | Fireworks AI | Groq | HF Inference | Hyperbolic | Nebius AI Studio | Novita AI | Nscale | Public AI | Replicate | Sambanova | Scaleway | Together | Wavespeed | Zai | -| --------------------------------------------------- | ----------------- | -------- | -------- | ------ | ------ | -------------- | ------------ | ---- | ------------ | ---------- | ---------------- | --------- | ------ | ---------- | --------- | --------- | --------- | -------- | --------- | ---- | -| [`~InferenceClient.audio_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.audio_to_audio`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.automatic_speech_recognition`] | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.chat_completion`] | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | -| [`~InferenceClient.document_question_answering`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.feature_extraction`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | -| [`~InferenceClient.fill_mask`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.image_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.image_segmentation`] | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.image_to_image`] | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | -| [`~InferenceClient.image_to_video`] | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | -| [`~InferenceClient.image_to_text`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.object_detection`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.question_answering`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.sentence_similarity`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.summarization`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.table_question_answering`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.text_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.text_generation`] | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | -| [`~InferenceClient.text_to_image`] | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | -| [`~InferenceClient.text_to_speech`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.text_to_video`] | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | -| [`~InferenceClient.tabular_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.tabular_regression`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.token_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.translation`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.visual_question_answering`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.zero_shot_image_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.zero_shot_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| Task | Black Forest Labs | Cerebras | Clarifai | Cohere | fal-ai | Featherless AI | Fireworks AI | Groq | HF Inference | Hyperbolic | Nebius AI Studio | Novita AI | Nscale | OVHcloud | Public AI | Replicate | Sambanova | Scaleway | Together | Wavespeed | Zai | +| --------------------------------------------------- | ----------------- | -------- | -------- | ------ | ------ | -------------- | ------------ | ---- | ------------ | ---------- | ---------------- | --------- | ------ | -------- | ---------- | --------- | --------- | --------- | -------- | --------- | ---- | +| [`~InferenceClient.audio_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.audio_to_audio`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.automatic_speech_recognition`] | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.chat_completion`] | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | +| [`~InferenceClient.document_question_answering`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.feature_extraction`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | +| [`~InferenceClient.fill_mask`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.image_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.image_segmentation`] | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.image_to_image`] | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | +| [`~InferenceClient.image_to_video`] | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | +| [`~InferenceClient.image_to_text`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.object_detection`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.question_answering`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.sentence_similarity`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.summarization`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.table_question_answering`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.text_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.text_generation`] | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | +| [`~InferenceClient.text_to_image`] | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | +| [`~InferenceClient.text_to_speech`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.text_to_video`] | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | +| [`~InferenceClient.tabular_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.tabular_regression`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.token_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.translation`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.visual_question_answering`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.zero_shot_image_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [`~InferenceClient.zero_shot_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | > [!TIP] > Check out the [Tasks](https://huggingface.co/tasks) page to learn more about each task. diff --git a/src/huggingface_hub/inference/_client.py b/src/huggingface_hub/inference/_client.py index 7676ffebfa..336f71c3f1 100644 --- a/src/huggingface_hub/inference/_client.py +++ b/src/huggingface_hub/inference/_client.py @@ -135,7 +135,7 @@ class InferenceClient: Note: for better compatibility with OpenAI's client, `model` has been aliased as `base_url`. Those 2 arguments are mutually exclusive. If a URL is passed as `model` or `base_url` for chat completion, the `(/v1)/chat/completions` suffix path will be appended to the URL. provider (`str`, *optional*): - Name of the provider to use for inference. Can be `"black-forest-labs"`, `"cerebras"`, `"clarifai"`, `"cohere"`, `"fal-ai"`, `"featherless-ai"`, `"fireworks-ai"`, `"groq"`, `"hf-inference"`, `"hyperbolic"`, `"nebius"`, `"novita"`, `"nscale"`, `"openai"`, `"publicai"`, `"replicate"`, `"sambanova"`, `"scaleway"`, `"together"`, `"wavespeed"` or `"zai-org"`. + Name of the provider to use for inference. Can be `"black-forest-labs"`, `"cerebras"`, `"clarifai"`, `"cohere"`, `"fal-ai"`, `"featherless-ai"`, `"fireworks-ai"`, `"groq"`, `"hf-inference"`, `"hyperbolic"`, `"nebius"`, `"novita"`, `"nscale"`, `"openai"`, `"ovhcloud"`, `"publicai"`, `"replicate"`, `"sambanova"`, `"scaleway"`, `"together"`, `"wavespeed"` or `"zai-org"`. Defaults to "auto" i.e. the first of the providers available for the model, sorted by the user's order in https://hf.co/settings/inference-providers. If model is a URL or `base_url` is passed, then `provider` is not used. token (`str`, *optional*): diff --git a/src/huggingface_hub/inference/_generated/_async_client.py b/src/huggingface_hub/inference/_generated/_async_client.py index 6143a26350..612ed3c40f 100644 --- a/src/huggingface_hub/inference/_generated/_async_client.py +++ b/src/huggingface_hub/inference/_generated/_async_client.py @@ -126,7 +126,7 @@ class AsyncInferenceClient: Note: for better compatibility with OpenAI's client, `model` has been aliased as `base_url`. Those 2 arguments are mutually exclusive. If a URL is passed as `model` or `base_url` for chat completion, the `(/v1)/chat/completions` suffix path will be appended to the URL. provider (`str`, *optional*): - Name of the provider to use for inference. Can be `"black-forest-labs"`, `"cerebras"`, `"clarifai"`, `"cohere"`, `"fal-ai"`, `"featherless-ai"`, `"fireworks-ai"`, `"groq"`, `"hf-inference"`, `"hyperbolic"`, `"nebius"`, `"novita"`, `"nscale"`, `"openai"`, `"publicai"`, `"replicate"`, `"sambanova"`, `"scaleway"`, `"together"`, `"wavespeed"` or `"zai-org"`. + Name of the provider to use for inference. Can be `"black-forest-labs"`, `"cerebras"`, `"clarifai"`, `"cohere"`, `"fal-ai"`, `"featherless-ai"`, `"fireworks-ai"`, `"groq"`, `"hf-inference"`, `"hyperbolic"`, `"nebius"`, `"novita"`, `"nscale"`, `"openai"`, `"ovhcloud"`, `"publicai"`, `"replicate"`, `"sambanova"`, `"scaleway"`, `"together"`, `"wavespeed"` or `"zai-org"`. Defaults to "auto" i.e. the first of the providers available for the model, sorted by the user's order in https://hf.co/settings/inference-providers. If model is a URL or `base_url` is passed, then `provider` is not used. token (`str`, *optional*): diff --git a/src/huggingface_hub/inference/_providers/__init__.py b/src/huggingface_hub/inference/_providers/__init__.py index aaf04ba1e1..4f5e59057d 100644 --- a/src/huggingface_hub/inference/_providers/__init__.py +++ b/src/huggingface_hub/inference/_providers/__init__.py @@ -38,6 +38,7 @@ from .novita import NovitaConversationalTask, NovitaTextGenerationTask, NovitaTextToVideoTask from .nscale import NscaleConversationalTask, NscaleTextToImageTask from .openai import OpenAIConversationalTask +from .ovhcloud import OVHcloudAIEndpointsAutomaticSpeechRecognitionTask, OVHcloudAIEndpointsConversationalTask, OVHcloudAIEndpointsFeatureExtractionTask, OVHcloudAIEndpointsTextToImageTask from .publicai import PublicAIConversationalTask from .replicate import ( ReplicateAutomaticSpeechRecognitionTask, @@ -76,6 +77,7 @@ "novita", "nscale", "openai", + "ovhcloud", "publicai", "replicate", "sambanova", @@ -172,6 +174,12 @@ "openai": { "conversational": OpenAIConversationalTask(), }, + "ovhcloud": { + "conversational": OVHcloudAIEndpointsConversationalTask(), + "text-to-image": OVHcloudAIEndpointsTextToImageTask(), + "feature-extraction": OVHcloudAIEndpointsFeatureExtractionTask(), + "automatic-speech-recognition": OVHcloudAIEndpointsAutomaticSpeechRecognitionTask(), + }, "publicai": { "conversational": PublicAIConversationalTask(), }, diff --git a/src/huggingface_hub/inference/_providers/_common.py b/src/huggingface_hub/inference/_providers/_common.py index 7b8c0300e7..4a8bda9ea8 100644 --- a/src/huggingface_hub/inference/_providers/_common.py +++ b/src/huggingface_hub/inference/_providers/_common.py @@ -32,6 +32,7 @@ "hyperbolic": {}, "nebius": {}, "nscale": {}, + "ovhcloud": {}, "replicate": {}, "sambanova": {}, "scaleway": {}, diff --git a/src/huggingface_hub/inference/_providers/ovhcloud.py b/src/huggingface_hub/inference/_providers/ovhcloud.py new file mode 100644 index 0000000000..f42c106a5c --- /dev/null +++ b/src/huggingface_hub/inference/_providers/ovhcloud.py @@ -0,0 +1,84 @@ +import base64 +from abc import ABC +from typing import Any, Dict, Optional, Union + +from huggingface_hub.hf_api import InferenceProviderMapping +from huggingface_hub.inference._common import RequestParameters, _as_dict +from huggingface_hub.inference._providers._common import ( + TaskProviderHelper, + filter_none, +) + +_PROVIDER = "ovhcloud" +_BASE_URL = "https://oai.endpoints.kepler.ai.cloud.ovh.net" + +class OVHcloudAIEndpointsTask(TaskProviderHelper, ABC): + def __init__(self, task: str): + super().__init__(provider=_PROVIDER, base_url=_BASE_URL, task=task) + + def _prepare_route(self, mapped_model: str, api_key: str) -> str: + if self.task == "text-to-image": + return "/v1/images/generations" + elif self.task == "conversational": + return "/v1/chat/completions" + elif self.task == "feature-extraction": + return "/v1/embeddings" + elif self.task == "automatic-speech-recognition": + return "/v1/audio/transcriptions" + raise ValueError(f"Unsupported task '{self.task}' for OVHcloud AI Endpoints.") + + def _prepare_payload_as_dict( + self, messages: Any, parameters: Dict, provider_mapping_info: InferenceProviderMapping + ) -> Optional[Dict]: + return {"messages": messages, "model": provider_mapping_info.provider_id, **filter_none(parameters)} + + +class OVHcloudAIEndpointsConversationalTask(OVHcloudAIEndpointsTask): + def __init__(self): + super().__init__("conversational") + + def _prepare_payload_as_dict( + self, messages: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping + ) -> Optional[dict]: + return super()._prepare_payload_as_dict(messages, parameters, provider_mapping_info) + + +class OVHcloudAIEndpointsTextToImageTask(OVHcloudAIEndpointsTask): + def __init__(self): + super().__init__("text-to-image") + + def _prepare_payload_as_dict( + self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping + ) -> Optional[dict]: + mapped_model = provider_mapping_info.provider_id + return {"prompt": inputs, "model": mapped_model, **filter_none(parameters)} + + def get_response(self, response: Union[bytes, dict], request_params: Optional[RequestParameters] = None) -> Any: + response_dict = _as_dict(response) + return base64.b64decode(response_dict["data"][0]["b64_json"]) + +class OVHcloudAIEndpointsFeatureExtractionTask(OVHcloudAIEndpointsTask): + def __init__(self): + super().__init__("feature-extraction") + + def _prepare_payload_as_dict( + self, inputs: Any, parameters: Dict, provider_mapping_info: InferenceProviderMapping + ) -> Optional[Dict]: + return {"input": inputs, "model": provider_mapping_info.provider_id, **filter_none(parameters)} + + def get_response(self, response: Union[bytes, dict], request_params: Optional[RequestParameters] = None) -> Any: + embeddings = _as_dict(response)["data"] + return [embedding["embedding"] for embedding in embeddings] + +class OVHcloudAIEndpointsAutomaticSpeechRecognitionTask(OVHcloudAIEndpointsTask): + def __init__(self): + super().__init__("automatic-speech-recognition") + + def _prepare_payload_as_dict( + self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping + ) -> Optional[dict]: + return {"file": inputs, "model": provider_mapping_info.provider_id, **filter_none(parameters)} + + def get_response(self, response: Union[bytes, dict], request_params: Optional[RequestParameters] = None) -> Any: + response_dict = _as_dict(response) + return response_dict["text"] diff --git a/tests/test_inference_client.py b/tests/test_inference_client.py index cedf6b4b89..8c06ca6f2f 100644 --- a/tests/test_inference_client.py +++ b/tests/test_inference_client.py @@ -117,6 +117,12 @@ "text-generation": "NousResearch/Nous-Hermes-Llama2-13b", "conversational": "meta-llama/Llama-3.1-8B-Instruct", }, + "ovhcloud": { + "automatic-speech-recognition": "openai/whisper-large-v3", + "conversational": "meta-llama/Llama-3.1-8B-Instruct", + "feature-extraction": "BAAI/bge-m3", + "text-to-image": "stabilityai/stable-diffusion-xl-base-1.0", + }, "replicate": { "text-to-image": "ByteDance/SDXL-Lightning", }, diff --git a/tests/test_inference_providers.py b/tests/test_inference_providers.py index 9ed78d9e31..8c8d6bd953 100644 --- a/tests/test_inference_providers.py +++ b/tests/test_inference_providers.py @@ -46,6 +46,7 @@ from huggingface_hub.inference._providers.novita import NovitaConversationalTask, NovitaTextGenerationTask from huggingface_hub.inference._providers.nscale import NscaleConversationalTask, NscaleTextToImageTask from huggingface_hub.inference._providers.openai import OpenAIConversationalTask +from huggingface_hub.inference._providers.ovhcloud import OVHcloudAIEndpointsAutomaticSpeechRecognitionTask, OVHcloudAIEndpointsConversationalTask, OVHcloudAIEndpointsFeatureExtractionTask, OVHcloudAIEndpointsTextToImageTask from huggingface_hub.inference._providers.publicai import PublicAIConversationalTask from huggingface_hub.inference._providers.replicate import ( ReplicateAutomaticSpeechRecognitionTask, @@ -1423,6 +1424,142 @@ def test_prepare_url(self): assert helper._prepare_url("sk-XXXXXX", "gpt-4o-mini") == "https://api.openai.com/v1/chat/completions" +class TestOVHcloudAIEndpointsProvider: + def test_prepare_hf_url_conversational(self): + helper = OVHcloudAIEndpointsConversationalTask() + url = helper._prepare_url("hf_token", "username/repo_name") + assert url == "https://router.huggingface.co/ovhcloud/v1/chat/completions" + + def test_prepare_url_conversational(self): + helper = OVHcloudAIEndpointsConversationalTask() + url = helper._prepare_url("ovhcloud_token", "username/repo_name") + assert url == "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1/chat/completions" + + def test_prepare_payload_as_dict(self): + helper = OVHcloudAIEndpointsConversationalTask() + payload = helper._prepare_payload_as_dict( + [ + {"role": "system", "content": "You are a helpful assistant"}, + {"role": "user", "content": "Hello!"}, + ], + { + "max_tokens": 512, + "temperature": 0.15, + "top_p": 1, + "presence_penalty": 0, + "stream": True, + }, + InferenceProviderMapping( + provider="ovhcloud", + hf_model_id="meta-llama/Llama-3.1-8B-Instruct", + providerId="Llama-3.1-8B-Instruct", + task="conversational", + status="live", + ), + ) + assert payload == { + "max_tokens": 512, + "messages": [ + {"content": "You are a helpful assistant", "role": "system"}, + {"role": "user", "content": "Hello!"}, + ], + "model": "Llama-3.1-8B-Instruct", + "presence_penalty": 0, + "stream": True, + "temperature": 0.15, + "top_p": 1, + } + + def test_prepare_url_feature_extraction(self): + helper = OVHcloudAIEndpointsFeatureExtractionTask() + assert ( + helper._prepare_url("hf_token", "username/repo_name") + == "https://router.huggingface.co/ovhcloud/v1/embeddings" + ) + + def test_prepare_payload_as_dict_feature_extraction(self): + helper = OVHcloudAIEndpointsFeatureExtractionTask() + payload = helper._prepare_payload_as_dict( + "Example text to embed", + {"truncate": True}, + InferenceProviderMapping( + provider="ovhcloud", + hf_model_id="BAAI/bge-m3", + providerId="BGE-M3", + task="feature-extraction", + status="live", + ), + ) + assert payload == {"input": "Example text to embed", "model": "BGE-M3", "truncate": True} + + def test_prepare_url_text_to_image(self): + helper = OVHcloudAIEndpointsTextToImageTask() + assert ( + helper._prepare_url("hf_token", "username/repo_name") + == "https://router.huggingface.co/ovhcloud/v1/images/generations" + ) + + url = helper._prepare_url("ovhcloud_token", "username/repo_name") + assert url == "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1/images/generations" + + def test_prepare_payload_as_dict_text_to_image(self): + helper = OVHcloudAIEndpointsTextToImageTask() + payload = helper._prepare_payload_as_dict( + inputs="a beautiful cat", + provider_mapping_info=InferenceProviderMapping( + provider="ovhcloud", + hf_model_id="stabilityai/stable-diffusion-xl-base-1.0", + providerId="stable-diffusion-xl-base-v10", + task="text-to-image", + status="live", + ), + parameters={} + ) + assert payload == { + "prompt": "a beautiful cat", + "model": "stable-diffusion-xl-base-v10", + } + + def test_text_to_image_get_response(self): + helper = OVHcloudAIEndpointsTextToImageTask() + response = helper.get_response({"data": [{"b64_json": base64.b64encode(b"image_bytes").decode()}]}) + assert response == b"image_bytes" + + def test_prepare_url_automatic_speech_recognition(self): + helper = OVHcloudAIEndpointsAutomaticSpeechRecognitionTask() + assert ( + helper._prepare_url("hf_token", "username/repo_name") + == "https://router.huggingface.co/ovhcloud/v1/audio/transcriptions" + ) + + url = helper._prepare_url("ovhcloud_token", "username/repo_name") + assert url == "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1/audio/transcriptions" + + def test_prepare_payload_as_dict_automatic_speech_recognition(self): + helper = OVHcloudAIEndpointsAutomaticSpeechRecognitionTask() + + payload = helper._prepare_payload_as_dict( + f"data:audio/mpeg;base64,{base64.b64encode(b'dummy_audio_data').decode()}", + {}, + InferenceProviderMapping( + provider="ovhcloud", + hf_model_id="openai/whisper-large-v3", + providerId="whisper-large-v3", + task="automatic-speech-recognition", + status="live", + ), + ) + assert payload == { + "file": f"data:audio/mpeg;base64,{base64.b64encode(b'dummy_audio_data').decode()}", + "model": "whisper-large-v3", + } + + def test_automatic_speech_recognition_get_response(self): + helper = OVHcloudAIEndpointsAutomaticSpeechRecognitionTask() + response = helper.get_response({"text": "Hello world"}) + assert response == "Hello world" + + class TestReplicateProvider: def test_automatic_speech_recognition_payload(self): helper = ReplicateAutomaticSpeechRecognitionTask() From b9c7c08efefcd0a95883ef6e2fe04d33ad44fffe Mon Sep 17 00:00:00 2001 From: Elias TOURNEUX Date: Wed, 12 Nov 2025 10:01:44 -0500 Subject: [PATCH 2/5] Only add text-generation and conversational task from feedback --- docs/source/en/guides/inference.md | 2 +- .../inference/_providers/__init__.py | 6 +- .../inference/_providers/ovhcloud.py | 88 +++---------- tests/test_inference_client.py | 4 +- tests/test_inference_providers.py | 121 +++++++----------- 5 files changed, 69 insertions(+), 152 deletions(-) diff --git a/docs/source/en/guides/inference.md b/docs/source/en/guides/inference.md index e4624881cc..031d7435a9 100644 --- a/docs/source/en/guides/inference.md +++ b/docs/source/en/guides/inference.md @@ -192,7 +192,7 @@ For more details, refer to the [Inference Providers pricing documentation](https [`InferenceClient`]'s goal is to provide the easiest interface to run inference on Hugging Face models, on any provider. It has a simple API that supports the most common tasks. Here is a table showing which providers support which tasks: -| Task | Black Forest Labs | Cerebras | Clarifai | Cohere | fal-ai | Featherless AI | Fireworks AI | Groq | HF Inference | Hyperbolic | Nebius AI Studio | Novita AI | Nscale | OVHcloud | Public AI | Replicate | Sambanova | Scaleway | Together | Wavespeed | Zai | +| Task | Black Forest Labs | Cerebras | Clarifai | Cohere | fal-ai | Featherless AI | Fireworks AI | Groq | HF Inference | Hyperbolic | Nebius AI Studio | Novita AI | Nscale | OVHcloud AI Endpoints | Public AI | Replicate | Sambanova | Scaleway | Together | Wavespeed | Zai | | --------------------------------------------------- | ----------------- | -------- | -------- | ------ | ------ | -------------- | ------------ | ---- | ------------ | ---------- | ---------------- | --------- | ------ | -------- | ---------- | --------- | --------- | --------- | -------- | --------- | ---- | | [`~InferenceClient.audio_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | [`~InferenceClient.audio_to_audio`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | diff --git a/src/huggingface_hub/inference/_providers/__init__.py b/src/huggingface_hub/inference/_providers/__init__.py index 4f5e59057d..6e881d8d6e 100644 --- a/src/huggingface_hub/inference/_providers/__init__.py +++ b/src/huggingface_hub/inference/_providers/__init__.py @@ -38,7 +38,7 @@ from .novita import NovitaConversationalTask, NovitaTextGenerationTask, NovitaTextToVideoTask from .nscale import NscaleConversationalTask, NscaleTextToImageTask from .openai import OpenAIConversationalTask -from .ovhcloud import OVHcloudAIEndpointsAutomaticSpeechRecognitionTask, OVHcloudAIEndpointsConversationalTask, OVHcloudAIEndpointsFeatureExtractionTask, OVHcloudAIEndpointsTextToImageTask +from .ovhcloud import OVHcloudAIEndpointsConversationalTask, OVHcloudAIEndpointsTextGenerationTask from .publicai import PublicAIConversationalTask from .replicate import ( ReplicateAutomaticSpeechRecognitionTask, @@ -176,9 +176,7 @@ }, "ovhcloud": { "conversational": OVHcloudAIEndpointsConversationalTask(), - "text-to-image": OVHcloudAIEndpointsTextToImageTask(), - "feature-extraction": OVHcloudAIEndpointsFeatureExtractionTask(), - "automatic-speech-recognition": OVHcloudAIEndpointsAutomaticSpeechRecognitionTask(), + "text-generation": OVHcloudAIEndpointsTextGenerationTask(), }, "publicai": { "conversational": PublicAIConversationalTask(), diff --git a/src/huggingface_hub/inference/_providers/ovhcloud.py b/src/huggingface_hub/inference/_providers/ovhcloud.py index f42c106a5c..9028244756 100644 --- a/src/huggingface_hub/inference/_providers/ovhcloud.py +++ b/src/huggingface_hub/inference/_providers/ovhcloud.py @@ -1,84 +1,34 @@ -import base64 -from abc import ABC -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union -from huggingface_hub.hf_api import InferenceProviderMapping from huggingface_hub.inference._common import RequestParameters, _as_dict -from huggingface_hub.inference._providers._common import ( - TaskProviderHelper, - filter_none, -) +from huggingface_hub.inference._providers._common import BaseConversationalTask, BaseTextGenerationTask + _PROVIDER = "ovhcloud" _BASE_URL = "https://oai.endpoints.kepler.ai.cloud.ovh.net" -class OVHcloudAIEndpointsTask(TaskProviderHelper, ABC): - def __init__(self, task: str): - super().__init__(provider=_PROVIDER, base_url=_BASE_URL, task=task) - - def _prepare_route(self, mapped_model: str, api_key: str) -> str: - if self.task == "text-to-image": - return "/v1/images/generations" - elif self.task == "conversational": - return "/v1/chat/completions" - elif self.task == "feature-extraction": - return "/v1/embeddings" - elif self.task == "automatic-speech-recognition": - return "/v1/audio/transcriptions" - raise ValueError(f"Unsupported task '{self.task}' for OVHcloud AI Endpoints.") - - def _prepare_payload_as_dict( - self, messages: Any, parameters: Dict, provider_mapping_info: InferenceProviderMapping - ) -> Optional[Dict]: - return {"messages": messages, "model": provider_mapping_info.provider_id, **filter_none(parameters)} - - -class OVHcloudAIEndpointsConversationalTask(OVHcloudAIEndpointsTask): - def __init__(self): - super().__init__("conversational") - - def _prepare_payload_as_dict( - self, messages: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping - ) -> Optional[dict]: - return super()._prepare_payload_as_dict(messages, parameters, provider_mapping_info) - -class OVHcloudAIEndpointsTextToImageTask(OVHcloudAIEndpointsTask): +class OVHcloudAIEndpointsConversationalTask(BaseConversationalTask): def __init__(self): - super().__init__("text-to-image") + super().__init__(provider=_PROVIDER, base_url=_BASE_URL) - def _prepare_payload_as_dict( - self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping - ) -> Optional[dict]: - mapped_model = provider_mapping_info.provider_id - return {"prompt": inputs, "model": mapped_model, **filter_none(parameters)} + def _prepare_route(self, mapped_model: str, api_key: str) -> str: + return "/v1/chat/completions" - def get_response(self, response: Union[bytes, dict], request_params: Optional[RequestParameters] = None) -> Any: - response_dict = _as_dict(response) - return base64.b64decode(response_dict["data"][0]["b64_json"]) - -class OVHcloudAIEndpointsFeatureExtractionTask(OVHcloudAIEndpointsTask): - def __init__(self): - super().__init__("feature-extraction") - def _prepare_payload_as_dict( - self, inputs: Any, parameters: Dict, provider_mapping_info: InferenceProviderMapping - ) -> Optional[Dict]: - return {"input": inputs, "model": provider_mapping_info.provider_id, **filter_none(parameters)} - - def get_response(self, response: Union[bytes, dict], request_params: Optional[RequestParameters] = None) -> Any: - embeddings = _as_dict(response)["data"] - return [embedding["embedding"] for embedding in embeddings] - -class OVHcloudAIEndpointsAutomaticSpeechRecognitionTask(OVHcloudAIEndpointsTask): +class OVHcloudAIEndpointsTextGenerationTask(BaseTextGenerationTask): def __init__(self): - super().__init__("automatic-speech-recognition") + super().__init__(provider=_PROVIDER, base_url=_BASE_URL) - def _prepare_payload_as_dict( - self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping - ) -> Optional[dict]: - return {"file": inputs, "model": provider_mapping_info.provider_id, **filter_none(parameters)} + def _prepare_route(self, mapped_model: str, api_key: str) -> str: + return "/v1/chat/completions" def get_response(self, response: Union[bytes, dict], request_params: Optional[RequestParameters] = None) -> Any: - response_dict = _as_dict(response) - return response_dict["text"] + output = _as_dict(response)["choices"][0] + return { + "generated_text": output["text"], + "details": { + "finish_reason": output.get("finish_reason"), + "seed": output.get("seed"), + }, + } diff --git a/tests/test_inference_client.py b/tests/test_inference_client.py index 8c06ca6f2f..b5efc2e1c4 100644 --- a/tests/test_inference_client.py +++ b/tests/test_inference_client.py @@ -118,10 +118,8 @@ "conversational": "meta-llama/Llama-3.1-8B-Instruct", }, "ovhcloud": { - "automatic-speech-recognition": "openai/whisper-large-v3", "conversational": "meta-llama/Llama-3.1-8B-Instruct", - "feature-extraction": "BAAI/bge-m3", - "text-to-image": "stabilityai/stable-diffusion-xl-base-1.0", + "text-generation": "meta-llama/Llama-3.1-8B-Instruct", }, "replicate": { "text-to-image": "ByteDance/SDXL-Lightning", diff --git a/tests/test_inference_providers.py b/tests/test_inference_providers.py index 8c8d6bd953..2a464feec2 100644 --- a/tests/test_inference_providers.py +++ b/tests/test_inference_providers.py @@ -46,7 +46,10 @@ from huggingface_hub.inference._providers.novita import NovitaConversationalTask, NovitaTextGenerationTask from huggingface_hub.inference._providers.nscale import NscaleConversationalTask, NscaleTextToImageTask from huggingface_hub.inference._providers.openai import OpenAIConversationalTask -from huggingface_hub.inference._providers.ovhcloud import OVHcloudAIEndpointsAutomaticSpeechRecognitionTask, OVHcloudAIEndpointsConversationalTask, OVHcloudAIEndpointsFeatureExtractionTask, OVHcloudAIEndpointsTextToImageTask +from huggingface_hub.inference._providers.ovhcloud import ( + OVHcloudAIEndpointsConversationalTask, + OVHcloudAIEndpointsTextGenerationTask, +) from huggingface_hub.inference._providers.publicai import PublicAIConversationalTask from huggingface_hub.inference._providers.replicate import ( ReplicateAutomaticSpeechRecognitionTask, @@ -1470,94 +1473,62 @@ def test_prepare_payload_as_dict(self): "top_p": 1, } - def test_prepare_url_feature_extraction(self): - helper = OVHcloudAIEndpointsFeatureExtractionTask() - assert ( - helper._prepare_url("hf_token", "username/repo_name") - == "https://router.huggingface.co/ovhcloud/v1/embeddings" - ) + def test_prepare_route_conversational(self): + helper = OVHcloudAIEndpointsConversationalTask() + assert helper._prepare_route("username/repo_name", "hf_token") == "/v1/chat/completions" - def test_prepare_payload_as_dict_feature_extraction(self): - helper = OVHcloudAIEndpointsFeatureExtractionTask() - payload = helper._prepare_payload_as_dict( - "Example text to embed", - {"truncate": True}, - InferenceProviderMapping( - provider="ovhcloud", - hf_model_id="BAAI/bge-m3", - providerId="BGE-M3", - task="feature-extraction", - status="live", - ), - ) - assert payload == {"input": "Example text to embed", "model": "BGE-M3", "truncate": True} + def test_prepare_url_text_generation(self): + helper = OVHcloudAIEndpointsTextGenerationTask() + url = helper._prepare_url("hf_token", "username/repo_name") + assert url == "https://router.huggingface.co/ovhcloud/v1/chat/completions" - def test_prepare_url_text_to_image(self): - helper = OVHcloudAIEndpointsTextToImageTask() - assert ( - helper._prepare_url("hf_token", "username/repo_name") - == "https://router.huggingface.co/ovhcloud/v1/images/generations" - ) - url = helper._prepare_url("ovhcloud_token", "username/repo_name") - assert url == "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1/images/generations" - - def test_prepare_payload_as_dict_text_to_image(self): - helper = OVHcloudAIEndpointsTextToImageTask() - payload = helper._prepare_payload_as_dict( - inputs="a beautiful cat", - provider_mapping_info=InferenceProviderMapping( - provider="ovhcloud", - hf_model_id="stabilityai/stable-diffusion-xl-base-1.0", - providerId="stable-diffusion-xl-base-v10", - task="text-to-image", - status="live", - ), - parameters={} - ) - assert payload == { - "prompt": "a beautiful cat", - "model": "stable-diffusion-xl-base-v10", - } - - def test_text_to_image_get_response(self): - helper = OVHcloudAIEndpointsTextToImageTask() - response = helper.get_response({"data": [{"b64_json": base64.b64encode(b"image_bytes").decode()}]}) - assert response == b"image_bytes" + assert url == "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1/chat/completions" - def test_prepare_url_automatic_speech_recognition(self): - helper = OVHcloudAIEndpointsAutomaticSpeechRecognitionTask() - assert ( - helper._prepare_url("hf_token", "username/repo_name") - == "https://router.huggingface.co/ovhcloud/v1/audio/transcriptions" - ) - - url = helper._prepare_url("ovhcloud_token", "username/repo_name") - assert url == "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1/audio/transcriptions" + def test_prepare_route_text_generation(self): + helper = OVHcloudAIEndpointsTextGenerationTask() + assert helper._prepare_route("username/repo_name", "hf_token") == "/v1/chat/completions" - def test_prepare_payload_as_dict_automatic_speech_recognition(self): - helper = OVHcloudAIEndpointsAutomaticSpeechRecognitionTask() - + def test_prepare_payload_as_dict_text_generation(self): + helper = OVHcloudAIEndpointsTextGenerationTask() payload = helper._prepare_payload_as_dict( - f"data:audio/mpeg;base64,{base64.b64encode(b'dummy_audio_data').decode()}", - {}, + "Once upon a time", + {"temperature": 0.7, "max_tokens": 100}, InferenceProviderMapping( provider="ovhcloud", - hf_model_id="openai/whisper-large-v3", - providerId="whisper-large-v3", - task="automatic-speech-recognition", + hf_model_id="meta-llama/Llama-3.1-8B-Instruct", + providerId="Llama-3.1-8B-Instruct", + task="text-generation", status="live", ), ) assert payload == { - "file": f"data:audio/mpeg;base64,{base64.b64encode(b'dummy_audio_data').decode()}", - "model": "whisper-large-v3", + "prompt": "Once upon a time", + "temperature": 0.7, + "max_tokens": 100, + "model": "Llama-3.1-8B-Instruct", } - def test_automatic_speech_recognition_get_response(self): - helper = OVHcloudAIEndpointsAutomaticSpeechRecognitionTask() - response = helper.get_response({"text": "Hello world"}) - assert response == "Hello world" + def test_text_generation_get_response(self): + helper = OVHcloudAIEndpointsTextGenerationTask() + response = helper.get_response( + { + "choices": [ + { + "text": " there was a beautiful princess", + "finish_reason": "stop", + "seed": 42, + } + ] + } + ) + assert response == { + "generated_text": " there was a beautiful princess", + "details": { + "finish_reason": "stop", + "seed": 42, + }, + } class TestReplicateProvider: From 9ff40479695cd3eb1fd20e2ed853dcad55e7e5f5 Mon Sep 17 00:00:00 2001 From: Elias TOURNEUX Date: Wed, 12 Nov 2025 15:57:38 -0500 Subject: [PATCH 3/5] Edit name of class and text-generation --- .../inference/_providers/__init__.py | 6 ++--- .../inference/_providers/ovhcloud.py | 10 ++----- tests/test_inference_providers.py | 26 +++++++++---------- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/huggingface_hub/inference/_providers/__init__.py b/src/huggingface_hub/inference/_providers/__init__.py index 6e881d8d6e..086787b855 100644 --- a/src/huggingface_hub/inference/_providers/__init__.py +++ b/src/huggingface_hub/inference/_providers/__init__.py @@ -38,7 +38,7 @@ from .novita import NovitaConversationalTask, NovitaTextGenerationTask, NovitaTextToVideoTask from .nscale import NscaleConversationalTask, NscaleTextToImageTask from .openai import OpenAIConversationalTask -from .ovhcloud import OVHcloudAIEndpointsConversationalTask, OVHcloudAIEndpointsTextGenerationTask +from .ovhcloud import OVHcloudConversationalTask, OVHcloudTextGenerationTask from .publicai import PublicAIConversationalTask from .replicate import ( ReplicateAutomaticSpeechRecognitionTask, @@ -175,8 +175,8 @@ "conversational": OpenAIConversationalTask(), }, "ovhcloud": { - "conversational": OVHcloudAIEndpointsConversationalTask(), - "text-generation": OVHcloudAIEndpointsTextGenerationTask(), + "conversational": OVHcloudConversationalTask(), + "text-generation": OVHcloudTextGenerationTask(), }, "publicai": { "conversational": PublicAIConversationalTask(), diff --git a/src/huggingface_hub/inference/_providers/ovhcloud.py b/src/huggingface_hub/inference/_providers/ovhcloud.py index 9028244756..3e24150b66 100644 --- a/src/huggingface_hub/inference/_providers/ovhcloud.py +++ b/src/huggingface_hub/inference/_providers/ovhcloud.py @@ -8,21 +8,15 @@ _BASE_URL = "https://oai.endpoints.kepler.ai.cloud.ovh.net" -class OVHcloudAIEndpointsConversationalTask(BaseConversationalTask): +class OVHcloudConversationalTask(BaseConversationalTask): def __init__(self): super().__init__(provider=_PROVIDER, base_url=_BASE_URL) - def _prepare_route(self, mapped_model: str, api_key: str) -> str: - return "/v1/chat/completions" - -class OVHcloudAIEndpointsTextGenerationTask(BaseTextGenerationTask): +class OVHcloudTextGenerationTask(BaseTextGenerationTask): def __init__(self): super().__init__(provider=_PROVIDER, base_url=_BASE_URL) - def _prepare_route(self, mapped_model: str, api_key: str) -> str: - return "/v1/chat/completions" - def get_response(self, response: Union[bytes, dict], request_params: Optional[RequestParameters] = None) -> Any: output = _as_dict(response)["choices"][0] return { diff --git a/tests/test_inference_providers.py b/tests/test_inference_providers.py index 2a464feec2..d98a72b439 100644 --- a/tests/test_inference_providers.py +++ b/tests/test_inference_providers.py @@ -47,8 +47,8 @@ from huggingface_hub.inference._providers.nscale import NscaleConversationalTask, NscaleTextToImageTask from huggingface_hub.inference._providers.openai import OpenAIConversationalTask from huggingface_hub.inference._providers.ovhcloud import ( - OVHcloudAIEndpointsConversationalTask, - OVHcloudAIEndpointsTextGenerationTask, + OVHcloudConversationalTask, + OVHcloudTextGenerationTask, ) from huggingface_hub.inference._providers.publicai import PublicAIConversationalTask from huggingface_hub.inference._providers.replicate import ( @@ -1429,17 +1429,17 @@ def test_prepare_url(self): class TestOVHcloudAIEndpointsProvider: def test_prepare_hf_url_conversational(self): - helper = OVHcloudAIEndpointsConversationalTask() + helper = OVHcloudConversationalTask() url = helper._prepare_url("hf_token", "username/repo_name") assert url == "https://router.huggingface.co/ovhcloud/v1/chat/completions" def test_prepare_url_conversational(self): - helper = OVHcloudAIEndpointsConversationalTask() + helper = OVHcloudConversationalTask() url = helper._prepare_url("ovhcloud_token", "username/repo_name") assert url == "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1/chat/completions" def test_prepare_payload_as_dict(self): - helper = OVHcloudAIEndpointsConversationalTask() + helper = OVHcloudConversationalTask() payload = helper._prepare_payload_as_dict( [ {"role": "system", "content": "You are a helpful assistant"}, @@ -1474,23 +1474,23 @@ def test_prepare_payload_as_dict(self): } def test_prepare_route_conversational(self): - helper = OVHcloudAIEndpointsConversationalTask() + helper = OVHcloudConversationalTask() assert helper._prepare_route("username/repo_name", "hf_token") == "/v1/chat/completions" def test_prepare_url_text_generation(self): - helper = OVHcloudAIEndpointsTextGenerationTask() + helper = OVHcloudTextGenerationTask() url = helper._prepare_url("hf_token", "username/repo_name") - assert url == "https://router.huggingface.co/ovhcloud/v1/chat/completions" + assert url == "https://router.huggingface.co/ovhcloud/v1/completions" url = helper._prepare_url("ovhcloud_token", "username/repo_name") - assert url == "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1/chat/completions" + assert url == "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1/completions" def test_prepare_route_text_generation(self): - helper = OVHcloudAIEndpointsTextGenerationTask() - assert helper._prepare_route("username/repo_name", "hf_token") == "/v1/chat/completions" + helper = OVHcloudTextGenerationTask() + assert helper._prepare_route("username/repo_name", "hf_token") == "/v1/completions" def test_prepare_payload_as_dict_text_generation(self): - helper = OVHcloudAIEndpointsTextGenerationTask() + helper = OVHcloudTextGenerationTask() payload = helper._prepare_payload_as_dict( "Once upon a time", {"temperature": 0.7, "max_tokens": 100}, @@ -1510,7 +1510,7 @@ def test_prepare_payload_as_dict_text_generation(self): } def test_text_generation_get_response(self): - helper = OVHcloudAIEndpointsTextGenerationTask() + helper = OVHcloudTextGenerationTask() response = helper.get_response( { "choices": [ From f0d2faf04e9eed0921d602d3aec6e139b792e3bc Mon Sep 17 00:00:00 2001 From: Elias TOURNEUX Date: Thu, 13 Nov 2025 12:24:51 -0500 Subject: [PATCH 4/5] Remove text_generation capability --- docs/source/en/guides/inference.md | 2 +- .../inference/_providers/__init__.py | 3 +- .../inference/_providers/ovhcloud.py | 21 +------ tests/test_inference_client.py | 1 - tests/test_inference_providers.py | 58 +------------------ 5 files changed, 4 insertions(+), 81 deletions(-) diff --git a/docs/source/en/guides/inference.md b/docs/source/en/guides/inference.md index 031d7435a9..172f7bed80 100644 --- a/docs/source/en/guides/inference.md +++ b/docs/source/en/guides/inference.md @@ -212,7 +212,7 @@ For more details, refer to the [Inference Providers pricing documentation](https | [`~InferenceClient.summarization`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | [`~InferenceClient.table_question_answering`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | [`~InferenceClient.text_classification`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| [`~InferenceClient.text_generation`] | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | +| [`~InferenceClient.text_generation`] | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | | [`~InferenceClient.text_to_image`] | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | | [`~InferenceClient.text_to_speech`] | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | [`~InferenceClient.text_to_video`] | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | diff --git a/src/huggingface_hub/inference/_providers/__init__.py b/src/huggingface_hub/inference/_providers/__init__.py index 086787b855..d8092c1daa 100644 --- a/src/huggingface_hub/inference/_providers/__init__.py +++ b/src/huggingface_hub/inference/_providers/__init__.py @@ -38,7 +38,7 @@ from .novita import NovitaConversationalTask, NovitaTextGenerationTask, NovitaTextToVideoTask from .nscale import NscaleConversationalTask, NscaleTextToImageTask from .openai import OpenAIConversationalTask -from .ovhcloud import OVHcloudConversationalTask, OVHcloudTextGenerationTask +from .ovhcloud import OVHcloudConversationalTask from .publicai import PublicAIConversationalTask from .replicate import ( ReplicateAutomaticSpeechRecognitionTask, @@ -176,7 +176,6 @@ }, "ovhcloud": { "conversational": OVHcloudConversationalTask(), - "text-generation": OVHcloudTextGenerationTask(), }, "publicai": { "conversational": PublicAIConversationalTask(), diff --git a/src/huggingface_hub/inference/_providers/ovhcloud.py b/src/huggingface_hub/inference/_providers/ovhcloud.py index 3e24150b66..0b23ff400f 100644 --- a/src/huggingface_hub/inference/_providers/ovhcloud.py +++ b/src/huggingface_hub/inference/_providers/ovhcloud.py @@ -1,8 +1,4 @@ -from typing import Any, Optional, Union - -from huggingface_hub.inference._common import RequestParameters, _as_dict -from huggingface_hub.inference._providers._common import BaseConversationalTask, BaseTextGenerationTask - +from huggingface_hub.inference._providers._common import BaseConversationalTask _PROVIDER = "ovhcloud" _BASE_URL = "https://oai.endpoints.kepler.ai.cloud.ovh.net" @@ -11,18 +7,3 @@ class OVHcloudConversationalTask(BaseConversationalTask): def __init__(self): super().__init__(provider=_PROVIDER, base_url=_BASE_URL) - - -class OVHcloudTextGenerationTask(BaseTextGenerationTask): - def __init__(self): - super().__init__(provider=_PROVIDER, base_url=_BASE_URL) - - def get_response(self, response: Union[bytes, dict], request_params: Optional[RequestParameters] = None) -> Any: - output = _as_dict(response)["choices"][0] - return { - "generated_text": output["text"], - "details": { - "finish_reason": output.get("finish_reason"), - "seed": output.get("seed"), - }, - } diff --git a/tests/test_inference_client.py b/tests/test_inference_client.py index b5efc2e1c4..4bbf5895ab 100644 --- a/tests/test_inference_client.py +++ b/tests/test_inference_client.py @@ -119,7 +119,6 @@ }, "ovhcloud": { "conversational": "meta-llama/Llama-3.1-8B-Instruct", - "text-generation": "meta-llama/Llama-3.1-8B-Instruct", }, "replicate": { "text-to-image": "ByteDance/SDXL-Lightning", diff --git a/tests/test_inference_providers.py b/tests/test_inference_providers.py index d98a72b439..54cb7b150d 100644 --- a/tests/test_inference_providers.py +++ b/tests/test_inference_providers.py @@ -46,10 +46,7 @@ from huggingface_hub.inference._providers.novita import NovitaConversationalTask, NovitaTextGenerationTask from huggingface_hub.inference._providers.nscale import NscaleConversationalTask, NscaleTextToImageTask from huggingface_hub.inference._providers.openai import OpenAIConversationalTask -from huggingface_hub.inference._providers.ovhcloud import ( - OVHcloudConversationalTask, - OVHcloudTextGenerationTask, -) +from huggingface_hub.inference._providers.ovhcloud import OVHcloudConversationalTask from huggingface_hub.inference._providers.publicai import PublicAIConversationalTask from huggingface_hub.inference._providers.replicate import ( ReplicateAutomaticSpeechRecognitionTask, @@ -1477,59 +1474,6 @@ def test_prepare_route_conversational(self): helper = OVHcloudConversationalTask() assert helper._prepare_route("username/repo_name", "hf_token") == "/v1/chat/completions" - def test_prepare_url_text_generation(self): - helper = OVHcloudTextGenerationTask() - url = helper._prepare_url("hf_token", "username/repo_name") - assert url == "https://router.huggingface.co/ovhcloud/v1/completions" - - url = helper._prepare_url("ovhcloud_token", "username/repo_name") - assert url == "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1/completions" - - def test_prepare_route_text_generation(self): - helper = OVHcloudTextGenerationTask() - assert helper._prepare_route("username/repo_name", "hf_token") == "/v1/completions" - - def test_prepare_payload_as_dict_text_generation(self): - helper = OVHcloudTextGenerationTask() - payload = helper._prepare_payload_as_dict( - "Once upon a time", - {"temperature": 0.7, "max_tokens": 100}, - InferenceProviderMapping( - provider="ovhcloud", - hf_model_id="meta-llama/Llama-3.1-8B-Instruct", - providerId="Llama-3.1-8B-Instruct", - task="text-generation", - status="live", - ), - ) - assert payload == { - "prompt": "Once upon a time", - "temperature": 0.7, - "max_tokens": 100, - "model": "Llama-3.1-8B-Instruct", - } - - def test_text_generation_get_response(self): - helper = OVHcloudTextGenerationTask() - response = helper.get_response( - { - "choices": [ - { - "text": " there was a beautiful princess", - "finish_reason": "stop", - "seed": 42, - } - ] - } - ) - assert response == { - "generated_text": " there was a beautiful princess", - "details": { - "finish_reason": "stop", - "seed": 42, - }, - } - class TestReplicateProvider: def test_automatic_speech_recognition_payload(self): From 4777b1dee4e932b6d0a1a26d7154b671d93b2b5f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 14 Nov 2025 10:23:11 +0000 Subject: [PATCH 5/5] Apply style fixes --- src/huggingface_hub/inference/_providers/ovhcloud.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/huggingface_hub/inference/_providers/ovhcloud.py b/src/huggingface_hub/inference/_providers/ovhcloud.py index 0b23ff400f..79be8d5508 100644 --- a/src/huggingface_hub/inference/_providers/ovhcloud.py +++ b/src/huggingface_hub/inference/_providers/ovhcloud.py @@ -1,5 +1,6 @@ from huggingface_hub.inference._providers._common import BaseConversationalTask + _PROVIDER = "ovhcloud" _BASE_URL = "https://oai.endpoints.kepler.ai.cloud.ovh.net"