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
@@ -0,0 +1,326 @@
1
+ import base64
2
+ import os
3
+ from io import BytesIO
4
+ from pathlib import Path
5
+ from typing import Optional, Callable, List, Union, Dict
6
+ import json
7
+ import requests
8
+
9
+ from lollms_client.lollms_discussion import LollmsDiscussion, LollmsMessage
10
+ from lollms_client.lollms_llm_binding import LollmsLLMBinding
11
+ from lollms_client.lollms_types import MSG_TYPE
12
+ from ascii_colors import ASCIIColors, trace_exception
13
+
14
+ import pipmaster as pm
15
+
16
+ # Ensure the required packages are installed
17
+ pm.ensure_packages(["requests", "tiktoken"])
18
+
19
+ import tiktoken
20
+
21
+ BindingName = "PerplexityBinding"
22
+ API_BASE_URL = "https://api.perplexity.ai"
23
+
24
+ # A hardcoded list of models based on Perplexity's documentation
25
+ # The API does not provide a models listing endpoint.
26
+ # Sourced from: https://docs.perplexity.ai/docs/models
27
+ _FALLBACK_MODELS = [
28
+ # Sonar Models
29
+ {'model_name': 'llama-3.1-sonar-small-128k-chat', 'display_name': 'Llama 3.1 Sonar Small Chat (128k)', 'description': 'Fast and cost-effective conversational model.', 'owned_by': 'Perplexity'},
30
+ {'model_name': 'llama-3.1-sonar-small-128k-online', 'display_name': 'Llama 3.1 Sonar Small Online (128k)', 'description': 'Fast and cost-effective conversational model with web access.', 'owned_by': 'Perplexity'},
31
+ {'model_name': 'llama-3.1-sonar-large-128k-chat', 'display_name': 'Llama 3.1 Sonar Large Chat (128k)', 'description': 'State-of-the-art conversational model.', 'owned_by': 'Perplexity'},
32
+ {'model_name': 'llama-3.1-sonar-large-128k-online', 'display_name': 'Llama 3.1 Sonar Large Online (128k)', 'description': 'State-of-the-art conversational model with web access.', 'owned_by': 'Perplexity'},
33
+ # Llama 3 Instruct Models
34
+ {'model_name': 'llama-3-8b-instruct', 'display_name': 'Llama 3 8B Instruct', 'description': 'Meta\'s Llama 3 8B instruction-tuned model.', 'owned_by': 'Meta'},
35
+ {'model_name': 'llama-3-70b-instruct', 'display_name': 'Llama 3 70B Instruct', 'description': 'Meta\'s Llama 3 70B instruction-tuned model.', 'owned_by': 'Meta'},
36
+ # Mixtral Model
37
+ {'model_name': 'mixtral-8x7b-instruct', 'display_name': 'Mixtral 8x7B Instruct', 'description': 'Mistral AI\'s Mixtral 8x7B instruction-tuned model.', 'owned_by': 'Mistral AI'},
38
+ # Legacy Sonar Models
39
+ {'model_name': 'sonar-small-32k-chat', 'display_name': 'Sonar Small Chat (32k)', 'description': 'Legacy small conversational model.', 'owned_by': 'Perplexity'},
40
+ {'model_name': 'sonar-small-32k-online', 'display_name': 'Sonar Small Online (32k)', 'description': 'Legacy small conversational model with web access.', 'owned_by': 'Perplexity'},
41
+ {'model_name': 'sonar-medium-32k-chat', 'display_name': 'Sonar Medium Chat (32k)', 'description': 'Legacy medium conversational model.', 'owned_by': 'Perplexity'},
42
+ {'model_name': 'sonar-medium-32k-online', 'display_name': 'Sonar Medium Online (32k)', 'description': 'Legacy medium conversational model with web access.', 'owned_by': 'Perplexity'},
43
+ ]
44
+
45
+ class PerplexityBinding(LollmsLLMBinding):
46
+ """Perplexity AI-specific binding implementation."""
47
+
48
+ def __init__(self, **kwargs):
49
+ """
50
+ Initialize the Perplexity binding.
51
+
52
+ Args:
53
+ model_name (str): Name of the Perplexity model to use.
54
+ service_key (str): Perplexity API key.
55
+ """
56
+ super().__init__(BindingName, **kwargs)
57
+ self.model_name = kwargs.get("model_name")
58
+ self.service_key = kwargs.get("service_key")
59
+
60
+ if not self.service_key:
61
+ self.service_key = os.getenv("PERPLEXITY_API_KEY")
62
+
63
+ if not self.service_key:
64
+ raise ValueError("Perplexity API key is required. Please set it via the 'service_key' parameter or the PERPLEXITY_API_KEY environment variable.")
65
+
66
+ self.headers = {
67
+ "Authorization": f"Bearer {self.service_key}",
68
+ "Content-Type": "application/json",
69
+ "Accept": "application/json"
70
+ }
71
+
72
+ def _construct_parameters(self,
73
+ temperature: float,
74
+ top_p: float,
75
+ top_k: int,
76
+ n_predict: int,
77
+ presence_penalty: float,
78
+ frequency_penalty: float) -> Dict[str, any]:
79
+ """Builds a parameters dictionary for the Perplexity API."""
80
+ params = {}
81
+ if temperature is not None: params['temperature'] = float(temperature)
82
+ if top_p is not None: params['top_p'] = top_p
83
+ if top_k is not None: params['top_k'] = top_k
84
+ if n_predict is not None: params['max_tokens'] = n_predict
85
+ if presence_penalty is not None: params['presence_penalty'] = presence_penalty
86
+ if frequency_penalty is not None: params['frequency_penalty'] = frequency_penalty
87
+ return params
88
+
89
+ def chat(self,
90
+ discussion: LollmsDiscussion,
91
+ branch_tip_id: Optional[str] = None,
92
+ n_predict: Optional[int] = 2048,
93
+ stream: Optional[bool] = False,
94
+ temperature: float = 0.7,
95
+ top_k: int = 50,
96
+ top_p: float = 0.9,
97
+ repeat_penalty: float = 1.1, # maps to frequency_penalty
98
+ presence_penalty: Optional[float] = 0.0,
99
+ seed: Optional[int] = None, # Not supported
100
+ n_threads: Optional[int] = None, # Not applicable
101
+ ctx_size: Optional[int] = None, # Determined by model
102
+ streaming_callback: Optional[Callable[[str, MSG_TYPE], None]] = None
103
+ ) -> Union[str, dict]:
104
+ """
105
+ Conduct a chat session with the Perplexity model using a LollmsDiscussion object.
106
+ """
107
+ system_prompt = discussion.system_prompt
108
+ messages = discussion.get_messages(branch_tip_id)
109
+
110
+ history = []
111
+ if system_prompt and system_prompt.strip():
112
+ history.append({"role": "system", "content": system_prompt})
113
+
114
+ for msg in messages:
115
+ if msg.sender_type == "user":
116
+ role = "user"
117
+ else:
118
+ role = "assistant"
119
+
120
+ if msg.images:
121
+ ASCIIColors.warning("Perplexity API does not support images. They will be ignored.")
122
+
123
+ if msg.content and msg.content.strip():
124
+ history.append({"role": role, "content": msg.content})
125
+
126
+ if not history:
127
+ return {"status": "error", "message": "Cannot start chat with an empty discussion."}
128
+
129
+ api_params = self._construct_parameters(
130
+ temperature, top_p, top_k, n_predict, presence_penalty, repeat_penalty
131
+ )
132
+
133
+ payload = {
134
+ "model": self.model_name,
135
+ "messages": history,
136
+ "stream": stream,
137
+ **api_params
138
+ }
139
+
140
+ url = f"{API_BASE_URL}/chat/completions"
141
+ full_response_text = ""
142
+
143
+ try:
144
+ if stream:
145
+ with requests.post(url, headers=self.headers, json=payload, stream=True) as response:
146
+ response.raise_for_status()
147
+ for line in response.iter_lines():
148
+ if line:
149
+ decoded_line = line.decode('utf-8')
150
+ if decoded_line.startswith("data:"):
151
+ content = decoded_line[len("data: "):].strip()
152
+ if content == "[DONE]":
153
+ break
154
+ try:
155
+ chunk = json.loads(content)
156
+ delta = chunk.get("choices", [{}])[0].get("delta", {})
157
+ text_chunk = delta.get("content", "")
158
+ if text_chunk:
159
+ full_response_text += text_chunk
160
+ if streaming_callback:
161
+ if not streaming_callback(text_chunk, MSG_TYPE.MSG_TYPE_CHUNK):
162
+ break
163
+ except json.JSONDecodeError:
164
+ ASCIIColors.error(f"Failed to decode JSON chunk: {content}")
165
+ continue
166
+ return full_response_text
167
+ else:
168
+ response = requests.post(url, headers=self.headers, json=payload)
169
+ response.raise_for_status()
170
+ data = response.json()
171
+ return data["choices"][0]["message"]["content"]
172
+ except requests.exceptions.RequestException as e:
173
+ error_message = f"An error occurred with the Perplexity API: {e}"
174
+ trace_exception(e)
175
+ return {"status": "error", "message": str(e)}
176
+ except Exception as ex:
177
+ error_message = f"An unexpected error occurred: {str(ex)}"
178
+ trace_exception(ex)
179
+ return {"status": "error", "message": error_message}
180
+
181
+ def tokenize(self, text: str) -> list:
182
+ """
183
+ Tokenize the input text. Perplexity uses the same tokenizer as GPT-4.
184
+ """
185
+ try:
186
+ encoding = tiktoken.get_encoding("cl100k_base")
187
+ return encoding.encode(text)
188
+ except Exception as e:
189
+ ASCIIColors.error(f"Could not use tiktoken, falling back to simple encoding: {e}")
190
+ return list(text.encode('utf-8'))
191
+
192
+ def detokenize(self, tokens: list) -> str:
193
+ """
194
+ Detokenize a list of tokens.
195
+ """
196
+ try:
197
+ encoding = tiktoken.get_encoding("cl100k_base")
198
+ return encoding.decode(tokens)
199
+ except Exception as e:
200
+ ASCIIColors.error(f"Could not use tiktoken, falling back to simple decoding: {e}")
201
+ return bytes(tokens).decode('utf-8', errors='ignore')
202
+
203
+ def count_tokens(self, text: str) -> int:
204
+ """
205
+ Count tokens from a text.
206
+ """
207
+ return len(self.tokenize(text))
208
+
209
+ def embed(self, text: str, **kwargs) -> List[float]:
210
+ """
211
+ Get embeddings for the input text.
212
+ """
213
+ ASCIIColors.warning("Perplexity does not offer a public embedding API. This method is not implemented.")
214
+ raise NotImplementedError("Perplexity binding does not support embeddings.")
215
+
216
+ def get_model_info(self) -> dict:
217
+ """Return information about the current model setup."""
218
+ return {
219
+ "name": self.binding_name,
220
+ "version": "1.0",
221
+ "host_address": API_BASE_URL,
222
+ "model_name": self.model_name,
223
+ "supports_vision": False,
224
+ "supports_structured_output": False
225
+ }
226
+
227
+ def list_models(self) -> List[Dict[str, str]]:
228
+ """
229
+ Lists available models. Perplexity API does not have a models endpoint,
230
+ so a hardcoded list is returned.
231
+ """
232
+ return sorted(_FALLBACK_MODELS, key=lambda x: x['display_name'])
233
+
234
+ def load_model(self, model_name: str) -> bool:
235
+ """Set the model name for subsequent operations."""
236
+ self.model_name = model_name
237
+ ASCIIColors.info(f"Perplexity model set to: {model_name}.")
238
+ return True
239
+
240
+ if __name__ == '__main__':
241
+ if 'PERPLEXITY_API_KEY' not in os.environ:
242
+ ASCIIColors.red("Error: PERPLEXITY_API_KEY environment variable not set.")
243
+ print("Please get your key from Perplexity AI and set it.")
244
+ exit(1)
245
+
246
+ ASCIIColors.yellow("--- Testing PerplexityBinding ---")
247
+
248
+ test_model_name = "llama-3.1-sonar-small-128k-online"
249
+
250
+ try:
251
+ # --- Initialization ---
252
+ ASCIIColors.cyan("\n--- Initializing Binding ---")
253
+ binding = PerplexityBinding(model_name=test_model_name)
254
+ ASCIIColors.green("Binding initialized successfully.")
255
+
256
+ # --- List Models ---
257
+ ASCIIColors.cyan("\n--- Listing Models (static list) ---")
258
+ models = binding.list_models()
259
+ if models:
260
+ ASCIIColors.green(f"Found {len(models)} models.")
261
+ for m in models:
262
+ print(f"- {m['model_name']} ({m['display_name']})")
263
+ else:
264
+ ASCIIColors.error("Failed to list models.")
265
+
266
+ # --- Count Tokens ---
267
+ ASCIIColors.cyan("\n--- Counting Tokens ---")
268
+ sample_text = "Hello, world! This is a test."
269
+ token_count = binding.count_tokens(sample_text)
270
+ ASCIIColors.green(f"Token count for '{sample_text}': {token_count}")
271
+
272
+ # --- Chat (Non-Streaming) ---
273
+ ASCIIColors.cyan("\n--- Chat (Non-Streaming) ---")
274
+ discussion_non_stream = LollmsDiscussion.from_messages(
275
+ messages=[
276
+ {"sender":"user", "content": "What is the capital of France?"}
277
+ ],
278
+ system_prompt="You are a helpful and concise assistant."
279
+ )
280
+ ASCIIColors.info(f"Prompt: What is the capital of France?")
281
+ generated_text = binding.chat(discussion_non_stream, n_predict=50, stream=False)
282
+ if isinstance(generated_text, str):
283
+ ASCIIColors.green(f"Generated text:\n{generated_text}")
284
+ else:
285
+ ASCIIColors.error(f"Generation failed: {generated_text}")
286
+
287
+ # --- Chat (Streaming) ---
288
+ ASCIIColors.cyan("\n--- Chat (Streaming) ---")
289
+
290
+ captured_chunks = []
291
+ def stream_callback(chunk: str, msg_type: int):
292
+ ASCIIColors.green(chunk, end="", flush=True)
293
+ captured_chunks.append(chunk)
294
+ return True
295
+
296
+ discussion_stream = LollmsDiscussion.from_messages(
297
+ messages=[
298
+ {"sender":"user", "content": "Explain the importance of bees in one short paragraph."}
299
+ ],
300
+ system_prompt="You are a helpful assistant."
301
+ )
302
+ ASCIIColors.info(f"Prompt: Explain the importance of bees in one short paragraph.")
303
+ result = binding.chat(
304
+ discussion_stream,
305
+ n_predict=150,
306
+ stream=True,
307
+ streaming_callback=stream_callback
308
+ )
309
+ print("\n--- End of Stream ---")
310
+ full_streamed_text = "".join(captured_chunks)
311
+ assert result == full_streamed_text
312
+
313
+ # --- Embeddings (Expected to fail) ---
314
+ ASCIIColors.cyan("\n--- Embeddings ---")
315
+ try:
316
+ binding.embed("This should not work.")
317
+ except NotImplementedError as e:
318
+ ASCIIColors.green(f"Successfully caught expected error for embeddings: {e}")
319
+ except Exception as e:
320
+ ASCIIColors.error(f"Caught an unexpected error for embeddings: {e}")
321
+
322
+ except Exception as e:
323
+ ASCIIColors.error(f"An error occurred during testing: {e}")
324
+ trace_exception(e)
325
+
326
+ ASCIIColors.yellow("\nPerplexityBinding test finished.")
@@ -422,7 +422,7 @@ class PythonLlamaCppBinding(LollmsLLMBinding):
422
422
  "config": self.llama_config
423
423
  }
424
424
 
425
- def listModels(self, force_rescan: bool = False) -> List[Dict[str, str]]: # type: ignore
425
+ def list_models(self, force_rescan: bool = False) -> List[Dict[str, str]]: # type: ignore
426
426
  """
427
427
  Lists available GGUF models.
428
428
 
@@ -528,10 +528,10 @@ if __name__ == '__main__':
528
528
 
529
529
  # --- List Models ---
530
530
  ASCIIColors.cyan("\n--- Listing Models (force_rescan=True) ---")
531
- model_list = active_binding.listModels(force_rescan=True)
531
+ model_list = active_binding.list_models(force_rescan=True)
532
532
  print(json.dumps(model_list, indent=2))
533
533
  assert len(model_list) == 2, "Model discovery failed to find all dummy models."
534
- assert any(m['loaded'] for m in model_list), "listModels did not correctly report a loaded model."
534
+ assert any(m['loaded'] for m in model_list), "list_models did not correctly report a loaded model."
535
535
 
536
536
 
537
537
  if is_dummy_model:
@@ -442,7 +442,7 @@ class VLLMBinding(LollmsLLMBinding):
442
442
  }
443
443
  return info
444
444
 
445
- def listModels(self) -> List[Dict[str, Any]]:
445
+ def list_models(self) -> List[Dict[str, Any]]:
446
446
  local_models = []
447
447
  if not self.models_folder.exists(): return []
448
448
  for item_path in self.models_folder.rglob('*'):