lollms-client 1.1.3__py3-none-any.whl → 1.3.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.

@@ -0,0 +1,124 @@
1
+ from pathlib import Path
2
+ from typing import Optional, List, Dict, Any, Union
3
+ import base64
4
+ import requests
5
+ from io import BytesIO
6
+ from ascii_colors import trace_exception
7
+ from openai import OpenAI
8
+ from lollms_client.lollms_tti_binding import LollmsTTIBinding
9
+
10
+ BindingName = "OpenAITTIBinding"
11
+
12
+
13
+ class OpenAITTIBinding(LollmsTTIBinding):
14
+ """
15
+ OpenAI Text-to-Image (TTI) binding for LoLLMS.
16
+
17
+ This binding provides access to OpenAI's image generation models
18
+ (`gpt-image-1`, `dall-e-2`, `dall-e-3`).
19
+
20
+ Parameters can be set globally at initialization or per-request during
21
+ generation. Runtime parameters override initialization ones.
22
+
23
+ ----------------------------
24
+ Global parameters (init):
25
+ ----------------------------
26
+ - model (str): Model name. ["gpt-image-1", "dall-e-2", "dall-e-3"]. Default: "gpt-image-1".
27
+ - api_key (str): OpenAI API key. If empty, uses OPENAI_API_KEY from environment.
28
+ - size (str): Default image size. ["256x256", "512x512", "1024x1024", "2048x2048"]. Default: "1024x1024".
29
+ - n (int): Default number of images per request (max 10). Default: 1.
30
+ - quality (str): Image quality. ["standard", "hd"]. Only supported by "dall-e-3". Default: "standard".
31
+
32
+ ----------------------------
33
+ Runtime parameters (kwargs in generate or edit):
34
+ ----------------------------
35
+ - prompt (str): Required. The text prompt for image generation or editing.
36
+ - size (str): Output size. Overrides global. Default: global "size".
37
+ - n (int): Number of images to generate. Overrides global. Default: global "n".
38
+ - quality (str): Image quality. Only for dall-e-3. Overrides global. Default: global "quality".
39
+ - image (Union[str, Path]): For edit. Input image (base64, URL, or file path).
40
+ - mask (Union[str, Path]): For edit. Optional mask image.
41
+
42
+ Methods:
43
+ --------
44
+ - generate_image(prompt: str, **kwargs) -> List[bytes]:
45
+ Generates images from a prompt.
46
+
47
+ - edit_image(prompt: str, image: Union[str, Path], mask: Optional[Union[str, Path]] = None, **kwargs) -> List[bytes]:
48
+ Edits an existing image using a prompt and optional mask.
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ model: str = "gpt-image-1",
54
+ api_key: Optional[str] = None,
55
+ size: str = "1024x1024",
56
+ n: int = 1,
57
+ quality: str = "standard",
58
+ **kwargs,
59
+ ):
60
+ self.client = OpenAI(api_key=api_key)
61
+ self.global_params = {
62
+ "model": model,
63
+ "size": size,
64
+ "n": n,
65
+ "quality": quality,
66
+ }
67
+
68
+ def _resolve_param(self, name: str, kwargs: Dict[str, Any], default: Any) -> Any:
69
+ """Resolve a parameter from runtime kwargs, global config, or default."""
70
+ return kwargs.get(name, self.global_params.get(name, default))
71
+
72
+ def _load_image(self, image: Union[str, Path]) -> Any:
73
+ """Helper to load an image from path, URL, or base64 string."""
74
+ if isinstance(image, Path) or Path(str(image)).exists():
75
+ with open(image, "rb") as f:
76
+ return f.read()
77
+ if isinstance(image, str) and image.startswith("http"):
78
+ return image
79
+ if isinstance(image, str) and image.strip().startswith(("iVBOR", "/9j/")): # base64
80
+ return base64.b64decode(image)
81
+ return image
82
+
83
+ def generate_image(self, prompt: str, **kwargs) -> List[bytes]:
84
+ model = self._resolve_param("model", kwargs, "gpt-image-1")
85
+ size = self._resolve_param("size", kwargs, "1024x1024")
86
+ n = self._resolve_param("n", kwargs, 1)
87
+ quality = self._resolve_param("quality", kwargs, "standard")
88
+
89
+ response = self.client.images.generate(
90
+ model=model,
91
+ prompt=prompt,
92
+ size=size,
93
+ n=n,
94
+ **({"quality": quality} if model == "dall-e-3" else {}),
95
+ )
96
+
97
+ return [base64.b64decode(img.b64_json) for img in response.data]
98
+
99
+ def edit_image(
100
+ self,
101
+ prompt: str,
102
+ image: Union[str, Path],
103
+ mask: Optional[Union[str, Path]] = None,
104
+ **kwargs,
105
+ ) -> List[bytes]:
106
+ model = self._resolve_param("model", kwargs, "gpt-image-1")
107
+ size = self._resolve_param("size", kwargs, "1024x1024")
108
+ n = self._resolve_param("n", kwargs, 1)
109
+ quality = self._resolve_param("quality", kwargs, "standard")
110
+
111
+ image_data = self._load_image(image)
112
+ mask_data = self._load_image(mask) if mask else None
113
+
114
+ response = self.client.images.edit(
115
+ model=model,
116
+ prompt=prompt,
117
+ image=image_data,
118
+ mask=mask_data,
119
+ size=size,
120
+ n=n,
121
+ **({"quality": quality} if model == "dall-e-3" else {}),
122
+ )
123
+
124
+ return [base64.b64decode(img.b64_json) for img in response.data]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lollms_client
3
- Version: 1.1.3
3
+ Version: 1.3.0
4
4
  Summary: A client library for LoLLMs generate endpoint
5
5
  Author-email: ParisNeo <parisneoai@gmail.com>
6
6
  License: Apache Software License
@@ -1,7 +1,7 @@
1
- lollms_client/__init__.py,sha256=rXPMm8tuBofHB9T-0sMMe1e0XF_-OdYFuS1OfUbuKtM,1146
1
+ lollms_client/__init__.py,sha256=SayNkzMpB6FbZcZc_162cog86cn7oSCvbV6nEaDnEq0,1146
2
2
  lollms_client/lollms_config.py,sha256=goEseDwDxYJf3WkYJ4IrLXwg3Tfw73CXV2Avg45M_hE,21876
3
- lollms_client/lollms_core.py,sha256=zqaxEJJDXiwpDd3E-PFDLJOnmG1d9cOiL_CrYRUa7Z0,167361
4
- lollms_client/lollms_discussion.py,sha256=wkadV6qiegxOzukMVn5vukdeJivnlyygSzZBkzOi9Gc,106714
3
+ lollms_client/lollms_core.py,sha256=4U2izPsGgm4H9GR59vsx9P9mKPT7keVWU8mwadAXU0I,171028
4
+ lollms_client/lollms_discussion.py,sha256=4vOnXJp4nLDtL2gRmnkTB4-mjYyIHsgp35pRSJPeT9U,117527
5
5
  lollms_client/lollms_js_analyzer.py,sha256=01zUvuO2F_lnUe_0NLxe1MF5aHE1hO8RZi48mNPv-aw,8361
6
6
  lollms_client/lollms_llm_binding.py,sha256=5-Vknm0YILPd6ZiwZynsXMfns__Yd_1tDDc2fciRiiA,25020
7
7
  lollms_client/lollms_mcp_binding.py,sha256=psb27A23VFWDfZsR2WUbQXQxiZDW5yfOak6ZtbMfszI,10222
@@ -9,7 +9,7 @@ lollms_client/lollms_mcp_security.py,sha256=FhVTDhSBjksGEZnopVnjFmEF5dv7D8bBTqoa
9
9
  lollms_client/lollms_personality.py,sha256=O-9nqZhazcITOkxjT24ENTxTmIoZLgqIsQ9WtWs0Id0,8719
10
10
  lollms_client/lollms_python_analyzer.py,sha256=7gf1fdYgXCOkPUkBAPNmr6S-66hMH4_KonOMsADASxc,10246
11
11
  lollms_client/lollms_stt_binding.py,sha256=jAUhLouEhh2hmm1bK76ianfw_6B59EHfY3FmLv6DU-g,5111
12
- lollms_client/lollms_tti_binding.py,sha256=zsKA8K4E54n_60wKjRIwLxEetKAI_IAggQUzkpip5YE,10406
12
+ lollms_client/lollms_tti_binding.py,sha256=B38nzBCSPV9jVRZa-x8W7l9nJEW0RyS1MMJoueb8kt0,8519
13
13
  lollms_client/lollms_ttm_binding.py,sha256=FjVVSNXOZXK1qvcKEfxdiX6l2b4XdGOSNnZ0utAsbDg,4167
14
14
  lollms_client/lollms_tts_binding.py,sha256=5cJYECj8PYLJAyB6SEH7_fhHYK3Om-Y3arkygCnZ24o,4342
15
15
  lollms_client/lollms_ttv_binding.py,sha256=KkTaHLBhEEdt4sSVBlbwr5i_g_TlhcrwrT-7DjOsjWQ,4131
@@ -25,12 +25,12 @@ lollms_client/llm_bindings/groq/__init__.py,sha256=EGrMh9vuCoM4pskDw8ydfsAWYgEb4
25
25
  lollms_client/llm_bindings/hugging_face_inference_api/__init__.py,sha256=SFcj5XQTDmN9eR4of82IgQa9iRYZaGlF6rMlF5S5wWg,13938
26
26
  lollms_client/llm_bindings/litellm/__init__.py,sha256=lRH4VfZMUG5JCCj6a7hk2PTfSyDowAu-ujLOM-XPl-8,12756
27
27
  lollms_client/llm_bindings/llamacpp/__init__.py,sha256=4CbNYpfquVEgfsxuLsxQta_dZRSpbSBL-VWhyDMdBAc,59379
28
- lollms_client/llm_bindings/lollms/__init__.py,sha256=a4gNH4axiDgsri8NGAcq0OitgYdnzBDLNkzUMhkFArA,24781
28
+ lollms_client/llm_bindings/lollms/__init__.py,sha256=7DgTGHtrFjhRnjx0YYlNTip2p5TSV-_4GN00ekEUd3g,24855
29
29
  lollms_client/llm_bindings/lollms_webui/__init__.py,sha256=iuDfhZZoLC-PDEPLHrcjk5-962S5c7OeCI7PMdJxI_A,17753
30
30
  lollms_client/llm_bindings/mistral/__init__.py,sha256=cddz9xIj8NRFLKHe2JMxzstpUrNIu5s9juci3mhiHfo,14133
31
31
  lollms_client/llm_bindings/ollama/__init__.py,sha256=W-4Z_lDzNA77e3xniWcPhkHGPlxwdBELVnGe-2y29uw,43587
32
32
  lollms_client/llm_bindings/open_router/__init__.py,sha256=cAFWtCWJx0WjIe1w2JReCf6WlAZjrXYA4jZ8l3zqxMs,14915
33
- lollms_client/llm_bindings/openai/__init__.py,sha256=J8v7XU9TrvXJd1ffwhYkya5YeXxWnNiFuNBAwRfoHDk,26066
33
+ lollms_client/llm_bindings/openai/__init__.py,sha256=ElLbtHLwR61Uj3W6G4g6QIhxtCqUGOCQBYwhQyN60us,26142
34
34
  lollms_client/llm_bindings/openllm/__init__.py,sha256=RC9dVeopslS-zXTsSJ7VC4iVsKgZCBwfmccmr_LCHA0,29971
35
35
  lollms_client/llm_bindings/pythonllamacpp/__init__.py,sha256=ZTuVa5ngu9GPVImjs_g8ArV7Bx7a1Rze518Tz8AFJ3U,31807
36
36
  lollms_client/llm_bindings/tensor_rt/__init__.py,sha256=xiT-JAyNI_jo6CE0nle9Xoc7U8-UHAfEHrnCwmDTiOE,32023
@@ -48,10 +48,10 @@ lollms_client/stt_bindings/lollms/__init__.py,sha256=9Vmn1sQQZKLGLe7nZnc-0LnNeSY
48
48
  lollms_client/stt_bindings/whisper/__init__.py,sha256=1Ej67GdRKBy1bba14jMaYDYHiZkxJASkWm5eF07ztDQ,15363
49
49
  lollms_client/stt_bindings/whispercpp/__init__.py,sha256=xSAQRjAhljak3vWCpkP0Vmdb6WmwTzPjXyaIB85KLGU,21439
50
50
  lollms_client/tti_bindings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
- lollms_client/tti_bindings/dalle/__init__.py,sha256=1nE36XamKEJOMpm6QUow8OyM1KdpejCLM0KUSXlcePo,24135
52
- lollms_client/tti_bindings/diffusers/__init__.py,sha256=YI9-VoqdQafoQgkSS0e5GhPNd30CxfI9m3AzdhNWhbs,37021
51
+ lollms_client/tti_bindings/diffusers/__init__.py,sha256=XJz42oOT3m-ek7DxlnXhbOY7_1V-9iORMNnFQRd8cu8,40092
53
52
  lollms_client/tti_bindings/gemini/__init__.py,sha256=f9fPuqnrBZ1Z-obcoP6EVvbEXNbNCSg21cd5efLCk8U,16707
54
53
  lollms_client/tti_bindings/lollms/__init__.py,sha256=5Tnsn4b17djvieQkcjtIDBm3qf0pg5ZWWov-4_2wmo0,8762
54
+ lollms_client/tti_bindings/openai/__init__.py,sha256=YWJolJSQfIzTJvrLQVe8rQewP7rddf6z87g4rnp-lTs,4932
55
55
  lollms_client/ttm_bindings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
56
  lollms_client/ttm_bindings/audiocraft/__init__.py,sha256=a0k6wTrHth6GaVOiNnVboeFY3oKVvCQPbQlqO38XEyc,14328
57
57
  lollms_client/ttm_bindings/bark/__init__.py,sha256=Pr3ou2a-7hNYDqbkxrAbghZpO5HvGUhz7e-7VGXIHHA,18976
@@ -63,8 +63,8 @@ lollms_client/tts_bindings/piper_tts/__init__.py,sha256=0IEWG4zH3_sOkSb9WbZzkeV5
63
63
  lollms_client/tts_bindings/xtts/__init__.py,sha256=FgcdUH06X6ZR806WQe5ixaYx0QoxtAcOgYo87a2qxYc,18266
64
64
  lollms_client/ttv_bindings/__init__.py,sha256=UZ8o2izQOJLQgtZ1D1cXoNST7rzqW22rL2Vufc7ddRc,3141
65
65
  lollms_client/ttv_bindings/lollms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
- lollms_client-1.1.3.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
67
- lollms_client-1.1.3.dist-info/METADATA,sha256=okzCbWUYkhl1WhMt59fGpoPsURWYdluHDxyj2qCiThw,58549
68
- lollms_client-1.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
69
- lollms_client-1.1.3.dist-info/top_level.txt,sha256=Bk_kz-ri6Arwsk7YG-T5VsRorV66uVhcHGvb_g2WqgE,14
70
- lollms_client-1.1.3.dist-info/RECORD,,
66
+ lollms_client-1.3.0.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
67
+ lollms_client-1.3.0.dist-info/METADATA,sha256=WJ_pYuQRpgyvBPvqcsCUXmzdgpEMvCSlJqWujIs8CoY,58549
68
+ lollms_client-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
69
+ lollms_client-1.3.0.dist-info/top_level.txt,sha256=Bk_kz-ri6Arwsk7YG-T5VsRorV66uVhcHGvb_g2WqgE,14
70
+ lollms_client-1.3.0.dist-info/RECORD,,
@@ -1,454 +0,0 @@
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
- class DalleTTIBinding_Impl(LollmsTTIBinding):
39
- """
40
- Concrete implementation of LollmsTTIBinding for OpenAI's DALL-E API.
41
- """
42
- def __init__(self, **kwargs):
43
- """
44
- Initialize the DALL-E TTI binding.
45
-
46
- Args:
47
- api_key (Optional[str]): OpenAI API key. If None or empty, attempts to read
48
- from the OPENAI_API_KEY environment variable.
49
- model_name (str): Name of the DALL-E model to use (e.g., "dall-e-3", "dall-e-2").
50
- default_size (Optional[str]): Default image size (e.g., "1024x1024").
51
- If None, uses model's default.
52
- default_quality (Optional[str]): Default image quality for DALL-E 3 ("standard", "hd").
53
- If None, uses model's default if applicable.
54
- default_style (Optional[str]): Default image style for DALL-E 3 ("vivid", "natural").
55
- If None, uses model's default if applicable.
56
- host_address (str): The API host address. Defaults to OpenAI's public API.
57
- verify_ssl_certificate (bool): Whether to verify SSL certificates.
58
- **kwargs: Catches other potential parameters like 'service_key' or 'client_id'.
59
- """
60
- super().__init__(binding_name="dalle")
61
-
62
- # Extract parameters from kwargs, providing defaults
63
- self.api_key = kwargs.get("service_key")
64
- self.model_name = kwargs.get("model_name")
65
- self.default_size = kwargs.get("default_size")
66
- self.default_quality = kwargs.get("default_quality")
67
- self.default_style = kwargs.get("default_style")
68
- self.host_address = kwargs.get("host_address", DALLE_API_HOST) # Provide default
69
- self.verify_ssl_certificate = kwargs.get("verify_ssl_certificate", True) # Provide default
70
-
71
- # Resolve API key from kwargs or environment variable
72
- resolved_api_key = self.api_key
73
- if not resolved_api_key:
74
- ASCIIColors.info(f"API key not provided directly, checking environment variable '{OPENAI_API_KEY_ENV_VAR}'...")
75
- resolved_api_key = os.environ.get(OPENAI_API_KEY_ENV_VAR)
76
-
77
- if not resolved_api_key:
78
- raise ValueError(f"OpenAI API key is required. Provide it directly or set the '{OPENAI_API_KEY_ENV_VAR}' environment variable.")
79
-
80
- self.api_key = resolved_api_key
81
-
82
- # Model name validation
83
- if not self.model_name:
84
- ASCIIColors.warning("Model name is required.")
85
- if self.model_name not in DALLE_MODELS:
86
- ASCIIColors.warning(f"Unsupported DALL-E model: {self.model_name}. Supported models: {list(DALLE_MODELS.keys())}")
87
- self.model_name = list(DALLE_MODELS.keys())[1]
88
- ASCIIColors.warning(f"Defaulting to {self.model_name}")
89
-
90
- model_props = DALLE_MODELS[self.model_name]
91
-
92
- # Size
93
- self.current_size = self.default_size or model_props["default_size"]
94
- if self.current_size not in model_props["sizes"]:
95
- raise ValueError(f"Unsupported size '{self.current_size}' for model '{self.model_name}'. Supported sizes: {model_props['sizes']}")
96
-
97
- # Quality
98
- if model_props["supports_quality"]:
99
- self.current_quality = self.default_quality or model_props["default_quality"]
100
- if self.current_quality not in model_props["qualities"]:
101
- raise ValueError(f"Unsupported quality '{self.current_quality}' for model '{self.model_name}'. Supported qualities: {model_props['qualities']}")
102
- else:
103
- self.current_quality = None # Explicitly None if not supported
104
-
105
- # Style
106
- if model_props["supports_style"]:
107
- self.current_style = self.default_style or model_props["default_style"]
108
- if self.current_style not in model_props["styles"]:
109
- raise ValueError(f"Unsupported style '{self.current_style}' for model '{self.model_name}'. Supported styles: {model_props['styles']}")
110
- else:
111
- self.current_style = None # Explicitly None if not supported
112
-
113
- # Client ID
114
- self.client_id = kwargs.get("service_key", kwargs.get("client_id", "dalle_client_user"))
115
-
116
-
117
- def _get_model_properties(self, model_name: Optional[str] = None) -> Dict[str, Any]:
118
- """Helper to get properties for a given model name, or the instance's current model."""
119
- return DALLE_MODELS.get(model_name or self.model_name, {})
120
-
121
- def generate_image(self,
122
- prompt: str,
123
- negative_prompt: Optional[str] = "",
124
- width: int = 1024, # Default width
125
- height: int = 1024, # Default height
126
- **kwargs) -> bytes:
127
- """
128
- Generates image data using the DALL-E API.
129
-
130
- Args:
131
- prompt (str): The positive text prompt.
132
- negative_prompt (Optional[str]): The negative prompt. For DALL-E 3, this is
133
- appended to the main prompt. For DALL-E 2, it's ignored.
134
- width (int): Image width.
135
- height (int): Image height.
136
- **kwargs: Additional parameters:
137
- - model (str): Override the instance's default model for this call.
138
- - quality (str): Override quality ("standard", "hd" for DALL-E 3).
139
- - style (str): Override style ("vivid", "natural" for DALL-E 3).
140
- - n (int): Number of images to generate (OpenAI supports >1, but this binding returns one). Default 1.
141
- - user (str): A unique identifier for your end-user (OpenAI abuse monitoring).
142
- Returns:
143
- bytes: The generated image data (PNG format from DALL-E).
144
-
145
- Raises:
146
- Exception: If the request fails or image generation fails on the server.
147
- """
148
- model_override = kwargs.get("model")
149
- active_model_name = model_override if model_override else self.model_name
150
-
151
- model_props = self._get_model_properties(active_model_name)
152
- if not model_props:
153
- raise ValueError(f"Model {active_model_name} properties not found. Supported: {list(DALLE_MODELS.keys())}")
154
-
155
- # Format size string and validate against the active model for this generation
156
- size_str = f"{width}x{height}"
157
- if size_str not in model_props["sizes"]:
158
- ASCIIColors.warning(f"Unsupported size '{size_str}' for model '{active_model_name}'. Supported sizes: {model_props['sizes']}. Adjust width/height for this model.")
159
- size_str = model_props["sizes"][0]
160
-
161
- # Handle prompt and negative prompt based on the active model
162
- final_prompt = prompt
163
- if active_model_name == "dall-e-3" and negative_prompt:
164
- final_prompt = f"{prompt}. Avoid: {negative_prompt}."
165
- ASCIIColors.info(f"DALL-E 3: Appended negative prompt. Final prompt: '{final_prompt[:100]}...'")
166
- elif active_model_name == "dall-e-2" and negative_prompt:
167
- ASCIIColors.warning("DALL-E 2 does not support negative_prompt. It will be ignored.")
168
-
169
- # Truncate prompt if too long for the active model
170
- max_len = model_props.get("max_prompt_length", 4000)
171
- if len(final_prompt) > max_len:
172
- ASCIIColors.warning(f"Prompt for {active_model_name} is too long ({len(final_prompt)} chars). Truncating to {max_len} characters.")
173
- final_prompt = final_prompt[:max_len]
174
-
175
- endpoint = f"{self.host_address}/images/generations"
176
- headers = {
177
- "Authorization": f"Bearer {self.api_key}",
178
- "Content-Type": "application/json"
179
- }
180
-
181
- payload = {
182
- "model": active_model_name,
183
- "prompt": final_prompt,
184
- "n": kwargs.get("n", 1), # This binding expects to return one image
185
- "size": size_str,
186
- "response_format": "b64_json" # Request base64 encoded image
187
- }
188
-
189
- # Add model-specific parameters (quality, style)
190
- # Use kwargs if provided, otherwise instance defaults, but only if the active model supports them.
191
- if model_props["supports_quality"]:
192
- payload["quality"] = kwargs.get("quality", self.current_quality)
193
- if model_props["supports_style"]:
194
- payload["style"] = kwargs.get("style", self.current_style)
195
-
196
- if "user" in kwargs: # Pass user param if provided for moderation
197
- payload["user"] = kwargs["user"]
198
-
199
- try:
200
- response = requests.post(endpoint, json=payload, headers=headers, verify=self.verify_ssl_certificate)
201
- response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
202
-
203
- response_json = response.json()
204
-
205
- if not response_json.get("data") or not response_json["data"][0].get("b64_json"):
206
- raise Exception("Server did not return image data in expected b64_json format.")
207
-
208
- img_base64 = response_json["data"][0]["b64_json"]
209
- img_bytes = base64.b64decode(img_base64)
210
- return img_bytes
211
-
212
- except requests.exceptions.HTTPError as e:
213
- error_detail = "Unknown server error"
214
- if e.response is not None:
215
- try:
216
- err_json = e.response.json()
217
- if "error" in err_json and isinstance(err_json["error"], dict) and "message" in err_json["error"]:
218
- error_detail = err_json["error"]["message"]
219
- elif "detail" in err_json: # Fallback for other error structures
220
- error_detail = err_json["detail"]
221
- else: # If no specific error message, use raw text (limited)
222
- error_detail = e.response.text[:500]
223
- except (json.JSONDecodeError, requests.exceptions.JSONDecodeError): # If response is not JSON
224
- error_detail = e.response.text[:500]
225
- trace_exception(e)
226
- raise Exception(f"HTTP request failed: {e.response.status_code} {e.response.reason} - Detail: {error_detail}") from e
227
- else: # HTTPError without a response object (less common)
228
- trace_exception(e)
229
- raise Exception(f"HTTP request failed without a response body: {e}") from e
230
- except requests.exceptions.RequestException as e: # Catches other network errors (DNS, ConnectionError, etc.)
231
- trace_exception(e)
232
- raise Exception(f"Request failed due to network issue: {e}") from e
233
- except Exception as e: # Catches other errors (e.g., base64 decoding, unexpected issues)
234
- trace_exception(e)
235
- raise Exception(f"Image generation process failed: {e}") from e
236
-
237
-
238
- def list_services(self, **kwargs) -> List[Dict[str, str]]:
239
- """
240
- Lists available DALL-E models supported by this binding.
241
- `client_id` from kwargs is ignored as DALL-E auth is via API key.
242
- """
243
- services = []
244
- for model_name, props in DALLE_MODELS.items():
245
- caption = f"OpenAI {model_name.upper()}"
246
- help_text = f"Size options: {', '.join(props['sizes'])}. "
247
- if props["supports_quality"]:
248
- help_text += f"Qualities: {', '.join(props['qualities'])}. "
249
- if props["supports_style"]:
250
- help_text += f"Styles: {', '.join(props['styles'])}. "
251
- services.append({
252
- "name": model_name,
253
- "caption": caption,
254
- "help": help_text.strip()
255
- })
256
- return services
257
-
258
- def get_settings(self, **kwargs) -> List[Dict[str, Any]]:
259
- """
260
- Retrieves the current configurable default settings for the DALL-E binding.
261
- `client_id` from kwargs is ignored. Returns settings in a ConfigTemplate-like format.
262
- """
263
- model_props = self._get_model_properties(self.model_name) # Settings relative to current default model
264
-
265
- settings = [
266
- {
267
- "name": "model_name",
268
- "type": "str",
269
- "value": self.model_name,
270
- "description": "Default DALL-E model for generation.",
271
- "options": list(DALLE_MODELS.keys()),
272
- "category": "Model Configuration"
273
- },
274
- {
275
- "name": "current_size",
276
- "type": "str",
277
- "value": self.current_size,
278
- "description": "Default image size (e.g., 1024x1024). Format: widthxheight.",
279
- "options": model_props.get("sizes", []), # Options relevant to the current default model
280
- "category": "Image Generation Defaults"
281
- }
282
- ]
283
-
284
- if model_props.get("supports_quality", False):
285
- settings.append({
286
- "name": "current_quality",
287
- "type": "str",
288
- "value": self.current_quality,
289
- "description": "Default image quality (e.g., 'standard', 'hd' for DALL-E 3).",
290
- "options": model_props.get("qualities", []),
291
- "category": "Image Generation Defaults"
292
- })
293
-
294
- if model_props.get("supports_style", False):
295
- settings.append({
296
- "name": "current_style",
297
- "type": "str",
298
- "value": self.current_style,
299
- "description": "Default image style (e.g., 'vivid', 'natural' for DALL-E 3).",
300
- "options": model_props.get("styles", []),
301
- "category": "Image Generation Defaults"
302
- })
303
-
304
- settings.append({
305
- "name": "api_key_status",
306
- "type": "str",
307
- "value": "Set (loaded)" if self.api_key else "Not Set", # Indicate if API key is present
308
- "description": f"OpenAI API Key status (set at initialization or via '{OPENAI_API_KEY_ENV_VAR}', not changeable here).",
309
- "category": "Authentication",
310
- "read_only": True # Custom attribute indicating it's informational
311
- })
312
-
313
- return settings
314
-
315
-
316
- def set_settings(self, settings: Union[Dict[str, Any], List[Dict[str, Any]]], **kwargs) -> bool:
317
- """
318
- Applies new default settings to the DALL-E binding instance.
319
- `client_id` from kwargs is ignored.
320
-
321
- Args:
322
- settings (Union[Dict[str, Any], List[Dict[str, Any]]]):
323
- New settings to apply.
324
- Can be a flat dict: `{"model_name": "dall-e-2", "current_size": "512x512"}`
325
- Or a list of dicts (ConfigTemplate format):
326
- `[{"name": "model_name", "value": "dall-e-2"}, ...]`
327
-
328
- Returns:
329
- bool: True if at least one setting was successfully applied, False otherwise.
330
- """
331
- applied_some_settings = False
332
- original_model_name = self.model_name # To detect if model changes
333
-
334
- # Normalize settings input to a flat dictionary
335
- if isinstance(settings, list):
336
- parsed_settings = {}
337
- for item in settings:
338
- if isinstance(item, dict) and "name" in item and "value" in item:
339
- if item["name"] == "api_key_status": # This is read-only
340
- continue
341
- parsed_settings[item["name"]] = item["value"]
342
- settings_dict = parsed_settings
343
- elif isinstance(settings, dict):
344
- settings_dict = settings
345
- else:
346
- ASCIIColors.error("Invalid settings format. Expected a dictionary or list of dictionaries.")
347
- return False
348
-
349
- try:
350
- # Phase 1: Apply model_name change if present, as it affects other settings' validity
351
- if "model_name" in settings_dict:
352
- new_model_name = settings_dict["model_name"]
353
- if new_model_name not in DALLE_MODELS:
354
- ASCIIColors.warning(f"Invalid model_name '{new_model_name}' provided in settings. Keeping current model '{self.model_name}'.")
355
- elif self.model_name != new_model_name:
356
- self.model_name = new_model_name
357
- ASCIIColors.info(f"Default model changed to: {self.model_name}")
358
- applied_some_settings = True # Mark that model name was processed
359
-
360
- # Phase 2: If model changed, or for initial setup, adjust dependent settings to be consistent
361
- # Run this phase if model_name was specifically in settings_dict and changed, OR if this is the first time settings are processed.
362
- # The 'applied_some_settings' flag after model_name processing indicates a change.
363
- if "model_name" in settings_dict and applied_some_settings :
364
- new_model_props = self._get_model_properties(self.model_name)
365
-
366
- # Update current_size if invalid for new model or if model changed
367
- if self.current_size not in new_model_props["sizes"]:
368
- old_val = self.current_size
369
- self.current_size = new_model_props["default_size"]
370
- if old_val != self.current_size: ASCIIColors.info(f"Default size reset to '{self.current_size}' for model '{self.model_name}'.")
371
-
372
- # Update current_quality
373
- if new_model_props["supports_quality"]:
374
- if self.current_quality not in new_model_props.get("qualities", []):
375
- old_val = self.current_quality
376
- self.current_quality = new_model_props["default_quality"]
377
- if old_val != self.current_quality: ASCIIColors.info(f"Default quality reset to '{self.current_quality}' for model '{self.model_name}'.")
378
- elif self.current_quality is not None: # New model doesn't support quality
379
- self.current_quality = None
380
- ASCIIColors.info(f"Quality setting removed as model '{self.model_name}' does not support it.")
381
-
382
- # Update current_style
383
- if new_model_props["supports_style"]:
384
- if self.current_style not in new_model_props.get("styles", []):
385
- old_val = self.current_style
386
- self.current_style = new_model_props["default_style"]
387
- if old_val != self.current_style: ASCIIColors.info(f"Default style reset to '{self.current_style}' for model '{self.model_name}'.")
388
- elif self.current_style is not None: # New model doesn't support style
389
- self.current_style = None
390
- ASCIIColors.info(f"Style setting removed as model '{self.model_name}' does not support it.")
391
-
392
- # Phase 3: Apply other specific settings from input, validating against the (potentially new) model
393
- current_model_props = self._get_model_properties(self.model_name) # Re-fetch props if model changed
394
-
395
- if "current_size" in settings_dict:
396
- new_size = settings_dict["current_size"]
397
- if new_size not in current_model_props["sizes"]:
398
- ASCIIColors.warning(f"Invalid size '{new_size}' for model '{self.model_name}'. Keeping '{self.current_size}'. Supported: {current_model_props['sizes']}")
399
- elif self.current_size != new_size:
400
- self.current_size = new_size
401
- ASCIIColors.info(f"Default size set to: {self.current_size}")
402
- applied_some_settings = True
403
-
404
- if "current_quality" in settings_dict:
405
- if current_model_props["supports_quality"]:
406
- new_quality = settings_dict["current_quality"]
407
- if new_quality not in current_model_props["qualities"]:
408
- ASCIIColors.warning(f"Invalid quality '{new_quality}' for model '{self.model_name}'. Keeping '{self.current_quality}'. Supported: {current_model_props['qualities']}")
409
- elif self.current_quality != new_quality:
410
- self.current_quality = new_quality
411
- ASCIIColors.info(f"Default quality set to: {self.current_quality}")
412
- applied_some_settings = True
413
- elif "current_quality" in settings_dict: # Only warn if user explicitly tried to set it
414
- ASCIIColors.warning(f"Model '{self.model_name}' does not support quality. Ignoring 'current_quality' setting.")
415
-
416
- if "current_style" in settings_dict:
417
- if current_model_props["supports_style"]:
418
- new_style = settings_dict["current_style"]
419
- if new_style not in current_model_props["styles"]:
420
- ASCIIColors.warning(f"Invalid style '{new_style}' for model '{self.model_name}'. Keeping '{self.current_style}'. Supported: {current_model_props['styles']}")
421
- elif self.current_style != new_style:
422
- self.current_style = new_style
423
- ASCIIColors.info(f"Default style set to: {self.current_style}")
424
- applied_some_settings = True
425
- elif "current_style" in settings_dict: # Only warn if user explicitly tried to set it
426
- ASCIIColors.warning(f"Model '{self.model_name}' does not support style. Ignoring 'current_style' setting.")
427
-
428
- if "api_key" in settings_dict: # Should not be settable here
429
- ASCIIColors.warning("API key cannot be changed after initialization via set_settings. This setting was ignored.")
430
-
431
- return applied_some_settings
432
-
433
- except Exception as e:
434
- trace_exception(e)
435
- ASCIIColors.error(f"Failed to apply settings due to an unexpected error: {e}")
436
- return False
437
-
438
- def listModels(self) -> list:
439
- """Lists models"""
440
- formatted_models=[
441
- {
442
- 'model_name': "dall-e-2",
443
- 'display_name': "Dall-e 2",
444
- 'description': "Dalle 2 model",
445
- 'owned_by': 'openai'
446
- },
447
- {
448
- 'model_name': "dall-e-3",
449
- 'display_name': "Dall-e 3",
450
- 'description': "Dalle 3 model",
451
- 'owned_by': 'openai'
452
- }
453
- ]
454
- return formatted_models