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

lollms_client/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- from lollms_client.lollms_core import LollmsClient, ELF_GENERATION_FORMAT, ELF_COMPLETION_FORMAT
1
+ from lollms_client.lollms_core import LollmsClient, ELF_COMPLETION_FORMAT
2
2
  from lollms_client.lollms_tasks import TasksLibrary
3
3
  from lollms_client.lollms_types import MSG_TYPE
4
4
  from lollms_client.lollms_personality import LollmsPersonality
@@ -0,0 +1 @@
1
+ # to be done
@@ -0,0 +1,302 @@
1
+ # bindings/lollms/binding.py
2
+ import requests
3
+ from lollms_client.lollms_llm_binding import LollmsLLMBinding
4
+ from lollms_client.lollms_types import MSG_TYPE
5
+ from lollms_client.lollms_utilities import encode_image
6
+ from lollms_client.lollms_types import ELF_COMPLETION_FORMAT
7
+ from ascii_colors import ASCIIColors, trace_exception
8
+ from typing import Optional, Callable, List, Union
9
+ import json
10
+
11
+ BindingName = "LollmsLLMBinding"
12
+
13
+ class LollmsLLMBinding(LollmsLLMBinding):
14
+ """LOLLMS-specific binding implementation"""
15
+
16
+ DEFAULT_HOST_ADDRESS = "http://localhost:9600"
17
+
18
+ def __init__(self,
19
+ host_address: str = None,
20
+ model_name: str = "",
21
+ service_key: str = None,
22
+ verify_ssl_certificate: bool = True,
23
+ personality: Optional[int] = None,
24
+ default_completion_format: ELF_COMPLETION_FORMAT = ELF_COMPLETION_FORMAT.Chat):
25
+ """
26
+ Initialize the LOLLMS binding.
27
+
28
+ Args:
29
+ host_address (str): Host address for the LOLLMS service. Defaults to DEFAULT_HOST_ADDRESS.
30
+ model_name (str): Name of the model to use. Defaults to empty string.
31
+ service_key (str): Authentication key for the service. Defaults to None.
32
+ verify_ssl_certificate (bool): Whether to verify SSL certificates. Defaults to True.
33
+ personality (Optional[int]): Personality ID for generation. Defaults to None.
34
+ """
35
+ super().__init__(
36
+ host_address=host_address if host_address is not None else self.DEFAULT_HOST_ADDRESS,
37
+ model_name=model_name,
38
+ service_key=service_key,
39
+ verify_ssl_certificate=verify_ssl_certificate,
40
+ default_completion_format=default_completion_format
41
+ )
42
+ self.personality = personality
43
+ self.model = None
44
+
45
+ def generate_text(self,
46
+ prompt: str,
47
+ images: Optional[List[str]] = None,
48
+ n_predict: Optional[int] = None,
49
+ stream: bool = False,
50
+ temperature: float = 0.1,
51
+ top_k: int = 50,
52
+ top_p: float = 0.95,
53
+ repeat_penalty: float = 0.8,
54
+ repeat_last_n: int = 40,
55
+ seed: Optional[int] = None,
56
+ n_threads: int = 8,
57
+ ctx_size: int | None = None,
58
+ streaming_callback: Optional[Callable[[str, str], None]] = None) -> Union[str, dict]:
59
+ """
60
+ Generate text using the LOLLMS service, with optional image support.
61
+
62
+ Args:
63
+ prompt (str): The input prompt for text generation.
64
+ images (Optional[List[str]]): List of image file paths for multimodal generation.
65
+ If provided, uses the /lollms_generate_with_images endpoint.
66
+ n_predict (Optional[int]): Maximum number of tokens to generate.
67
+ stream (bool): Whether to stream the output. Defaults to False.
68
+ temperature (float): Sampling temperature. Defaults to 0.1.
69
+ top_k (int): Top-k sampling parameter. Defaults to 50.
70
+ top_p (float): Top-p sampling parameter. Defaults to 0.95.
71
+ repeat_penalty (float): Penalty for repeated tokens. Defaults to 0.8.
72
+ repeat_last_n (int): Number of previous tokens to consider for repeat penalty. Defaults to 40.
73
+ seed (Optional[int]): Random seed for generation.
74
+ n_threads (int): Number of threads to use. Defaults to 8.
75
+ streaming_callback (Optional[Callable[[str, str], None]]): Callback for streaming output.
76
+ - First parameter (str): The chunk of text received from the stream.
77
+ - Second parameter (str): The message type (typically MSG_TYPE.MSG_TYPE_CHUNK).
78
+
79
+ Returns:
80
+ Union[str, dict]: Generated text if successful, or a dictionary with status and error if failed.
81
+ """
82
+ # Determine endpoint based on presence of images
83
+ endpoint = "/lollms_generate_with_images" if images else "/lollms_generate"
84
+ url = f"{self.host_address}{endpoint}"
85
+
86
+ # Set headers
87
+ headers = {
88
+ 'Content-Type': 'application/json',
89
+ }
90
+ if self.service_key:
91
+ headers['Authorization'] = f'Bearer {self.service_key}'
92
+
93
+ # Handle images if provided
94
+ image_data = []
95
+ if images:
96
+ for image_path in images:
97
+ try:
98
+ encoded_image = encode_image(image_path)
99
+ image_data.append(encoded_image)
100
+ except Exception as e:
101
+ return {"status": False, "error": f"Failed to process image {image_path}: {str(e)}"}
102
+
103
+ # Prepare request data
104
+ data = {
105
+ "prompt": prompt,
106
+ "model_name": self.model_name,
107
+ "personality": self.personality,
108
+ "n_predict": n_predict,
109
+ "stream": stream,
110
+ "temperature": temperature,
111
+ "top_k": top_k,
112
+ "top_p": top_p,
113
+ "repeat_penalty": repeat_penalty,
114
+ "repeat_last_n": repeat_last_n,
115
+ "seed": seed,
116
+ "n_threads": n_threads
117
+ }
118
+
119
+ if image_data:
120
+ data["images"] = image_data
121
+
122
+ # Make the request
123
+ response = requests.post(
124
+ url,
125
+ json=data,
126
+ headers=headers,
127
+ stream=stream,
128
+ verify=self.verify_ssl_certificate
129
+ )
130
+
131
+ if not stream:
132
+ if response.status_code == 200:
133
+ try:
134
+ text = response.text.strip().rstrip('!')
135
+ return text
136
+ except Exception as ex:
137
+ return {"status": False, "error": str(ex)}
138
+ else:
139
+ return {"status": False, "error": response.text}
140
+ else:
141
+ text = ""
142
+ if response.status_code == 200:
143
+ try:
144
+ for line in response.iter_lines():
145
+ chunk = line.decode("utf-8")
146
+ text += chunk
147
+ if streaming_callback:
148
+ streaming_callback(chunk, MSG_TYPE.MSG_TYPE_CHUNK)
149
+ # Handle potential quotes from streaming response
150
+ if text and text[0] == '"':
151
+ text = text[1:]
152
+ if text and text[-1] == '"':
153
+ text = text[:-1]
154
+ return text.rstrip('!')
155
+ except Exception as ex:
156
+ return {"status": False, "error": str(ex)}
157
+ else:
158
+ return {"status": False, "error": response.text}
159
+
160
+ def tokenize(self, text: str) -> list:
161
+ """
162
+ Tokenize the input text into a list of tokens using the /lollms_tokenize endpoint.
163
+
164
+ Args:
165
+ text (str): The text to tokenize.
166
+
167
+ Returns:
168
+ list: List of tokens.
169
+ """
170
+ try:
171
+ # Prepare the request payload
172
+ payload = {
173
+ "prompt": text,
174
+ "return_named": False # Set to True if you want named tokens
175
+ }
176
+
177
+ # Make the POST request to the /lollms_tokenize endpoint
178
+ response = requests.post(f"{self.host_address}/lollms_tokenize", json=payload)
179
+
180
+ # Check if the request was successful
181
+ if response.status_code == 200:
182
+ return response.json()
183
+ else:
184
+ raise Exception(f"Failed to tokenize text: {response.text}")
185
+ except Exception as ex:
186
+ trace_exception(ex)
187
+ raise Exception(f"Failed to tokenize text: {response.text}")
188
+
189
+ def detokenize(self, tokens: list) -> str:
190
+ """
191
+ Convert a list of tokens back to text using the /lollms_detokenize endpoint.
192
+
193
+ Args:
194
+ tokens (list): List of tokens to detokenize.
195
+
196
+ Returns:
197
+ str: Detokenized text.
198
+ """
199
+ try:
200
+ # Prepare the request payload
201
+ payload = {
202
+ "tokens": tokens,
203
+ "return_named": False # Set to True if you want named tokens
204
+ }
205
+
206
+ # Make the POST request to the /lollms_detokenize endpoint
207
+ response = requests.post(f"{self.host_address}/lollms_detokenize", json=payload)
208
+
209
+ # Check if the request was successful
210
+ if response.status_code == 200:
211
+ return response.json()
212
+ else:
213
+ raise Exception(f"Failed to detokenize tokens: {response.text}")
214
+ except Exception as ex:
215
+ return {"status": False, "error": str(ex)}
216
+
217
+
218
+ def embed(self, text: str, **kwargs) -> list:
219
+ """
220
+ Get embeddings for the input text using Ollama API
221
+
222
+ Args:
223
+ text (str or List[str]): Input text to embed
224
+ **kwargs: Additional arguments like model, truncate, options, keep_alive
225
+
226
+ Returns:
227
+ dict: Response containing embeddings
228
+ """
229
+ api_key = kwargs.pop("api_key", None)
230
+ headers = (
231
+ {"Content-Type": "application/json", "Authorization": api_key}
232
+ if api_key
233
+ else {"Content-Type": "application/json"}
234
+ )
235
+ embeddings = []
236
+ request_data = {"text": text}
237
+ response = requests.post(f"{self.host_address}/lollms_embed", json=request_data, headers=headers)
238
+ response.raise_for_status()
239
+ result = response.json()
240
+ return result["vector"]
241
+
242
+ def get_model_info(self) -> dict:
243
+ """
244
+ Return information about the current LOLLMS model.
245
+
246
+ Returns:
247
+ dict: Dictionary containing model name, version, host address, and personality.
248
+ """
249
+ return {
250
+ "name": "lollms",
251
+ "version": "1.0",
252
+ "host_address": self.host_address,
253
+ "model_name": self.model_name,
254
+ "personality": self.personality
255
+ }
256
+
257
+
258
+ def listModels(self) -> dict:
259
+ """Lists models"""
260
+ url = f"{self.host_address}/list_models"
261
+
262
+ response = requests.get(url)
263
+
264
+ if response.status_code == 200:
265
+ try:
266
+ text = json.loads(response.content.decode("utf-8"))
267
+ return text
268
+ except Exception as ex:
269
+ return {"status": False, "error": str(ex)}
270
+ else:
271
+ return {"status": False, "error": response.text}
272
+
273
+
274
+ def load_model(self, model_name: str) -> bool:
275
+ """
276
+ Load a specific model into the LOLLMS binding.
277
+
278
+ Args:
279
+ model_name (str): Name of the model to load.
280
+
281
+ Returns:
282
+ bool: True if model loaded successfully.
283
+ """
284
+ self.model = model_name
285
+ self.model_name = model_name
286
+ return True
287
+
288
+ # Lollms specific methods
289
+ def lollms_listMountedPersonalities(self, host_address:str=None):
290
+ host_address = host_address if host_address else self.host_address
291
+ url = f"{host_address}/list_mounted_personalities"
292
+
293
+ response = requests.get(url)
294
+
295
+ if response.status_code == 200:
296
+ try:
297
+ text = json.loads(response.content.decode("utf-8"))
298
+ return text
299
+ except Exception as ex:
300
+ return {"status": False, "error": str(ex)}
301
+ else:
302
+ return {"status": False, "error": response.text}
@@ -0,0 +1,297 @@
1
+ # bindings/ollama/binding.py
2
+ import requests
3
+ import json
4
+ from lollms_client.lollms_llm_binding import LollmsLLMBinding
5
+ from lollms_client.lollms_types import MSG_TYPE
6
+ from lollms_client.lollms_utilities import encode_image
7
+ from lollms_client.lollms_types import ELF_COMPLETION_FORMAT
8
+ from typing import Optional, Callable, List, Union
9
+ from ascii_colors import ASCIIColors, trace_exception
10
+
11
+ BindingName = "OllamaBinding"
12
+
13
+
14
+ class OllamaBinding(LollmsLLMBinding):
15
+ """Ollama-specific binding implementation"""
16
+
17
+ DEFAULT_HOST_ADDRESS = "http://localhost:11434"
18
+
19
+ def __init__(self,
20
+ host_address: str = None,
21
+ model_name: str = "",
22
+ service_key: str = None,
23
+ verify_ssl_certificate: bool = True,
24
+ default_completion_format: ELF_COMPLETION_FORMAT = ELF_COMPLETION_FORMAT.Chat
25
+ ):
26
+ """
27
+ Initialize the Ollama binding.
28
+
29
+ Args:
30
+ host_address (str): Host address for the Ollama service. Defaults to DEFAULT_HOST_ADDRESS.
31
+ model_name (str): Name of the model to use. Defaults to empty string.
32
+ service_key (str): Authentication key for the service. Defaults to None.
33
+ verify_ssl_certificate (bool): Whether to verify SSL certificates. Defaults to True.
34
+ personality (Optional[int]): Ignored parameter for compatibility with LollmsLLMBinding.
35
+ """
36
+ super().__init__(
37
+ host_address=host_address if host_address is not None else self.DEFAULT_HOST_ADDRESS,
38
+ model_name=model_name,
39
+ service_key=service_key,
40
+ verify_ssl_certificate=verify_ssl_certificate,
41
+ default_completion_format=default_completion_format
42
+ )
43
+ self.model = None
44
+
45
+ def generate_text(self,
46
+ prompt: str,
47
+ images: Optional[List[str]] = None,
48
+ n_predict: Optional[int] = None,
49
+ stream: bool = False,
50
+ temperature: float = 0.1,
51
+ top_k: int = 50,
52
+ top_p: float = 0.95,
53
+ repeat_penalty: float = 0.8,
54
+ repeat_last_n: int = 40,
55
+ seed: Optional[int] = None,
56
+ n_threads: int = 8,
57
+ ctx_size: int | None = None,
58
+ streaming_callback: Optional[Callable[[str, str], None]] = None) -> Union[str, dict]:
59
+ """
60
+ Generate text using the Ollama service, with optional image support.
61
+
62
+ Args:
63
+ prompt (str): The input prompt for text generation.
64
+ images (Optional[List[str]]): List of image file paths for multimodal generation.
65
+ If provided, uses the /api endpoint with message format.
66
+ n_predict (Optional[int]): Maximum number of tokens to generate.
67
+ stream (bool): Whether to stream the output. Defaults to False.
68
+ temperature (float): Sampling temperature. Defaults to 0.1.
69
+ top_k (int): Top-k sampling parameter. Defaults to 50 (not used in Ollama API directly).
70
+ top_p (float): Top-p sampling parameter. Defaults to 0.95 (not used in Ollama API directly).
71
+ repeat_penalty (float): Penalty for repeated tokens. Defaults to 0.8 (not used in Ollama API directly).
72
+ repeat_last_n (int): Number of previous tokens to consider for repeat penalty. Defaults to 40 (not used).
73
+ seed (Optional[int]): Random seed for generation.
74
+ n_threads (int): Number of threads to use. Defaults to 8 (not used in Ollama API directly).
75
+ streaming_callback (Optional[Callable[[str, str], None]]): Callback for streaming output.
76
+ - First parameter (str): The chunk of text received from the stream.
77
+ - Second parameter (str): The message type (typically MSG_TYPE.MSG_TYPE_CHUNK).
78
+
79
+ Returns:
80
+ Union[str, dict]: Generated text if successful, or a dictionary with status and error if failed.
81
+
82
+ Note:
83
+ Some parameters (top_k, top_p, repeat_penalty, repeat_last_n, n_threads) are included for interface
84
+ consistency but are not directly used in the Ollama API implementation.
85
+ """
86
+ # Set headers
87
+ headers = {
88
+ 'Content-Type': 'application/json',
89
+ }
90
+ if self.service_key:
91
+ headers['Authorization'] = f'Bearer {self.service_key}'
92
+
93
+ # Clean host address
94
+ host_address = self.host_address.rstrip('/')
95
+
96
+ # Prepare data based on whether images are provided
97
+ if images:
98
+ # Multimodal generation using /api endpoint
99
+ images_list = [encode_image(image_path) for image_path in images]
100
+ data = {
101
+ 'model': self.model_name,
102
+ 'messages': [{
103
+ "role": "user",
104
+ "content": [
105
+ {"type": "text", "text": prompt}
106
+ ] + [
107
+ {
108
+ "type": "image_url",
109
+ "image_url": {"url": f"data:image/jpeg;base64,{img}"}
110
+ } for img in images_list
111
+ ]
112
+ }],
113
+ "stream": stream,
114
+ "temperature": float(temperature),
115
+ "max_tokens": n_predict,
116
+ }
117
+ if ctx_size is not None:
118
+ data["num_ctx"] = ctx_size
119
+ url = f'{host_address}/api/chat'
120
+ else:
121
+ # Text-only generation using /api/generate endpoint
122
+ data = {
123
+ 'model': self.model_name,
124
+ 'prompt': prompt,
125
+ "stream": stream,
126
+ "temperature": float(temperature),
127
+ "max_tokens": n_predict
128
+ }
129
+ url = f'{host_address}/api/generate'
130
+
131
+ # Make the request
132
+ response = requests.post(url, json=data, headers=headers, stream=stream)
133
+
134
+ # Handle response
135
+ if not stream:
136
+ if response.status_code == 200:
137
+ try:
138
+ if images:
139
+ # For multimodal, response is in chat format
140
+ return response.json()["message"]["content"]
141
+ else:
142
+ # For text-only
143
+ return response.json()["response"]
144
+ except Exception as ex:
145
+ return {"status": False, "error": str(ex)}
146
+ elif response.status_code == 404:
147
+ ASCIIColors.error(response.content.decode("utf-8", errors='ignore'))
148
+ return {"status": False, "error": "404 Not Found"}
149
+ else:
150
+ return {"status": False, "error": response.text}
151
+ else:
152
+ text = ""
153
+ if response.status_code == 200:
154
+ try:
155
+ for line in response.iter_lines():
156
+ decoded = line.decode("utf-8")
157
+ if images:
158
+ # Streaming with images (chat format)
159
+ if decoded.startswith("data: "):
160
+ json_data = json.loads(decoded[5:].strip())
161
+ chunk = json_data["message"]["content"] if "message" in json_data else ""
162
+ else:
163
+ continue
164
+ else:
165
+ # Streaming without images (generate format)
166
+ json_data = json.loads(decoded)
167
+ chunk = json_data["response"]
168
+
169
+ text += chunk
170
+ if streaming_callback:
171
+ if not streaming_callback(chunk, MSG_TYPE.MSG_TYPE_CHUNK):
172
+ break
173
+ return text
174
+ except Exception as ex:
175
+ return {"status": False, "error": str(ex)}
176
+ elif response.status_code == 404:
177
+ ASCIIColors.error(response.content.decode("utf-8", errors='ignore'))
178
+ return {"status": False, "error": "404 Not Found"}
179
+ elif response.status_code == 400:
180
+ try:
181
+ content = json.loads(response.content.decode("utf8"))
182
+ return {"status": False, "error": content.get("error", {}).get("message", content.get("message", "Unknown error"))}
183
+ except:
184
+ return {"status": False, "error": response.content.decode("utf8")}
185
+ else:
186
+ return {"status": False, "error": response.text}
187
+
188
+ def tokenize(self, text: str) -> list:
189
+ """
190
+ Tokenize the input text into a list of characters.
191
+
192
+ Args:
193
+ text (str): The text to tokenize.
194
+
195
+ Returns:
196
+ list: List of individual characters.
197
+ """
198
+ return list(text)
199
+
200
+ def detokenize(self, tokens: list) -> str:
201
+ """
202
+ Convert a list of tokens back to text.
203
+
204
+ Args:
205
+ tokens (list): List of tokens (characters) to detokenize.
206
+
207
+ Returns:
208
+ str: Detokenized text.
209
+ """
210
+ return "".join(tokens)
211
+
212
+ def embed(self, text: str, **kwargs) -> list:
213
+ """
214
+ Get embeddings for the input text using Ollama API
215
+
216
+ Args:
217
+ text (str or List[str]): Input text to embed
218
+ **kwargs: Additional arguments like model, truncate, options, keep_alive
219
+
220
+ Returns:
221
+ dict: Response containing embeddings
222
+ """
223
+ import requests
224
+
225
+ url = f"{self.base_url}/api/embed"
226
+
227
+ # Prepare the request payload
228
+ payload = {
229
+ "input": text,
230
+ "model": kwargs.get("model", "llama2") # default model
231
+ }
232
+
233
+ # Add optional parameters if provided
234
+ if "truncate" in kwargs:
235
+ payload["truncate"] = kwargs["truncate"]
236
+ if "options" in kwargs:
237
+ payload["options"] = kwargs["options"]
238
+ if "keep_alive" in kwargs:
239
+ payload["keep_alive"] = kwargs["keep_alive"]
240
+
241
+ try:
242
+ response = requests.post(url, json=payload)
243
+ response.raise_for_status() # Raise exception for bad status codes
244
+ return response.json()
245
+ except requests.exceptions.RequestException as e:
246
+ raise Exception(f"Embedding request failed: {str(e)}")
247
+
248
+
249
+ def get_model_info(self) -> dict:
250
+ """
251
+ Return information about the current Ollama model.
252
+
253
+ Returns:
254
+ dict: Dictionary containing model name, version, and host address.
255
+ """
256
+ return {
257
+ "name": "ollama",
258
+ "version": "2.0",
259
+ "host_address": self.host_address,
260
+ "model_name": self.model_name
261
+ }
262
+ def listModels(self):
263
+ """ Lists available models """
264
+ url = f'{self.host_address}/api/tags'
265
+ headers = {
266
+ 'accept': 'application/json',
267
+ 'Authorization': f'Bearer {self.service_key}'
268
+ }
269
+ response = requests.get(url, headers=headers, verify= self.verify_ssl_certificate)
270
+ try:
271
+ ASCIIColors.debug("Listing ollama models")
272
+ data = response.json()
273
+ model_info = []
274
+
275
+ for model in data['models']:
276
+ model_name = model['name']
277
+ owned_by = ""
278
+ created_datetime = model["modified_at"]
279
+ model_info.append({'model_name': model_name, 'owned_by': owned_by, 'created_datetime': created_datetime})
280
+
281
+ return model_info
282
+ except Exception as ex:
283
+ trace_exception(ex)
284
+ return []
285
+ def load_model(self, model_name: str) -> bool:
286
+ """
287
+ Load a specific model into the Ollama binding.
288
+
289
+ Args:
290
+ model_name (str): Name of the model to load.
291
+
292
+ Returns:
293
+ bool: True if model loaded successfully.
294
+ """
295
+ self.model = model_name
296
+ self.model_name = model_name
297
+ return True