vectorvein 0.1.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.
@@ -0,0 +1,71 @@
1
+ # @Author: Bi Ying
2
+ # @Date: 2024-07-27 00:30:56
3
+ from typing import List, Dict
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+ from ..types import defaults as defs
8
+ from ..types.enums import BackendType
9
+ from ..types.llm_parameters import BackendSettings, EndpointSetting
10
+
11
+
12
+ class Settings(BaseModel):
13
+ endpoints: List[EndpointSetting] = Field(
14
+ default_factory=list, description="Available endpoints for the LLM service."
15
+ )
16
+
17
+ anthropic_models: BackendSettings = Field(
18
+ default_factory=BackendSettings, description="Anthropic models settings."
19
+ )
20
+ deepseek_models: BackendSettings = Field(default_factory=BackendSettings, description="Deepseek models settings.")
21
+ gemini_models: BackendSettings = Field(default_factory=BackendSettings, description="Gemini models settings.")
22
+ groq_models: BackendSettings = Field(default_factory=BackendSettings, description="Groq models settings.")
23
+ local_models: BackendSettings = Field(default_factory=BackendSettings, description="Local models settings.")
24
+ minimax_models: BackendSettings = Field(default_factory=BackendSettings, description="Minimax models settings.")
25
+ mistral_models: BackendSettings = Field(default_factory=BackendSettings, description="Mistral models settings.")
26
+ moonshot_models: BackendSettings = Field(default_factory=BackendSettings, description="Moonshot models settings.")
27
+ openai_models: BackendSettings = Field(default_factory=BackendSettings, description="OpenAI models settings.")
28
+ qwen_models: BackendSettings = Field(default_factory=BackendSettings, description="Qwen models settings.")
29
+ yi_models: BackendSettings = Field(default_factory=BackendSettings, description="Yi models settings.")
30
+ zhipuai_models: BackendSettings = Field(default_factory=BackendSettings, description="Zhipuai models settings.")
31
+
32
+ def __init__(self, **data):
33
+ model_types = {
34
+ "anthropic_models": defs.ANTHROPIC_MODELS,
35
+ "deepseek_models": defs.DEEPSEEK_MODELS,
36
+ "gemini_models": defs.GEMINI_MODELS,
37
+ "groq_models": defs.GROQ_MODELS,
38
+ "local_models": {},
39
+ "minimax_models": defs.MINIMAX_MODELS,
40
+ "mistral_models": defs.MISTRAL_MODELS,
41
+ "moonshot_models": defs.MOONSHOT_MODELS,
42
+ "openai_models": defs.OPENAI_MODELS,
43
+ "qwen_models": defs.QWEN_MODELS,
44
+ "yi_models": defs.YI_MODELS,
45
+ "zhipuai_models": defs.ZHIPUAI_MODELS,
46
+ }
47
+
48
+ for model_type, default_models in model_types.items():
49
+ if model_type in data:
50
+ model_settings = BackendSettings()
51
+ model_settings.update_models(default_models, data[model_type])
52
+ data[model_type] = model_settings
53
+ else:
54
+ data[model_type] = BackendSettings(models=default_models)
55
+
56
+ super().__init__(**data)
57
+
58
+ def load(self, settings_dict: Dict):
59
+ self.__init__(**settings_dict)
60
+
61
+ def get_endpoint(self, endpoint_id: str) -> EndpointSetting:
62
+ for endpoint in self.endpoints:
63
+ if endpoint.id == endpoint_id:
64
+ return endpoint
65
+ return EndpointSetting()
66
+
67
+ def get_backend(self, backend: BackendType) -> BackendSettings:
68
+ return getattr(self, f"{backend.value.lower()}_models")
69
+
70
+
71
+ settings = Settings()
@@ -0,0 +1,396 @@
1
+ # @Author: Bi Ying
2
+ # @Date: 2024-07-27 00:02:34
3
+ from .enums import ContextLengthControlType
4
+
5
+ CONTEXT_LENGTH_CONTROL = ContextLengthControlType.Latest
6
+
7
+ ENDPOINT_CONCURRENT_REQUESTS = 20
8
+ ENDPOINT_RPM = 60
9
+ ENDPOINT_TPM = 300000
10
+
11
+ MODEL_CONTEXT_LENGTH = 32768
12
+
13
+ # Moonshot models
14
+ MOONSHOT_MODELS = {
15
+ "moonshot-v1-8k": {
16
+ "id": "moonshot-v1-8k",
17
+ "context_length": 8000,
18
+ "function_call_available": True,
19
+ "response_format_available": True,
20
+ },
21
+ "moonshot-v1-32k": {
22
+ "id": "moonshot-v1-32k",
23
+ "context_length": 32000,
24
+ "function_call_available": True,
25
+ "response_format_available": True,
26
+ },
27
+ "moonshot-v1-128k": {
28
+ "id": "moonshot-v1-128k",
29
+ "context_length": 128000,
30
+ "function_call_available": True,
31
+ "response_format_available": True,
32
+ },
33
+ }
34
+ MOONSHOT_DEFAULT_MODEL = "moonshot-v1-8k"
35
+
36
+ # Deepseek models
37
+ DEEPSEEK_MODELS = {
38
+ "deepseek-chat": {
39
+ "id": "deepseek-chat",
40
+ "context_length": 128000,
41
+ "function_call_available": True,
42
+ "response_format_available": True,
43
+ },
44
+ "deepseek-coder": {
45
+ "id": "deepseek-chat",
46
+ "context_length": 128000,
47
+ "function_call_available": True,
48
+ "response_format_available": True,
49
+ },
50
+ }
51
+ DEEPSEEK_DEFAULT_MODEL = "deepseek-chat"
52
+
53
+ # Groq models
54
+ GROQ_DEFAULT_MODEL = "llama3-70b-8192"
55
+ GROQ_MODELS = {
56
+ "mixtral-8x7b-32768": {
57
+ "id": "mixtral-8x7b-32768",
58
+ "context_length": 32768,
59
+ "function_call_available": True,
60
+ "response_format_available": True,
61
+ },
62
+ "llama3-70b-8192": {
63
+ "id": "llama3-70b-8192",
64
+ "context_length": 8192,
65
+ "function_call_available": True,
66
+ "response_format_available": True,
67
+ },
68
+ "llama3-8b-8192": {
69
+ "id": "llama3-8b-8192",
70
+ "context_length": 8192,
71
+ "function_call_available": True,
72
+ "response_format_available": True,
73
+ },
74
+ "gemma-7b-it": {
75
+ "id": "gemma-7b-it",
76
+ "context_length": 8192,
77
+ "function_call_available": True,
78
+ "response_format_available": True,
79
+ },
80
+ }
81
+
82
+ # Qwen models
83
+ QWEN_DEFAULT_MODEL = "qwen2-72b-instruct"
84
+ QWEN_MODELS = {
85
+ "qwen1.5-1.8b-chat": {
86
+ "id": "qwen1.5-1.8b-chat",
87
+ "context_length": 30000,
88
+ "function_call_available": False,
89
+ "response_format_available": True,
90
+ },
91
+ "qwen1.5-4b-chat": {
92
+ "id": "qwen1.5-4b-chat",
93
+ "context_length": 30000,
94
+ "function_call_available": False,
95
+ "response_format_available": True,
96
+ },
97
+ "qwen1.5-7b-chat": {
98
+ "id": "qwen1.5-7b-chat",
99
+ "context_length": 30000,
100
+ "function_call_available": False,
101
+ "response_format_available": True,
102
+ },
103
+ "qwen1.5-14b-chat": {
104
+ "id": "qwen1.5-14b-chat",
105
+ "context_length": 30000,
106
+ "function_call_available": False,
107
+ "response_format_available": True,
108
+ },
109
+ "qwen1.5-32b-chat": {
110
+ "id": "qwen1.5-32b-chat",
111
+ "context_length": 30000,
112
+ "function_call_available": False,
113
+ "response_format_available": True,
114
+ },
115
+ "qwen1.5-72b-chat": {
116
+ "id": "qwen1.5-72b-chat",
117
+ "context_length": 30000,
118
+ "function_call_available": False,
119
+ "response_format_available": True,
120
+ },
121
+ "qwen1.5-110b-chat": {
122
+ "id": "qwen1.5-110b-chat",
123
+ "context_length": 30000,
124
+ "function_call_available": False,
125
+ "response_format_available": True,
126
+ },
127
+ "qwen2-72b-instruct": {
128
+ "id": "qwen2-72b-instruct",
129
+ "context_length": 30000,
130
+ "function_call_available": False,
131
+ "response_format_available": True,
132
+ },
133
+ }
134
+
135
+ # Yi models
136
+ YI_DEFAULT_MODEL = "yi-large-turbo"
137
+ YI_MODELS = {
138
+ "yi-large": {
139
+ "id": "yi-large",
140
+ "context_length": 32000,
141
+ "function_call_available": False,
142
+ "response_format_available": False,
143
+ },
144
+ "yi-large-turbo": {
145
+ "id": "yi-large-turbo",
146
+ "context_length": 16000,
147
+ "function_call_available": False,
148
+ "response_format_available": False,
149
+ },
150
+ "yi-large-fc": {
151
+ "id": "yi-large-fc",
152
+ "context_length": 32000,
153
+ "function_call_available": True,
154
+ "response_format_available": False,
155
+ },
156
+ "yi-medium": {
157
+ "id": "yi-medium",
158
+ "context_length": 16000,
159
+ "function_call_available": False,
160
+ "response_format_available": False,
161
+ },
162
+ "yi-medium-200k": {
163
+ "id": "yi-medium-200k",
164
+ "context_length": 200000,
165
+ "function_call_available": False,
166
+ "response_format_available": False,
167
+ },
168
+ "yi-spark": {
169
+ "id": "yi-spark",
170
+ "context_length": 16000,
171
+ "function_call_available": False,
172
+ "response_format_available": False,
173
+ },
174
+ "yi-vision": {
175
+ "id": "yi-vision",
176
+ "context_length": 4000,
177
+ "function_call_available": False,
178
+ "response_format_available": False,
179
+ },
180
+ }
181
+
182
+ # ZhiPuAI models
183
+ ZHIPUAI_DEFAULT_MODEL = "glm-4-air"
184
+ ZHIPUAI_MODELS = {
185
+ "glm-3-turbo": {
186
+ "id": "glm-3-turbo",
187
+ "context_length": 128000,
188
+ "function_call_available": True,
189
+ "response_format_available": False,
190
+ },
191
+ "glm-4": {
192
+ "id": "glm-4",
193
+ "context_length": 128000,
194
+ "function_call_available": True,
195
+ "response_format_available": False,
196
+ },
197
+ "glm-4-0520": {
198
+ "id": "glm-4-0520",
199
+ "context_length": 128000,
200
+ "function_call_available": True,
201
+ "response_format_available": False,
202
+ },
203
+ "glm-4-air": {
204
+ "id": "glm-4-air",
205
+ "context_length": 128000,
206
+ "function_call_available": True,
207
+ "response_format_available": False,
208
+ },
209
+ "glm-4-airx": {
210
+ "id": "glm-4-airx",
211
+ "context_length": 128000,
212
+ "function_call_available": True,
213
+ "response_format_available": False,
214
+ },
215
+ "glm-4-flash": {
216
+ "id": "glm-4-flash",
217
+ "context_length": 128000,
218
+ "function_call_available": True,
219
+ "response_format_available": False,
220
+ },
221
+ "glm-4v": {
222
+ "id": "glm-4v",
223
+ "context_length": 2000,
224
+ "function_call_available": False,
225
+ "response_format_available": False,
226
+ },
227
+ }
228
+
229
+ # Mistral models
230
+ MISTRAL_DEFAULT_MODEL = "mistral-small"
231
+ MISTRAL_MODELS = {
232
+ "open-mistral-7b": {
233
+ "id": "open-mistral-7b",
234
+ "context_length": 32000,
235
+ "function_call_available": False,
236
+ "response_format_available": True,
237
+ },
238
+ "open-mixtral-8x7b": {
239
+ "id": "open-mixtral-8x7b",
240
+ "context_length": 32000,
241
+ "function_call_available": False,
242
+ "response_format_available": True,
243
+ },
244
+ "open-mixtral-8x22b": {
245
+ "id": "open-mixtral-8x22b",
246
+ "context_length": 64000,
247
+ "function_call_available": True,
248
+ "response_format_available": True,
249
+ },
250
+ "open-mistral-nemo": {
251
+ "id": "open-mistral-nemo",
252
+ "context_length": 128000,
253
+ "function_call_available": False,
254
+ "response_format_available": True,
255
+ },
256
+ "codestral-latest": {
257
+ "id": "codestral-latest",
258
+ "context_length": 32000,
259
+ "function_call_available": False,
260
+ "response_format_available": True,
261
+ },
262
+ "mistral-small-latest": {
263
+ "id": "mistral-small-latest",
264
+ "context_length": 30000,
265
+ "function_call_available": True,
266
+ "response_format_available": True,
267
+ },
268
+ "mistral-medium-latest": {
269
+ "id": "mistral-medium-latest",
270
+ "context_length": 30000,
271
+ "function_call_available": False,
272
+ "response_format_available": True,
273
+ },
274
+ "mistral-large-latest": {
275
+ "id": "mistral-large-latest",
276
+ "context_length": 128000,
277
+ "function_call_available": True,
278
+ "response_format_available": True,
279
+ },
280
+ }
281
+
282
+ # OpenAI models
283
+ OPENAI_DEFAULT_MODEL = "gpt-4o"
284
+ OPENAI_MODELS = {
285
+ "gpt-35-turbo": {
286
+ "id": "gpt-35-turbo",
287
+ "context_length": 16385,
288
+ "function_call_available": True,
289
+ "response_format_available": True,
290
+ },
291
+ "gpt-4-turbo": {
292
+ "id": "gpt-4-turbo",
293
+ "context_length": 128000,
294
+ "function_call_available": True,
295
+ "response_format_available": True,
296
+ },
297
+ "gpt-4": {
298
+ "id": "gpt-4",
299
+ "context_length": 8192,
300
+ "function_call_available": True,
301
+ "response_format_available": True,
302
+ },
303
+ "gpt-4o": {
304
+ "id": "gpt-4o",
305
+ "context_length": 128000,
306
+ "function_call_available": True,
307
+ "response_format_available": True,
308
+ },
309
+ "gpt-4o-mini": {
310
+ "id": "gpt-4o-mini",
311
+ "context_length": 128000,
312
+ "function_call_available": True,
313
+ "response_format_available": True,
314
+ },
315
+ "gpt-4v": {
316
+ "id": "gpt-4v",
317
+ "context_length": 128000,
318
+ "function_call_available": True,
319
+ "response_format_available": True,
320
+ },
321
+ }
322
+
323
+ # Anthropic models
324
+ ANTHROPIC_DEFAULT_MODEL = "claude-3-5-sonnet-20240620"
325
+ ANTHROPIC_MODELS = {
326
+ "claude-3-opus-20240229": {
327
+ "id": "claude-3-opus-20240229",
328
+ "context_length": 200000,
329
+ "function_call_available": True,
330
+ "response_format_available": True,
331
+ },
332
+ "claude-3-sonnet-20240229": {
333
+ "id": "claude-3-sonnet-20240229",
334
+ "context_length": 200000,
335
+ "function_call_available": True,
336
+ "response_format_available": True,
337
+ },
338
+ "claude-3-haiku-20240307": {
339
+ "id": "claude-3-haiku-20240307",
340
+ "context_length": 200000,
341
+ "function_call_available": True,
342
+ "response_format_available": True,
343
+ },
344
+ "claude-3-5-sonnet-20240620": {
345
+ "id": "claude-3-5-sonnet-20240620",
346
+ "context_length": 200000,
347
+ "function_call_available": True,
348
+ "response_format_available": True,
349
+ },
350
+ }
351
+
352
+ # Minimax models
353
+ MINIMAX_DEFAULT_MODEL = "abab6.5s-chat"
354
+ MINIMAX_MODELS = {
355
+ "abab5-chat": {
356
+ "id": "abab5-chat",
357
+ "context_length": 6144,
358
+ "function_call_available": True,
359
+ "response_format_available": True,
360
+ },
361
+ "abab5.5-chat": {
362
+ "id": "abab5.5-chat",
363
+ "context_length": 16384,
364
+ "function_call_available": True,
365
+ "response_format_available": True,
366
+ },
367
+ "abab6-chat": {
368
+ "id": "abab6-chat",
369
+ "context_length": 32768,
370
+ "function_call_available": True,
371
+ "response_format_available": True,
372
+ },
373
+ "abab6.5s-chat": {
374
+ "id": "abab6.5s-chat",
375
+ "context_length": 245760,
376
+ "function_call_available": True,
377
+ "response_format_available": True,
378
+ },
379
+ }
380
+
381
+ # Gemini models
382
+ GEMINI_DEFAULT_MODEL = "gemini-1.5-pro"
383
+ GEMINI_MODELS = {
384
+ "gemini-1.5-pro": {
385
+ "id": "gemini-1.5-pro",
386
+ "context_length": 1048576,
387
+ "function_call_available": True,
388
+ "response_format_available": True,
389
+ },
390
+ "gemini-1.5-flash": {
391
+ "id": "gemini-1.5-flash",
392
+ "context_length": 1048576,
393
+ "function_call_available": True,
394
+ "response_format_available": True,
395
+ },
396
+ }
@@ -0,0 +1,83 @@
1
+ # @Author: Bi Ying
2
+ # @Date: 2024-07-26 23:52:52
3
+ from __future__ import annotations
4
+
5
+ from enum import Enum
6
+
7
+
8
+ class BackendType(str, Enum):
9
+ """BackendType enum class definition."""
10
+
11
+ # OpenAI
12
+ OpenAI = "openai"
13
+
14
+ # ZhiPuAI
15
+ ZhiPuAI = "zhipuai"
16
+
17
+ # MiniMax
18
+ MiniMax = "minimax"
19
+
20
+ # Moonshot
21
+ Moonshot = "moonshot"
22
+
23
+ # Anthropic
24
+ Anthropic = "anthropic"
25
+
26
+ # Mistral
27
+ Mistral = "mistral"
28
+
29
+ # DeepSeek
30
+ DeepSeek = "deepseek"
31
+
32
+ # Qwen
33
+ Qwen = "qwen"
34
+
35
+ # Groq
36
+ Groq = "groq"
37
+
38
+ # Local
39
+ Local = "local"
40
+
41
+ # Yi
42
+ Yi = "yi"
43
+
44
+ # Gemini
45
+ Gemini = "gemini"
46
+
47
+ def __repr__(self):
48
+ """Get a string representation."""
49
+ return f'"{self.value}"'
50
+
51
+
52
+ class LLMType(str, Enum):
53
+ """LLMType enum class definition."""
54
+
55
+ # Embeddings
56
+ OpenAIEmbedding = "openai_embedding"
57
+ AzureOpenAIEmbedding = "azure_openai_embedding"
58
+
59
+ # Raw Completion
60
+ OpenAI = "openai"
61
+ AzureOpenAI = "azure_openai"
62
+
63
+ # Chat Completion
64
+ OpenAIChat = "openai_chat"
65
+ AzureOpenAIChat = "azure_openai_chat"
66
+
67
+ # Debug
68
+ StaticResponse = "static_response"
69
+
70
+ def __repr__(self):
71
+ """Get a string representation."""
72
+ return f'"{self.value}"'
73
+
74
+
75
+ class ContextLengthControlType(str, Enum):
76
+ """ContextLengthControlType enum class definition."""
77
+
78
+ # latest
79
+ Latest = "latest"
80
+
81
+ def __repr__(self):
82
+ """Get a string representation."""
83
+ return f'"{self.value}"'
@@ -0,0 +1,69 @@
1
+ # @Author: Bi Ying
2
+ # @Date: 2024-07-26 23:48:04
3
+ from typing import List, Dict, Optional
4
+
5
+ from pydantic import BaseModel, Field
6
+ from openai.types.chat.chat_completion_message import ChatCompletionMessageToolCall
7
+
8
+ from . import defaults as defs
9
+
10
+
11
+ class EndpointSetting(BaseModel):
12
+ id: str = Field(..., description="The id of the endpoint.")
13
+ region: Optional[str] = Field(None, description="The region for the endpoint.")
14
+ api_base: str = Field(None, description="The base URL for the API.")
15
+ api_key: Optional[str] = Field(None, description="The API key for authentication.")
16
+ credentials: Optional[dict] = Field(None, description="Additional credentials if needed.")
17
+ is_azure: bool = Field(False, description="Indicates if the endpoint is for Azure.")
18
+ is_vertex: bool = Field(False, description="Indicates if the endpoint is for Vertex.")
19
+ rpm: int = Field(description="Requests per minute.", default=defs.ENDPOINT_RPM)
20
+ tpm: int = Field(description="Tokens per minute.", default=defs.ENDPOINT_TPM)
21
+ concurrent_requests: int = Field(
22
+ description="Whether to use concurrent requests for the LLM service.",
23
+ default=defs.ENDPOINT_CONCURRENT_REQUESTS,
24
+ )
25
+
26
+
27
+ class ModelSetting(BaseModel):
28
+ id: Optional[str] = Field(None, description="The id of the model.")
29
+ endpoints: List[str] = Field(default_factory=list, description="Available endpoints for the model.")
30
+ function_call_available: bool = Field(False, description="Indicates if function call is available.")
31
+ response_format_available: bool = Field(False, description="Indicates if response format is available.")
32
+ context_length: int = Field(32768, description="The context length for the model.")
33
+ max_output_tokens: Optional[int] = Field(None, description="Maximum number of output tokens allowed.")
34
+
35
+
36
+ class BackendSettings(BaseModel):
37
+ models: Dict[str, ModelSetting] = Field(default_factory=dict)
38
+
39
+ def update_models(self, default_models: Dict[str, Dict], input_models: Dict[str, Dict]):
40
+ updated_models = {}
41
+ for model_name, model_data in default_models.items():
42
+ updated_model = ModelSetting(**model_data)
43
+ if model_name in input_models:
44
+ updated_model = updated_model.model_copy(update=input_models[model_name])
45
+ updated_models[model_name] = updated_model
46
+
47
+ # Add any new models from input that weren't in defaults
48
+ for model_name, model_data in input_models.items():
49
+ if model_name not in updated_models:
50
+ updated_models[model_name] = ModelSetting(**model_data)
51
+
52
+ self.models = updated_models
53
+
54
+
55
+ class Usage(BaseModel):
56
+ completion_tokens: int
57
+ prompt_tokens: int
58
+ total_tokens: int
59
+
60
+
61
+ class ModelOutput(BaseModel):
62
+ content: Optional[str] = None
63
+
64
+ tool_calls: Optional[List[ChatCompletionMessageToolCall]] = None
65
+ """The tool calls generated by the model, such as function calls."""
66
+
67
+ function_call_arguments: Optional[dict] = None
68
+
69
+ usage: Optional[Usage] = None
@@ -0,0 +1,70 @@
1
+ # @Author: Bi Ying
2
+ # @Date: 2024-07-27 12:03:49
3
+ import base64
4
+ from io import BytesIO
5
+ from pathlib import Path
6
+ from functools import cached_property
7
+
8
+ import httpx
9
+ from PIL import Image
10
+
11
+
12
+
13
+ class ImageProcessor:
14
+ def __init__(self, image_source: Image.Image | str | Path, max_size: int | None = 5 * 1024 * 1024):
15
+ self.image_source = image_source
16
+ if isinstance(image_source, (Image.Image, Path)):
17
+ self.is_local = True
18
+ else:
19
+ self.is_local = not image_source.startswith("http")
20
+ self.max_size = max_size
21
+ self._image = self._load_image()
22
+
23
+ def _load_image(self):
24
+ if not self.is_local:
25
+ image_url = self.image_source
26
+ response = httpx.get(image_url)
27
+ return Image.open(BytesIO(response.content))
28
+ else:
29
+ return Image.open(self.image_source)
30
+
31
+ def _resize_image(self, img, max_size):
32
+ img_bytes = BytesIO()
33
+ img.save(img_bytes, format=img.format, optimize=True)
34
+
35
+ if img_bytes.getbuffer().nbytes <= max_size:
36
+ return img_bytes
37
+
38
+ original_size = img.size
39
+ scale_factor = 0.9
40
+
41
+ while True:
42
+ new_size = (int(original_size[0] * scale_factor), int(original_size[1] * scale_factor))
43
+ img_resized = img.resize(new_size, Image.Resampling.LANCZOS)
44
+
45
+ img_bytes_resized = BytesIO()
46
+ img_resized.save(img_bytes_resized, format=img.format, optimize=True)
47
+
48
+ if img_bytes_resized.getbuffer().nbytes <= max_size:
49
+ return img_bytes_resized
50
+
51
+ scale_factor -= 0.1
52
+ if scale_factor < 0.1:
53
+ return img_bytes_resized
54
+
55
+ @cached_property
56
+ def base64_image(self):
57
+ if self.max_size is None:
58
+ return base64.b64encode(self._image.getvalue()).decode()
59
+
60
+ img_bytes_resized = self._resize_image(self._image, self.max_size)
61
+ return base64.b64encode(img_bytes_resized.getvalue()).decode()
62
+
63
+ @cached_property
64
+ def mime_type(self):
65
+ return Image.MIME[self._image.format]
66
+
67
+ @cached_property
68
+ def data_url(self):
69
+ return f"data:{self.mime_type};base64,{self.base64_image}"
70
+