xinference-client 3.2.0__tar.gz → 3.4.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.
- {xinference_client-3.2.0 → xinference_client-3.4.0}/PKG-INFO +2 -1
- {xinference_client-3.2.0 → xinference_client-3.4.0}/setup.cfg +1 -0
- xinference_client-3.4.0/tests/test_async_client.py +37 -0
- xinference_client-3.4.0/tests/test_async_client_cleanup.py +126 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client/_version.py +3 -3
- {xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client/client/common.py +42 -14
- {xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client/client/restful/async_restful_client.py +138 -24
- {xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client/client/restful/restful_client.py +98 -16
- {xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client/types.py +22 -2
- {xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client.egg-info/PKG-INFO +2 -1
- {xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client.egg-info/SOURCES.txt +2 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/LICENSE +0 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/MANIFEST.in +0 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/README.md +0 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/pyproject.toml +0 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/setup.py +0 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/third_party/inference/xinference/thirdparty/matcha/utils/monotonic_align/core.pyx +0 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/versioneer.py +0 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client/__init__.py +0 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client/_compat.py +0 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client/client/__init__.py +0 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client/client/restful/__init__.py +0 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client/fields.py +0 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client.egg-info/dependency_links.txt +0 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client.egg-info/not-zip-safe +0 -0
- {xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client.egg-info/requires.txt +0 -0
- {xinference_client-3.2.0 → xinference_client-3.4.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.
|
|
3
|
+
Version: 3.4.0
|
|
4
4
|
Summary: Client for Xinference
|
|
5
5
|
Home-page: https://github.com/xorbitsai/inference-client
|
|
6
6
|
Author: Qin Xuye
|
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
20
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
20
21
|
Classifier: Topic :: Software Development :: Libraries
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
@@ -17,6 +17,7 @@ classifier =
|
|
|
17
17
|
Programming Language :: Python :: 3.11
|
|
18
18
|
Programming Language :: Python :: 3.12
|
|
19
19
|
Programming Language :: Python :: 3.13
|
|
20
|
+
Programming Language :: Python :: 3.14
|
|
20
21
|
Programming Language :: Python :: Implementation :: CPython
|
|
21
22
|
Topic :: Software Development :: Libraries
|
|
22
23
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from aiohttp import web
|
|
3
|
+
|
|
4
|
+
from xinference_client import AsyncRESTfulClient
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.asyncio
|
|
8
|
+
async def test_list_models_and_explicit_close(monkeypatch):
|
|
9
|
+
# Authentication discovery is synchronous; isolate it from the async server.
|
|
10
|
+
monkeypatch.setattr(
|
|
11
|
+
AsyncRESTfulClient, "_check_cluster_authenticated", lambda self: None
|
|
12
|
+
)
|
|
13
|
+
model = {"id": "test-model", "model_type": "LLM"}
|
|
14
|
+
|
|
15
|
+
async def list_models(request):
|
|
16
|
+
return web.json_response({"data": [model]})
|
|
17
|
+
|
|
18
|
+
app = web.Application()
|
|
19
|
+
app.router.add_get("/v1/models", list_models)
|
|
20
|
+
runner = web.AppRunner(app)
|
|
21
|
+
await runner.setup()
|
|
22
|
+
client = None
|
|
23
|
+
try:
|
|
24
|
+
site = web.TCPSite(runner, "127.0.0.1", 0)
|
|
25
|
+
await site.start()
|
|
26
|
+
port = runner.addresses[0][1]
|
|
27
|
+
client = AsyncRESTfulClient(f"http://127.0.0.1:{port}")
|
|
28
|
+
session = client.session
|
|
29
|
+
assert await client.list_models() == {"test-model": model}
|
|
30
|
+
await client.close()
|
|
31
|
+
assert session.closed
|
|
32
|
+
assert client.session is None
|
|
33
|
+
await client.close()
|
|
34
|
+
finally:
|
|
35
|
+
if client is not None:
|
|
36
|
+
await client.close()
|
|
37
|
+
await runner.cleanup()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Copyright 2022-2026 Xinference Holdings Pte. Ltd
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import gc
|
|
17
|
+
import threading
|
|
18
|
+
|
|
19
|
+
import pytest
|
|
20
|
+
|
|
21
|
+
from xinference_client.client.restful import async_restful_client as client_module
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.parametrize(
|
|
25
|
+
"client_class",
|
|
26
|
+
[client_module.AsyncRESTfulModelHandle, client_module.AsyncClient],
|
|
27
|
+
)
|
|
28
|
+
def test_destructor_without_initialized_owner_loop(client_class, monkeypatch):
|
|
29
|
+
def fail_get_event_loop():
|
|
30
|
+
raise AssertionError("destructor must not request an implicit event loop")
|
|
31
|
+
|
|
32
|
+
monkeypatch.setattr(client_module.asyncio, "get_event_loop", fail_get_event_loop)
|
|
33
|
+
client = client_class.__new__(client_class)
|
|
34
|
+
client.session = object()
|
|
35
|
+
|
|
36
|
+
client.__del__()
|
|
37
|
+
|
|
38
|
+
client.session = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.parametrize(
|
|
42
|
+
"client_class",
|
|
43
|
+
[client_module.AsyncRESTfulModelHandle, client_module.AsyncClient],
|
|
44
|
+
)
|
|
45
|
+
def test_destructor_after_owner_loop_closed(client_class):
|
|
46
|
+
client = client_class.__new__(client_class)
|
|
47
|
+
client.session = object()
|
|
48
|
+
client._session_loop = asyncio.new_event_loop()
|
|
49
|
+
client._session_loop.close()
|
|
50
|
+
client.__del__()
|
|
51
|
+
client.session = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.mark.asyncio
|
|
55
|
+
@pytest.mark.parametrize(
|
|
56
|
+
"client_class",
|
|
57
|
+
[client_module.AsyncRESTfulModelHandle, client_module.AsyncClient],
|
|
58
|
+
)
|
|
59
|
+
async def test_destructor_schedules_close_on_running_loop(client_class):
|
|
60
|
+
client = client_class.__new__(client_class)
|
|
61
|
+
client._session_loop = asyncio.get_running_loop()
|
|
62
|
+
client.session = object()
|
|
63
|
+
closed = asyncio.Event()
|
|
64
|
+
|
|
65
|
+
async def close():
|
|
66
|
+
client.session = None
|
|
67
|
+
closed.set()
|
|
68
|
+
|
|
69
|
+
client.close = close
|
|
70
|
+
|
|
71
|
+
client.__del__()
|
|
72
|
+
|
|
73
|
+
await asyncio.wait_for(closed.wait(), timeout=1)
|
|
74
|
+
assert client.session is None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.mark.parametrize(
|
|
78
|
+
"client_class",
|
|
79
|
+
[client_module.AsyncRESTfulModelHandle, client_module.AsyncClient],
|
|
80
|
+
)
|
|
81
|
+
@pytest.mark.parametrize("other_thread", [False, True])
|
|
82
|
+
def test_real_session_cleanup_on_paused_owner_loop(
|
|
83
|
+
client_class, other_thread, monkeypatch
|
|
84
|
+
):
|
|
85
|
+
monkeypatch.setattr(
|
|
86
|
+
client_module.AsyncClient, "_check_cluster_authenticated", lambda self: None
|
|
87
|
+
)
|
|
88
|
+
loop = asyncio.new_event_loop()
|
|
89
|
+
loop.set_debug(True)
|
|
90
|
+
|
|
91
|
+
async def create():
|
|
92
|
+
if client_class is client_module.AsyncClient:
|
|
93
|
+
return client_class("http://localhost:9997")
|
|
94
|
+
return client_class("model", "http://localhost:9997", {})
|
|
95
|
+
|
|
96
|
+
clients = [loop.run_until_complete(create())]
|
|
97
|
+
session = clients[0].session
|
|
98
|
+
closed_on = []
|
|
99
|
+
original_close = session.close
|
|
100
|
+
|
|
101
|
+
async def close():
|
|
102
|
+
closed_on.append(asyncio.get_running_loop())
|
|
103
|
+
await original_close()
|
|
104
|
+
|
|
105
|
+
monkeypatch.setattr(session, "close", close)
|
|
106
|
+
|
|
107
|
+
async def drop():
|
|
108
|
+
clients.clear()
|
|
109
|
+
gc.collect()
|
|
110
|
+
await asyncio.sleep(0)
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
if other_thread:
|
|
114
|
+
thread = threading.Thread(target=lambda: asyncio.run(drop()))
|
|
115
|
+
thread.start()
|
|
116
|
+
thread.join()
|
|
117
|
+
else:
|
|
118
|
+
clients.clear()
|
|
119
|
+
gc.collect()
|
|
120
|
+
assert not session.closed
|
|
121
|
+
loop.run_until_complete(asyncio.sleep(0))
|
|
122
|
+
assert session.closed
|
|
123
|
+
assert closed_on == [loop]
|
|
124
|
+
finally:
|
|
125
|
+
loop.run_until_complete(original_close())
|
|
126
|
+
loop.close()
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2026-
|
|
11
|
+
"date": "2026-09-11T20:38:48+0800",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "3.
|
|
14
|
+
"full-revisionid": "9ed05c51a3b9121ab6c7dac6f161f50fd4e28755",
|
|
15
|
+
"version": "3.4.0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -12,10 +12,26 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
import base64
|
|
15
16
|
import json
|
|
17
|
+
import os
|
|
16
18
|
from typing import Any, AsyncIterator, Iterator, Union
|
|
17
19
|
|
|
18
20
|
|
|
21
|
+
def encode_world_reference(reference: Union[str, bytes], media_type: str) -> str:
|
|
22
|
+
if isinstance(reference, str) and not os.path.isfile(reference):
|
|
23
|
+
return reference
|
|
24
|
+
if isinstance(reference, str):
|
|
25
|
+
with open(reference, "rb") as reference_file:
|
|
26
|
+
data = reference_file.read()
|
|
27
|
+
elif isinstance(reference, bytes):
|
|
28
|
+
data = reference
|
|
29
|
+
else:
|
|
30
|
+
raise TypeError("World generation references must be strings or bytes")
|
|
31
|
+
encoded = base64.b64encode(data).decode("ascii")
|
|
32
|
+
return f"data:{media_type};base64,{encoded}"
|
|
33
|
+
|
|
34
|
+
|
|
19
35
|
def convert_float_to_int_or_str(model_size: float) -> Union[int, str]:
|
|
20
36
|
"""convert float to int or string
|
|
21
37
|
|
|
@@ -64,7 +80,7 @@ def streaming_response_iterator(
|
|
|
64
80
|
|
|
65
81
|
|
|
66
82
|
async def async_streaming_response_iterator(
|
|
67
|
-
|
|
83
|
+
response_or_lines: Any,
|
|
68
84
|
) -> AsyncIterator[Any]:
|
|
69
85
|
"""
|
|
70
86
|
Create an AsyncIterator to handle the streaming type of generation.
|
|
@@ -76,8 +92,8 @@ async def async_streaming_response_iterator(
|
|
|
76
92
|
|
|
77
93
|
Parameters
|
|
78
94
|
----------
|
|
79
|
-
|
|
80
|
-
|
|
95
|
+
response_or_lines: aiohttp.ClientResponse or AsyncIterator[bytes]
|
|
96
|
+
The owning HTTP response, or generated lines for compatibility.
|
|
81
97
|
|
|
82
98
|
Returns
|
|
83
99
|
-------
|
|
@@ -86,14 +102,26 @@ async def async_streaming_response_iterator(
|
|
|
86
102
|
|
|
87
103
|
"""
|
|
88
104
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
105
|
+
response = (
|
|
106
|
+
response_or_lines
|
|
107
|
+
if hasattr(response_or_lines, "content")
|
|
108
|
+
and hasattr(response_or_lines, "release")
|
|
109
|
+
else None
|
|
110
|
+
)
|
|
111
|
+
response_lines = response.content if response is not None else response_or_lines
|
|
112
|
+
try:
|
|
113
|
+
async for line in response_lines:
|
|
114
|
+
line = line.strip()
|
|
115
|
+
if line.startswith(b"data:"):
|
|
116
|
+
json_str = line[len(b"data:") :].strip()
|
|
117
|
+
if json_str == b"[DONE]":
|
|
118
|
+
continue
|
|
119
|
+
data = json.loads(json_str.decode("utf-8"))
|
|
120
|
+
error = data.get("error", None)
|
|
121
|
+
if error is not None:
|
|
122
|
+
raise Exception(str(error))
|
|
123
|
+
yield data
|
|
124
|
+
finally:
|
|
125
|
+
if response is not None:
|
|
126
|
+
response.release()
|
|
127
|
+
await response.wait_for_close()
|
|
@@ -18,7 +18,11 @@ from urllib.parse import quote
|
|
|
18
18
|
|
|
19
19
|
import aiohttp
|
|
20
20
|
|
|
21
|
-
from ..common import
|
|
21
|
+
from ..common import (
|
|
22
|
+
async_streaming_response_iterator,
|
|
23
|
+
convert_float_to_int_or_str,
|
|
24
|
+
encode_world_reference,
|
|
25
|
+
)
|
|
22
26
|
|
|
23
27
|
if TYPE_CHECKING:
|
|
24
28
|
from ...types import (
|
|
@@ -66,6 +70,25 @@ async def _release_response(response: aiohttp.ClientResponse):
|
|
|
66
70
|
await response.wait_for_close()
|
|
67
71
|
|
|
68
72
|
|
|
73
|
+
def _schedule_session_close(client: Any) -> None:
|
|
74
|
+
if not getattr(client, "session", None):
|
|
75
|
+
return
|
|
76
|
+
loop = getattr(client, "_session_loop", None)
|
|
77
|
+
if loop is None or loop.is_closed():
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
def close_on_owner_loop():
|
|
81
|
+
loop.create_task(client.close())
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
# Also queues cleanup while a manually driven loop is paused, and when
|
|
85
|
+
# the last reference is released by another thread.
|
|
86
|
+
loop.call_soon_threadsafe(close_on_owner_loop)
|
|
87
|
+
except RuntimeError:
|
|
88
|
+
# The owning loop may have closed since the check above.
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
|
|
69
92
|
class AsyncRESTfulModelHandle:
|
|
70
93
|
"""
|
|
71
94
|
A sync model interface (for RESTful client) which provides type hints that makes it much easier to use xinference
|
|
@@ -77,6 +100,7 @@ class AsyncRESTfulModelHandle:
|
|
|
77
100
|
self._base_url = base_url
|
|
78
101
|
self.auth_headers = auth_headers
|
|
79
102
|
self.timeout = aiohttp.ClientTimeout(total=1800)
|
|
103
|
+
self._session_loop = asyncio.get_running_loop()
|
|
80
104
|
self.session = aiohttp.ClientSession(
|
|
81
105
|
connector=aiohttp.TCPConnector(force_close=True)
|
|
82
106
|
)
|
|
@@ -88,23 +112,32 @@ class AsyncRESTfulModelHandle:
|
|
|
88
112
|
self.session = None
|
|
89
113
|
|
|
90
114
|
def __del__(self):
|
|
91
|
-
|
|
92
|
-
loop = asyncio.get_event_loop()
|
|
93
|
-
loop.create_task(self.close())
|
|
115
|
+
_schedule_session_close(self)
|
|
94
116
|
|
|
95
117
|
|
|
96
118
|
class AsyncRESTfulEmbeddingModelHandle(AsyncRESTfulModelHandle):
|
|
97
119
|
async def create_embedding(
|
|
98
|
-
self,
|
|
120
|
+
self,
|
|
121
|
+
input: Union[
|
|
122
|
+
str,
|
|
123
|
+
List[str],
|
|
124
|
+
List[int],
|
|
125
|
+
List[List[int]],
|
|
126
|
+
Dict[str, Any],
|
|
127
|
+
List[Dict[str, Any]],
|
|
128
|
+
List[Union[str, Dict[str, Any]]],
|
|
129
|
+
],
|
|
130
|
+
**kwargs,
|
|
99
131
|
) -> "Embedding":
|
|
100
132
|
"""
|
|
101
133
|
Create an Embedding from user input via RESTful APIs.
|
|
102
134
|
|
|
103
135
|
Parameters
|
|
104
136
|
----------
|
|
105
|
-
input: Union[str, List[str]]
|
|
106
|
-
|
|
107
|
-
|
|
137
|
+
input: Union[str, List[str], List[int], List[List[int]], Dict[str, Any],
|
|
138
|
+
List[Dict[str, Any]], List[Union[str, Dict[str, Any]]]]
|
|
139
|
+
Text, token IDs, or multimodal input. Multimodal dictionaries may
|
|
140
|
+
contain text, image, video, or interleaved role/content messages.
|
|
108
141
|
|
|
109
142
|
Returns
|
|
110
143
|
-------
|
|
@@ -179,8 +212,8 @@ class AsyncRESTfulEmbeddingModelHandle(AsyncRESTfulModelHandle):
|
|
|
179
212
|
class AsyncRESTfulRerankModelHandle(AsyncRESTfulModelHandle):
|
|
180
213
|
async def rerank(
|
|
181
214
|
self,
|
|
182
|
-
documents: List[str],
|
|
183
|
-
query: str,
|
|
215
|
+
documents: List[Union[str, Dict[str, Any]]],
|
|
216
|
+
query: Union[str, Dict[str, Any]],
|
|
184
217
|
top_n: Optional[int] = None,
|
|
185
218
|
max_chunks_per_doc: Optional[int] = None,
|
|
186
219
|
return_documents: Optional[bool] = None,
|
|
@@ -192,10 +225,10 @@ class AsyncRESTfulRerankModelHandle(AsyncRESTfulModelHandle):
|
|
|
192
225
|
|
|
193
226
|
Parameters
|
|
194
227
|
----------
|
|
195
|
-
query: str
|
|
196
|
-
|
|
197
|
-
documents: List[str]
|
|
198
|
-
|
|
228
|
+
query: Union[str, Dict[str, Any]]
|
|
229
|
+
Text or multimodal query.
|
|
230
|
+
documents: List[Union[str, Dict[str, Any]]]
|
|
231
|
+
Text or multimodal documents to rerank.
|
|
199
232
|
top_n: int
|
|
200
233
|
The number of results to return, defaults to returning all results
|
|
201
234
|
max_chunks_per_doc: int
|
|
@@ -795,6 +828,52 @@ class AsyncRESTfulVideoModelHandle(AsyncRESTfulModelHandle):
|
|
|
795
828
|
return response_data
|
|
796
829
|
|
|
797
830
|
|
|
831
|
+
class AsyncRESTfulWorldModelHandle(AsyncRESTfulModelHandle):
|
|
832
|
+
async def generate(
|
|
833
|
+
self,
|
|
834
|
+
prompt: str,
|
|
835
|
+
image: Optional[Union[str, bytes]] = None,
|
|
836
|
+
video: Optional[Union[str, bytes]] = None,
|
|
837
|
+
generation_config: Optional[Dict[str, Any]] = None,
|
|
838
|
+
**model_kwargs,
|
|
839
|
+
) -> "VideoList":
|
|
840
|
+
"""Generate a world video from text and an optional image or video."""
|
|
841
|
+
if image is not None and video is not None:
|
|
842
|
+
raise ValueError("Only one of image and video may be provided")
|
|
843
|
+
encoded_image = (
|
|
844
|
+
await asyncio.to_thread(encode_world_reference, image, "image/png")
|
|
845
|
+
if image is not None
|
|
846
|
+
else None
|
|
847
|
+
)
|
|
848
|
+
encoded_video = (
|
|
849
|
+
await asyncio.to_thread(encode_world_reference, video, "video/mp4")
|
|
850
|
+
if video is not None
|
|
851
|
+
else None
|
|
852
|
+
)
|
|
853
|
+
request_body = {
|
|
854
|
+
"model": self._model_uid,
|
|
855
|
+
"prompt": prompt,
|
|
856
|
+
"image": encoded_image,
|
|
857
|
+
"video": encoded_video,
|
|
858
|
+
"generation_config": generation_config or {},
|
|
859
|
+
"extra_body": model_kwargs,
|
|
860
|
+
}
|
|
861
|
+
response = await self.session.post(
|
|
862
|
+
f"{self._base_url}/v1/worlds/generations",
|
|
863
|
+
json=request_body,
|
|
864
|
+
headers=self.auth_headers,
|
|
865
|
+
timeout=self.timeout,
|
|
866
|
+
)
|
|
867
|
+
if response.status != 200:
|
|
868
|
+
raise RuntimeError(
|
|
869
|
+
"Failed to generate the world, detail: "
|
|
870
|
+
f"{await _get_error_string(response)}"
|
|
871
|
+
)
|
|
872
|
+
response_data = await response.json()
|
|
873
|
+
await _release_response(response)
|
|
874
|
+
return response_data
|
|
875
|
+
|
|
876
|
+
|
|
798
877
|
class AsyncRESTfulGenerateModelHandle(AsyncRESTfulModelHandle):
|
|
799
878
|
async def generate(
|
|
800
879
|
self,
|
|
@@ -844,7 +923,7 @@ class AsyncRESTfulGenerateModelHandle(AsyncRESTfulModelHandle):
|
|
|
844
923
|
)
|
|
845
924
|
|
|
846
925
|
if stream:
|
|
847
|
-
return async_streaming_response_iterator(response
|
|
926
|
+
return async_streaming_response_iterator(response)
|
|
848
927
|
response_data = await response.json()
|
|
849
928
|
await _release_response(response)
|
|
850
929
|
return response_data
|
|
@@ -925,7 +1004,7 @@ class AsyncRESTfulChatModelHandle(AsyncRESTfulGenerateModelHandle):
|
|
|
925
1004
|
)
|
|
926
1005
|
|
|
927
1006
|
if stream:
|
|
928
|
-
return async_streaming_response_iterator(response
|
|
1007
|
+
return async_streaming_response_iterator(response)
|
|
929
1008
|
|
|
930
1009
|
response_data = await response.json()
|
|
931
1010
|
await _release_response(response)
|
|
@@ -1216,6 +1295,7 @@ class AsyncClient:
|
|
|
1216
1295
|
self._headers: Dict[str, str] = {}
|
|
1217
1296
|
self._cluster_authed = False
|
|
1218
1297
|
self.timeout = aiohttp.ClientTimeout(total=1800)
|
|
1298
|
+
self._session_loop = asyncio.get_running_loop()
|
|
1219
1299
|
self.session = aiohttp.ClientSession(
|
|
1220
1300
|
connector=aiohttp.TCPConnector(force_close=True), timeout=self.timeout
|
|
1221
1301
|
)
|
|
@@ -1230,9 +1310,7 @@ class AsyncClient:
|
|
|
1230
1310
|
self.session = None
|
|
1231
1311
|
|
|
1232
1312
|
def __del__(self):
|
|
1233
|
-
|
|
1234
|
-
loop = asyncio.get_event_loop()
|
|
1235
|
-
loop.create_task(self.close())
|
|
1313
|
+
_schedule_session_close(self)
|
|
1236
1314
|
|
|
1237
1315
|
def _set_token(self, token: Optional[str]):
|
|
1238
1316
|
if not self._cluster_authed or token is None:
|
|
@@ -1345,6 +1423,10 @@ class AsyncClient:
|
|
|
1345
1423
|
replica_config: Optional[List[Dict]] = None,
|
|
1346
1424
|
model_path: Optional[str] = None,
|
|
1347
1425
|
enable_thinking: Optional[bool] = None,
|
|
1426
|
+
enable_virtual_env: Optional[bool] = None,
|
|
1427
|
+
virtual_env_packages: Optional[List[str]] = None,
|
|
1428
|
+
envs: Optional[Dict[str, str]] = None,
|
|
1429
|
+
virtual_env_find_links: Optional[List[str]] = None,
|
|
1348
1430
|
**kwargs,
|
|
1349
1431
|
) -> str:
|
|
1350
1432
|
"""
|
|
@@ -1357,7 +1439,7 @@ class AsyncClient:
|
|
|
1357
1439
|
model_type: str
|
|
1358
1440
|
type of model.
|
|
1359
1441
|
model_engine: Optional[str]
|
|
1360
|
-
Specify the inference engine
|
|
1442
|
+
Specify the inference engine to use when launching the model.
|
|
1361
1443
|
model_uid: str
|
|
1362
1444
|
UID of model, auto generate a UUID if is None.
|
|
1363
1445
|
model_size_in_billions: Optional[Union[int, str, float]]
|
|
@@ -1396,6 +1478,14 @@ class AsyncClient:
|
|
|
1396
1478
|
enable_thinking: Optional[bool]
|
|
1397
1479
|
Enable or disable thinking mode for hybrid reasoning LLMs (e.g., Qwen3). None uses
|
|
1398
1480
|
the model default.
|
|
1481
|
+
enable_virtual_env: Optional[bool]
|
|
1482
|
+
If enable virtual env.
|
|
1483
|
+
virtual_env_packages: Optional[List[str]]
|
|
1484
|
+
Packages to specify in virtual env, can be used to override builtin packages in virtual env.
|
|
1485
|
+
virtual_env_find_links: Optional[List[str]]
|
|
1486
|
+
Worker-local wheel directories to use when installing virtual env packages.
|
|
1487
|
+
envs: Optional[Dict[str, str]]
|
|
1488
|
+
Environment variables to pass when launching model.
|
|
1399
1489
|
**kwargs:
|
|
1400
1490
|
Any other parameters been specified. e.g. multimodal_projector for multimodal inference with the llama.cpp backend.
|
|
1401
1491
|
|
|
@@ -1430,6 +1520,10 @@ class AsyncClient:
|
|
|
1430
1520
|
"replica_config": replica_config,
|
|
1431
1521
|
"model_path": model_path,
|
|
1432
1522
|
"enable_thinking": enable_thinking,
|
|
1523
|
+
"enable_virtual_env": enable_virtual_env,
|
|
1524
|
+
"virtual_env_packages": virtual_env_packages,
|
|
1525
|
+
"virtual_env_find_links": virtual_env_find_links,
|
|
1526
|
+
"envs": envs,
|
|
1433
1527
|
}
|
|
1434
1528
|
|
|
1435
1529
|
wait_ready = kwargs.pop("wait_ready", True)
|
|
@@ -1524,15 +1618,18 @@ class AsyncClient:
|
|
|
1524
1618
|
async def add_model_replica(
|
|
1525
1619
|
self,
|
|
1526
1620
|
model_uid: str,
|
|
1527
|
-
replica_config: Optional[dict] = None,
|
|
1621
|
+
replica_config: Optional[Union[dict, List[dict]]] = None,
|
|
1622
|
+
replica: int = 1,
|
|
1623
|
+
model_engine: Optional[str] = None,
|
|
1624
|
+
n_gpu: Optional[Union[int, str]] = None,
|
|
1528
1625
|
) -> dict:
|
|
1529
|
-
"""Add
|
|
1626
|
+
"""Add one or more replicas to a running model (scale-up).
|
|
1530
1627
|
|
|
1531
1628
|
Parameters
|
|
1532
1629
|
----------
|
|
1533
1630
|
model_uid : str
|
|
1534
1631
|
The UID of the running model to extend.
|
|
1535
|
-
replica_config : Optional[dict]
|
|
1632
|
+
replica_config : Optional[Union[dict, List[dict]]]
|
|
1536
1633
|
Optional single-device placement config, e.g.::
|
|
1537
1634
|
|
|
1538
1635
|
{
|
|
@@ -1543,16 +1640,29 @@ class AsyncClient:
|
|
|
1543
1640
|
}
|
|
1544
1641
|
|
|
1545
1642
|
Omit to let the supervisor auto-select a worker and GPU.
|
|
1643
|
+
replica : int
|
|
1644
|
+
Number of replicas to add, default is 1.
|
|
1645
|
+
model_engine : Optional[str]
|
|
1646
|
+
Override the model engine for the new replicas only.
|
|
1647
|
+
n_gpu : Optional[Union[int, str]]
|
|
1648
|
+
Override GPU usage for the new replicas. Use 0 for CPU or ``"auto"``.
|
|
1546
1649
|
|
|
1547
1650
|
Returns
|
|
1548
1651
|
-------
|
|
1549
1652
|
dict
|
|
1550
|
-
``{"
|
|
1653
|
+
A single-replica result, or ``{"replica": int, "replicas": list}``
|
|
1654
|
+
when more than one replica is requested.
|
|
1551
1655
|
"""
|
|
1552
1656
|
url = f"{self.base_url}/v1/models/{model_uid}/replicas"
|
|
1553
1657
|
payload: Dict[str, Any] = {}
|
|
1554
1658
|
if replica_config is not None:
|
|
1555
1659
|
payload["replica_config"] = replica_config
|
|
1660
|
+
if replica != 1:
|
|
1661
|
+
payload["replica"] = replica
|
|
1662
|
+
if model_engine is not None:
|
|
1663
|
+
payload["model_engine"] = model_engine
|
|
1664
|
+
if n_gpu is not None:
|
|
1665
|
+
payload["n_gpu"] = n_gpu
|
|
1556
1666
|
response = await self.session.post(url, json=payload, headers=self._headers)
|
|
1557
1667
|
if response.status != 200:
|
|
1558
1668
|
raise RuntimeError(
|
|
@@ -1715,6 +1825,10 @@ class AsyncClient:
|
|
|
1715
1825
|
return AsyncRESTfulVideoModelHandle(
|
|
1716
1826
|
model_uid, self.base_url, auth_headers=self._headers
|
|
1717
1827
|
)
|
|
1828
|
+
elif desc["model_type"] == "world":
|
|
1829
|
+
return AsyncRESTfulWorldModelHandle(
|
|
1830
|
+
model_uid, self.base_url, auth_headers=self._headers
|
|
1831
|
+
)
|
|
1718
1832
|
elif desc["model_type"] == "flexible":
|
|
1719
1833
|
return AsyncRESTfulFlexibleModelHandle(
|
|
1720
1834
|
model_uid, self.base_url, auth_headers=self._headers
|
|
@@ -17,7 +17,11 @@ from urllib.parse import quote
|
|
|
17
17
|
|
|
18
18
|
import requests
|
|
19
19
|
|
|
20
|
-
from ..common import
|
|
20
|
+
from ..common import (
|
|
21
|
+
convert_float_to_int_or_str,
|
|
22
|
+
encode_world_reference,
|
|
23
|
+
streaming_response_iterator,
|
|
24
|
+
)
|
|
21
25
|
|
|
22
26
|
if TYPE_CHECKING:
|
|
23
27
|
from ...types import (
|
|
@@ -72,15 +76,28 @@ class RESTfulModelHandle:
|
|
|
72
76
|
|
|
73
77
|
|
|
74
78
|
class RESTfulEmbeddingModelHandle(RESTfulModelHandle):
|
|
75
|
-
def create_embedding(
|
|
79
|
+
def create_embedding(
|
|
80
|
+
self,
|
|
81
|
+
input: Union[
|
|
82
|
+
str,
|
|
83
|
+
List[str],
|
|
84
|
+
List[int],
|
|
85
|
+
List[List[int]],
|
|
86
|
+
Dict[str, Any],
|
|
87
|
+
List[Dict[str, Any]],
|
|
88
|
+
List[Union[str, Dict[str, Any]]],
|
|
89
|
+
],
|
|
90
|
+
**kwargs,
|
|
91
|
+
) -> "Embedding":
|
|
76
92
|
"""
|
|
77
93
|
Create an Embedding from user input via RESTful APIs.
|
|
78
94
|
|
|
79
95
|
Parameters
|
|
80
96
|
----------
|
|
81
|
-
input: Union[str, List[str]]
|
|
82
|
-
|
|
83
|
-
|
|
97
|
+
input: Union[str, List[str], List[int], List[List[int]], Dict[str, Any],
|
|
98
|
+
List[Dict[str, Any]], List[Union[str, Dict[str, Any]]]]
|
|
99
|
+
Text, token IDs, or multimodal input. Multimodal dictionaries may
|
|
100
|
+
contain text, image, video, or interleaved role/content messages.
|
|
84
101
|
|
|
85
102
|
Returns
|
|
86
103
|
-------
|
|
@@ -149,8 +166,8 @@ class RESTfulEmbeddingModelHandle(RESTfulModelHandle):
|
|
|
149
166
|
class RESTfulRerankModelHandle(RESTfulModelHandle):
|
|
150
167
|
def rerank(
|
|
151
168
|
self,
|
|
152
|
-
documents: List[str],
|
|
153
|
-
query: str,
|
|
169
|
+
documents: List[Union[str, Dict[str, Any]]],
|
|
170
|
+
query: Union[str, Dict[str, Any]],
|
|
154
171
|
top_n: Optional[int] = None,
|
|
155
172
|
max_chunks_per_doc: Optional[int] = None,
|
|
156
173
|
return_documents: Optional[bool] = None,
|
|
@@ -162,10 +179,10 @@ class RESTfulRerankModelHandle(RESTfulModelHandle):
|
|
|
162
179
|
|
|
163
180
|
Parameters
|
|
164
181
|
----------
|
|
165
|
-
query: str
|
|
166
|
-
|
|
167
|
-
documents: List[str]
|
|
168
|
-
|
|
182
|
+
query: Union[str, Dict[str, Any]]
|
|
183
|
+
Text or multimodal query.
|
|
184
|
+
documents: List[Union[str, Dict[str, Any]]]
|
|
185
|
+
Text or multimodal documents to rerank.
|
|
169
186
|
top_n: int
|
|
170
187
|
The number of results to return, defaults to returning all results
|
|
171
188
|
max_chunks_per_doc: int
|
|
@@ -710,6 +727,47 @@ class RESTfulVideoModelHandle(RESTfulModelHandle):
|
|
|
710
727
|
return response_data
|
|
711
728
|
|
|
712
729
|
|
|
730
|
+
class RESTfulWorldModelHandle(RESTfulModelHandle):
|
|
731
|
+
def generate(
|
|
732
|
+
self,
|
|
733
|
+
prompt: str,
|
|
734
|
+
image: Optional[Union[str, bytes]] = None,
|
|
735
|
+
video: Optional[Union[str, bytes]] = None,
|
|
736
|
+
generation_config: Optional[Dict[str, Any]] = None,
|
|
737
|
+
**model_kwargs,
|
|
738
|
+
) -> "VideoList":
|
|
739
|
+
"""Generate a world video from text and an optional image or video."""
|
|
740
|
+
if image is not None and video is not None:
|
|
741
|
+
raise ValueError("Only one of image and video may be provided")
|
|
742
|
+
request_body = {
|
|
743
|
+
"model": self._model_uid,
|
|
744
|
+
"prompt": prompt,
|
|
745
|
+
"image": (
|
|
746
|
+
encode_world_reference(image, "image/png")
|
|
747
|
+
if image is not None
|
|
748
|
+
else None
|
|
749
|
+
),
|
|
750
|
+
"video": (
|
|
751
|
+
encode_world_reference(video, "video/mp4")
|
|
752
|
+
if video is not None
|
|
753
|
+
else None
|
|
754
|
+
),
|
|
755
|
+
"generation_config": generation_config or {},
|
|
756
|
+
"extra_body": model_kwargs,
|
|
757
|
+
}
|
|
758
|
+
response = self.session.post(
|
|
759
|
+
f"{self._base_url}/v1/worlds/generations",
|
|
760
|
+
json=request_body,
|
|
761
|
+
headers=self.auth_headers,
|
|
762
|
+
)
|
|
763
|
+
if response.status_code != 200:
|
|
764
|
+
raise RuntimeError(
|
|
765
|
+
"Failed to generate the world, detail: "
|
|
766
|
+
f"{_get_error_string(response)}"
|
|
767
|
+
)
|
|
768
|
+
return response.json()
|
|
769
|
+
|
|
770
|
+
|
|
713
771
|
class RESTfulGenerateModelHandle(RESTfulModelHandle):
|
|
714
772
|
def generate(
|
|
715
773
|
self,
|
|
@@ -1232,6 +1290,7 @@ class Client:
|
|
|
1232
1290
|
enable_virtual_env: Optional[bool] = None,
|
|
1233
1291
|
virtual_env_packages: Optional[List[str]] = None,
|
|
1234
1292
|
envs: Optional[Dict[str, str]] = None,
|
|
1293
|
+
virtual_env_find_links: Optional[List[str]] = None,
|
|
1235
1294
|
**kwargs,
|
|
1236
1295
|
) -> str:
|
|
1237
1296
|
"""
|
|
@@ -1244,7 +1303,7 @@ class Client:
|
|
|
1244
1303
|
model_type: str
|
|
1245
1304
|
type of model.
|
|
1246
1305
|
model_engine: Optional[str]
|
|
1247
|
-
Specify the inference engine
|
|
1306
|
+
Specify the inference engine to use when launching the model.
|
|
1248
1307
|
model_uid: str
|
|
1249
1308
|
UID of model, auto generate a UUID if is None.
|
|
1250
1309
|
model_size_in_billions: Optional[Union[int, str, float]]
|
|
@@ -1287,6 +1346,8 @@ class Client:
|
|
|
1287
1346
|
If enable virtual env.
|
|
1288
1347
|
virtual_env_packages: Optional[List[str]]
|
|
1289
1348
|
Packages to specify in virtual env, can be used to override builtin packages in virtual env.
|
|
1349
|
+
virtual_env_find_links: Optional[List[str]]
|
|
1350
|
+
Worker-local wheel directories to use when installing virtual env packages.
|
|
1290
1351
|
envs: Optional[Dict[str, str]]
|
|
1291
1352
|
Environment variables to pass when launching model.
|
|
1292
1353
|
|
|
@@ -1326,6 +1387,7 @@ class Client:
|
|
|
1326
1387
|
"enable_thinking": enable_thinking,
|
|
1327
1388
|
"enable_virtual_env": enable_virtual_env,
|
|
1328
1389
|
"virtual_env_packages": virtual_env_packages,
|
|
1390
|
+
"virtual_env_find_links": virtual_env_find_links,
|
|
1329
1391
|
"envs": envs,
|
|
1330
1392
|
}
|
|
1331
1393
|
|
|
@@ -1411,15 +1473,18 @@ class Client:
|
|
|
1411
1473
|
def add_model_replica(
|
|
1412
1474
|
self,
|
|
1413
1475
|
model_uid: str,
|
|
1414
|
-
replica_config: Optional[dict] = None,
|
|
1476
|
+
replica_config: Optional[Union[dict, List[dict]]] = None,
|
|
1477
|
+
replica: int = 1,
|
|
1478
|
+
model_engine: Optional[str] = None,
|
|
1479
|
+
n_gpu: Optional[Union[int, str]] = None,
|
|
1415
1480
|
) -> dict:
|
|
1416
|
-
"""Add
|
|
1481
|
+
"""Add one or more replicas to a running model (scale-up).
|
|
1417
1482
|
|
|
1418
1483
|
Parameters
|
|
1419
1484
|
----------
|
|
1420
1485
|
model_uid : str
|
|
1421
1486
|
The UID of the running model to extend.
|
|
1422
|
-
replica_config : Optional[dict]
|
|
1487
|
+
replica_config : Optional[Union[dict, List[dict]]]
|
|
1423
1488
|
Optional single-device placement config, e.g.::
|
|
1424
1489
|
|
|
1425
1490
|
{
|
|
@@ -1430,16 +1495,29 @@ class Client:
|
|
|
1430
1495
|
}
|
|
1431
1496
|
|
|
1432
1497
|
Omit to let the supervisor auto-select a worker and GPU.
|
|
1498
|
+
replica : int
|
|
1499
|
+
Number of replicas to add, default is 1.
|
|
1500
|
+
model_engine : Optional[str]
|
|
1501
|
+
Override the model engine for the new replicas only.
|
|
1502
|
+
n_gpu : Optional[Union[int, str]]
|
|
1503
|
+
Override GPU usage for the new replicas. Use 0 for CPU or ``"auto"``.
|
|
1433
1504
|
|
|
1434
1505
|
Returns
|
|
1435
1506
|
-------
|
|
1436
1507
|
dict
|
|
1437
|
-
``{"
|
|
1508
|
+
A single-replica result, or ``{"replica": int, "replicas": list}``
|
|
1509
|
+
when more than one replica is requested.
|
|
1438
1510
|
"""
|
|
1439
1511
|
url = f"{self.base_url}/v1/models/{model_uid}/replicas"
|
|
1440
1512
|
payload: Dict[str, Any] = {}
|
|
1441
1513
|
if replica_config is not None:
|
|
1442
1514
|
payload["replica_config"] = replica_config
|
|
1515
|
+
if replica != 1:
|
|
1516
|
+
payload["replica"] = replica
|
|
1517
|
+
if model_engine is not None:
|
|
1518
|
+
payload["model_engine"] = model_engine
|
|
1519
|
+
if n_gpu is not None:
|
|
1520
|
+
payload["n_gpu"] = n_gpu
|
|
1443
1521
|
response = self.session.post(url, json=payload, headers=self._headers)
|
|
1444
1522
|
if response.status_code != 200:
|
|
1445
1523
|
raise RuntimeError(
|
|
@@ -1594,6 +1672,10 @@ class Client:
|
|
|
1594
1672
|
return RESTfulVideoModelHandle(
|
|
1595
1673
|
model_uid, self.base_url, auth_headers=self._headers
|
|
1596
1674
|
)
|
|
1675
|
+
elif desc["model_type"] == "world":
|
|
1676
|
+
return RESTfulWorldModelHandle(
|
|
1677
|
+
model_uid, self.base_url, auth_headers=self._headers
|
|
1678
|
+
)
|
|
1597
1679
|
elif desc["model_type"] == "flexible":
|
|
1598
1680
|
return RESTfulFlexibleModelHandle(
|
|
1599
1681
|
model_uid, self.base_url, auth_headers=self._headers
|
|
@@ -201,6 +201,18 @@ class ToolCalls(TypedDict):
|
|
|
201
201
|
function: ToolCallFunction
|
|
202
202
|
|
|
203
203
|
|
|
204
|
+
class ToolCallDeltaFunction(TypedDict, total=False):
|
|
205
|
+
name: str
|
|
206
|
+
arguments: str
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class ToolCallDelta(TypedDict):
|
|
210
|
+
index: int
|
|
211
|
+
id: NotRequired[str]
|
|
212
|
+
type: NotRequired[Literal["function"]]
|
|
213
|
+
function: NotRequired[ToolCallDeltaFunction]
|
|
214
|
+
|
|
215
|
+
|
|
204
216
|
class CompletionChoice(TypedDict):
|
|
205
217
|
text: NotRequired[str]
|
|
206
218
|
index: int
|
|
@@ -209,10 +221,15 @@ class CompletionChoice(TypedDict):
|
|
|
209
221
|
tool_calls: NotRequired[List[ToolCalls]]
|
|
210
222
|
|
|
211
223
|
|
|
224
|
+
class PromptTokensDetails(TypedDict):
|
|
225
|
+
cached_tokens: int
|
|
226
|
+
|
|
227
|
+
|
|
212
228
|
class CompletionUsage(TypedDict):
|
|
213
229
|
prompt_tokens: int
|
|
214
230
|
completion_tokens: int
|
|
215
231
|
total_tokens: int
|
|
232
|
+
prompt_tokens_details: NotRequired[PromptTokensDetails]
|
|
216
233
|
|
|
217
234
|
|
|
218
235
|
class CompletionChunk(TypedDict):
|
|
@@ -269,7 +286,7 @@ class ChatCompletionChunkDelta(TypedDict):
|
|
|
269
286
|
role: NotRequired[str]
|
|
270
287
|
reasoning_content: NotRequired[Union[str, None]]
|
|
271
288
|
content: NotRequired[Union[str, None]]
|
|
272
|
-
tool_calls: NotRequired[List[
|
|
289
|
+
tool_calls: NotRequired[List[ToolCallDelta]]
|
|
273
290
|
|
|
274
291
|
|
|
275
292
|
class ChatCompletionChunkChoice(TypedDict):
|
|
@@ -472,12 +489,14 @@ class CreateChatCompletion( # type: ignore
|
|
|
472
489
|
|
|
473
490
|
|
|
474
491
|
class LoRA:
|
|
475
|
-
def __init__(self, lora_name: str, local_path: str):
|
|
492
|
+
def __init__(self, lora_name: str, local_path: str, lora_scale: float = 1.0):
|
|
493
|
+
self.lora_scale = lora_scale
|
|
476
494
|
self.lora_name = lora_name
|
|
477
495
|
self.local_path = local_path
|
|
478
496
|
|
|
479
497
|
def to_dict(self):
|
|
480
498
|
return {
|
|
499
|
+
**({"lora_scale": self.lora_scale} if self.lora_scale != 1.0 else {}),
|
|
481
500
|
"lora_name": self.lora_name,
|
|
482
501
|
"local_path": self.local_path,
|
|
483
502
|
}
|
|
@@ -485,6 +504,7 @@ class LoRA:
|
|
|
485
504
|
@classmethod
|
|
486
505
|
def from_dict(cls, data: Dict):
|
|
487
506
|
return cls(
|
|
507
|
+
lora_scale=data.get("lora_scale", 1.0),
|
|
488
508
|
lora_name=data["lora_name"],
|
|
489
509
|
local_path=data["local_path"],
|
|
490
510
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xinference-client
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
4
4
|
Summary: Client for Xinference
|
|
5
5
|
Home-page: https://github.com/xorbitsai/inference-client
|
|
6
6
|
Author: Qin Xuye
|
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
20
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
20
21
|
Classifier: Topic :: Software Development :: Libraries
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
@@ -5,6 +5,8 @@ pyproject.toml
|
|
|
5
5
|
setup.cfg
|
|
6
6
|
setup.py
|
|
7
7
|
versioneer.py
|
|
8
|
+
tests/test_async_client.py
|
|
9
|
+
tests/test_async_client_cleanup.py
|
|
8
10
|
third_party/inference/xinference/thirdparty/matcha/utils/monotonic_align/core.pyx
|
|
9
11
|
xinference_client/__init__.py
|
|
10
12
|
xinference_client/_compat.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client/client/restful/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xinference_client-3.2.0 → xinference_client-3.4.0}/xinference_client.egg-info/top_level.txt
RENAMED
|
File without changes
|