huggingface-hub 0.29.0rc2__py3-none-any.whl → 1.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. huggingface_hub/__init__.py +160 -46
  2. huggingface_hub/_commit_api.py +277 -71
  3. huggingface_hub/_commit_scheduler.py +15 -15
  4. huggingface_hub/_inference_endpoints.py +33 -22
  5. huggingface_hub/_jobs_api.py +301 -0
  6. huggingface_hub/_local_folder.py +18 -3
  7. huggingface_hub/_login.py +31 -63
  8. huggingface_hub/_oauth.py +460 -0
  9. huggingface_hub/_snapshot_download.py +241 -81
  10. huggingface_hub/_space_api.py +18 -10
  11. huggingface_hub/_tensorboard_logger.py +15 -19
  12. huggingface_hub/_upload_large_folder.py +196 -76
  13. huggingface_hub/_webhooks_payload.py +3 -3
  14. huggingface_hub/_webhooks_server.py +15 -25
  15. huggingface_hub/{commands → cli}/__init__.py +1 -15
  16. huggingface_hub/cli/_cli_utils.py +173 -0
  17. huggingface_hub/cli/auth.py +147 -0
  18. huggingface_hub/cli/cache.py +841 -0
  19. huggingface_hub/cli/download.py +189 -0
  20. huggingface_hub/cli/hf.py +60 -0
  21. huggingface_hub/cli/inference_endpoints.py +377 -0
  22. huggingface_hub/cli/jobs.py +772 -0
  23. huggingface_hub/cli/lfs.py +175 -0
  24. huggingface_hub/cli/repo.py +315 -0
  25. huggingface_hub/cli/repo_files.py +94 -0
  26. huggingface_hub/{commands/env.py → cli/system.py} +10 -13
  27. huggingface_hub/cli/upload.py +294 -0
  28. huggingface_hub/cli/upload_large_folder.py +117 -0
  29. huggingface_hub/community.py +20 -12
  30. huggingface_hub/constants.py +83 -59
  31. huggingface_hub/dataclasses.py +609 -0
  32. huggingface_hub/errors.py +99 -30
  33. huggingface_hub/fastai_utils.py +30 -41
  34. huggingface_hub/file_download.py +606 -346
  35. huggingface_hub/hf_api.py +2445 -1132
  36. huggingface_hub/hf_file_system.py +269 -152
  37. huggingface_hub/hub_mixin.py +61 -66
  38. huggingface_hub/inference/_client.py +501 -630
  39. huggingface_hub/inference/_common.py +133 -121
  40. huggingface_hub/inference/_generated/_async_client.py +536 -722
  41. huggingface_hub/inference/_generated/types/__init__.py +6 -1
  42. huggingface_hub/inference/_generated/types/automatic_speech_recognition.py +5 -6
  43. huggingface_hub/inference/_generated/types/base.py +10 -7
  44. huggingface_hub/inference/_generated/types/chat_completion.py +77 -31
  45. huggingface_hub/inference/_generated/types/depth_estimation.py +2 -2
  46. huggingface_hub/inference/_generated/types/document_question_answering.py +2 -2
  47. huggingface_hub/inference/_generated/types/feature_extraction.py +2 -2
  48. huggingface_hub/inference/_generated/types/fill_mask.py +2 -2
  49. huggingface_hub/inference/_generated/types/image_to_image.py +8 -2
  50. huggingface_hub/inference/_generated/types/image_to_text.py +2 -3
  51. huggingface_hub/inference/_generated/types/image_to_video.py +60 -0
  52. huggingface_hub/inference/_generated/types/sentence_similarity.py +3 -3
  53. huggingface_hub/inference/_generated/types/summarization.py +2 -2
  54. huggingface_hub/inference/_generated/types/table_question_answering.py +5 -5
  55. huggingface_hub/inference/_generated/types/text2text_generation.py +2 -2
  56. huggingface_hub/inference/_generated/types/text_generation.py +11 -11
  57. huggingface_hub/inference/_generated/types/text_to_audio.py +1 -2
  58. huggingface_hub/inference/_generated/types/text_to_speech.py +1 -2
  59. huggingface_hub/inference/_generated/types/text_to_video.py +2 -2
  60. huggingface_hub/inference/_generated/types/token_classification.py +2 -2
  61. huggingface_hub/inference/_generated/types/translation.py +2 -2
  62. huggingface_hub/inference/_generated/types/zero_shot_classification.py +2 -2
  63. huggingface_hub/inference/_generated/types/zero_shot_image_classification.py +2 -2
  64. huggingface_hub/inference/_generated/types/zero_shot_object_detection.py +1 -3
  65. huggingface_hub/inference/_mcp/__init__.py +0 -0
  66. huggingface_hub/inference/_mcp/_cli_hacks.py +88 -0
  67. huggingface_hub/inference/_mcp/agent.py +100 -0
  68. huggingface_hub/inference/_mcp/cli.py +247 -0
  69. huggingface_hub/inference/_mcp/constants.py +81 -0
  70. huggingface_hub/inference/_mcp/mcp_client.py +395 -0
  71. huggingface_hub/inference/_mcp/types.py +45 -0
  72. huggingface_hub/inference/_mcp/utils.py +128 -0
  73. huggingface_hub/inference/_providers/__init__.py +149 -20
  74. huggingface_hub/inference/_providers/_common.py +160 -37
  75. huggingface_hub/inference/_providers/black_forest_labs.py +12 -9
  76. huggingface_hub/inference/_providers/cerebras.py +6 -0
  77. huggingface_hub/inference/_providers/clarifai.py +13 -0
  78. huggingface_hub/inference/_providers/cohere.py +32 -0
  79. huggingface_hub/inference/_providers/fal_ai.py +231 -22
  80. huggingface_hub/inference/_providers/featherless_ai.py +38 -0
  81. huggingface_hub/inference/_providers/fireworks_ai.py +22 -1
  82. huggingface_hub/inference/_providers/groq.py +9 -0
  83. huggingface_hub/inference/_providers/hf_inference.py +143 -33
  84. huggingface_hub/inference/_providers/hyperbolic.py +9 -5
  85. huggingface_hub/inference/_providers/nebius.py +47 -5
  86. huggingface_hub/inference/_providers/novita.py +48 -5
  87. huggingface_hub/inference/_providers/nscale.py +44 -0
  88. huggingface_hub/inference/_providers/openai.py +25 -0
  89. huggingface_hub/inference/_providers/publicai.py +6 -0
  90. huggingface_hub/inference/_providers/replicate.py +46 -9
  91. huggingface_hub/inference/_providers/sambanova.py +37 -1
  92. huggingface_hub/inference/_providers/scaleway.py +28 -0
  93. huggingface_hub/inference/_providers/together.py +34 -5
  94. huggingface_hub/inference/_providers/wavespeed.py +138 -0
  95. huggingface_hub/inference/_providers/zai_org.py +17 -0
  96. huggingface_hub/lfs.py +33 -100
  97. huggingface_hub/repocard.py +34 -38
  98. huggingface_hub/repocard_data.py +79 -59
  99. huggingface_hub/serialization/__init__.py +0 -1
  100. huggingface_hub/serialization/_base.py +12 -15
  101. huggingface_hub/serialization/_dduf.py +8 -8
  102. huggingface_hub/serialization/_torch.py +69 -69
  103. huggingface_hub/utils/__init__.py +27 -8
  104. huggingface_hub/utils/_auth.py +7 -7
  105. huggingface_hub/utils/_cache_manager.py +92 -147
  106. huggingface_hub/utils/_chunk_utils.py +2 -3
  107. huggingface_hub/utils/_deprecation.py +1 -1
  108. huggingface_hub/utils/_dotenv.py +55 -0
  109. huggingface_hub/utils/_experimental.py +7 -5
  110. huggingface_hub/utils/_fixes.py +0 -10
  111. huggingface_hub/utils/_git_credential.py +5 -5
  112. huggingface_hub/utils/_headers.py +8 -30
  113. huggingface_hub/utils/_http.py +399 -237
  114. huggingface_hub/utils/_pagination.py +6 -6
  115. huggingface_hub/utils/_parsing.py +98 -0
  116. huggingface_hub/utils/_paths.py +5 -5
  117. huggingface_hub/utils/_runtime.py +74 -22
  118. huggingface_hub/utils/_safetensors.py +21 -21
  119. huggingface_hub/utils/_subprocess.py +13 -11
  120. huggingface_hub/utils/_telemetry.py +4 -4
  121. huggingface_hub/{commands/_cli_utils.py → utils/_terminal.py} +4 -4
  122. huggingface_hub/utils/_typing.py +25 -5
  123. huggingface_hub/utils/_validators.py +55 -74
  124. huggingface_hub/utils/_verification.py +167 -0
  125. huggingface_hub/utils/_xet.py +235 -0
  126. huggingface_hub/utils/_xet_progress_reporting.py +162 -0
  127. huggingface_hub/utils/insecure_hashlib.py +3 -5
  128. huggingface_hub/utils/logging.py +8 -11
  129. huggingface_hub/utils/tqdm.py +33 -4
  130. {huggingface_hub-0.29.0rc2.dist-info → huggingface_hub-1.1.3.dist-info}/METADATA +94 -82
  131. huggingface_hub-1.1.3.dist-info/RECORD +155 -0
  132. {huggingface_hub-0.29.0rc2.dist-info → huggingface_hub-1.1.3.dist-info}/WHEEL +1 -1
  133. huggingface_hub-1.1.3.dist-info/entry_points.txt +6 -0
  134. huggingface_hub/commands/delete_cache.py +0 -428
  135. huggingface_hub/commands/download.py +0 -200
  136. huggingface_hub/commands/huggingface_cli.py +0 -61
  137. huggingface_hub/commands/lfs.py +0 -200
  138. huggingface_hub/commands/repo_files.py +0 -128
  139. huggingface_hub/commands/scan_cache.py +0 -181
  140. huggingface_hub/commands/tag.py +0 -159
  141. huggingface_hub/commands/upload.py +0 -299
  142. huggingface_hub/commands/upload_large_folder.py +0 -129
  143. huggingface_hub/commands/user.py +0 -304
  144. huggingface_hub/commands/version.py +0 -37
  145. huggingface_hub/inference_api.py +0 -217
  146. huggingface_hub/keras_mixin.py +0 -500
  147. huggingface_hub/repository.py +0 -1477
  148. huggingface_hub/serialization/_tensorflow.py +0 -95
  149. huggingface_hub/utils/_hf_folder.py +0 -68
  150. huggingface_hub-0.29.0rc2.dist-info/RECORD +0 -131
  151. huggingface_hub-0.29.0rc2.dist-info/entry_points.txt +0 -6
  152. {huggingface_hub-0.29.0rc2.dist-info → huggingface_hub-1.1.3.dist-info/licenses}/LICENSE +0 -0
  153. {huggingface_hub-0.29.0rc2.dist-info → huggingface_hub-1.1.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,32 @@
1
+ from typing import Any, Optional
2
+
3
+ from huggingface_hub.hf_api import InferenceProviderMapping
4
+
5
+ from ._common import BaseConversationalTask
6
+
7
+
8
+ _PROVIDER = "cohere"
9
+ _BASE_URL = "https://api.cohere.com"
10
+
11
+
12
+ class CohereConversationalTask(BaseConversationalTask):
13
+ def __init__(self):
14
+ super().__init__(provider=_PROVIDER, base_url=_BASE_URL)
15
+
16
+ def _prepare_route(self, mapped_model: str, api_key: str) -> str:
17
+ return "/compatibility/v1/chat/completions"
18
+
19
+ def _prepare_payload_as_dict(
20
+ self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
21
+ ) -> Optional[dict]:
22
+ payload = super()._prepare_payload_as_dict(inputs, parameters, provider_mapping_info)
23
+ response_format = parameters.get("response_format")
24
+ if isinstance(response_format, dict) and response_format.get("type") == "json_schema":
25
+ json_schema_details = response_format.get("json_schema")
26
+ if isinstance(json_schema_details, dict) and "schema" in json_schema_details:
27
+ payload["response_format"] = { # type: ignore [index]
28
+ "type": "json_object",
29
+ "schema": json_schema_details["schema"],
30
+ }
31
+
32
+ return payload
@@ -1,31 +1,98 @@
1
1
  import base64
2
+ import time
2
3
  from abc import ABC
3
- from typing import Any, Dict, Optional, Union
4
+ from typing import Any, Optional, Union
5
+ from urllib.parse import urlparse
4
6
 
5
- from huggingface_hub.inference._common import _as_dict
7
+ from huggingface_hub import constants
8
+ from huggingface_hub.hf_api import InferenceProviderMapping
9
+ from huggingface_hub.inference._common import RequestParameters, _as_dict, _as_url
6
10
  from huggingface_hub.inference._providers._common import TaskProviderHelper, filter_none
7
- from huggingface_hub.utils import get_session
11
+ from huggingface_hub.utils import get_session, hf_raise_for_status
12
+ from huggingface_hub.utils.logging import get_logger
13
+
14
+
15
+ logger = get_logger(__name__)
16
+
17
+ # Arbitrary polling interval
18
+ _POLLING_INTERVAL = 0.5
8
19
 
9
20
 
10
21
  class FalAITask(TaskProviderHelper, ABC):
11
22
  def __init__(self, task: str):
12
23
  super().__init__(provider="fal-ai", base_url="https://fal.run", task=task)
13
24
 
14
- def _prepare_headers(self, headers: Dict, api_key: str) -> Dict:
25
+ def _prepare_headers(self, headers: dict, api_key: str) -> dict[str, Any]:
15
26
  headers = super()._prepare_headers(headers, api_key)
16
27
  if not api_key.startswith("hf_"):
17
28
  headers["authorization"] = f"Key {api_key}"
18
29
  return headers
19
30
 
20
- def _prepare_route(self, mapped_model: str) -> str:
31
+ def _prepare_route(self, mapped_model: str, api_key: str) -> str:
21
32
  return f"/{mapped_model}"
22
33
 
23
34
 
35
+ class FalAIQueueTask(TaskProviderHelper, ABC):
36
+ def __init__(self, task: str):
37
+ super().__init__(provider="fal-ai", base_url="https://queue.fal.run", task=task)
38
+
39
+ def _prepare_headers(self, headers: dict, api_key: str) -> dict[str, Any]:
40
+ headers = super()._prepare_headers(headers, api_key)
41
+ if not api_key.startswith("hf_"):
42
+ headers["authorization"] = f"Key {api_key}"
43
+ return headers
44
+
45
+ def _prepare_route(self, mapped_model: str, api_key: str) -> str:
46
+ if api_key.startswith("hf_"):
47
+ # Use the queue subdomain for HF routing
48
+ return f"/{mapped_model}?_subdomain=queue"
49
+ return f"/{mapped_model}"
50
+
51
+ def get_response(
52
+ self,
53
+ response: Union[bytes, dict],
54
+ request_params: Optional[RequestParameters] = None,
55
+ ) -> Any:
56
+ response_dict = _as_dict(response)
57
+
58
+ request_id = response_dict.get("request_id")
59
+ if not request_id:
60
+ raise ValueError("No request ID found in the response")
61
+ if request_params is None:
62
+ raise ValueError(
63
+ f"A `RequestParameters` object should be provided to get {self.task} responses with Fal AI."
64
+ )
65
+
66
+ # extract the base url and query params
67
+ parsed_url = urlparse(request_params.url)
68
+ # a bit hacky way to concatenate the provider name without parsing `parsed_url.path`
69
+ base_url = f"{parsed_url.scheme}://{parsed_url.netloc}{'/fal-ai' if parsed_url.netloc == 'router.huggingface.co' else ''}"
70
+ query_param = f"?{parsed_url.query}" if parsed_url.query else ""
71
+
72
+ # extracting the provider model id for status and result urls
73
+ # from the response as it might be different from the mapped model in `request_params.url`
74
+ model_id = urlparse(response_dict.get("response_url")).path
75
+ status_url = f"{base_url}{str(model_id)}/status{query_param}"
76
+ result_url = f"{base_url}{str(model_id)}{query_param}"
77
+
78
+ status = response_dict.get("status")
79
+ logger.info("Generating the output.. this can take several minutes.")
80
+ while status != "COMPLETED":
81
+ time.sleep(_POLLING_INTERVAL)
82
+ status_response = get_session().get(status_url, headers=request_params.headers)
83
+ hf_raise_for_status(status_response)
84
+ status = status_response.json().get("status")
85
+
86
+ return get_session().get(result_url, headers=request_params.headers).json()
87
+
88
+
24
89
  class FalAIAutomaticSpeechRecognitionTask(FalAITask):
25
90
  def __init__(self):
26
91
  super().__init__("automatic-speech-recognition")
27
92
 
28
- def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
93
+ def _prepare_payload_as_dict(
94
+ self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
95
+ ) -> Optional[dict]:
29
96
  if isinstance(inputs, str) and inputs.startswith(("http://", "https://")):
30
97
  # If input is a URL, pass it directly
31
98
  audio_url = inputs
@@ -41,7 +108,7 @@ class FalAIAutomaticSpeechRecognitionTask(FalAITask):
41
108
 
42
109
  return {"audio_url": audio_url, **filter_none(parameters)}
43
110
 
44
- def get_response(self, response: Union[bytes, Dict]) -> Any:
111
+ def get_response(self, response: Union[bytes, dict], request_params: Optional[RequestParameters] = None) -> Any:
45
112
  text = _as_dict(response)["text"]
46
113
  if not isinstance(text, str):
47
114
  raise ValueError(f"Unexpected output format from FalAI API. Expected string, got {type(text)}.")
@@ -52,16 +119,33 @@ class FalAITextToImageTask(FalAITask):
52
119
  def __init__(self):
53
120
  super().__init__("text-to-image")
54
121
 
55
- def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
56
- parameters = filter_none(parameters)
57
- if "width" in parameters and "height" in parameters:
58
- parameters["image_size"] = {
59
- "width": parameters.pop("width"),
60
- "height": parameters.pop("height"),
122
+ def _prepare_payload_as_dict(
123
+ self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
124
+ ) -> Optional[dict]:
125
+ payload: dict[str, Any] = {
126
+ "prompt": inputs,
127
+ **filter_none(parameters),
128
+ }
129
+ if "width" in payload and "height" in payload:
130
+ payload["image_size"] = {
131
+ "width": payload.pop("width"),
132
+ "height": payload.pop("height"),
61
133
  }
62
- return {"prompt": inputs, **parameters}
134
+ if provider_mapping_info.adapter_weights_path is not None:
135
+ lora_path = constants.HUGGINGFACE_CO_URL_TEMPLATE.format(
136
+ repo_id=provider_mapping_info.hf_model_id,
137
+ revision="main",
138
+ filename=provider_mapping_info.adapter_weights_path,
139
+ )
140
+ payload["loras"] = [{"path": lora_path, "scale": 1}]
141
+ if provider_mapping_info.provider_id == "fal-ai/lora":
142
+ # little hack: fal requires the base model for stable-diffusion-based loras but not for flux-based
143
+ # See payloads in https://fal.ai/models/fal-ai/lora/api vs https://fal.ai/models/fal-ai/flux-lora/api
144
+ payload["model_name"] = "stabilityai/stable-diffusion-xl-base-1.0"
145
+
146
+ return payload
63
147
 
64
- def get_response(self, response: Union[bytes, Dict]) -> Any:
148
+ def get_response(self, response: Union[bytes, dict], request_params: Optional[RequestParameters] = None) -> Any:
65
149
  url = _as_dict(response)["images"][0]["url"]
66
150
  return get_session().get(url).content
67
151
 
@@ -70,21 +154,146 @@ class FalAITextToSpeechTask(FalAITask):
70
154
  def __init__(self):
71
155
  super().__init__("text-to-speech")
72
156
 
73
- def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
74
- return {"lyrics": inputs, **filter_none(parameters)}
157
+ def _prepare_payload_as_dict(
158
+ self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
159
+ ) -> Optional[dict]:
160
+ return {"text": inputs, **filter_none(parameters)}
75
161
 
76
- def get_response(self, response: Union[bytes, Dict]) -> Any:
162
+ def get_response(self, response: Union[bytes, dict], request_params: Optional[RequestParameters] = None) -> Any:
77
163
  url = _as_dict(response)["audio"]["url"]
78
164
  return get_session().get(url).content
79
165
 
80
166
 
81
- class FalAITextToVideoTask(FalAITask):
167
+ class FalAITextToVideoTask(FalAIQueueTask):
82
168
  def __init__(self):
83
169
  super().__init__("text-to-video")
84
170
 
85
- def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
171
+ def _prepare_payload_as_dict(
172
+ self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
173
+ ) -> Optional[dict]:
86
174
  return {"prompt": inputs, **filter_none(parameters)}
87
175
 
88
- def get_response(self, response: Union[bytes, Dict]) -> Any:
89
- url = _as_dict(response)["video"]["url"]
176
+ def get_response(
177
+ self,
178
+ response: Union[bytes, dict],
179
+ request_params: Optional[RequestParameters] = None,
180
+ ) -> Any:
181
+ output = super().get_response(response, request_params)
182
+ url = _as_dict(output)["video"]["url"]
90
183
  return get_session().get(url).content
184
+
185
+
186
+ class FalAIImageToImageTask(FalAIQueueTask):
187
+ def __init__(self):
188
+ super().__init__("image-to-image")
189
+
190
+ def _prepare_payload_as_dict(
191
+ self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
192
+ ) -> Optional[dict]:
193
+ image_url = _as_url(inputs, default_mime_type="image/jpeg")
194
+ if "target_size" in parameters:
195
+ parameters["image_size"] = parameters.pop("target_size")
196
+ payload: dict[str, Any] = {
197
+ "image_url": image_url,
198
+ **filter_none(parameters),
199
+ }
200
+ if provider_mapping_info.adapter_weights_path is not None:
201
+ lora_path = constants.HUGGINGFACE_CO_URL_TEMPLATE.format(
202
+ repo_id=provider_mapping_info.hf_model_id,
203
+ revision="main",
204
+ filename=provider_mapping_info.adapter_weights_path,
205
+ )
206
+ payload["loras"] = [{"path": lora_path, "scale": 1}]
207
+
208
+ return payload
209
+
210
+ def get_response(
211
+ self,
212
+ response: Union[bytes, dict],
213
+ request_params: Optional[RequestParameters] = None,
214
+ ) -> Any:
215
+ output = super().get_response(response, request_params)
216
+ url = _as_dict(output)["images"][0]["url"]
217
+ return get_session().get(url).content
218
+
219
+
220
+ class FalAIImageToVideoTask(FalAIQueueTask):
221
+ def __init__(self):
222
+ super().__init__("image-to-video")
223
+
224
+ def _prepare_payload_as_dict(
225
+ self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
226
+ ) -> Optional[dict]:
227
+ image_url = _as_url(inputs, default_mime_type="image/jpeg")
228
+ payload: dict[str, Any] = {
229
+ "image_url": image_url,
230
+ **filter_none(parameters),
231
+ }
232
+ if provider_mapping_info.adapter_weights_path is not None:
233
+ lora_path = constants.HUGGINGFACE_CO_URL_TEMPLATE.format(
234
+ repo_id=provider_mapping_info.hf_model_id,
235
+ revision="main",
236
+ filename=provider_mapping_info.adapter_weights_path,
237
+ )
238
+ payload["loras"] = [{"path": lora_path, "scale": 1}]
239
+ return payload
240
+
241
+ def get_response(
242
+ self,
243
+ response: Union[bytes, dict],
244
+ request_params: Optional[RequestParameters] = None,
245
+ ) -> Any:
246
+ output = super().get_response(response, request_params)
247
+ url = _as_dict(output)["video"]["url"]
248
+ return get_session().get(url).content
249
+
250
+
251
+ class FalAIImageSegmentationTask(FalAIQueueTask):
252
+ def __init__(self):
253
+ super().__init__("image-segmentation")
254
+
255
+ def _prepare_payload_as_dict(
256
+ self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
257
+ ) -> Optional[dict]:
258
+ image_url = _as_url(inputs, default_mime_type="image/png")
259
+ payload: dict[str, Any] = {
260
+ "image_url": image_url,
261
+ **filter_none(parameters),
262
+ "sync_mode": True,
263
+ }
264
+ return payload
265
+
266
+ def get_response(
267
+ self,
268
+ response: Union[bytes, dict],
269
+ request_params: Optional[RequestParameters] = None,
270
+ ) -> Any:
271
+ result = super().get_response(response, request_params)
272
+ result_dict = _as_dict(result)
273
+
274
+ if "image" not in result_dict:
275
+ raise ValueError(f"Response from fal ai image-segmentation API does not contain an image: {result_dict}")
276
+
277
+ image_data = result_dict["image"]
278
+ if "url" not in image_data:
279
+ raise ValueError(f"Image data from fal ai image-segmentation API does not contain a URL: {image_data}")
280
+
281
+ image_url = image_data["url"]
282
+
283
+ if isinstance(image_url, str) and image_url.startswith("data:"):
284
+ if "," in image_url:
285
+ mask_base64 = image_url.split(",", 1)[1]
286
+ else:
287
+ raise ValueError(f"Invalid data URL format: {image_url}")
288
+ else:
289
+ # or it's a regular URL, fetch it
290
+ mask_response = get_session().get(image_url)
291
+ hf_raise_for_status(mask_response)
292
+ mask_base64 = base64.b64encode(mask_response.content).decode()
293
+
294
+ return [
295
+ {
296
+ "label": "mask",
297
+ "mask": mask_base64,
298
+ }
299
+ ]
@@ -0,0 +1,38 @@
1
+ from typing import Any, Optional, Union
2
+
3
+ from huggingface_hub.hf_api import InferenceProviderMapping
4
+ from huggingface_hub.inference._common import RequestParameters, _as_dict
5
+
6
+ from ._common import BaseConversationalTask, BaseTextGenerationTask, filter_none
7
+
8
+
9
+ _PROVIDER = "featherless-ai"
10
+ _BASE_URL = "https://api.featherless.ai"
11
+
12
+
13
+ class FeatherlessTextGenerationTask(BaseTextGenerationTask):
14
+ def __init__(self):
15
+ super().__init__(provider=_PROVIDER, base_url=_BASE_URL)
16
+
17
+ def _prepare_payload_as_dict(
18
+ self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
19
+ ) -> Optional[dict]:
20
+ params = filter_none(parameters.copy())
21
+ params["max_tokens"] = params.pop("max_new_tokens", None)
22
+
23
+ return {"prompt": inputs, **params, "model": provider_mapping_info.provider_id}
24
+
25
+ def get_response(self, response: Union[bytes, dict], request_params: Optional[RequestParameters] = None) -> Any:
26
+ output = _as_dict(response)["choices"][0]
27
+ return {
28
+ "generated_text": output["text"],
29
+ "details": {
30
+ "finish_reason": output.get("finish_reason"),
31
+ "seed": output.get("seed"),
32
+ },
33
+ }
34
+
35
+
36
+ class FeatherlessConversationalTask(BaseConversationalTask):
37
+ def __init__(self):
38
+ super().__init__(provider=_PROVIDER, base_url=_BASE_URL)
@@ -1,6 +1,27 @@
1
+ from typing import Any, Optional
2
+
3
+ from huggingface_hub.hf_api import InferenceProviderMapping
4
+
1
5
  from ._common import BaseConversationalTask
2
6
 
3
7
 
4
8
  class FireworksAIConversationalTask(BaseConversationalTask):
5
9
  def __init__(self):
6
- super().__init__(provider="fireworks-ai", base_url="https://api.fireworks.ai/inference")
10
+ super().__init__(provider="fireworks-ai", base_url="https://api.fireworks.ai")
11
+
12
+ def _prepare_route(self, mapped_model: str, api_key: str) -> str:
13
+ return "/inference/v1/chat/completions"
14
+
15
+ def _prepare_payload_as_dict(
16
+ self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
17
+ ) -> Optional[dict]:
18
+ payload = super()._prepare_payload_as_dict(inputs, parameters, provider_mapping_info)
19
+ response_format = parameters.get("response_format")
20
+ if isinstance(response_format, dict) and response_format.get("type") == "json_schema":
21
+ json_schema_details = response_format.get("json_schema")
22
+ if isinstance(json_schema_details, dict) and "schema" in json_schema_details:
23
+ payload["response_format"] = { # type: ignore [index]
24
+ "type": "json_object",
25
+ "schema": json_schema_details["schema"],
26
+ }
27
+ return payload
@@ -0,0 +1,9 @@
1
+ from ._common import BaseConversationalTask
2
+
3
+
4
+ class GroqConversationalTask(BaseConversationalTask):
5
+ def __init__(self):
6
+ super().__init__(provider="groq", base_url="https://api.groq.com")
7
+
8
+ def _prepare_route(self, mapped_model: str, api_key: str) -> str:
9
+ return "/openai/v1/chat/completions"
@@ -1,10 +1,18 @@
1
1
  import json
2
2
  from functools import lru_cache
3
3
  from pathlib import Path
4
- from typing import Any, Dict, Optional
4
+ from typing import Any, Optional, Union
5
+ from urllib.parse import urlparse, urlunparse
5
6
 
6
7
  from huggingface_hub import constants
7
- from huggingface_hub.inference._common import _b64_encode, _open_as_binary
8
+ from huggingface_hub.hf_api import InferenceProviderMapping
9
+ from huggingface_hub.inference._common import (
10
+ MimeBytes,
11
+ RequestParameters,
12
+ _b64_encode,
13
+ _bytes_to_dict,
14
+ _open_as_mime_bytes,
15
+ )
8
16
  from huggingface_hub.inference._providers._common import TaskProviderHelper, filter_none
9
17
  from huggingface_hub.utils import build_hf_headers, get_session, get_token, hf_raise_for_status
10
18
 
@@ -23,16 +31,21 @@ class HFInferenceTask(TaskProviderHelper):
23
31
  # special case: for HF Inference we allow not providing an API key
24
32
  return api_key or get_token() # type: ignore[return-value]
25
33
 
26
- def _prepare_mapped_model(self, model: Optional[str]) -> str:
27
- if model is not None:
28
- return model
29
- model = _fetch_recommended_models().get(self.task)
30
- if model is None:
34
+ def _prepare_mapping_info(self, model: Optional[str]) -> InferenceProviderMapping:
35
+ if model is not None and model.startswith(("http://", "https://")):
36
+ return InferenceProviderMapping(
37
+ provider="hf-inference", providerId=model, hf_model_id=model, task=self.task, status="live"
38
+ )
39
+ model_id = model if model is not None else _fetch_recommended_models().get(self.task)
40
+ if model_id is None:
31
41
  raise ValueError(
32
42
  f"Task {self.task} has no recommended model for HF Inference. Please specify a model"
33
43
  " explicitly. Visit https://huggingface.co/tasks for more info."
34
44
  )
35
- return model
45
+ _check_supported_task(model_id, self.task)
46
+ return InferenceProviderMapping(
47
+ provider="hf-inference", providerId=model_id, hf_model_id=model_id, task=self.task, status="live"
48
+ )
36
49
 
37
50
  def _prepare_url(self, api_key: str, mapped_model: str) -> str:
38
51
  # hf-inference provider can handle URLs (e.g. Inference Endpoints or TGI deployment)
@@ -40,28 +53,36 @@ class HFInferenceTask(TaskProviderHelper):
40
53
  return mapped_model
41
54
  return (
42
55
  # Feature-extraction and sentence-similarity are the only cases where we handle models with several tasks.
43
- f"{self.base_url}/pipeline/{self.task}/{mapped_model}"
56
+ f"{self.base_url}/models/{mapped_model}/pipeline/{self.task}"
44
57
  if self.task in ("feature-extraction", "sentence-similarity")
45
58
  # Otherwise, we use the default endpoint
46
59
  else f"{self.base_url}/models/{mapped_model}"
47
60
  )
48
61
 
49
- def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
62
+ def _prepare_payload_as_dict(
63
+ self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
64
+ ) -> Optional[dict]:
50
65
  if isinstance(inputs, bytes):
51
66
  raise ValueError(f"Unexpected binary input for task {self.task}.")
52
67
  if isinstance(inputs, Path):
53
68
  raise ValueError(f"Unexpected path input for task {self.task} (got {inputs})")
54
- return {"inputs": inputs, "parameters": filter_none(parameters)}
69
+ return filter_none({"inputs": inputs, "parameters": parameters})
55
70
 
56
71
 
57
72
  class HFInferenceBinaryInputTask(HFInferenceTask):
58
- def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
73
+ def _prepare_payload_as_dict(
74
+ self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
75
+ ) -> Optional[dict]:
59
76
  return None
60
77
 
61
78
  def _prepare_payload_as_bytes(
62
- self, inputs: Any, parameters: Dict, mapped_model: str, extra_payload: Optional[Dict]
63
- ) -> Optional[bytes]:
64
- parameters = filter_none({k: v for k, v in parameters.items() if v is not None})
79
+ self,
80
+ inputs: Any,
81
+ parameters: dict,
82
+ provider_mapping_info: InferenceProviderMapping,
83
+ extra_payload: Optional[dict],
84
+ ) -> Optional[MimeBytes]:
85
+ parameters = filter_none(parameters)
65
86
  extra_payload = extra_payload or {}
66
87
  has_parameters = len(parameters) > 0 or len(extra_payload) > 0
67
88
 
@@ -71,21 +92,36 @@ class HFInferenceBinaryInputTask(HFInferenceTask):
71
92
 
72
93
  # Send inputs as raw content when no parameters are provided
73
94
  if not has_parameters:
74
- with _open_as_binary(inputs) as data:
75
- data_as_bytes = data if isinstance(data, bytes) else data.read()
76
- return data_as_bytes
95
+ return _open_as_mime_bytes(inputs)
77
96
 
78
97
  # Otherwise encode as b64
79
- return json.dumps({"inputs": _b64_encode(inputs), "parameters": parameters, **extra_payload}).encode("utf-8")
98
+ return MimeBytes(
99
+ json.dumps({"inputs": _b64_encode(inputs), "parameters": parameters, **extra_payload}).encode("utf-8"),
100
+ mime_type="application/json",
101
+ )
80
102
 
81
103
 
82
104
  class HFInferenceConversational(HFInferenceTask):
83
105
  def __init__(self):
84
- super().__init__("text-generation")
85
-
86
- def _prepare_payload_as_dict(self, inputs: Any, parameters: Dict, mapped_model: str) -> Optional[Dict]:
87
- payload_model = "tgi" if mapped_model.startswith(("http://", "https://")) else mapped_model
88
- return {**filter_none(parameters), "model": payload_model, "messages": inputs}
106
+ super().__init__("conversational")
107
+
108
+ def _prepare_payload_as_dict(
109
+ self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
110
+ ) -> Optional[dict]:
111
+ payload = filter_none(parameters)
112
+ mapped_model = provider_mapping_info.provider_id
113
+ payload_model = parameters.get("model") or mapped_model
114
+
115
+ if payload_model is None or payload_model.startswith(("http://", "https://")):
116
+ payload_model = "dummy"
117
+
118
+ response_format = parameters.get("response_format")
119
+ if isinstance(response_format, dict) and response_format.get("type") == "json_schema":
120
+ payload["response_format"] = {
121
+ "type": "json_object",
122
+ "value": response_format["json_schema"]["schema"],
123
+ }
124
+ return {**payload, "model": payload_model, "messages": inputs}
89
125
 
90
126
  def _prepare_url(self, api_key: str, mapped_model: str) -> str:
91
127
  base_url = (
@@ -97,22 +133,96 @@ class HFInferenceConversational(HFInferenceTask):
97
133
 
98
134
 
99
135
  def _build_chat_completion_url(model_url: str) -> str:
100
- # Strip trailing /
101
- model_url = model_url.rstrip("/")
136
+ parsed = urlparse(model_url)
137
+ path = parsed.path.rstrip("/")
102
138
 
103
- # Append /chat/completions if not already present
104
- if model_url.endswith("/v1"):
105
- model_url += "/chat/completions"
139
+ # If the path already ends with /chat/completions, we're done!
140
+ if path.endswith("/chat/completions"):
141
+ return model_url
106
142
 
143
+ # Append /chat/completions if not already present
144
+ if path.endswith("/v1"):
145
+ new_path = path + "/chat/completions"
146
+ # If path was empty or just "/", set the full path
147
+ elif not path:
148
+ new_path = "/v1/chat/completions"
107
149
  # Append /v1/chat/completions if not already present
108
- if not model_url.endswith("/chat/completions"):
109
- model_url += "/v1/chat/completions"
150
+ else:
151
+ new_path = path + "/v1/chat/completions"
110
152
 
111
- return model_url
153
+ # Reconstruct the URL with the new path and original query parameters.
154
+ new_parsed = parsed._replace(path=new_path)
155
+ return str(urlunparse(new_parsed))
112
156
 
113
157
 
114
158
  @lru_cache(maxsize=1)
115
- def _fetch_recommended_models() -> Dict[str, Optional[str]]:
159
+ def _fetch_recommended_models() -> dict[str, Optional[str]]:
116
160
  response = get_session().get(f"{constants.ENDPOINT}/api/tasks", headers=build_hf_headers())
117
161
  hf_raise_for_status(response)
118
162
  return {task: next(iter(details["widgetModels"]), None) for task, details in response.json().items()}
163
+
164
+
165
+ @lru_cache(maxsize=None)
166
+ def _check_supported_task(model: str, task: str) -> None:
167
+ from huggingface_hub.hf_api import HfApi
168
+
169
+ model_info = HfApi().model_info(model)
170
+ pipeline_tag = model_info.pipeline_tag
171
+ tags = model_info.tags or []
172
+ is_conversational = "conversational" in tags
173
+ if task in ("text-generation", "conversational"):
174
+ if pipeline_tag == "text-generation":
175
+ # text-generation + conversational tag -> both tasks allowed
176
+ if is_conversational:
177
+ return
178
+ # text-generation without conversational tag -> only text-generation allowed
179
+ if task == "text-generation":
180
+ return
181
+ raise ValueError(f"Model '{model}' doesn't support task '{task}'.")
182
+
183
+ if pipeline_tag == "text2text-generation":
184
+ if task == "text-generation":
185
+ return
186
+ raise ValueError(f"Model '{model}' doesn't support task '{task}'.")
187
+
188
+ if pipeline_tag == "image-text-to-text":
189
+ if is_conversational and task == "conversational":
190
+ return # Only conversational allowed if tagged as conversational
191
+ raise ValueError("Non-conversational image-text-to-text task is not supported.")
192
+
193
+ if (
194
+ task in ("feature-extraction", "sentence-similarity")
195
+ and pipeline_tag in ("feature-extraction", "sentence-similarity")
196
+ and task in tags
197
+ ):
198
+ # feature-extraction and sentence-similarity are interchangeable for HF Inference
199
+ return
200
+
201
+ # For all other tasks, just check pipeline tag
202
+ if pipeline_tag != task:
203
+ raise ValueError(
204
+ f"Model '{model}' doesn't support task '{task}'. Supported tasks: '{pipeline_tag}', got: '{task}'"
205
+ )
206
+ return
207
+
208
+
209
+ class HFInferenceFeatureExtractionTask(HFInferenceTask):
210
+ def __init__(self):
211
+ super().__init__("feature-extraction")
212
+
213
+ def _prepare_payload_as_dict(
214
+ self, inputs: Any, parameters: dict, provider_mapping_info: InferenceProviderMapping
215
+ ) -> Optional[dict]:
216
+ if isinstance(inputs, bytes):
217
+ raise ValueError(f"Unexpected binary input for task {self.task}.")
218
+ if isinstance(inputs, Path):
219
+ raise ValueError(f"Unexpected path input for task {self.task} (got {inputs})")
220
+
221
+ # Parameters are sent at root-level for feature-extraction task
222
+ # See specs: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/src/tasks/feature-extraction/spec/input.json
223
+ return {"inputs": inputs, **filter_none(parameters)}
224
+
225
+ def get_response(self, response: Union[bytes, dict], request_params: Optional[RequestParameters] = None) -> Any:
226
+ if isinstance(response, bytes):
227
+ return _bytes_to_dict(response)
228
+ return response