xinference-client 3.0.0__tar.gz → 3.2.0__tar.gz

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 (25) hide show
  1. {xinference_client-3.0.0 → xinference_client-3.2.0}/PKG-INFO +1 -1
  2. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client/_version.py +3 -3
  3. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client/client/restful/async_restful_client.py +99 -14
  4. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client/client/restful/restful_client.py +81 -0
  5. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client/types.py +50 -0
  6. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client.egg-info/PKG-INFO +1 -1
  7. {xinference_client-3.0.0 → xinference_client-3.2.0}/LICENSE +0 -0
  8. {xinference_client-3.0.0 → xinference_client-3.2.0}/MANIFEST.in +0 -0
  9. {xinference_client-3.0.0 → xinference_client-3.2.0}/README.md +0 -0
  10. {xinference_client-3.0.0 → xinference_client-3.2.0}/pyproject.toml +0 -0
  11. {xinference_client-3.0.0 → xinference_client-3.2.0}/setup.cfg +0 -0
  12. {xinference_client-3.0.0 → xinference_client-3.2.0}/setup.py +0 -0
  13. {xinference_client-3.0.0 → xinference_client-3.2.0}/third_party/inference/xinference/thirdparty/matcha/utils/monotonic_align/core.pyx +0 -0
  14. {xinference_client-3.0.0 → xinference_client-3.2.0}/versioneer.py +0 -0
  15. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client/__init__.py +0 -0
  16. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client/_compat.py +0 -0
  17. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client/client/__init__.py +0 -0
  18. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client/client/common.py +0 -0
  19. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client/client/restful/__init__.py +0 -0
  20. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client/fields.py +0 -0
  21. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client.egg-info/SOURCES.txt +0 -0
  22. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client.egg-info/dependency_links.txt +0 -0
  23. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client.egg-info/not-zip-safe +0 -0
  24. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client.egg-info/requires.txt +0 -0
  25. {xinference_client-3.0.0 → xinference_client-3.2.0}/xinference_client.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xinference-client
3
- Version: 3.0.0
3
+ Version: 3.2.0
4
4
  Summary: Client for Xinference
5
5
  Home-page: https://github.com/xorbitsai/inference-client
6
6
  Author: Qin Xuye
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2026-07-19T01:13:11+0800",
11
+ "date": "2026-08-15T14:17:59+0800",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "a9a54d21006dab4780ddaf26c51057541351f950",
15
- "version": "3.0.0"
14
+ "full-revisionid": "3ed6a1c10c0b3793ebe05c29c6cc6ba331af318b",
15
+ "version": "3.2.0"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -22,6 +22,7 @@ from ..common import async_streaming_response_iterator, convert_float_to_int_or_
22
22
 
23
23
  if TYPE_CHECKING:
24
24
  from ...types import (
25
+ AudioEmbedding,
25
26
  ChatCompletion,
26
27
  ChatCompletionChunk,
27
28
  Completion,
@@ -601,16 +602,15 @@ class AsyncRESTfulImageModelHandle(AsyncRESTfulModelHandle):
601
602
 
602
603
  async def ocr(self, image: Union[str, bytes], **kwargs):
603
604
  url = f"{self._base_url}/v1/images/ocr"
604
- params = {
605
- "model": self._model_uid,
606
- "kwargs": json.dumps(kwargs),
607
- }
608
- params = _filter_params(params)
609
- files: List[Any] = []
610
- for key, value in params.items():
611
- files.append((key, (None, value)))
612
- files.append(("image", ("image", image, "application/octet-stream")))
613
- response = await self.session.post(url, data=files, headers=self.auth_headers)
605
+ # aiohttp does not understand requests-style (key, (filename, value))
606
+ # tuples; build a proper multipart form instead.
607
+ form = aiohttp.FormData()
608
+ form.add_field("model", self._model_uid)
609
+ form.add_field("kwargs", json.dumps(kwargs))
610
+ form.add_field(
611
+ "image", image, filename="image", content_type="application/octet-stream"
612
+ )
613
+ response = await self.session.post(url, data=form, headers=self.auth_headers)
614
614
  if response.status != 200:
615
615
  raise RuntimeError(
616
616
  f"Failed to ocr the images, detail: {await _get_error_string(response)}"
@@ -667,6 +667,7 @@ class AsyncRESTfulVideoModelHandle(AsyncRESTfulModelHandle):
667
667
  prompt: str,
668
668
  negative_prompt: Optional[str] = None,
669
669
  n: int = 1,
670
+ video: Optional[Union[str, bytes]] = None,
670
671
  **kwargs,
671
672
  ) -> "VideoList":
672
673
  """
@@ -682,6 +683,8 @@ class AsyncRESTfulVideoModelHandle(AsyncRESTfulModelHandle):
682
683
  The prompt or prompts not to guide the image generation.
683
684
  n: `int`, defaults to 1
684
685
  The number of videos to generate per prompt. Must be between 1 and 10.
686
+ video: `Union[str, bytes]`, optional
687
+ The driving video for character animation models.
685
688
  Returns
686
689
  -------
687
690
  VideoList
@@ -696,11 +699,25 @@ class AsyncRESTfulVideoModelHandle(AsyncRESTfulModelHandle):
696
699
  "kwargs": json.dumps(kwargs),
697
700
  }
698
701
  params = _filter_params(params)
699
- files: List[Any] = []
702
+ data = aiohttp.FormData()
700
703
  for key, value in params.items():
701
- files.append((key, (None, value)))
702
- files.append(("image", ("image", image, "application/octet-stream")))
703
- response = await self.session.post(url, data=files, headers=self.auth_headers)
704
+ data.add_field(key, str(value))
705
+ data.add_field(
706
+ "image", image, filename="image", content_type="application/octet-stream"
707
+ )
708
+ if video is not None:
709
+ if isinstance(video, str):
710
+ with open(video, "rb") as f:
711
+ video_data = f.read()
712
+ else:
713
+ video_data = video
714
+ data.add_field(
715
+ "video",
716
+ video_data,
717
+ filename="video",
718
+ content_type="application/octet-stream",
719
+ )
720
+ response = await self.session.post(url, data=data, headers=self.auth_headers)
704
721
  if response.status != 200:
705
722
  raise RuntimeError(
706
723
  f"Failed to create the video from image, detail: {await _get_error_string(response)}"
@@ -916,6 +933,24 @@ class AsyncRESTfulChatModelHandle(AsyncRESTfulGenerateModelHandle):
916
933
 
917
934
 
918
935
  class AsyncRESTfulAudioModelHandle(AsyncRESTfulModelHandle):
936
+ async def create_embedding(self, audio: bytes) -> "AudioEmbedding":
937
+ """Create a speaker embedding from encoded audio bytes."""
938
+ url = f"{self._base_url}/v1/audio/embeddings"
939
+ data = aiohttp.FormData()
940
+ data.add_field("model", self._model_uid)
941
+ data.add_field(
942
+ "file", audio, filename="file", content_type="application/octet-stream"
943
+ )
944
+ response = await self.session.post(url, data=data, headers=self.auth_headers)
945
+ if response.status != 200:
946
+ raise RuntimeError(
947
+ "Failed to create the audio embedding, "
948
+ f"detail: {await _get_error_string(response)}"
949
+ )
950
+ response_data = await response.json()
951
+ await _release_response(response)
952
+ return response_data
953
+
919
954
  async def transcriptions(
920
955
  self,
921
956
  audio: bytes,
@@ -1307,6 +1342,7 @@ class AsyncClient:
1307
1342
  request_limits: Optional[int] = None,
1308
1343
  worker_ip: Optional[str] = None,
1309
1344
  gpu_idx: Optional[Union[int, List[int]]] = None,
1345
+ replica_config: Optional[List[Dict]] = None,
1310
1346
  model_path: Optional[str] = None,
1311
1347
  enable_thinking: Optional[bool] = None,
1312
1348
  **kwargs,
@@ -1348,6 +1384,13 @@ class AsyncClient:
1348
1384
  Specify the worker ip where the model is located in a distributed scenario.
1349
1385
  gpu_idx: Optional[Union[int, List[int]]]
1350
1386
  Specify the GPU index where the model is located.
1387
+ replica_config: Optional[List[Dict]]
1388
+ Per-replica placement spec. Each item is ``{"replica_uid": str|None,
1389
+ "devices": [{"worker_ip": "ip:port", "n_gpu": int|"auto",
1390
+ "gpu_idx": [int]|None}]}``. When set, each replica is pinned to the
1391
+ given worker/GPU. Do not combine it with worker_ip/n_gpu/gpu_idx.
1392
+ An omitted replica_uid defaults to ``{model_uid}-{replica_index}``.
1393
+ ``devices`` length must be 1 (no cross-worker sharding per replica).
1351
1394
  model_path: Optional[str]
1352
1395
  Model path, if gguf format, should be the file path, otherwise, should be directory of the model.
1353
1396
  enable_thinking: Optional[bool]
@@ -1384,6 +1427,7 @@ class AsyncClient:
1384
1427
  "request_limits": request_limits,
1385
1428
  "worker_ip": worker_ip,
1386
1429
  "gpu_idx": gpu_idx,
1430
+ "replica_config": replica_config,
1387
1431
  "model_path": model_path,
1388
1432
  "enable_thinking": enable_thinking,
1389
1433
  }
@@ -1477,6 +1521,47 @@ class AsyncClient:
1477
1521
  )
1478
1522
  await _release_response(response)
1479
1523
 
1524
+ async def add_model_replica(
1525
+ self,
1526
+ model_uid: str,
1527
+ replica_config: Optional[dict] = None,
1528
+ ) -> dict:
1529
+ """Add a new replica to a running model (scale-up).
1530
+
1531
+ Parameters
1532
+ ----------
1533
+ model_uid : str
1534
+ The UID of the running model to extend.
1535
+ replica_config : Optional[dict]
1536
+ Optional single-device placement config, e.g.::
1537
+
1538
+ {
1539
+ "replica_uid": "my-replica-label",
1540
+ "devices": [
1541
+ {"worker_ip": "192.168.1.100:9999", "gpu_idx": [0, 1]}
1542
+ ]
1543
+ }
1544
+
1545
+ Omit to let the supervisor auto-select a worker and GPU.
1546
+
1547
+ Returns
1548
+ -------
1549
+ dict
1550
+ ``{"replica_id": int, "replica_model_uid": str, "worker_address": str}``
1551
+ """
1552
+ url = f"{self.base_url}/v1/models/{model_uid}/replicas"
1553
+ payload: Dict[str, Any] = {}
1554
+ if replica_config is not None:
1555
+ payload["replica_config"] = replica_config
1556
+ response = await self.session.post(url, json=payload, headers=self._headers)
1557
+ if response.status != 200:
1558
+ raise RuntimeError(
1559
+ f"Failed to add model replica, detail: {await _get_error_string(response)}"
1560
+ )
1561
+ result = await response.json()
1562
+ await _release_response(response)
1563
+ return result
1564
+
1480
1565
  async def terminate_model_replica(self, model_uid: str, replica_id: int) -> int:
1481
1566
  """Terminate a specific replica of a running model."""
1482
1567
 
@@ -21,6 +21,7 @@ from ..common import convert_float_to_int_or_str, streaming_response_iterator
21
21
 
22
22
  if TYPE_CHECKING:
23
23
  from ...types import (
24
+ AudioEmbedding,
24
25
  ChatCompletion,
25
26
  ChatCompletionChunk,
26
27
  Completion,
@@ -599,6 +600,7 @@ class RESTfulVideoModelHandle(RESTfulModelHandle):
599
600
  prompt: str,
600
601
  negative_prompt: Optional[str] = None,
601
602
  n: int = 1,
603
+ video: Optional[Union[str, bytes]] = None,
602
604
  **kwargs,
603
605
  ) -> "VideoList":
604
606
  """
@@ -614,6 +616,8 @@ class RESTfulVideoModelHandle(RESTfulModelHandle):
614
616
  The prompt or prompts not to guide the image generation.
615
617
  n: `int`, defaults to 1
616
618
  The number of videos to generate per prompt. Must be between 1 and 10.
619
+ video: `Union[str, bytes]`, optional
620
+ The driving video for character animation models.
617
621
  Returns
618
622
  -------
619
623
  VideoList
@@ -631,6 +635,18 @@ class RESTfulVideoModelHandle(RESTfulModelHandle):
631
635
  for key, value in params.items():
632
636
  files.append((key, (None, value)))
633
637
  files.append(("image", ("image", image, "application/octet-stream")))
638
+ if video is not None:
639
+ if isinstance(video, str):
640
+ with open(video, "rb") as f:
641
+ video_data = f.read()
642
+ else:
643
+ video_data = video
644
+ files.append(
645
+ (
646
+ "video",
647
+ ("video", video_data, "application/octet-stream"),
648
+ )
649
+ )
634
650
  response = self.session.post(url, files=files, headers=self.auth_headers)
635
651
  if response.status_code != 200:
636
652
  raise RuntimeError(
@@ -831,6 +847,23 @@ class RESTfulChatModelHandle(RESTfulGenerateModelHandle):
831
847
 
832
848
 
833
849
  class RESTfulAudioModelHandle(RESTfulModelHandle):
850
+ def create_embedding(self, audio: bytes) -> "AudioEmbedding":
851
+ """Create a speaker embedding from encoded audio bytes."""
852
+ url = f"{self._base_url}/v1/audio/embeddings"
853
+ files = [("file", ("file", audio, "application/octet-stream"))]
854
+ response = self.session.post(
855
+ url,
856
+ data={"model": self._model_uid},
857
+ files=files,
858
+ headers=self.auth_headers,
859
+ )
860
+ if response.status_code != 200:
861
+ raise RuntimeError(
862
+ "Failed to create the audio embedding, "
863
+ f"detail: {_get_error_string(response)}"
864
+ )
865
+ return response.json()
866
+
834
867
  def transcriptions(
835
868
  self,
836
869
  audio: bytes,
@@ -1193,6 +1226,7 @@ class Client:
1193
1226
  request_limits: Optional[int] = None,
1194
1227
  worker_ip: Optional[str] = None,
1195
1228
  gpu_idx: Optional[Union[int, List[int]]] = None,
1229
+ replica_config: Optional[List[Dict]] = None,
1196
1230
  model_path: Optional[str] = None,
1197
1231
  enable_thinking: Optional[bool] = None,
1198
1232
  enable_virtual_env: Optional[bool] = None,
@@ -1237,6 +1271,13 @@ class Client:
1237
1271
  Specify the worker ip where the model is located in a distributed scenario.
1238
1272
  gpu_idx: Optional[Union[int, List[int]]]
1239
1273
  Specify the GPU index where the model is located.
1274
+ replica_config: Optional[List[Dict]]
1275
+ Per-replica placement spec. Each item is ``{"replica_uid": str|None,
1276
+ "devices": [{"worker_ip": "ip:port", "n_gpu": int|"auto",
1277
+ "gpu_idx": [int]|None}]}``. When set, each replica is pinned to the
1278
+ given worker/GPU. Do not combine it with worker_ip/n_gpu/gpu_idx.
1279
+ An omitted replica_uid defaults to ``{model_uid}-{replica_index}``.
1280
+ ``devices`` length must be 1 (no cross-worker sharding per replica).
1240
1281
  model_path: Optional[str]
1241
1282
  Model path, if gguf format, should be the file path, otherwise, should be directory of the model.
1242
1283
  enable_thinking: Optional[bool]
@@ -1280,6 +1321,7 @@ class Client:
1280
1321
  "request_limits": request_limits,
1281
1322
  "worker_ip": worker_ip,
1282
1323
  "gpu_idx": gpu_idx,
1324
+ "replica_config": replica_config,
1283
1325
  "model_path": model_path,
1284
1326
  "enable_thinking": enable_thinking,
1285
1327
  "enable_virtual_env": enable_virtual_env,
@@ -1366,6 +1408,45 @@ class Client:
1366
1408
  f"Failed to terminate model, detail: {_get_error_string(response)}"
1367
1409
  )
1368
1410
 
1411
+ def add_model_replica(
1412
+ self,
1413
+ model_uid: str,
1414
+ replica_config: Optional[dict] = None,
1415
+ ) -> dict:
1416
+ """Add a new replica to a running model (scale-up).
1417
+
1418
+ Parameters
1419
+ ----------
1420
+ model_uid : str
1421
+ The UID of the running model to extend.
1422
+ replica_config : Optional[dict]
1423
+ Optional single-device placement config, e.g.::
1424
+
1425
+ {
1426
+ "replica_uid": "my-replica-label",
1427
+ "devices": [
1428
+ {"worker_ip": "192.168.1.100:9999", "gpu_idx": [0, 1]}
1429
+ ]
1430
+ }
1431
+
1432
+ Omit to let the supervisor auto-select a worker and GPU.
1433
+
1434
+ Returns
1435
+ -------
1436
+ dict
1437
+ ``{"replica_id": int, "replica_model_uid": str, "worker_address": str}``
1438
+ """
1439
+ url = f"{self.base_url}/v1/models/{model_uid}/replicas"
1440
+ payload: Dict[str, Any] = {}
1441
+ if replica_config is not None:
1442
+ payload["replica_config"] = replica_config
1443
+ response = self.session.post(url, json=payload, headers=self._headers)
1444
+ if response.status_code != 200:
1445
+ raise RuntimeError(
1446
+ f"Failed to add model replica, detail: {_get_error_string(response)}"
1447
+ )
1448
+ return response.json()
1449
+
1369
1450
  def terminate_model_replica(self, model_uid: str, replica_id: int) -> int:
1370
1451
  """Terminate a specific replica of a running model."""
1371
1452
 
@@ -92,6 +92,13 @@ class Embedding(TypedDict):
92
92
  usage: EmbeddingUsage
93
93
 
94
94
 
95
+ class AudioEmbedding(TypedDict):
96
+ object: Literal["embedding"]
97
+ model: str
98
+ dimensions: int
99
+ embedding: List[float]
100
+
101
+
95
102
  class Document(TypedDict):
96
103
  text: str
97
104
 
@@ -142,6 +149,47 @@ class CompletionLogprobs(TypedDict):
142
149
  top_logprobs: List[Optional[Dict[str, float]]]
143
150
 
144
151
 
152
+ class ChatCompletionTopLogprob(TypedDict):
153
+ """A single alternative token in a chat ``logprobs.content[].top_logprobs`` entry.
154
+
155
+ Distinct from the legacy parallel-list :class:`CompletionLogprobs`: chat
156
+ completions expose per-token ``token`` / ``bytes`` / ``logprob`` objects so
157
+ ``openai-python`` parses ``choice.logprobs.content`` instead of dropping the
158
+ legacy fields as extras.
159
+ """
160
+
161
+ token: str
162
+ bytes: Optional[List[int]]
163
+ logprob: float
164
+
165
+
166
+ class ChatCompletionLogprob(TypedDict):
167
+ """A token entry in chat completion ``logprobs.content[]``.
168
+
169
+ ``logprob`` is required (non-optional): the OpenAI chat-completions schema
170
+ mandates a ``float`` on every ``content[]`` entry, and ``openai-python``
171
+ rejects ``null``. Tokens whose logprob is unknown (``None`` in the legacy
172
+ ``CompletionLogprobs.token_logprobs`` shape) are omitted from ``content[]``
173
+ by the chat builder rather than emitted with a null or fabricated logprob.
174
+ """
175
+
176
+ token: str
177
+ bytes: Optional[List[int]]
178
+ logprob: float
179
+ top_logprobs: List[ChatCompletionTopLogprob]
180
+
181
+
182
+ class ChatCompletionLogprobs(TypedDict):
183
+ """Chat Completions logprobs (``content`` list shape).
184
+
185
+ vLLM emits logprobs in the legacy parallel-list :class:`CompletionLogprobs`
186
+ shape; chat clients expect ``logprobs.content[]``. The chat builder converts
187
+ at the boundary so the legacy shape never reaches a chat choice.
188
+ """
189
+
190
+ content: Optional[List[ChatCompletionLogprob]]
191
+
192
+
145
193
  class ToolCallFunction(TypedDict):
146
194
  name: str
147
195
  arguments: str
@@ -204,6 +252,7 @@ class ChatCompletionMessage(TypedDict):
204
252
  class ChatCompletionChoice(TypedDict):
205
253
  index: int
206
254
  message: ChatCompletionMessage
255
+ logprobs: NotRequired[Optional[ChatCompletionLogprobs]]
207
256
  finish_reason: Optional[str]
208
257
 
209
258
 
@@ -226,6 +275,7 @@ class ChatCompletionChunkDelta(TypedDict):
226
275
  class ChatCompletionChunkChoice(TypedDict):
227
276
  index: int
228
277
  delta: ChatCompletionChunkDelta
278
+ logprobs: NotRequired[Optional[ChatCompletionLogprobs]]
229
279
  finish_reason: Optional[str]
230
280
 
231
281
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xinference-client
3
- Version: 3.0.0
3
+ Version: 3.2.0
4
4
  Summary: Client for Xinference
5
5
  Home-page: https://github.com/xorbitsai/inference-client
6
6
  Author: Qin Xuye