abstractcore 2.4.3__py3-none-any.whl → 2.4.5__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.
- abstractcore/cli/__init__.py +9 -0
- abstractcore/cli/main.py +759 -0
- abstractcore/cli/vision_config.py +491 -0
- abstractcore/media/handlers/__init__.py +16 -0
- abstractcore/media/handlers/anthropic_handler.py +326 -0
- abstractcore/media/handlers/local_handler.py +541 -0
- abstractcore/media/handlers/openai_handler.py +281 -0
- abstractcore/media/processors/__init__.py +13 -0
- abstractcore/media/processors/image_processor.py +610 -0
- abstractcore/media/processors/office_processor.py +490 -0
- abstractcore/media/processors/pdf_processor.py +485 -0
- abstractcore/media/processors/text_processor.py +557 -0
- abstractcore/media/utils/__init__.py +22 -0
- abstractcore/media/utils/image_scaler.py +306 -0
- abstractcore/providers/base.py +97 -0
- abstractcore/providers/huggingface_provider.py +17 -8
- abstractcore/utils/version.py +1 -1
- {abstractcore-2.4.3.dist-info → abstractcore-2.4.5.dist-info}/METADATA +1 -1
- {abstractcore-2.4.3.dist-info → abstractcore-2.4.5.dist-info}/RECORD +23 -9
- {abstractcore-2.4.3.dist-info → abstractcore-2.4.5.dist-info}/entry_points.txt +2 -0
- {abstractcore-2.4.3.dist-info → abstractcore-2.4.5.dist-info}/WHEEL +0 -0
- {abstractcore-2.4.3.dist-info → abstractcore-2.4.5.dist-info}/licenses/LICENSE +0 -0
- {abstractcore-2.4.3.dist-info → abstractcore-2.4.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Image scaling utility for AbstractCore media handling.
|
|
3
|
+
|
|
4
|
+
Provides intelligent image scaling based on model-specific requirements
|
|
5
|
+
and capabilities for vision models.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Tuple, Optional, Union, Dict, Any
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from PIL import Image, ImageOps
|
|
15
|
+
PIL_AVAILABLE = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
PIL_AVAILABLE = False
|
|
18
|
+
|
|
19
|
+
from ..base import MediaProcessingError
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ScalingMode(Enum):
|
|
23
|
+
"""Image scaling modes."""
|
|
24
|
+
FIT = "fit" # Scale to fit within target size, maintaining aspect ratio
|
|
25
|
+
FILL = "fill" # Scale to fill target size, may crop, maintaining aspect ratio
|
|
26
|
+
STRETCH = "stretch" # Stretch to exact target size, may distort aspect ratio
|
|
27
|
+
PAD = "pad" # Scale to fit and pad with background to exact target size
|
|
28
|
+
CROP_CENTER = "crop_center" # Scale to fill and crop from center
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ModelOptimizedScaler:
|
|
32
|
+
"""
|
|
33
|
+
Intelligent image scaler that optimizes images for specific vision models.
|
|
34
|
+
|
|
35
|
+
Uses model capability information to determine optimal scaling strategies.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self):
|
|
39
|
+
self.logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
if not PIL_AVAILABLE:
|
|
42
|
+
raise MediaProcessingError("PIL (Pillow) is required for image scaling")
|
|
43
|
+
|
|
44
|
+
def get_optimal_resolution(self, model_name: str, original_size: Tuple[int, int],
|
|
45
|
+
model_capabilities: Optional[Dict[str, Any]] = None) -> Tuple[int, int]:
|
|
46
|
+
"""
|
|
47
|
+
Get optimal resolution for a specific model.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
model_name: Name of the model
|
|
51
|
+
original_size: Original image size (width, height)
|
|
52
|
+
model_capabilities: Model capability information
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Optimal target size (width, height)
|
|
56
|
+
"""
|
|
57
|
+
if model_capabilities is None:
|
|
58
|
+
model_capabilities = self._get_model_capabilities(model_name)
|
|
59
|
+
|
|
60
|
+
max_resolution = model_capabilities.get("max_image_resolution", "variable")
|
|
61
|
+
image_patch_size = model_capabilities.get("image_patch_size", 16)
|
|
62
|
+
adaptive_windowing = model_capabilities.get("adaptive_windowing", False)
|
|
63
|
+
|
|
64
|
+
# Handle different resolution strategies
|
|
65
|
+
if max_resolution == "variable":
|
|
66
|
+
return self._optimize_variable_resolution(original_size, image_patch_size)
|
|
67
|
+
elif max_resolution == "896x896":
|
|
68
|
+
return (896, 896)
|
|
69
|
+
elif max_resolution == "3584x3584":
|
|
70
|
+
return self._optimize_large_resolution(original_size, (3584, 3584), image_patch_size)
|
|
71
|
+
elif "x" in str(max_resolution):
|
|
72
|
+
# Parse specific resolution like "1568x1568"
|
|
73
|
+
w, h = map(int, str(max_resolution).split("x"))
|
|
74
|
+
return (w, h)
|
|
75
|
+
else:
|
|
76
|
+
# Default fallback
|
|
77
|
+
return self._optimize_variable_resolution(original_size, image_patch_size)
|
|
78
|
+
|
|
79
|
+
def _optimize_variable_resolution(self, original_size: Tuple[int, int],
|
|
80
|
+
patch_size: int = 16) -> Tuple[int, int]:
|
|
81
|
+
"""Optimize for variable resolution models like Qwen3-VL."""
|
|
82
|
+
width, height = original_size
|
|
83
|
+
|
|
84
|
+
# For variable resolution, aim for reasonable size that's efficient
|
|
85
|
+
# while maintaining good quality
|
|
86
|
+
max_dimension = 1024 # Good balance for most use cases
|
|
87
|
+
|
|
88
|
+
# Scale down if too large
|
|
89
|
+
if max(width, height) > max_dimension:
|
|
90
|
+
if width > height:
|
|
91
|
+
new_width = max_dimension
|
|
92
|
+
new_height = int(height * (max_dimension / width))
|
|
93
|
+
else:
|
|
94
|
+
new_height = max_dimension
|
|
95
|
+
new_width = int(width * (max_dimension / height))
|
|
96
|
+
else:
|
|
97
|
+
new_width, new_height = width, height
|
|
98
|
+
|
|
99
|
+
# Round to nearest patch size multiple for efficiency
|
|
100
|
+
new_width = ((new_width + patch_size - 1) // patch_size) * patch_size
|
|
101
|
+
new_height = ((new_height + patch_size - 1) // patch_size) * patch_size
|
|
102
|
+
|
|
103
|
+
return (new_width, new_height)
|
|
104
|
+
|
|
105
|
+
def _optimize_large_resolution(self, original_size: Tuple[int, int],
|
|
106
|
+
max_size: Tuple[int, int],
|
|
107
|
+
patch_size: int = 14) -> Tuple[int, int]:
|
|
108
|
+
"""Optimize for large resolution models like Qwen2.5-VL."""
|
|
109
|
+
width, height = original_size
|
|
110
|
+
max_width, max_height = max_size
|
|
111
|
+
|
|
112
|
+
# Scale to fit within max size while maintaining aspect ratio
|
|
113
|
+
scale = min(max_width / width, max_height / height)
|
|
114
|
+
|
|
115
|
+
if scale < 1: # Only scale down, never up
|
|
116
|
+
new_width = int(width * scale)
|
|
117
|
+
new_height = int(height * scale)
|
|
118
|
+
else:
|
|
119
|
+
new_width, new_height = width, height
|
|
120
|
+
|
|
121
|
+
# Round to nearest patch size multiple
|
|
122
|
+
new_width = ((new_width + patch_size - 1) // patch_size) * patch_size
|
|
123
|
+
new_height = ((new_height + patch_size - 1) // patch_size) * patch_size
|
|
124
|
+
|
|
125
|
+
return (new_width, new_height)
|
|
126
|
+
|
|
127
|
+
def scale_image(self, image: Image.Image, target_size: Tuple[int, int],
|
|
128
|
+
mode: ScalingMode = ScalingMode.FIT,
|
|
129
|
+
background_color: Tuple[int, int, int] = (255, 255, 255)) -> Image.Image:
|
|
130
|
+
"""
|
|
131
|
+
Scale image to target size using specified mode.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
image: PIL Image to scale
|
|
135
|
+
target_size: Target size (width, height)
|
|
136
|
+
mode: Scaling mode
|
|
137
|
+
background_color: Background color for padding (RGB)
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Scaled PIL Image
|
|
141
|
+
"""
|
|
142
|
+
target_width, target_height = target_size
|
|
143
|
+
|
|
144
|
+
if mode == ScalingMode.FIT:
|
|
145
|
+
# Scale to fit within target size, maintaining aspect ratio
|
|
146
|
+
image.thumbnail((target_width, target_height), Image.Resampling.LANCZOS)
|
|
147
|
+
return image
|
|
148
|
+
|
|
149
|
+
elif mode == ScalingMode.FILL:
|
|
150
|
+
# Scale to fill target size, may crop
|
|
151
|
+
return ImageOps.fit(image, target_size, Image.Resampling.LANCZOS)
|
|
152
|
+
|
|
153
|
+
elif mode == ScalingMode.STRETCH:
|
|
154
|
+
# Stretch to exact target size
|
|
155
|
+
return image.resize(target_size, Image.Resampling.LANCZOS)
|
|
156
|
+
|
|
157
|
+
elif mode == ScalingMode.PAD:
|
|
158
|
+
# Scale to fit and pad to exact size
|
|
159
|
+
image.thumbnail((target_width, target_height), Image.Resampling.LANCZOS)
|
|
160
|
+
|
|
161
|
+
# Create new image with background color
|
|
162
|
+
new_image = Image.new('RGB', target_size, background_color)
|
|
163
|
+
|
|
164
|
+
# Paste scaled image centered
|
|
165
|
+
paste_x = (target_width - image.width) // 2
|
|
166
|
+
paste_y = (target_height - image.height) // 2
|
|
167
|
+
new_image.paste(image, (paste_x, paste_y))
|
|
168
|
+
|
|
169
|
+
return new_image
|
|
170
|
+
|
|
171
|
+
elif mode == ScalingMode.CROP_CENTER:
|
|
172
|
+
# Scale to fill and crop from center
|
|
173
|
+
return ImageOps.fit(image, target_size, Image.Resampling.LANCZOS, centering=(0.5, 0.5))
|
|
174
|
+
|
|
175
|
+
else:
|
|
176
|
+
raise MediaProcessingError(f"Unknown scaling mode: {mode}")
|
|
177
|
+
|
|
178
|
+
def scale_for_model(self, image: Image.Image, model_name: str,
|
|
179
|
+
scaling_mode: ScalingMode = ScalingMode.FIT,
|
|
180
|
+
model_capabilities: Optional[Dict[str, Any]] = None) -> Image.Image:
|
|
181
|
+
"""
|
|
182
|
+
Scale image optimally for a specific model.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
image: PIL Image to scale
|
|
186
|
+
model_name: Name of the target model
|
|
187
|
+
scaling_mode: How to scale the image
|
|
188
|
+
model_capabilities: Model capability information
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Optimally scaled PIL Image for the model
|
|
192
|
+
"""
|
|
193
|
+
original_size = image.size
|
|
194
|
+
target_size = self.get_optimal_resolution(model_name, original_size, model_capabilities)
|
|
195
|
+
|
|
196
|
+
self.logger.debug(f"Scaling image for {model_name}: {original_size} -> {target_size}")
|
|
197
|
+
|
|
198
|
+
# For fixed resolution models, always use PAD mode to maintain exact size
|
|
199
|
+
if model_capabilities and model_capabilities.get("max_image_resolution") == "896x896":
|
|
200
|
+
scaling_mode = ScalingMode.PAD
|
|
201
|
+
|
|
202
|
+
return self.scale_image(image, target_size, scaling_mode)
|
|
203
|
+
|
|
204
|
+
def _get_model_capabilities(self, model_name: str) -> Dict[str, Any]:
|
|
205
|
+
"""
|
|
206
|
+
Get model capabilities from the capabilities JSON.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
model_name: Name of the model
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Model capabilities dictionary
|
|
213
|
+
"""
|
|
214
|
+
try:
|
|
215
|
+
from ..capabilities import get_media_capabilities
|
|
216
|
+
return get_media_capabilities(model_name).__dict__
|
|
217
|
+
except ImportError:
|
|
218
|
+
# Fallback capability detection
|
|
219
|
+
return self._fallback_model_capabilities(model_name)
|
|
220
|
+
|
|
221
|
+
def _fallback_model_capabilities(self, model_name: str) -> Dict[str, Any]:
|
|
222
|
+
"""Fallback capability detection when capabilities module not available."""
|
|
223
|
+
model_lower = model_name.lower()
|
|
224
|
+
|
|
225
|
+
# Gemma models - fixed 896x896
|
|
226
|
+
if any(gem in model_lower for gem in ["gemma3", "gemma-3n"]):
|
|
227
|
+
return {
|
|
228
|
+
"max_image_resolution": "896x896",
|
|
229
|
+
"image_patch_size": 16,
|
|
230
|
+
"adaptive_windowing": True
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# Qwen2.5-VL models - up to 3584x3584
|
|
234
|
+
elif "qwen2.5" in model_lower and "vl" in model_lower:
|
|
235
|
+
return {
|
|
236
|
+
"max_image_resolution": "3584x3584",
|
|
237
|
+
"image_patch_size": 14,
|
|
238
|
+
"pixel_grouping": "28x28"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
# Qwen3-VL models - variable resolution
|
|
242
|
+
elif "qwen3" in model_lower and "vl" in model_lower:
|
|
243
|
+
return {
|
|
244
|
+
"max_image_resolution": "variable",
|
|
245
|
+
"image_patch_size": 16,
|
|
246
|
+
"pixel_grouping": "32x32"
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
# Claude models - up to 1568x1568
|
|
250
|
+
elif "claude" in model_lower:
|
|
251
|
+
return {
|
|
252
|
+
"max_image_resolution": "1568x1568",
|
|
253
|
+
"image_patch_size": 14
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
# Default fallback
|
|
257
|
+
else:
|
|
258
|
+
return {
|
|
259
|
+
"max_image_resolution": "variable",
|
|
260
|
+
"image_patch_size": 16
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
# Convenience functions for easy usage
|
|
265
|
+
_scaler_instance = None
|
|
266
|
+
|
|
267
|
+
def get_scaler() -> ModelOptimizedScaler:
|
|
268
|
+
"""Get shared scaler instance."""
|
|
269
|
+
global _scaler_instance
|
|
270
|
+
if _scaler_instance is None:
|
|
271
|
+
_scaler_instance = ModelOptimizedScaler()
|
|
272
|
+
return _scaler_instance
|
|
273
|
+
|
|
274
|
+
def scale_image_for_model(image: Union[Image.Image, str, Path],
|
|
275
|
+
model_name: str,
|
|
276
|
+
scaling_mode: ScalingMode = ScalingMode.FIT) -> Image.Image:
|
|
277
|
+
"""
|
|
278
|
+
Convenience function to scale an image for a specific model.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
image: PIL Image, or path to image file
|
|
282
|
+
model_name: Name of the target model
|
|
283
|
+
scaling_mode: How to scale the image
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Optimally scaled PIL Image
|
|
287
|
+
"""
|
|
288
|
+
if isinstance(image, (str, Path)):
|
|
289
|
+
image = Image.open(image)
|
|
290
|
+
|
|
291
|
+
scaler = get_scaler()
|
|
292
|
+
return scaler.scale_for_model(image, model_name, scaling_mode)
|
|
293
|
+
|
|
294
|
+
def get_optimal_size_for_model(model_name: str, original_size: Tuple[int, int]) -> Tuple[int, int]:
|
|
295
|
+
"""
|
|
296
|
+
Get optimal image size for a specific model.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
model_name: Name of the target model
|
|
300
|
+
original_size: Original image size (width, height)
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
Optimal target size (width, height)
|
|
304
|
+
"""
|
|
305
|
+
scaler = get_scaler()
|
|
306
|
+
return scaler.get_optimal_resolution(model_name, original_size)
|
abstractcore/providers/base.py
CHANGED
|
@@ -867,6 +867,103 @@ class BaseProvider(AbstractCoreInterface, ABC):
|
|
|
867
867
|
"""
|
|
868
868
|
pass
|
|
869
869
|
|
|
870
|
+
def health(self, timeout: Optional[float] = 5.0) -> Dict[str, Any]:
|
|
871
|
+
"""
|
|
872
|
+
Check provider health and connectivity.
|
|
873
|
+
|
|
874
|
+
This method tests if the provider is online and accessible by attempting
|
|
875
|
+
to list available models. A successful model listing indicates the provider
|
|
876
|
+
is healthy and ready to serve requests.
|
|
877
|
+
|
|
878
|
+
Args:
|
|
879
|
+
timeout: Maximum time in seconds to wait for health check (default: 5.0).
|
|
880
|
+
None means unlimited timeout (not recommended for health checks).
|
|
881
|
+
|
|
882
|
+
Returns:
|
|
883
|
+
Dict with health status information:
|
|
884
|
+
{
|
|
885
|
+
"status": bool, # True if provider is healthy/online
|
|
886
|
+
"provider": str, # Provider class name (e.g., "OpenAIProvider")
|
|
887
|
+
"models": List[str] | None, # Available models if online, None if offline
|
|
888
|
+
"model_count": int, # Number of models available (0 if offline)
|
|
889
|
+
"error": str | None, # Error message if offline, None if healthy
|
|
890
|
+
"latency_ms": float # Time taken for health check in milliseconds
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
Example:
|
|
894
|
+
>>> provider = OllamaProvider(model="llama2")
|
|
895
|
+
>>> health = provider.health(timeout=3.0)
|
|
896
|
+
>>> if health["status"]:
|
|
897
|
+
>>> print(f"Healthy! {health['model_count']} models available")
|
|
898
|
+
>>> else:
|
|
899
|
+
>>> print(f"Offline: {health['error']}")
|
|
900
|
+
|
|
901
|
+
Note:
|
|
902
|
+
- This method never raises exceptions; errors are captured in the response
|
|
903
|
+
- Uses list_available_models() as the connectivity test
|
|
904
|
+
- Providers can override this method for custom health check logic
|
|
905
|
+
"""
|
|
906
|
+
import time as time_module
|
|
907
|
+
|
|
908
|
+
start_time = time_module.time()
|
|
909
|
+
provider_name = self.__class__.__name__
|
|
910
|
+
|
|
911
|
+
try:
|
|
912
|
+
# Attempt to list models as connectivity test
|
|
913
|
+
# Store original timeout if provider has HTTP client
|
|
914
|
+
original_timeout = None
|
|
915
|
+
timeout_changed = False
|
|
916
|
+
|
|
917
|
+
if timeout is not None and hasattr(self, '_timeout'):
|
|
918
|
+
original_timeout = self._timeout
|
|
919
|
+
if original_timeout != timeout:
|
|
920
|
+
self.set_timeout(timeout)
|
|
921
|
+
timeout_changed = True
|
|
922
|
+
|
|
923
|
+
try:
|
|
924
|
+
models = self.list_available_models()
|
|
925
|
+
|
|
926
|
+
# Restore original timeout if changed
|
|
927
|
+
if timeout_changed and original_timeout is not None:
|
|
928
|
+
self.set_timeout(original_timeout)
|
|
929
|
+
|
|
930
|
+
latency_ms = (time_module.time() - start_time) * 1000
|
|
931
|
+
|
|
932
|
+
return {
|
|
933
|
+
"status": True,
|
|
934
|
+
"provider": provider_name,
|
|
935
|
+
"models": models,
|
|
936
|
+
"model_count": len(models) if models else 0,
|
|
937
|
+
"error": None,
|
|
938
|
+
"latency_ms": round(latency_ms, 2)
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
except Exception as e:
|
|
942
|
+
# Restore original timeout on error
|
|
943
|
+
if timeout_changed and original_timeout is not None:
|
|
944
|
+
try:
|
|
945
|
+
self.set_timeout(original_timeout)
|
|
946
|
+
except:
|
|
947
|
+
pass # Best effort restoration
|
|
948
|
+
raise # Re-raise to be caught by outer handler
|
|
949
|
+
|
|
950
|
+
except Exception as e:
|
|
951
|
+
latency_ms = (time_module.time() - start_time) * 1000
|
|
952
|
+
|
|
953
|
+
# Extract meaningful error message
|
|
954
|
+
error_message = str(e)
|
|
955
|
+
if not error_message:
|
|
956
|
+
error_message = f"{type(e).__name__} occurred during health check"
|
|
957
|
+
|
|
958
|
+
return {
|
|
959
|
+
"status": False,
|
|
960
|
+
"provider": provider_name,
|
|
961
|
+
"models": None,
|
|
962
|
+
"model_count": 0,
|
|
963
|
+
"error": error_message,
|
|
964
|
+
"latency_ms": round(latency_ms, 2)
|
|
965
|
+
}
|
|
966
|
+
|
|
870
967
|
def _needs_tag_rewriting(self, tool_call_tags) -> bool:
|
|
871
968
|
"""Check if tag rewriting is needed (tags are non-standard)"""
|
|
872
969
|
try:
|
|
@@ -864,19 +864,14 @@ class HuggingFaceProvider(BaseProvider):
|
|
|
864
864
|
if outputs and len(outputs) > 0:
|
|
865
865
|
response_text = outputs[0]['generated_text'].strip()
|
|
866
866
|
|
|
867
|
-
# Calculate token usage
|
|
868
|
-
|
|
869
|
-
output_tokens = len(self.tokenizer.encode(response_text))
|
|
867
|
+
# Calculate token usage using centralized utilities
|
|
868
|
+
usage = self._calculate_usage(input_text, response_text)
|
|
870
869
|
|
|
871
870
|
return GenerateResponse(
|
|
872
871
|
content=response_text,
|
|
873
872
|
model=self.model,
|
|
874
873
|
finish_reason="stop",
|
|
875
|
-
usage=
|
|
876
|
-
"prompt_tokens": input_tokens,
|
|
877
|
-
"completion_tokens": output_tokens,
|
|
878
|
-
"total_tokens": input_tokens + output_tokens
|
|
879
|
-
}
|
|
874
|
+
usage=usage
|
|
880
875
|
)
|
|
881
876
|
else:
|
|
882
877
|
return GenerateResponse(
|
|
@@ -892,6 +887,20 @@ class HuggingFaceProvider(BaseProvider):
|
|
|
892
887
|
finish_reason="error"
|
|
893
888
|
)
|
|
894
889
|
|
|
890
|
+
def _calculate_usage(self, prompt: str, response: str) -> Dict[str, int]:
|
|
891
|
+
"""Calculate token usage using centralized token utilities."""
|
|
892
|
+
from ..utils.token_utils import TokenUtils
|
|
893
|
+
|
|
894
|
+
prompt_tokens = TokenUtils.estimate_tokens(prompt, self.model)
|
|
895
|
+
completion_tokens = TokenUtils.estimate_tokens(response, self.model)
|
|
896
|
+
total_tokens = prompt_tokens + completion_tokens
|
|
897
|
+
|
|
898
|
+
return {
|
|
899
|
+
"prompt_tokens": prompt_tokens,
|
|
900
|
+
"completion_tokens": completion_tokens,
|
|
901
|
+
"total_tokens": total_tokens
|
|
902
|
+
}
|
|
903
|
+
|
|
895
904
|
def _stream_generate_transformers(self, input_text: str, max_new_tokens: int,
|
|
896
905
|
temperature: float, top_p: float, tool_call_tags: Optional[str] = None) -> Iterator[GenerateResponse]:
|
|
897
906
|
"""Stream response using transformers (simulated, original implementation) with tool tag rewriting support"""
|
abstractcore/utils/version.py
CHANGED
|
@@ -11,4 +11,4 @@ including when the package is installed from PyPI where pyproject.toml is not av
|
|
|
11
11
|
|
|
12
12
|
# Package version - update this when releasing new versions
|
|
13
13
|
# This must be manually synchronized with the version in pyproject.toml
|
|
14
|
-
__version__ = "2.4.
|
|
14
|
+
__version__ = "2.4.5"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: abstractcore
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.5
|
|
4
4
|
Summary: Unified interface to all LLM providers with essential infrastructure for tool calling, streaming, and model management
|
|
5
5
|
Author-email: Laurent-Philippe Albou <contact@abstractcore.ai>
|
|
6
6
|
Maintainer-email: Laurent-Philippe Albou <contact@abstractcore.ai>
|
|
@@ -11,6 +11,9 @@ abstractcore/architectures/enums.py,sha256=9vIv2vDBEKhxwzwH9iaSAyf-iVj3p8y9loMeN
|
|
|
11
11
|
abstractcore/assets/architecture_formats.json,sha256=CIf6SaR_IJs1D7Uvd1K3zWngIXJ_yq3DM_IE3wnpCHY,16076
|
|
12
12
|
abstractcore/assets/model_capabilities.json,sha256=iUkDiljyZUZzPlpYCOFgStXyc6e7dvOfReYQ0HFrX9Q,49703
|
|
13
13
|
abstractcore/assets/session_schema.json,sha256=b6HTAWxRVlVhAzA7FqaKpunK1yO6jilBOsD5sQkqJTo,10580
|
|
14
|
+
abstractcore/cli/__init__.py,sha256=rUjLjZSK3wENSw4g_iN43Bc2i5cggcEmj4NPXBMohdc,241
|
|
15
|
+
abstractcore/cli/main.py,sha256=QD38nnfrInavO452WbkXCI37SVsdIu9VhvjEOojXBGY,31834
|
|
16
|
+
abstractcore/cli/vision_config.py,sha256=jJzO4zBexh8SqSKp6YKOXdMDSv4AL4Ztl5Xi-5c4KyY,17869
|
|
14
17
|
abstractcore/core/__init__.py,sha256=2h-86U4QkCQ4gzZ4iRusSTMlkODiUS6tKjZHiEXz6rM,684
|
|
15
18
|
abstractcore/core/enums.py,sha256=BhkVnHC-X1_377JDmqd-2mnem9GdBLqixWlYzlP_FJU,695
|
|
16
19
|
abstractcore/core/factory.py,sha256=UdrNwQAvifvFS3LMjF5KO87m-2n1bJBryTs9pvesYcI,2804
|
|
@@ -29,14 +32,25 @@ abstractcore/media/base.py,sha256=vWdxscqTGTvd3oc4IzzsBTWhUrznWcqM7M_sFyq6-eE,15
|
|
|
29
32
|
abstractcore/media/capabilities.py,sha256=qqKvXGkUT-FNnbFS-EYx8KCT9SZOovO2h4N7ucrHgBA,12844
|
|
30
33
|
abstractcore/media/types.py,sha256=jG-g_2_gzl8eOgEalk9x3Ikhni9GoGfoRjkZWaBhV30,10165
|
|
31
34
|
abstractcore/media/vision_fallback.py,sha256=XcEV5T9ekqd4DRBrhJvxgX5j_puxSlofvuUIfQc2vmg,10629
|
|
35
|
+
abstractcore/media/handlers/__init__.py,sha256=HBqFo15JX1q7RM11076iFQUfPvInLlOizX-LGSznLuI,404
|
|
36
|
+
abstractcore/media/handlers/anthropic_handler.py,sha256=iwcHKnHgHoQGpJKlJmwFJWBvrYg9lAzAnndybwsWZRA,12427
|
|
37
|
+
abstractcore/media/handlers/local_handler.py,sha256=xfMV2Ztre3eUkDno4aSGob96oWUlgicZ3VChs-txjXU,23033
|
|
38
|
+
abstractcore/media/handlers/openai_handler.py,sha256=o0H_WQ_NQt133my55xYQmq6_QFGafghF8sPTrqr1f0Q,9726
|
|
39
|
+
abstractcore/media/processors/__init__.py,sha256=tExCZwVhD9Qzn3D99-zQcU-T1324YtiLkWjIfWLC708,442
|
|
40
|
+
abstractcore/media/processors/image_processor.py,sha256=wj-f1W71ZCs4AZdmyTKZvnMee83GkiXKuZ6QvJwd3Lo,22577
|
|
41
|
+
abstractcore/media/processors/office_processor.py,sha256=MqhLDWNtjHEpiMgpFaf7tbj8iDcTCf_zelWrHZkr7Z4,18580
|
|
42
|
+
abstractcore/media/processors/pdf_processor.py,sha256=qniYt7cTYYPVRi_cS1IsXztOldeY0bqdn7sdbELBU9k,17157
|
|
43
|
+
abstractcore/media/processors/text_processor.py,sha256=E28FtT2_jzsvMIDwZi6aVWuu_pSyAPSBa96fe4YYcU8,21092
|
|
44
|
+
abstractcore/media/utils/__init__.py,sha256=30-CTif91iRKOXJ4njGiduWAt-xp31U7NafMBNvgdO0,460
|
|
45
|
+
abstractcore/media/utils/image_scaler.py,sha256=QrYqoNQc8tzGu7I9Sf_E-Iv7ei2oLh714AGiX3yACNM,11338
|
|
32
46
|
abstractcore/processing/__init__.py,sha256=t6hiakQjcZROT4pw9ZFt2q6fF3vf5VpdMKG2EWlsFd8,540
|
|
33
47
|
abstractcore/processing/basic_extractor.py,sha256=3x-3BdIHgLvqLnLF6K1-P4qVaLIpAnNIIutaJi7lDQM,49832
|
|
34
48
|
abstractcore/processing/basic_judge.py,sha256=tKWJrg_tY4vCHzWgXxz0ZjgLXBYYfpMcpG7vl03hJcM,32218
|
|
35
49
|
abstractcore/processing/basic_summarizer.py,sha256=XHNxMQ_8aLStTeUo6_2JaThlct12Htpz7ORmm0iuJsg,25495
|
|
36
50
|
abstractcore/providers/__init__.py,sha256=t8Kp4flH5GvZEC6dx-iYJSPeSxMODa2spXb8MqtlPy4,1282
|
|
37
51
|
abstractcore/providers/anthropic_provider.py,sha256=tcOrARLd1kA4vRkH7MCgy99YIGVaegdCd3-Z8UaKP3Q,20705
|
|
38
|
-
abstractcore/providers/base.py,sha256=
|
|
39
|
-
abstractcore/providers/huggingface_provider.py,sha256=
|
|
52
|
+
abstractcore/providers/base.py,sha256=5YR64kqTYiCvWtIUBul5QfO0XRZr6_Aiho4atpDOh0o,50579
|
|
53
|
+
abstractcore/providers/huggingface_provider.py,sha256=mJGfi1lgsvjV3Lj4q7KCQZhQqw_o23af40i5WLg150o,47789
|
|
40
54
|
abstractcore/providers/lmstudio_provider.py,sha256=oPL_Y4gkJMAniecdWQVaDi7WozCZumSRs0lE7uFgvQk,20406
|
|
41
55
|
abstractcore/providers/mlx_provider.py,sha256=61i5VhpNw_QlhOwPcEcryaGbI45aYyL9q15TrpinIgs,17427
|
|
42
56
|
abstractcore/providers/mock_provider.py,sha256=tIjA57Hlwu3vNODOZShNn0tY9HWvz1p4z-HyD_bsvbo,5741
|
|
@@ -63,10 +77,10 @@ abstractcore/utils/message_preprocessor.py,sha256=GdHkm6tmrgjm3PwHRSCjIsq1XLkbhy
|
|
|
63
77
|
abstractcore/utils/self_fixes.py,sha256=QEDwNTW80iQM4ftfEY3Ghz69F018oKwLM9yeRCYZOvw,5886
|
|
64
78
|
abstractcore/utils/structured_logging.py,sha256=Vm-HviSa42G9DJCWmaEv4a0QG3NMsADD3ictLOs4En0,19952
|
|
65
79
|
abstractcore/utils/token_utils.py,sha256=eLwFmJ68p9WMFD_MHLMmeJRW6Oqx_4hKELB8FNQ2Mnk,21097
|
|
66
|
-
abstractcore/utils/version.py,sha256=
|
|
67
|
-
abstractcore-2.4.
|
|
68
|
-
abstractcore-2.4.
|
|
69
|
-
abstractcore-2.4.
|
|
70
|
-
abstractcore-2.4.
|
|
71
|
-
abstractcore-2.4.
|
|
72
|
-
abstractcore-2.4.
|
|
80
|
+
abstractcore/utils/version.py,sha256=WKRMOS_TRIxsNu-vJqfCYHA335-rya9xBBMzhx_S-Z8,605
|
|
81
|
+
abstractcore-2.4.5.dist-info/licenses/LICENSE,sha256=PI2v_4HMvd6050uDD_4AY_8PzBnu2asa3RKbdDjowTA,1078
|
|
82
|
+
abstractcore-2.4.5.dist-info/METADATA,sha256=9w6Q7NZXePxhRYnmloyJ6Nd2DFQ7YY4294OMF-6QYuY,27596
|
|
83
|
+
abstractcore-2.4.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
84
|
+
abstractcore-2.4.5.dist-info/entry_points.txt,sha256=UdVmchBC_Lt3H4Vlkt5js-QDAkVlBbkCu1yCsswk-KE,454
|
|
85
|
+
abstractcore-2.4.5.dist-info/top_level.txt,sha256=DiNHBI35SIawW3N9Z-z0y6cQYNbXd32pvBkW0RLfScs,13
|
|
86
|
+
abstractcore-2.4.5.dist-info/RECORD,,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
[console_scripts]
|
|
2
2
|
abstractcore = abstractcore.cli.main:main
|
|
3
|
+
abstractcore-chat = abstractcore.utils.cli:main
|
|
4
|
+
abstractcore-config = abstractcore.cli.main:main
|
|
3
5
|
abstractcore-extractor = abstractcore.apps.extractor:main
|
|
4
6
|
abstractcore-judge = abstractcore.apps.judge:main
|
|
5
7
|
abstractcore-summarizer = abstractcore.apps.summarizer:main
|
|
File without changes
|
|
File without changes
|
|
File without changes
|