lollms-client 1.6.4__py3-none-any.whl → 1.6.5__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.
Potentially problematic release.
This version of lollms-client might be problematic. Click here for more details.
- lollms_client/__init__.py +1 -1
- lollms_client/lollms_core.py +3 -1
- lollms_client/tti_bindings/diffusers/__init__.py +104 -57
- lollms_client/tti_bindings/diffusers/server/main.py +264 -112
- lollms_client/tti_bindings/gemini/__init__.py +179 -239
- {lollms_client-1.6.4.dist-info → lollms_client-1.6.5.dist-info}/METADATA +1 -1
- {lollms_client-1.6.4.dist-info → lollms_client-1.6.5.dist-info}/RECORD +10 -10
- {lollms_client-1.6.4.dist-info → lollms_client-1.6.5.dist-info}/WHEEL +0 -0
- {lollms_client-1.6.4.dist-info → lollms_client-1.6.5.dist-info}/licenses/LICENSE +0 -0
- {lollms_client-1.6.4.dist-info → lollms_client-1.6.5.dist-info}/top_level.txt +0 -0
lollms_client/__init__.py
CHANGED
|
@@ -8,7 +8,7 @@ from lollms_client.lollms_utilities import PromptReshaper # Keep general utiliti
|
|
|
8
8
|
from lollms_client.lollms_mcp_binding import LollmsMCPBinding, LollmsMCPBindingManager
|
|
9
9
|
from lollms_client.lollms_llm_binding import LollmsLLMBindingManager
|
|
10
10
|
|
|
11
|
-
__version__ = "1.6.
|
|
11
|
+
__version__ = "1.6.5" # Updated version
|
|
12
12
|
|
|
13
13
|
# Optionally, you could define __all__ if you want to be explicit about exports
|
|
14
14
|
__all__ = [
|
lollms_client/lollms_core.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lollms_client/lollms_core.py
|
|
2
|
+
# author: ParisNeo
|
|
3
|
+
# description: LollmsClient definition file
|
|
2
4
|
import requests
|
|
3
5
|
from ascii_colors import ASCIIColors, trace_exception
|
|
4
6
|
from lollms_client.lollms_types import MSG_TYPE, ELF_COMPLETION_FORMAT
|
|
@@ -519,7 +521,7 @@ class LollmsClient():
|
|
|
519
521
|
Union[str, dict]: Generated text or error dictionary if failed.
|
|
520
522
|
"""
|
|
521
523
|
if self.llm:
|
|
522
|
-
|
|
524
|
+
images = [str(image) for image in images] if images else None
|
|
523
525
|
ctx_size = ctx_size if ctx_size is not None else self.llm.default_ctx_size if self.llm.default_ctx_size else None
|
|
524
526
|
if ctx_size is None:
|
|
525
527
|
ctx_size = self.llm.get_ctx_size()
|
|
@@ -44,15 +44,16 @@ class DiffusersBinding(LollmsTTIBinding):
|
|
|
44
44
|
kwargs['model_name'] = kwargs.pop('model')
|
|
45
45
|
|
|
46
46
|
self.config = kwargs
|
|
47
|
-
self.host = kwargs.get("host", "localhost")
|
|
47
|
+
self.host = kwargs.get("host", "localhost")
|
|
48
48
|
self.port = kwargs.get("port", 9630)
|
|
49
49
|
self.auto_start_server = kwargs.get("auto_start_server", True)
|
|
50
50
|
self.server_process = None
|
|
51
51
|
self.base_url = f"http://{self.host}:{self.port}"
|
|
52
52
|
self.binding_root = Path(__file__).parent
|
|
53
53
|
self.server_dir = self.binding_root / "server"
|
|
54
|
-
self.venv_dir = Path("./venv/
|
|
55
|
-
self.models_path = Path(kwargs.get("models_path", "./diffusers_models")).resolve()
|
|
54
|
+
self.venv_dir = Path("./venv/tti_diffusers_venv")
|
|
55
|
+
self.models_path = Path(kwargs.get("models_path", "./data/models/diffusers_models")).resolve()
|
|
56
|
+
self.models_path.mkdir(exist_ok=True, parents=True)
|
|
56
57
|
if self.auto_start_server:
|
|
57
58
|
self.ensure_server_is_running()
|
|
58
59
|
|
|
@@ -66,25 +67,35 @@ class DiffusersBinding(LollmsTTIBinding):
|
|
|
66
67
|
return False
|
|
67
68
|
return False
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
|
|
71
|
+
def ensure_server_is_running(self, continue_if_locked: bool = True):
|
|
70
72
|
"""
|
|
71
73
|
Ensures the Diffusers server is running. If not, it attempts to start it
|
|
72
74
|
in a process-safe manner using a file lock.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
continue_if_locked (bool): If True, return immediately if another process
|
|
78
|
+
already holds the lock.
|
|
73
79
|
"""
|
|
74
80
|
self.server_dir.mkdir(exist_ok=True)
|
|
75
|
-
lock_path = self.
|
|
76
|
-
lock = FileLock(lock_path
|
|
81
|
+
lock_path = self.models_path / "diffusers_server.lock"
|
|
82
|
+
lock = FileLock(lock_path)
|
|
77
83
|
|
|
78
84
|
ASCIIColors.info("Attempting to start or connect to the Diffusers server...")
|
|
79
85
|
try:
|
|
80
|
-
|
|
86
|
+
# Try to acquire lock immediately if continue_if_locked=True
|
|
87
|
+
with lock.acquire(timeout=0 if continue_if_locked else 60):
|
|
81
88
|
if not self.is_server_running():
|
|
82
89
|
ASCIIColors.yellow("Lock acquired. Starting dedicated Diffusers server...")
|
|
83
90
|
self.start_server()
|
|
84
91
|
else:
|
|
85
92
|
ASCIIColors.green("Server was started by another process. Connected successfully.")
|
|
86
93
|
except Timeout:
|
|
87
|
-
|
|
94
|
+
if continue_if_locked:
|
|
95
|
+
ASCIIColors.yellow("Lock held by another process. Skipping server startup and continuing execution.")
|
|
96
|
+
return
|
|
97
|
+
else:
|
|
98
|
+
ASCIIColors.yellow("Could not acquire lock within timeout. Waiting for server to become available...")
|
|
88
99
|
|
|
89
100
|
self._wait_for_server()
|
|
90
101
|
|
|
@@ -97,6 +108,24 @@ class DiffusersBinding(LollmsTTIBinding):
|
|
|
97
108
|
pm_v = pm.PackageManager(venv_path=str(self.venv_dir))
|
|
98
109
|
|
|
99
110
|
# --- PyTorch Installation ---
|
|
111
|
+
ASCIIColors.info(f"Installing server dependencies")
|
|
112
|
+
pm_v.ensure_packages([
|
|
113
|
+
"requests", "uvicorn", "fastapi", "python-multipart", "filelock"
|
|
114
|
+
])
|
|
115
|
+
ASCIIColors.info(f"Installing parisneo libraries")
|
|
116
|
+
pm_v.ensure_packages([
|
|
117
|
+
"ascii_colors","pipmaster"
|
|
118
|
+
])
|
|
119
|
+
ASCIIColors.info(f"Installing misc libraries (numpy, tqdm...)")
|
|
120
|
+
pm_v.ensure_packages([
|
|
121
|
+
"tqdm", "numpy"
|
|
122
|
+
])
|
|
123
|
+
ASCIIColors.info(f"Installing Pillow")
|
|
124
|
+
pm_v.ensure_packages([
|
|
125
|
+
"pillow"
|
|
126
|
+
])
|
|
127
|
+
|
|
128
|
+
ASCIIColors.info(f"Installing pytorch")
|
|
100
129
|
torch_index_url = None
|
|
101
130
|
if sys.platform == "win32":
|
|
102
131
|
try:
|
|
@@ -104,21 +133,27 @@ class DiffusersBinding(LollmsTTIBinding):
|
|
|
104
133
|
result = subprocess.run(["nvidia-smi"], capture_output=True, text=True, check=True)
|
|
105
134
|
ASCIIColors.green("NVIDIA GPU detected. Installing CUDA-enabled PyTorch.")
|
|
106
135
|
# Using a common and stable CUDA version. Adjust if needed.
|
|
107
|
-
torch_index_url = "https://download.pytorch.org/whl/
|
|
136
|
+
torch_index_url = "https://download.pytorch.org/whl/cu128"
|
|
108
137
|
except (FileNotFoundError, subprocess.CalledProcessError):
|
|
109
138
|
ASCIIColors.yellow("`nvidia-smi` not found or failed. Installing standard PyTorch. If you have an NVIDIA GPU, please ensure drivers are installed and in PATH.")
|
|
110
|
-
|
|
139
|
+
|
|
111
140
|
# Base packages including torch. pm.ensure_packages handles verbose output.
|
|
112
141
|
pm_v.ensure_packages(["torch", "torchvision"], index_url=torch_index_url)
|
|
113
142
|
|
|
114
|
-
|
|
115
143
|
# Standard dependencies
|
|
144
|
+
ASCIIColors.info(f"Installing transformers dependencies")
|
|
116
145
|
pm_v.ensure_packages([
|
|
117
|
-
"
|
|
118
|
-
"accelerate", "uvicorn", "fastapi", "python-multipart", "filelock", "ascii_colors"
|
|
146
|
+
"transformers", "safetensors", "accelerate"
|
|
119
147
|
])
|
|
120
|
-
|
|
148
|
+
ASCIIColors.info(f"[Optional] Installing xformers")
|
|
149
|
+
try:
|
|
150
|
+
pm_v.ensure_packages([
|
|
151
|
+
"xformers"
|
|
152
|
+
])
|
|
153
|
+
except:
|
|
154
|
+
pass
|
|
121
155
|
# Git-based diffusers to get the latest version
|
|
156
|
+
ASCIIColors.info(f"Installing diffusers library from github")
|
|
122
157
|
pm_v.ensure_packages([
|
|
123
158
|
{
|
|
124
159
|
"name": "diffusers",
|
|
@@ -127,14 +162,6 @@ class DiffusersBinding(LollmsTTIBinding):
|
|
|
127
162
|
}
|
|
128
163
|
])
|
|
129
164
|
|
|
130
|
-
# XFormers (optional but recommended for NVIDIA)
|
|
131
|
-
if torch_index_url: # Only try to install xformers if CUDA is likely present
|
|
132
|
-
try:
|
|
133
|
-
ASCIIColors.info("Attempting to install xformers for performance optimization...")
|
|
134
|
-
pm_v.ensure_packages(["xformers"], upgrade=True)
|
|
135
|
-
except Exception as e:
|
|
136
|
-
ASCIIColors.warning(f"Could not install xformers. It's optional but recommended for performance on NVIDIA GPUs. Error: {e}")
|
|
137
|
-
|
|
138
165
|
ASCIIColors.green("Server dependencies are satisfied.")
|
|
139
166
|
|
|
140
167
|
def start_server(self):
|
|
@@ -167,7 +194,7 @@ class DiffusersBinding(LollmsTTIBinding):
|
|
|
167
194
|
# Use DETACHED_PROCESS on Windows to allow the server to run independently of the parent process.
|
|
168
195
|
# On Linux/macOS, the process will be daemonized enough to not be killed with the worker.
|
|
169
196
|
creationflags = subprocess.DETACHED_PROCESS if sys.platform == "win32" else 0
|
|
170
|
-
|
|
197
|
+
|
|
171
198
|
self.server_process = subprocess.Popen(command, creationflags=creationflags)
|
|
172
199
|
ASCIIColors.info("Diffusers server process launched in the background.")
|
|
173
200
|
|
|
@@ -191,11 +218,11 @@ class DiffusersBinding(LollmsTTIBinding):
|
|
|
191
218
|
time.sleep(2)
|
|
192
219
|
raise RuntimeError("Failed to connect to the Diffusers server within the specified timeout.")
|
|
193
220
|
|
|
194
|
-
def
|
|
195
|
-
"""Helper to make POST requests
|
|
221
|
+
def _post_json_request(self, endpoint: str, data: Optional[dict] = None) -> requests.Response:
|
|
222
|
+
"""Helper to make POST requests with a JSON body."""
|
|
196
223
|
try:
|
|
197
224
|
url = f"{self.base_url}{endpoint}"
|
|
198
|
-
response = requests.post(url, json=data,
|
|
225
|
+
response = requests.post(url, json=data, timeout=3600) # Long timeout for generation
|
|
199
226
|
response.raise_for_status()
|
|
200
227
|
return response
|
|
201
228
|
except requests.exceptions.RequestException as e:
|
|
@@ -208,6 +235,24 @@ class DiffusersBinding(LollmsTTIBinding):
|
|
|
208
235
|
ASCIIColors.error(f"Server raw response: {e.response.text}")
|
|
209
236
|
raise RuntimeError("Communication with the Diffusers server failed.") from e
|
|
210
237
|
|
|
238
|
+
def _post_multipart_request(self, endpoint: str, data: Optional[dict] = None, files: Optional[list] = None) -> requests.Response:
|
|
239
|
+
"""Helper to make multipart/form-data POST requests for file uploads."""
|
|
240
|
+
try:
|
|
241
|
+
url = f"{self.base_url}{endpoint}"
|
|
242
|
+
response = requests.post(url, data=data, files=files, timeout=3600)
|
|
243
|
+
response.raise_for_status()
|
|
244
|
+
return response
|
|
245
|
+
except requests.exceptions.RequestException as e:
|
|
246
|
+
# (Error handling is the same as above)
|
|
247
|
+
ASCIIColors.error(f"Failed to communicate with Diffusers server at {url}.")
|
|
248
|
+
ASCIIColors.error(f"Error details: {e}")
|
|
249
|
+
if hasattr(e, 'response') and e.response:
|
|
250
|
+
try:
|
|
251
|
+
ASCIIColors.error(f"Server response: {e.response.json().get('detail', e.response.text)}")
|
|
252
|
+
except json.JSONDecodeError:
|
|
253
|
+
ASCIIColors.error(f"Server raw response: {e.response.text}")
|
|
254
|
+
raise RuntimeError("Communication with the Diffusers server failed.") from e
|
|
255
|
+
|
|
211
256
|
def _get_request(self, endpoint: str, params: Optional[dict] = None) -> requests.Response:
|
|
212
257
|
"""Helper to make GET requests to the server."""
|
|
213
258
|
try:
|
|
@@ -222,13 +267,14 @@ class DiffusersBinding(LollmsTTIBinding):
|
|
|
222
267
|
def unload_model(self):
|
|
223
268
|
ASCIIColors.info("Requesting server to unload the current model...")
|
|
224
269
|
try:
|
|
225
|
-
self.
|
|
270
|
+
self._post_json_request("/unload_model")
|
|
226
271
|
except Exception as e:
|
|
227
272
|
ASCIIColors.warning(f"Could not send unload request to server: {e}")
|
|
228
273
|
pass
|
|
229
274
|
|
|
230
275
|
def generate_image(self, prompt: str, negative_prompt: str = "", **kwargs) -> bytes:
|
|
231
|
-
|
|
276
|
+
# This is a pure JSON request
|
|
277
|
+
response = self._post_json_request("/generate_image", data={
|
|
232
278
|
"prompt": prompt,
|
|
233
279
|
"negative_prompt": negative_prompt,
|
|
234
280
|
"params": kwargs
|
|
@@ -236,55 +282,56 @@ class DiffusersBinding(LollmsTTIBinding):
|
|
|
236
282
|
return response.content
|
|
237
283
|
|
|
238
284
|
def edit_image(self, images: Union[str, List[str], "Image.Image", List["Image.Image"]], prompt: str, **kwargs) -> bytes:
|
|
239
|
-
|
|
240
|
-
image_paths = []
|
|
241
|
-
|
|
285
|
+
images_b64 = []
|
|
242
286
|
if not isinstance(images, list):
|
|
243
287
|
images = [images]
|
|
244
288
|
|
|
245
|
-
|
|
246
|
-
|
|
289
|
+
|
|
290
|
+
for img in images:
|
|
291
|
+
# Case 1: Input is a PIL Image object
|
|
292
|
+
if hasattr(img, 'save'):
|
|
247
293
|
buffer = BytesIO()
|
|
248
294
|
img.save(buffer, format="PNG")
|
|
249
|
-
buffer.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
elif isinstance(img, str): # Handle base64 strings
|
|
295
|
+
b64_string = base64.b64encode(buffer.getvalue()).decode('utf-8')
|
|
296
|
+
images_b64.append(b64_string)
|
|
297
|
+
|
|
298
|
+
# Case 2: Input is a string (could be path or already base64)
|
|
299
|
+
elif isinstance(img, str):
|
|
255
300
|
try:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
img_bytes = base64.b64decode(b64_data)
|
|
260
|
-
files[f"image_{i}"] = (f"image_{i}.png", img_bytes, "image/png")
|
|
301
|
+
b64_string = img.split(";base64,")[1] if ";base64," in img else img
|
|
302
|
+
base64.b64decode(b64_string) # Validate
|
|
303
|
+
images_b64.append(b64_string)
|
|
261
304
|
except Exception:
|
|
262
|
-
|
|
305
|
+
ASCIIColors.warning(f"Warning: A string input was not a valid file path or base64. Skipping.")
|
|
263
306
|
else:
|
|
264
|
-
|
|
307
|
+
raise ValueError(f"Unsupported image type in edit_image: {type(img)}")
|
|
308
|
+
if not images_b64:
|
|
309
|
+
raise ValueError("No valid images were provided to the edit_image function.")
|
|
310
|
+
|
|
311
|
+
# Translate "mask" to "mask_image" for server compatibility
|
|
312
|
+
if "mask" in kwargs and kwargs["mask"]:
|
|
313
|
+
kwargs["mask_image"] = kwargs.pop("mask")
|
|
265
314
|
|
|
266
|
-
|
|
315
|
+
json_payload = {
|
|
267
316
|
"prompt": prompt,
|
|
268
|
-
"
|
|
317
|
+
"images_b64": images_b64,
|
|
269
318
|
"params": kwargs
|
|
270
319
|
}
|
|
271
|
-
|
|
272
|
-
# FastAPI needs separate form fields for json and files
|
|
273
|
-
response = self._post_request("/edit_image", data={"json_payload": json.dumps(data_payload)}, files=files)
|
|
320
|
+
response = self._post_json_request("/edit_image", data=json_payload)
|
|
274
321
|
return response.content
|
|
275
|
-
|
|
322
|
+
|
|
276
323
|
def list_models(self) -> List[Dict[str, Any]]:
|
|
277
324
|
return self._get_request("/list_models").json()
|
|
278
325
|
|
|
279
326
|
def list_local_models(self) -> List[str]:
|
|
280
327
|
return self._get_request("/list_local_models").json()
|
|
281
|
-
|
|
328
|
+
|
|
282
329
|
def list_available_models(self) -> List[str]:
|
|
283
330
|
return self._get_request("/list_available_models").json()
|
|
284
|
-
|
|
331
|
+
|
|
285
332
|
def list_services(self, **kwargs) -> List[Dict[str, str]]:
|
|
286
333
|
return self._get_request("/list_models").json()
|
|
287
|
-
|
|
334
|
+
|
|
288
335
|
def get_settings(self, **kwargs) -> List[Dict[str, Any]]:
|
|
289
336
|
# The server holds the state, so we fetch it.
|
|
290
337
|
return self._get_request("/get_settings").json()
|
|
@@ -292,7 +339,7 @@ class DiffusersBinding(LollmsTTIBinding):
|
|
|
292
339
|
def set_settings(self, settings: Union[Dict[str, Any], List[Dict[str, Any]]], **kwargs) -> bool:
|
|
293
340
|
# Normalize settings from list of dicts to a single dict if needed
|
|
294
341
|
parsed_settings = settings if isinstance(settings, dict) else {s["name"]: s["value"] for s in settings if "name" in s and "value" in s}
|
|
295
|
-
response = self.
|
|
342
|
+
response = self._post_json_request("/set_settings", data=parsed_settings)
|
|
296
343
|
return response.json().get("success", False)
|
|
297
344
|
|
|
298
345
|
def ps(self) -> List[dict]:
|
|
@@ -304,4 +351,4 @@ class DiffusersBinding(LollmsTTIBinding):
|
|
|
304
351
|
def __del__(self):
|
|
305
352
|
# The client destructor does not stop the server,
|
|
306
353
|
# as it is a shared resource for all worker processes.
|
|
307
|
-
pass
|
|
354
|
+
pass
|