lollms-client 1.4.1__py3-none-any.whl → 1.7.10__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.
Files changed (64) hide show
  1. lollms_client/__init__.py +1 -1
  2. lollms_client/llm_bindings/azure_openai/__init__.py +2 -2
  3. lollms_client/llm_bindings/claude/__init__.py +125 -34
  4. lollms_client/llm_bindings/gemini/__init__.py +261 -159
  5. lollms_client/llm_bindings/grok/__init__.py +52 -14
  6. lollms_client/llm_bindings/groq/__init__.py +2 -2
  7. lollms_client/llm_bindings/hugging_face_inference_api/__init__.py +2 -2
  8. lollms_client/llm_bindings/litellm/__init__.py +1 -1
  9. lollms_client/llm_bindings/llamacpp/__init__.py +18 -11
  10. lollms_client/llm_bindings/lollms/__init__.py +151 -32
  11. lollms_client/llm_bindings/lollms_webui/__init__.py +1 -1
  12. lollms_client/llm_bindings/mistral/__init__.py +2 -2
  13. lollms_client/llm_bindings/novita_ai/__init__.py +439 -0
  14. lollms_client/llm_bindings/ollama/__init__.py +309 -93
  15. lollms_client/llm_bindings/open_router/__init__.py +2 -2
  16. lollms_client/llm_bindings/openai/__init__.py +148 -29
  17. lollms_client/llm_bindings/openllm/__init__.py +362 -506
  18. lollms_client/llm_bindings/openwebui/__init__.py +465 -0
  19. lollms_client/llm_bindings/perplexity/__init__.py +326 -0
  20. lollms_client/llm_bindings/pythonllamacpp/__init__.py +3 -3
  21. lollms_client/llm_bindings/tensor_rt/__init__.py +1 -1
  22. lollms_client/llm_bindings/transformers/__init__.py +428 -632
  23. lollms_client/llm_bindings/vllm/__init__.py +1 -1
  24. lollms_client/lollms_agentic.py +4 -2
  25. lollms_client/lollms_base_binding.py +61 -0
  26. lollms_client/lollms_core.py +516 -1890
  27. lollms_client/lollms_discussion.py +55 -18
  28. lollms_client/lollms_llm_binding.py +112 -261
  29. lollms_client/lollms_mcp_binding.py +34 -75
  30. lollms_client/lollms_personality.py +5 -2
  31. lollms_client/lollms_stt_binding.py +85 -52
  32. lollms_client/lollms_tti_binding.py +23 -37
  33. lollms_client/lollms_ttm_binding.py +24 -42
  34. lollms_client/lollms_tts_binding.py +28 -17
  35. lollms_client/lollms_ttv_binding.py +24 -42
  36. lollms_client/lollms_types.py +4 -2
  37. lollms_client/stt_bindings/whisper/__init__.py +108 -23
  38. lollms_client/stt_bindings/whispercpp/__init__.py +7 -1
  39. lollms_client/tti_bindings/diffusers/__init__.py +418 -810
  40. lollms_client/tti_bindings/diffusers/server/main.py +1051 -0
  41. lollms_client/tti_bindings/gemini/__init__.py +182 -239
  42. lollms_client/tti_bindings/leonardo_ai/__init__.py +127 -0
  43. lollms_client/tti_bindings/lollms/__init__.py +4 -1
  44. lollms_client/tti_bindings/novita_ai/__init__.py +105 -0
  45. lollms_client/tti_bindings/openai/__init__.py +10 -11
  46. lollms_client/tti_bindings/stability_ai/__init__.py +178 -0
  47. lollms_client/ttm_bindings/audiocraft/__init__.py +7 -12
  48. lollms_client/ttm_bindings/beatoven_ai/__init__.py +129 -0
  49. lollms_client/ttm_bindings/lollms/__init__.py +4 -17
  50. lollms_client/ttm_bindings/replicate/__init__.py +115 -0
  51. lollms_client/ttm_bindings/stability_ai/__init__.py +117 -0
  52. lollms_client/ttm_bindings/topmediai/__init__.py +96 -0
  53. lollms_client/tts_bindings/bark/__init__.py +7 -10
  54. lollms_client/tts_bindings/lollms/__init__.py +6 -1
  55. lollms_client/tts_bindings/piper_tts/__init__.py +8 -11
  56. lollms_client/tts_bindings/xtts/__init__.py +157 -74
  57. lollms_client/tts_bindings/xtts/server/main.py +241 -280
  58. {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/METADATA +316 -6
  59. lollms_client-1.7.10.dist-info/RECORD +89 -0
  60. lollms_client/ttm_bindings/bark/__init__.py +0 -339
  61. lollms_client-1.4.1.dist-info/RECORD +0 -78
  62. {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/WHEEL +0 -0
  63. {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/licenses/LICENSE +0 -0
  64. {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/top_level.txt +0 -0
@@ -26,9 +26,11 @@ GROK_API_BASE_URL = "https://api.x.ai/v1"
26
26
 
27
27
  # A hardcoded list to be used as a fallback if the API call fails
28
28
  _FALLBACK_MODELS = [
29
- {'model_name': 'grok-1', 'display_name': 'Grok 1', 'description': 'The flagship conversational model from xAI.', 'owned_by': 'xAI'},
30
- {'model_name': 'grok-1.5', 'display_name': 'Grok 1.5', 'description': 'The latest multimodal model from xAI.', 'owned_by': 'xAI'},
31
- {'model_name': 'grok-1.5-vision-preview', 'display_name': 'Grok 1.5 Vision (Preview)', 'description': 'Multimodal model with vision capabilities (preview).', 'owned_by': 'xAI'},
29
+ {'model_name': 'grok-2-latest', 'display_name': 'Grok 2 Latest', 'description': 'The latest conversational model from xAI.', 'owned_by': 'xAI'},
30
+ {'model_name': 'grok-2', 'display_name': 'Grok 2', 'description': 'Grok 2 model.', 'owned_by': 'xAI'},
31
+ {'model_name': 'grok-2-vision-latest', 'display_name': 'Grok 2 Vision Latest', 'description': 'Latest multimodal model from xAI.', 'owned_by': 'xAI'},
32
+ {'model_name': 'grok-beta', 'display_name': 'Grok Beta', 'description': 'Beta model.', 'owned_by': 'xAI'},
33
+ {'model_name': 'grok-vision-beta', 'display_name': 'Grok Vision Beta', 'description': 'Beta vision model.', 'owned_by': 'xAI'},
32
34
  ]
33
35
 
34
36
  # Helper to check if a string is a valid path to an image
@@ -70,7 +72,7 @@ class GrokBinding(LollmsLLMBinding):
70
72
  service_key (str): xAI API key.
71
73
  """
72
74
  super().__init__(BindingName, **kwargs)
73
- self.model_name = kwargs.get("model_name", "grok-1.5-vision-preview")
75
+ self.model_name = kwargs.get("model_name", "grok-2-latest")
74
76
  self.service_key = kwargs.get("service_key")
75
77
  self.base_url = kwargs.get("base_url", GROK_API_BASE_URL)
76
78
  self._cached_models: Optional[List[Dict[str, str]]] = None
@@ -101,7 +103,8 @@ class GrokBinding(LollmsLLMBinding):
101
103
  def _process_and_handle_stream(self,
102
104
  response: requests.Response,
103
105
  stream: bool,
104
- streaming_callback: Optional[Callable[[str, MSG_TYPE], None]]
106
+ streaming_callback: Optional[Callable[[str, MSG_TYPE], None]],
107
+ think: bool = False
105
108
  ) -> Union[str, dict]:
106
109
  """Helper to process streaming responses from the API."""
107
110
  full_response_text = ""
@@ -119,6 +122,21 @@ class GrokBinding(LollmsLLMBinding):
119
122
  if chunk['choices']:
120
123
  delta = chunk['choices'][0].get('delta', {})
121
124
  content = delta.get('content', '')
125
+ # Check for reasoning content (DeepSeek-style) if Grok adopts it or if proxied
126
+ reasoning = delta.get('reasoning_content', '')
127
+
128
+ if reasoning:
129
+ # If thinking is requested and we get reasoning tokens
130
+ if think:
131
+ if streaming_callback:
132
+ # We just stream the reasoning as is, user UI typically handles tagging or we could inject <think>
133
+ # Here we assume just passing the text is safer unless we track state
134
+ streaming_callback(reasoning, MSG_TYPE.MSG_TYPE_CHUNK)
135
+ # We don't append reasoning to full_response_text usually if it's separate,
136
+ # unless we want to return it in the final string wrapped.
137
+ # Let's wrap it for the final return string.
138
+ full_response_text += f"<think>{reasoning}</think>" # Naive wrapping for stream accumulation
139
+
122
140
  if content:
123
141
  full_response_text += content
124
142
  if stream and streaming_callback:
@@ -154,6 +172,9 @@ class GrokBinding(LollmsLLMBinding):
154
172
  n_threads: Optional[int] = None, # Not applicable
155
173
  ctx_size: int | None = None, # Determined by model
156
174
  streaming_callback: Optional[Callable[[str, MSG_TYPE], None]] = None,
175
+ think: Optional[bool] = False,
176
+ reasoning_effort: Optional[str] = "low", # low, medium, high
177
+ reasoning_summary: Optional[bool] = False, # auto
157
178
  **kwargs
158
179
  ) -> Union[str, dict]:
159
180
  """
@@ -181,7 +202,9 @@ class GrokBinding(LollmsLLMBinding):
181
202
  b64_data = base64.b64encode(image_file.read()).decode('utf-8')
182
203
  else: # Assume it's a base64 string
183
204
  b64_data = image_data
184
- media_type = "image/png" # Assume PNG if raw base64
205
+ if b64_data.startswith("data:image"):
206
+ b64_data = b64_data.split(",")[1]
207
+ media_type = "image/png" # Default assumption
185
208
 
186
209
  user_content.append({
187
210
  "type": "image_url",
@@ -214,7 +237,7 @@ class GrokBinding(LollmsLLMBinding):
214
237
  )
215
238
  response.raise_for_status()
216
239
 
217
- return self._process_and_handle_stream(response, stream, streaming_callback)
240
+ return self._process_and_handle_stream(response, stream, streaming_callback, think=think)
218
241
 
219
242
  except requests.exceptions.RequestException as ex:
220
243
  error_message = f"Grok API request failed: {str(ex)}"
@@ -238,6 +261,9 @@ class GrokBinding(LollmsLLMBinding):
238
261
  temperature: float = 0.7,
239
262
  top_p: float = 0.9,
240
263
  streaming_callback: Optional[Callable[[str, MSG_TYPE], None]] = None,
264
+ think: Optional[bool] = False,
265
+ reasoning_effort: Optional[str] = "low", # low, medium, high
266
+ reasoning_summary: Optional[bool] = False, # auto
241
267
  **kwargs
242
268
  ) -> Union[str, dict]:
243
269
  """
@@ -273,6 +299,18 @@ class GrokBinding(LollmsLLMBinding):
273
299
  })
274
300
  except Exception as e:
275
301
  ASCIIColors.warning(f"Could not load image {file_path}: {e}")
302
+ else:
303
+ # Attempt to handle base64
304
+ try:
305
+ b64_data = file_path
306
+ if b64_data.startswith("data:image"):
307
+ b64_data = b64_data.split(",")[1]
308
+ content_parts.append({
309
+ "type": "image_url",
310
+ "image_url": {"url": f"data:image/png;base64,{b64_data}"}
311
+ })
312
+ except:
313
+ pass
276
314
 
277
315
  # Grok API expects content to be a string for assistant, or list for user.
278
316
  if role == 'user':
@@ -303,7 +341,7 @@ class GrokBinding(LollmsLLMBinding):
303
341
  )
304
342
  response.raise_for_status()
305
343
 
306
- return self._process_and_handle_stream(response, stream, streaming_callback)
344
+ return self._process_and_handle_stream(response, stream, streaming_callback, think=think)
307
345
 
308
346
  except requests.exceptions.RequestException as ex:
309
347
  error_message = f"Grok API request failed: {str(ex)}"
@@ -362,10 +400,10 @@ class GrokBinding(LollmsLLMBinding):
362
400
  "host_address": self.base_url,
363
401
  "model_name": self.model_name,
364
402
  "supports_structured_output": False,
365
- "supports_vision": "vision" in self.model_name or "grok-1.5" == self.model_name,
403
+ "supports_vision": "vision" in self.model_name or "grok-1.5" in self.model_name or "grok-2" in self.model_name,
366
404
  }
367
405
 
368
- def listModels(self) -> List[Dict[str, str]]:
406
+ def list_models(self) -> List[Dict[str, str]]:
369
407
  """
370
408
  Lists available models from the xAI API.
371
409
  Caches the result to avoid repeated API calls.
@@ -433,8 +471,8 @@ if __name__ == '__main__':
433
471
  ASCIIColors.yellow("--- Testing GrokBinding ---")
434
472
 
435
473
  # --- Configuration ---
436
- test_model_name = "grok-1"
437
- test_vision_model_name = "grok-1.5-vision-preview"
474
+ test_model_name = "grok-2-latest"
475
+ test_vision_model_name = "grok-2-vision-latest"
438
476
 
439
477
  try:
440
478
  # --- Initialization ---
@@ -444,7 +482,7 @@ if __name__ == '__main__':
444
482
 
445
483
  # --- List Models ---
446
484
  ASCIIColors.cyan("\n--- Listing Models (dynamic) ---")
447
- models = binding.listModels()
485
+ models = binding.list_models()
448
486
  if models:
449
487
  ASCIIColors.green(f"Found {len(models)} models.")
450
488
  for m in models:
@@ -462,7 +500,7 @@ if __name__ == '__main__':
462
500
  ASCIIColors.cyan("\n--- Text Generation (Non-Streaming) ---")
463
501
  prompt_text = "Explain who Elon Musk is in one sentence."
464
502
  ASCIIColors.info(f"Prompt: {prompt_text}")
465
- generated_text = binding.generate_text(prompt_text, n_predict=100, stream=False, system_prompt="Be very concise.")
503
+ generated_text = binding.generate_text(prompt_text, n_predict=100, stream=False, system_prompt="Be very concise.", think=True)
466
504
  if isinstance(generated_text, str):
467
505
  ASCIIColors.green(f"Generated text:\n{generated_text}")
468
506
  else:
@@ -179,7 +179,7 @@ class GroqBinding(LollmsLLMBinding):
179
179
  "supports_vision": False, # Groq models do not currently support vision
180
180
  }
181
181
 
182
- def listModels(self) -> List[Dict[str, str]]:
182
+ def list_models(self) -> List[Dict[str, str]]:
183
183
  """Lists available models from the Groq service."""
184
184
  if not self.client:
185
185
  ASCIIColors.error("Groq client not initialized. Cannot list models.")
@@ -229,7 +229,7 @@ if __name__ == '__main__':
229
229
 
230
230
  # --- List Models ---
231
231
  ASCIIColors.cyan("\n--- Listing Models ---")
232
- models = binding.listModels()
232
+ models = binding.list_models()
233
233
  if models:
234
234
  ASCIIColors.green(f"Found {len(models)} models on Groq. Available models:")
235
235
  for m in models:
@@ -196,7 +196,7 @@ class HuggingFaceInferenceAPIBinding(LollmsLLMBinding):
196
196
  "supports_vision": False, # Vision models use a different API call
197
197
  }
198
198
 
199
- def listModels(self) -> List[Dict[str, str]]:
199
+ def list_models(self) -> List[Dict[str, str]]:
200
200
  """Lists text-generation models from the Hugging Face Hub."""
201
201
  if not self.hf_api:
202
202
  ASCIIColors.error("HF API client not initialized. Cannot list models.")
@@ -252,7 +252,7 @@ if __name__ == '__main__':
252
252
 
253
253
  # --- List Models ---
254
254
  ASCIIColors.cyan("\n--- Listing Models ---")
255
- models = binding.listModels()
255
+ models = binding.list_models()
256
256
  if models:
257
257
  ASCIIColors.green(f"Successfully fetched {len(models)} text-generation models.")
258
258
  ASCIIColors.info("Top 5 most downloaded models:")
@@ -185,7 +185,7 @@ class LiteLLMBinding(LollmsLLMBinding):
185
185
  ASCIIColors.error(f"--- [LiteLLM Binding] Fallback method failed: {e}")
186
186
  return entries
187
187
 
188
- def listModels(self) -> List[Dict]:
188
+ def list_models(self) -> List[Dict]:
189
189
  url = f'{self.host_address}/model/info'
190
190
  headers = {'Authorization': f'Bearer {self.service_key}'}
191
191
  entries = []
@@ -66,20 +66,27 @@ pm.ensure_packages(["requests", "pillow", "psutil"]) # pillow for dummy image in
66
66
  if not pm.is_installed("llama-cpp-binaries"):
67
67
  def install_llama_cpp():
68
68
  system = platform.system()
69
- python_version_simple = f"py{sys.version_info.major}" # e.g. py310 for 3.10
70
-
71
- cuda_suffix = "+cu124"
69
+ python_version_simple = f"py{sys.version_info.major}{sys.version_info.minor}" # e.g. py310 for 3.10
72
70
 
71
+ version_tag = "v0.56.0"
72
+ cuda_suffix = "+cu124"
73
73
 
74
74
  if system == "Windows":
75
- url = f"https://github.com/oobabooga/llama-cpp-binaries/releases/download/v0.39.0/llama_cpp_binaries-0.39.0{cuda_suffix}-{python_version_simple}-none-win_amd64.whl"
76
- fallback_url = "https://github.com/oobabooga/llama-cpp-binaries/releases/download/v0.39.0/llama_cpp_binaries-0.39.0+cu124-py3-none-win_amd64.whl" # Generic py3
75
+ # Try version-specific URL first
76
+ url = f"https://github.com/oobabooga/llama-cpp-binaries/releases/download/{version_tag}/llama_cpp_binaries-{version_tag.lstrip('v')}{cuda_suffix}-{python_version_simple}-none-win_amd64.whl"
77
+ # Fallback to generic py3 if version-specific doesn't exist
78
+ fallback_url = f"https://github.com/oobabooga/llama-cpp-binaries/releases/download/{version_tag}/llama_cpp_binaries-{version_tag.lstrip('v')}{cuda_suffix}-py3-none-win_amd64.whl"
77
79
  elif system == "Linux":
78
- url = f"https://github.com/oobabooga/llama-cpp-binaries/releases/download/v0.39.0/llama_cpp_binaries-0.39.0{cuda_suffix}-{python_version_simple}-none-linux_x86_64.whl"
79
- fallback_url = "https://github.com/oobabooga/llama-cpp-binaries/releases/download/v0.39.0/llama_cpp_binaries-0.39.0+cu124-py3-none-linux_x86_64.whl" # Generic py3
80
+ # Try version-specific URL first
81
+ url = f"https://github.com/oobabooga/llama-cpp-binaries/releases/download/{version_tag}/llama_cpp_binaries-{version_tag.lstrip('v')}{cuda_suffix}-{python_version_simple}-none-linux_x86_64.whl"
82
+ # Fallback to generic py3 if version-specific doesn't exist
83
+ fallback_url = f"https://github.com/oobabooga/llama-cpp-binaries/releases/download/{version_tag}/llama_cpp_binaries-{version_tag.lstrip('v')}{cuda_suffix}-py3-none-linux_x86_64.whl"
80
84
  else:
81
- ASCIIColors.warning(f"Unsupported OS for prebuilt llama-cpp-binaries: {system}. Please install manually.")
82
- return
85
+ ASCIIColors.error(f"Unsupported OS for precompiled llama-cpp-binaries: {system}. "
86
+ "You might need to set 'llama_server_binary_path' in the binding config "
87
+ "to point to a manually compiled llama.cpp server binary.")
88
+ return False
89
+
83
90
 
84
91
  ASCIIColors.info(f"Attempting to install llama-cpp-binaries from: {url}")
85
92
  try:
@@ -628,7 +635,7 @@ class LlamaCppServerBinding(LollmsLLMBinding):
628
635
 
629
636
  if not model_to_load:
630
637
  self._scan_models()
631
- available_models = self.listModels()
638
+ available_models = self.list_models()
632
639
  if not available_models:
633
640
  ASCIIColors.error("No model specified and no GGUF models found in models path.")
634
641
  return False
@@ -964,7 +971,7 @@ class LlamaCppServerBinding(LollmsLLMBinding):
964
971
 
965
972
  ASCIIColors.info(f"Scanned {len(self._model_path_map)} models from {self.models_path}.")
966
973
 
967
- def listModels(self) -> List[Dict[str, Any]]:
974
+ def list_models(self) -> List[Dict[str, Any]]:
968
975
  self._scan_models()
969
976
  models_found = []
970
977
  for unique_name, model_path in self._model_path_map.items():
@@ -1,4 +1,4 @@
1
- # bindings/Lollms_chat/binding.py
1
+ # bindings/lollms/__init__.py
2
2
  import requests
3
3
  import json
4
4
  from lollms_client.lollms_llm_binding import LollmsLLMBinding
@@ -11,6 +11,8 @@ from ascii_colors import ASCIIColors, trace_exception
11
11
  from typing import List, Dict
12
12
  import httpx
13
13
  import pipmaster as pm
14
+ import mimetypes
15
+ import base64
14
16
 
15
17
  pm.ensure_packages(["openai","tiktoken"])
16
18
 
@@ -20,7 +22,63 @@ import os
20
22
 
21
23
  BindingName = "LollmsBinding"
22
24
 
23
-
25
+ def _read_file_as_base64(path):
26
+ with open(path, "rb") as f:
27
+ return base64.b64encode(f.read()).decode("utf-8")
28
+
29
+ def _extract_markdown_path(s):
30
+ s = s.strip()
31
+ if s.startswith("[") and s.endswith(")"):
32
+ lb, rb = s.find("["), s.find("]")
33
+ if lb != -1 and rb != -1 and rb > lb:
34
+ return s[lb+1:rb].strip()
35
+ return s
36
+
37
+ def _guess_mime_from_name(name, default="image/jpeg"):
38
+ mime, _ = mimetypes.guess_type(name)
39
+ return mime or default
40
+
41
+ def _to_data_url(b64_str, mime):
42
+ return f"data:{mime};base64,{b64_str}"
43
+
44
+ def normalize_image_input(img, default_mime="image/jpeg"):
45
+ """
46
+ Returns a Responses API-ready content block:
47
+ { "type": "input_image", "image_url": "data:<mime>;base64,<...>" }
48
+ Accepts:
49
+ - dict {'data': '<base64>', 'mime': 'image/png'}
50
+ - dict {'path': 'E:\\images\\x.png'}
51
+ - string raw base64
52
+ - string local path (Windows/POSIX), including markdown-like "[E:\\path\\img.png]()"
53
+ URLs are intentionally not supported (base64 only).
54
+ """
55
+ if isinstance(img, dict):
56
+ if "data" in img and isinstance(img["data"], str):
57
+ mime = img.get("mime", default_mime)
58
+ return {"type": "input_image", "image_url": _to_data_url(img["data"], mime)}
59
+ if "path" in img and isinstance(img["path"], str):
60
+ p = _extract_markdown_path(img["path"])
61
+ b64 = _read_file_as_base64(p)
62
+ mime = _guess_mime_from_name(p, default_mime)
63
+ return {"type": "input_image", "image_url": _to_data_url(b64, mime)}
64
+ if "url" in img:
65
+ raise ValueError("URL inputs not allowed here; provide base64 or local path")
66
+ raise ValueError("Unsupported dict format for image input")
67
+
68
+ if isinstance(img, str):
69
+ s = _extract_markdown_path(img)
70
+ # Accept already-correct data URLs as-is
71
+ if s.startswith("data:"):
72
+ return {"type": "input_image", "image_url": s}
73
+ # Local path heuristics: exists on disk or looks like a path
74
+ if os.path.exists(s) or (":" in s and "\\" in s) or s.startswith("/") or s.startswith("."):
75
+ b64 = _read_file_as_base64(s)
76
+ mime = _guess_mime_from_name(s, default_mime)
77
+ return {"type": "input_image", "image_url": _to_data_url(b64, mime)}
78
+ # Otherwise, treat as raw base64 payload
79
+ return {"type": "input_image", "image_url": _to_data_url(s, default_mime)}
80
+
81
+ raise ValueError("Unsupported image input type")
24
82
  class LollmsBinding(LollmsLLMBinding):
25
83
  """Lollms-specific binding implementation (open ai compatible with some extra parameters)"""
26
84
 
@@ -36,6 +94,7 @@ class LollmsBinding(LollmsLLMBinding):
36
94
  service_key (str): Authentication key for the service. Defaults to None. This is a key generated
37
95
  on the lollms interface (it is advised to use LOLLMS_API_KEY environment variable instead)
38
96
  verify_ssl_certificate (bool): Whether to verify SSL certificates. Defaults to True.
97
+ certificate_file_path (str): Path to a specific certificate file for SSL verification.
39
98
  personality (Optional[int]): Ignored parameter for compatibility with LollmsLLMBinding.
40
99
  """
41
100
  super().__init__(BindingName, **kwargs)
@@ -45,18 +104,28 @@ class LollmsBinding(LollmsLLMBinding):
45
104
  self.model_name=kwargs.get("model_name")
46
105
  self.service_key=kwargs.get("service_key")
47
106
  self.verify_ssl_certificate=kwargs.get("verify_ssl_certificate", True)
107
+ self.certificate_file_path=kwargs.get("certificate_file_path")
48
108
  self.default_completion_format=kwargs.get("default_completion_format", ELF_COMPLETION_FORMAT.Chat)
49
109
 
50
110
  if not self.service_key:
51
111
  self.service_key = os.getenv("LOLLMS_API_KEY", self.service_key)
52
- self.client = openai.OpenAI(api_key=self.service_key, base_url=None if self.host_address is None else self.host_address if len(self.host_address)>0 else None, http_client=httpx.Client(verify=self.verify_ssl_certificate))
112
+
113
+ # Determine verification strategy: specific file takes precedence, otherwise boolean flag
114
+ verify = self.certificate_file_path if self.certificate_file_path else self.verify_ssl_certificate
115
+
116
+ self.client = openai.OpenAI(api_key=self.service_key, base_url=None if self.host_address is None else self.host_address if len(self.host_address)>0 else None, http_client=httpx.Client(verify=verify))
53
117
  self.completion_format = ELF_COMPLETION_FORMAT.Chat
54
118
 
55
119
  def lollms_listMountedPersonalities(self, host_address:str|None=None):
56
120
  host_address = host_address if host_address else self.host_address
57
- url = f"{host_address}/list_mounted_personalities"
121
+ url = f"{host_address}/personalities"
122
+
123
+ headers = {
124
+ "Authorization": f"Bearer {self.service_key}",
125
+ "Accept": "application/json",
126
+ }
58
127
 
59
- response = requests.get(url)
128
+ response = requests.get(url, headers=headers, timeout=30)
60
129
 
61
130
  if response.status_code == 200:
62
131
  try:
@@ -86,6 +155,12 @@ class LollmsBinding(LollmsLLMBinding):
86
155
  "stop", "max_tokens", "presence_penalty", "frequency_penalty",
87
156
  "logit_bias", "stream", "user", "max_completion_tokens"
88
157
  }
158
+ if kwargs.get("think", False):
159
+ allowed_params.append("reasoning")
160
+ kwargs["reasoning"]={
161
+ "effort": allowed_params.append("reasoning_effort", "low"),
162
+ "summary": allowed_params.append("reasoning_summary", "auto")
163
+ }
89
164
 
90
165
  params = {
91
166
  "model": model,
@@ -127,7 +202,11 @@ class LollmsBinding(LollmsLLMBinding):
127
202
  streaming_callback: Optional[Callable[[str, MSG_TYPE], None]] = None,
128
203
  split: Optional[bool] = False,
129
204
  user_keyword: Optional[str] = "!@>user:",
130
- ai_keyword: Optional[str] = "!@>assistant:"
205
+ ai_keyword: Optional[str] = "!@>assistant:",
206
+ think: Optional[bool] = False,
207
+ reasoning_effort: Optional[bool] = "low", # low, medium, high
208
+ reasoning_summary: Optional[bool] = "auto", # auto
209
+ **kwargs
131
210
  ) -> Union[str, dict]:
132
211
 
133
212
  count = 0
@@ -136,17 +215,18 @@ class LollmsBinding(LollmsLLMBinding):
136
215
 
137
216
  if images:
138
217
  if split:
218
+ # Original call to split message roles
139
219
  messages += self.split_discussion(prompt, user_keyword=user_keyword, ai_keyword=ai_keyword)
140
- messages[-1]["content"] = [{"type": "text", "text": messages[-1]["content"]}] + [
141
- {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{encode_image(path)}"}}
142
- for path in images
143
- ]
220
+ # Convert the last message content to the structured content array
221
+ last = messages[-1]
222
+ text_block = {"type": "text", "text": last["content"]}
223
+ image_blocks = [normalize_image_input(img) for img in images]
224
+ last["content"] = [text_block] + image_blocks
144
225
  else:
145
226
  messages.append({
146
- 'role': 'user',
147
- 'content': [{"type": "text", "text": prompt}] + [
148
- {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{encode_image(path)}"}}
149
- for path in images
227
+ "role": "user",
228
+ "content": [{"type": "text", "text": prompt}] + [
229
+ normalize_image_input(img) for img in images
150
230
  ]
151
231
  })
152
232
  else:
@@ -163,12 +243,16 @@ class LollmsBinding(LollmsLLMBinding):
163
243
  temperature=temperature,
164
244
  top_p=top_p,
165
245
  repeat_penalty=repeat_penalty,
166
- seed=seed)
246
+ seed=seed,
247
+ think = think,
248
+ reasoning_effort=reasoning_effort,
249
+ reasoning_summary=reasoning_summary
250
+ )
167
251
  try:
168
252
  chat_completion = self.client.chat.completions.create(**params)
169
253
  except Exception as ex:
170
254
  # exception for new openai models
171
- params["max_completion_tokens"]=params["max_tokens"]
255
+ params["max_completion_tokens"]=params.get("max_tokens") or params.get("max_completion_tokens") or self.default_ctx_size
172
256
  params["temperature"]=1
173
257
  try: del params["max_tokens"]
174
258
  except Exception: pass
@@ -199,7 +283,10 @@ class LollmsBinding(LollmsLLMBinding):
199
283
  temperature=temperature,
200
284
  top_p=top_p,
201
285
  repeat_penalty=repeat_penalty,
202
- seed=seed)
286
+ seed=seed,
287
+ think = think,
288
+ reasoning_effort=reasoning_effort,
289
+ reasoning_summary=reasoning_summary)
203
290
  try:
204
291
  completion = self.client.completions.create(**params)
205
292
  except Exception as ex:
@@ -252,6 +339,9 @@ class LollmsBinding(LollmsLLMBinding):
252
339
  n_threads: Optional[int] = None,
253
340
  ctx_size: int | None = None,
254
341
  streaming_callback: Optional[Callable[[str, MSG_TYPE], None]] = None,
342
+ think: Optional[bool] = False,
343
+ reasoning_effort: Optional[bool] = "low", # low, medium, high
344
+ reasoning_summary: Optional[bool] = "auto", # auto
255
345
  **kwargs
256
346
  ) -> Union[str, dict]:
257
347
  # Build the request parameters
@@ -300,20 +390,22 @@ class LollmsBinding(LollmsLLMBinding):
300
390
  return output
301
391
 
302
392
  def chat(self,
303
- discussion: LollmsDiscussion,
304
- branch_tip_id: Optional[str] = None,
305
- n_predict: Optional[int] = None,
306
- stream: Optional[bool] = None,
307
- temperature: float = 0.7,
308
- top_k: int = 40,
309
- top_p: float = 0.9,
310
- repeat_penalty: float = 1.1,
311
- repeat_last_n: int = 64,
312
- seed: Optional[int] = None,
313
- n_threads: Optional[int] = None,
314
- ctx_size: Optional[int] = None,
315
- streaming_callback: Optional[Callable[[str, MSG_TYPE], None]] = None
316
- ) -> Union[str, dict]:
393
+ discussion: LollmsDiscussion,
394
+ branch_tip_id: Optional[str] = None,
395
+ n_predict: Optional[int] = None,
396
+ stream: Optional[bool] = None,
397
+ temperature: float = 0.7,
398
+ top_k: int = 40,
399
+ top_p: float = 0.9,
400
+ repeat_penalty: float = 1.1,
401
+ repeat_last_n: int = 64,
402
+ seed: Optional[int] = None,
403
+ n_threads: Optional[int] = None,
404
+ ctx_size: Optional[int] = None,
405
+ streaming_callback: Optional[Callable[[str, MSG_TYPE], None]] = None,
406
+ think: Optional[bool] = False,
407
+ **kwargs
408
+ ) -> Union[str, dict]:
317
409
  """
318
410
  Conduct a chat session with the OpenAI model using a LollmsDiscussion object.
319
411
 
@@ -500,7 +592,7 @@ class LollmsBinding(LollmsLLMBinding):
500
592
  "model_name": self.model_name
501
593
  }
502
594
 
503
- def listModels(self) -> List[Dict]:
595
+ def list_models(self) -> List[Dict]:
504
596
  # Known context lengths
505
597
  known_context_lengths = {
506
598
  "gpt-4o": 128000,
@@ -579,3 +671,30 @@ class LollmsBinding(LollmsLLMBinding):
579
671
  self.model = model_name
580
672
  self.model_name = model_name
581
673
  return True
674
+
675
+ def ps(self):
676
+ """
677
+ List models (simulating a process status command).
678
+ Since Lollms/OpenAI API doesn't have a specific 'ps' endpoint for running models with memory stats,
679
+ we list available models and populate structure with available info, leaving hardware stats empty.
680
+ """
681
+ # Since there is no dedicated ps endpoint to see *running* models in the standard OpenAI API,
682
+ # we list available models and try to map relevant info.
683
+ models = self.list_models()
684
+ standardized_models = []
685
+ for m in models:
686
+ standardized_models.append({
687
+ "model_name": m.get("model_name"),
688
+ "size": None,
689
+ "vram_size": None,
690
+ "gpu_usage_percent": None,
691
+ "cpu_usage_percent": None,
692
+ "expires_at": None,
693
+ "parameters_size": None,
694
+ "quantization_level": None,
695
+ "parent_model": None,
696
+ "context_size": m.get("context_length"),
697
+ "owned_by": m.get("owned_by"),
698
+ "created": m.get("created")
699
+ })
700
+ return standardized_models
@@ -375,7 +375,7 @@ class LollmsWebuiLLMBinding(LollmsLLMBinding):
375
375
  }
376
376
 
377
377
 
378
- def listModels(self) -> dict:
378
+ def list_models(self) -> dict:
379
379
  """Lists models"""
380
380
  url = f"{self.host_address}/list_models"
381
381
 
@@ -224,7 +224,7 @@ class MistralBinding(LollmsLLMBinding):
224
224
  "supports_vision": False, # Mistral API does not currently support vision
225
225
  }
226
226
 
227
- def listModels(self) -> List[Dict[str, str]]:
227
+ def list_models(self) -> List[Dict[str, str]]:
228
228
  """Lists available models from the Mistral service."""
229
229
  if not self.client:
230
230
  ASCIIColors.error("Mistral client not initialized. Cannot list models.")
@@ -273,7 +273,7 @@ if __name__ == '__main__':
273
273
 
274
274
  # --- List Models ---
275
275
  ASCIIColors.cyan("\n--- Listing Models ---")
276
- models = binding.listModels()
276
+ models = binding.list_models()
277
277
  if models:
278
278
  ASCIIColors.green(f"Found {len(models)} models on Mistral. Available models:")
279
279
  for m in models: