lollms-client 0.17.1__py3-none-any.whl → 0.19.0__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.

Files changed (25) hide show
  1. examples/function_calling_with_local_custom_mcp.py +250 -0
  2. examples/local_mcp.py +171 -0
  3. examples/text_2_image.py +8 -3
  4. examples/text_2_image_diffusers.py +274 -0
  5. lollms_client/__init__.py +7 -6
  6. lollms_client/llm_bindings/llamacpp/__init__.py +8 -8
  7. lollms_client/lollms_core.py +345 -10
  8. lollms_client/lollms_mcp_binding.py +198 -0
  9. lollms_client/mcp_bindings/local_mcp/__init__.py +311 -0
  10. lollms_client/mcp_bindings/local_mcp/default_tools/file_writer/file_writer.py +74 -0
  11. lollms_client/mcp_bindings/local_mcp/default_tools/generate_image_from_prompt/generate_image_from_prompt.py +195 -0
  12. lollms_client/mcp_bindings/local_mcp/default_tools/internet_search/internet_search.py +107 -0
  13. lollms_client/mcp_bindings/local_mcp/default_tools/python_interpreter/python_interpreter.py +141 -0
  14. lollms_client/stt_bindings/whisper/__init__.py +1 -1
  15. lollms_client/tti_bindings/dalle/__init__.py +433 -0
  16. lollms_client/tti_bindings/diffusers/__init__.py +692 -0
  17. lollms_client/tti_bindings/gemini/__init__.py +0 -0
  18. {lollms_client-0.17.1.dist-info → lollms_client-0.19.0.dist-info}/METADATA +1 -1
  19. {lollms_client-0.17.1.dist-info → lollms_client-0.19.0.dist-info}/RECORD +22 -13
  20. examples/function_call/functions_call_with images.py +0 -52
  21. lollms_client/lollms_functions.py +0 -72
  22. lollms_client/lollms_tasks.py +0 -691
  23. {lollms_client-0.17.1.dist-info → lollms_client-0.19.0.dist-info}/WHEEL +0 -0
  24. {lollms_client-0.17.1.dist-info → lollms_client-0.19.0.dist-info}/licenses/LICENSE +0 -0
  25. {lollms_client-0.17.1.dist-info → lollms_client-0.19.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,433 @@
1
+ # lollms_client/tti_bindings/dalle/__init__.py
2
+ import requests
3
+ import base64
4
+ from lollms_client.lollms_tti_binding import LollmsTTIBinding
5
+ from typing import Optional, List, Dict, Any, Union
6
+ from ascii_colors import trace_exception, ASCIIColors
7
+ import json # For json.JSONDecodeError in error handling, and general JSON operations
8
+ import os # Added for environment variable access
9
+
10
+ # Defines the binding name for the manager
11
+ BindingName = "DalleTTIBinding_Impl"
12
+
13
+ # DALL-E specific constants
14
+ DALLE_API_HOST = "https://api.openai.com/v1"
15
+ OPENAI_API_KEY_ENV_VAR = "OPENAI_API_KEY" # Environment variable name
16
+
17
+ # Supported models and their properties
18
+ DALLE_MODELS = {
19
+ "dall-e-2": {
20
+ "sizes": ["256x256", "512x512", "1024x1024"],
21
+ "default_size": "1024x1024",
22
+ "supports_quality": False,
23
+ "supports_style": False,
24
+ "max_prompt_length": 1000 # Characters
25
+ },
26
+ "dall-e-3": {
27
+ "sizes": ["1024x1024", "1792x1024", "1024x1792"],
28
+ "default_size": "1024x1024",
29
+ "qualities": ["standard", "hd"],
30
+ "default_quality": "standard",
31
+ "styles": ["vivid", "natural"],
32
+ "default_style": "vivid",
33
+ "supports_quality": True,
34
+ "supports_style": True,
35
+ "max_prompt_length": 4000 # Characters
36
+ }
37
+ }
38
+
39
+ class DalleTTIBinding_Impl(LollmsTTIBinding):
40
+ """
41
+ Concrete implementation of LollmsTTIBinding for OpenAI's DALL-E API.
42
+ """
43
+
44
+ def __init__(self,
45
+ api_key: Optional[str] = None, # Can be None to check env var
46
+ model_name: str = "dall-e-3", # Default to DALL-E 3
47
+ default_size: Optional[str] = None, # e.g. "1024x1024"
48
+ default_quality: Optional[str] = None, # "standard" or "hd" (DALL-E 3)
49
+ default_style: Optional[str] = None, # "vivid" or "natural" (DALL-E 3)
50
+ host_address: str = DALLE_API_HOST, # OpenAI API host
51
+ verify_ssl_certificate: bool = True,
52
+ **kwargs # To catch any other lollms_client specific params like service_key/client_id
53
+ ):
54
+ """
55
+ Initialize the DALL-E TTI binding.
56
+
57
+ Args:
58
+ api_key (Optional[str]): OpenAI API key. If None or empty, attempts to read
59
+ from the OPENAI_API_KEY environment variable.
60
+ model_name (str): Name of the DALL-E model to use (e.g., "dall-e-3", "dall-e-2").
61
+ default_size (Optional[str]): Default image size (e.g., "1024x1024").
62
+ If None, uses model's default.
63
+ default_quality (Optional[str]): Default image quality for DALL-E 3 ("standard", "hd").
64
+ If None, uses model's default if applicable.
65
+ default_style (Optional[str]): Default image style for DALL-E 3 ("vivid", "natural").
66
+ If None, uses model's default if applicable.
67
+ host_address (str): The API host address. Defaults to OpenAI's public API.
68
+ verify_ssl_certificate (bool): Whether to verify SSL certificates.
69
+ **kwargs: Catches other potential parameters like 'service_key' or 'client_id'.
70
+ """
71
+ super().__init__(binding_name="dalle")
72
+
73
+ resolved_api_key = api_key
74
+ if not resolved_api_key:
75
+ ASCIIColors.info(f"API key not provided directly, checking environment variable '{OPENAI_API_KEY_ENV_VAR}'...")
76
+ resolved_api_key = os.environ.get(OPENAI_API_KEY_ENV_VAR)
77
+
78
+ if not resolved_api_key:
79
+ raise ValueError(f"OpenAI API key is required. Provide it directly or set the '{OPENAI_API_KEY_ENV_VAR}' environment variable.")
80
+
81
+ self.api_key = resolved_api_key
82
+ self.host_address = host_address
83
+ self.verify_ssl_certificate = verify_ssl_certificate
84
+
85
+ if model_name not in DALLE_MODELS:
86
+ raise ValueError(f"Unsupported DALL-E model: {model_name}. Supported models: {list(DALLE_MODELS.keys())}")
87
+ self.model_name = model_name
88
+
89
+ model_props = DALLE_MODELS[self.model_name]
90
+
91
+ # Set defaults from model_props, overridden by user-provided defaults
92
+ self.current_size = default_size or model_props["default_size"]
93
+ if self.current_size not in model_props["sizes"]:
94
+ raise ValueError(f"Unsupported size '{self.current_size}' for model '{self.model_name}'. Supported sizes: {model_props['sizes']}")
95
+
96
+ if model_props["supports_quality"]:
97
+ self.current_quality = default_quality or model_props["default_quality"]
98
+ if self.current_quality not in model_props["qualities"]:
99
+ raise ValueError(f"Unsupported quality '{self.current_quality}' for model '{self.model_name}'. Supported qualities: {model_props['qualities']}")
100
+ else:
101
+ self.current_quality = None # Explicitly None if not supported
102
+
103
+ if model_props["supports_style"]:
104
+ self.current_style = default_style or model_props["default_style"]
105
+ if self.current_style not in model_props["styles"]:
106
+ raise ValueError(f"Unsupported style '{self.current_style}' for model '{self.model_name}'. Supported styles: {model_props['styles']}")
107
+ else:
108
+ self.current_style = None # Explicitly None if not supported
109
+
110
+ # For potential lollms client specific features, if `service_key` is passed as `client_id`
111
+ self.client_id = kwargs.get("service_key", kwargs.get("client_id", "dalle_client_user"))
112
+
113
+
114
+ def _get_model_properties(self, model_name: Optional[str] = None) -> Dict[str, Any]:
115
+ """Helper to get properties for a given model name, or the instance's current model."""
116
+ return DALLE_MODELS.get(model_name or self.model_name, {})
117
+
118
+ def generate_image(self,
119
+ prompt: str,
120
+ negative_prompt: Optional[str] = "",
121
+ width: int = 1024, # Default width
122
+ height: int = 1024, # Default height
123
+ **kwargs) -> bytes:
124
+ """
125
+ Generates image data using the DALL-E API.
126
+
127
+ Args:
128
+ prompt (str): The positive text prompt.
129
+ negative_prompt (Optional[str]): The negative prompt. For DALL-E 3, this is
130
+ appended to the main prompt. For DALL-E 2, it's ignored.
131
+ width (int): Image width.
132
+ height (int): Image height.
133
+ **kwargs: Additional parameters:
134
+ - model (str): Override the instance's default model for this call.
135
+ - quality (str): Override quality ("standard", "hd" for DALL-E 3).
136
+ - style (str): Override style ("vivid", "natural" for DALL-E 3).
137
+ - n (int): Number of images to generate (OpenAI supports >1, but this binding returns one). Default 1.
138
+ - user (str): A unique identifier for your end-user (OpenAI abuse monitoring).
139
+ Returns:
140
+ bytes: The generated image data (PNG format from DALL-E).
141
+
142
+ Raises:
143
+ Exception: If the request fails or image generation fails on the server.
144
+ """
145
+ model_override = kwargs.get("model")
146
+ active_model_name = model_override if model_override else self.model_name
147
+
148
+ model_props = self._get_model_properties(active_model_name)
149
+ if not model_props:
150
+ raise ValueError(f"Model {active_model_name} properties not found. Supported: {list(DALLE_MODELS.keys())}")
151
+
152
+ # Format size string and validate against the active model for this generation
153
+ size_str = f"{width}x{height}"
154
+ if size_str not in model_props["sizes"]:
155
+ ASCIIColors.warning(f"Unsupported size '{size_str}' for model '{active_model_name}'. Supported sizes: {model_props['sizes']}. Adjust width/height for this model.")
156
+ size_str = model_props["sizes"][0]
157
+
158
+ # Handle prompt and negative prompt based on the active model
159
+ final_prompt = prompt
160
+ if active_model_name == "dall-e-3" and negative_prompt:
161
+ final_prompt = f"{prompt}. Avoid: {negative_prompt}."
162
+ ASCIIColors.info(f"DALL-E 3: Appended negative prompt. Final prompt: '{final_prompt[:100]}...'")
163
+ elif active_model_name == "dall-e-2" and negative_prompt:
164
+ ASCIIColors.warning("DALL-E 2 does not support negative_prompt. It will be ignored.")
165
+
166
+ # Truncate prompt if too long for the active model
167
+ max_len = model_props.get("max_prompt_length", 4000)
168
+ if len(final_prompt) > max_len:
169
+ ASCIIColors.warning(f"Prompt for {active_model_name} is too long ({len(final_prompt)} chars). Truncating to {max_len} characters.")
170
+ final_prompt = final_prompt[:max_len]
171
+
172
+ endpoint = f"{self.host_address}/images/generations"
173
+ headers = {
174
+ "Authorization": f"Bearer {self.api_key}",
175
+ "Content-Type": "application/json"
176
+ }
177
+
178
+ payload = {
179
+ "model": active_model_name,
180
+ "prompt": final_prompt,
181
+ "n": kwargs.get("n", 1), # This binding expects to return one image
182
+ "size": size_str,
183
+ "response_format": "b64_json" # Request base64 encoded image
184
+ }
185
+
186
+ # Add model-specific parameters (quality, style)
187
+ # Use kwargs if provided, otherwise instance defaults, but only if the active model supports them.
188
+ if model_props["supports_quality"]:
189
+ payload["quality"] = kwargs.get("quality", self.current_quality)
190
+ if model_props["supports_style"]:
191
+ payload["style"] = kwargs.get("style", self.current_style)
192
+
193
+ if "user" in kwargs: # Pass user param if provided for moderation
194
+ payload["user"] = kwargs["user"]
195
+
196
+ try:
197
+ response = requests.post(endpoint, json=payload, headers=headers, verify=self.verify_ssl_certificate)
198
+ response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
199
+
200
+ response_json = response.json()
201
+
202
+ if not response_json.get("data") or not response_json["data"][0].get("b64_json"):
203
+ raise Exception("Server did not return image data in expected b64_json format.")
204
+
205
+ img_base64 = response_json["data"][0]["b64_json"]
206
+ img_bytes = base64.b64decode(img_base64)
207
+ return img_bytes
208
+
209
+ except requests.exceptions.HTTPError as e:
210
+ error_detail = "Unknown server error"
211
+ if e.response is not None:
212
+ try:
213
+ err_json = e.response.json()
214
+ if "error" in err_json and isinstance(err_json["error"], dict) and "message" in err_json["error"]:
215
+ error_detail = err_json["error"]["message"]
216
+ elif "detail" in err_json: # Fallback for other error structures
217
+ error_detail = err_json["detail"]
218
+ else: # If no specific error message, use raw text (limited)
219
+ error_detail = e.response.text[:500]
220
+ except (json.JSONDecodeError, requests.exceptions.JSONDecodeError): # If response is not JSON
221
+ error_detail = e.response.text[:500]
222
+ trace_exception(e)
223
+ raise Exception(f"HTTP request failed: {e.response.status_code} {e.response.reason} - Detail: {error_detail}") from e
224
+ else: # HTTPError without a response object (less common)
225
+ trace_exception(e)
226
+ raise Exception(f"HTTP request failed without a response body: {e}") from e
227
+ except requests.exceptions.RequestException as e: # Catches other network errors (DNS, ConnectionError, etc.)
228
+ trace_exception(e)
229
+ raise Exception(f"Request failed due to network issue: {e}") from e
230
+ except Exception as e: # Catches other errors (e.g., base64 decoding, unexpected issues)
231
+ trace_exception(e)
232
+ raise Exception(f"Image generation process failed: {e}") from e
233
+
234
+
235
+ def list_services(self, **kwargs) -> List[Dict[str, str]]:
236
+ """
237
+ Lists available DALL-E models supported by this binding.
238
+ `client_id` from kwargs is ignored as DALL-E auth is via API key.
239
+ """
240
+ services = []
241
+ for model_name, props in DALLE_MODELS.items():
242
+ caption = f"OpenAI {model_name.upper()}"
243
+ help_text = f"Size options: {', '.join(props['sizes'])}. "
244
+ if props["supports_quality"]:
245
+ help_text += f"Qualities: {', '.join(props['qualities'])}. "
246
+ if props["supports_style"]:
247
+ help_text += f"Styles: {', '.join(props['styles'])}. "
248
+ services.append({
249
+ "name": model_name,
250
+ "caption": caption,
251
+ "help": help_text.strip()
252
+ })
253
+ return services
254
+
255
+ def get_settings(self, **kwargs) -> List[Dict[str, Any]]:
256
+ """
257
+ Retrieves the current configurable default settings for the DALL-E binding.
258
+ `client_id` from kwargs is ignored. Returns settings in a ConfigTemplate-like format.
259
+ """
260
+ model_props = self._get_model_properties(self.model_name) # Settings relative to current default model
261
+
262
+ settings = [
263
+ {
264
+ "name": "model_name",
265
+ "type": "str",
266
+ "value": self.model_name,
267
+ "description": "Default DALL-E model for generation.",
268
+ "options": list(DALLE_MODELS.keys()),
269
+ "category": "Model Configuration"
270
+ },
271
+ {
272
+ "name": "current_size",
273
+ "type": "str",
274
+ "value": self.current_size,
275
+ "description": "Default image size (e.g., 1024x1024). Format: widthxheight.",
276
+ "options": model_props.get("sizes", []), # Options relevant to the current default model
277
+ "category": "Image Generation Defaults"
278
+ }
279
+ ]
280
+
281
+ if model_props.get("supports_quality", False):
282
+ settings.append({
283
+ "name": "current_quality",
284
+ "type": "str",
285
+ "value": self.current_quality,
286
+ "description": "Default image quality (e.g., 'standard', 'hd' for DALL-E 3).",
287
+ "options": model_props.get("qualities", []),
288
+ "category": "Image Generation Defaults"
289
+ })
290
+
291
+ if model_props.get("supports_style", False):
292
+ settings.append({
293
+ "name": "current_style",
294
+ "type": "str",
295
+ "value": self.current_style,
296
+ "description": "Default image style (e.g., 'vivid', 'natural' for DALL-E 3).",
297
+ "options": model_props.get("styles", []),
298
+ "category": "Image Generation Defaults"
299
+ })
300
+
301
+ settings.append({
302
+ "name": "api_key_status",
303
+ "type": "str",
304
+ "value": "Set (loaded)" if self.api_key else "Not Set", # Indicate if API key is present
305
+ "description": f"OpenAI API Key status (set at initialization or via '{OPENAI_API_KEY_ENV_VAR}', not changeable here).",
306
+ "category": "Authentication",
307
+ "read_only": True # Custom attribute indicating it's informational
308
+ })
309
+
310
+ return settings
311
+
312
+
313
+ def set_settings(self, settings: Union[Dict[str, Any], List[Dict[str, Any]]], **kwargs) -> bool:
314
+ """
315
+ Applies new default settings to the DALL-E binding instance.
316
+ `client_id` from kwargs is ignored.
317
+
318
+ Args:
319
+ settings (Union[Dict[str, Any], List[Dict[str, Any]]]):
320
+ New settings to apply.
321
+ Can be a flat dict: `{"model_name": "dall-e-2", "current_size": "512x512"}`
322
+ Or a list of dicts (ConfigTemplate format):
323
+ `[{"name": "model_name", "value": "dall-e-2"}, ...]`
324
+
325
+ Returns:
326
+ bool: True if at least one setting was successfully applied, False otherwise.
327
+ """
328
+ applied_some_settings = False
329
+ original_model_name = self.model_name # To detect if model changes
330
+
331
+ # Normalize settings input to a flat dictionary
332
+ if isinstance(settings, list):
333
+ parsed_settings = {}
334
+ for item in settings:
335
+ if isinstance(item, dict) and "name" in item and "value" in item:
336
+ if item["name"] == "api_key_status": # This is read-only
337
+ continue
338
+ parsed_settings[item["name"]] = item["value"]
339
+ settings_dict = parsed_settings
340
+ elif isinstance(settings, dict):
341
+ settings_dict = settings
342
+ else:
343
+ ASCIIColors.error("Invalid settings format. Expected a dictionary or list of dictionaries.")
344
+ return False
345
+
346
+ try:
347
+ # Phase 1: Apply model_name change if present, as it affects other settings' validity
348
+ if "model_name" in settings_dict:
349
+ new_model_name = settings_dict["model_name"]
350
+ if new_model_name not in DALLE_MODELS:
351
+ ASCIIColors.warning(f"Invalid model_name '{new_model_name}' provided in settings. Keeping current model '{self.model_name}'.")
352
+ elif self.model_name != new_model_name:
353
+ self.model_name = new_model_name
354
+ ASCIIColors.info(f"Default model changed to: {self.model_name}")
355
+ applied_some_settings = True # Mark that model name was processed
356
+
357
+ # Phase 2: If model changed, or for initial setup, adjust dependent settings to be consistent
358
+ # Run this phase if model_name was specifically in settings_dict and changed, OR if this is the first time settings are processed.
359
+ # The 'applied_some_settings' flag after model_name processing indicates a change.
360
+ if "model_name" in settings_dict and applied_some_settings :
361
+ new_model_props = self._get_model_properties(self.model_name)
362
+
363
+ # Update current_size if invalid for new model or if model changed
364
+ if self.current_size not in new_model_props["sizes"]:
365
+ old_val = self.current_size
366
+ self.current_size = new_model_props["default_size"]
367
+ if old_val != self.current_size: ASCIIColors.info(f"Default size reset to '{self.current_size}' for model '{self.model_name}'.")
368
+
369
+ # Update current_quality
370
+ if new_model_props["supports_quality"]:
371
+ if self.current_quality not in new_model_props.get("qualities", []):
372
+ old_val = self.current_quality
373
+ self.current_quality = new_model_props["default_quality"]
374
+ if old_val != self.current_quality: ASCIIColors.info(f"Default quality reset to '{self.current_quality}' for model '{self.model_name}'.")
375
+ elif self.current_quality is not None: # New model doesn't support quality
376
+ self.current_quality = None
377
+ ASCIIColors.info(f"Quality setting removed as model '{self.model_name}' does not support it.")
378
+
379
+ # Update current_style
380
+ if new_model_props["supports_style"]:
381
+ if self.current_style not in new_model_props.get("styles", []):
382
+ old_val = self.current_style
383
+ self.current_style = new_model_props["default_style"]
384
+ if old_val != self.current_style: ASCIIColors.info(f"Default style reset to '{self.current_style}' for model '{self.model_name}'.")
385
+ elif self.current_style is not None: # New model doesn't support style
386
+ self.current_style = None
387
+ ASCIIColors.info(f"Style setting removed as model '{self.model_name}' does not support it.")
388
+
389
+ # Phase 3: Apply other specific settings from input, validating against the (potentially new) model
390
+ current_model_props = self._get_model_properties(self.model_name) # Re-fetch props if model changed
391
+
392
+ if "current_size" in settings_dict:
393
+ new_size = settings_dict["current_size"]
394
+ if new_size not in current_model_props["sizes"]:
395
+ ASCIIColors.warning(f"Invalid size '{new_size}' for model '{self.model_name}'. Keeping '{self.current_size}'. Supported: {current_model_props['sizes']}")
396
+ elif self.current_size != new_size:
397
+ self.current_size = new_size
398
+ ASCIIColors.info(f"Default size set to: {self.current_size}")
399
+ applied_some_settings = True
400
+
401
+ if "current_quality" in settings_dict:
402
+ if current_model_props["supports_quality"]:
403
+ new_quality = settings_dict["current_quality"]
404
+ if new_quality not in current_model_props["qualities"]:
405
+ ASCIIColors.warning(f"Invalid quality '{new_quality}' for model '{self.model_name}'. Keeping '{self.current_quality}'. Supported: {current_model_props['qualities']}")
406
+ elif self.current_quality != new_quality:
407
+ self.current_quality = new_quality
408
+ ASCIIColors.info(f"Default quality set to: {self.current_quality}")
409
+ applied_some_settings = True
410
+ elif "current_quality" in settings_dict: # Only warn if user explicitly tried to set it
411
+ ASCIIColors.warning(f"Model '{self.model_name}' does not support quality. Ignoring 'current_quality' setting.")
412
+
413
+ if "current_style" in settings_dict:
414
+ if current_model_props["supports_style"]:
415
+ new_style = settings_dict["current_style"]
416
+ if new_style not in current_model_props["styles"]:
417
+ ASCIIColors.warning(f"Invalid style '{new_style}' for model '{self.model_name}'. Keeping '{self.current_style}'. Supported: {current_model_props['styles']}")
418
+ elif self.current_style != new_style:
419
+ self.current_style = new_style
420
+ ASCIIColors.info(f"Default style set to: {self.current_style}")
421
+ applied_some_settings = True
422
+ elif "current_style" in settings_dict: # Only warn if user explicitly tried to set it
423
+ ASCIIColors.warning(f"Model '{self.model_name}' does not support style. Ignoring 'current_style' setting.")
424
+
425
+ if "api_key" in settings_dict: # Should not be settable here
426
+ ASCIIColors.warning("API key cannot be changed after initialization via set_settings. This setting was ignored.")
427
+
428
+ return applied_some_settings
429
+
430
+ except Exception as e:
431
+ trace_exception(e)
432
+ ASCIIColors.error(f"Failed to apply settings due to an unexpected error: {e}")
433
+ return False