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.
@@ -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)
@@ -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
- input_tokens = len(self.tokenizer.encode(input_text))
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"""
@@ -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.3"
14
+ __version__ = "2.4.5"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: abstractcore
3
- Version: 2.4.3
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=kdgHjm_GYKi1Bag1B-hkiJuYO1b4NtnXUF2NVkiDrgA,46645
39
- abstractcore/providers/huggingface_provider.py,sha256=rM0e2E5KSmoBUmQ_gbAPwZoauqDJ3ls4xYY-qh9VI1w,47462
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=gF_Ck5Ba5iARL_Ruybdnb3eKPC8510xyhH3S4uLL5_k,605
67
- abstractcore-2.4.3.dist-info/licenses/LICENSE,sha256=PI2v_4HMvd6050uDD_4AY_8PzBnu2asa3RKbdDjowTA,1078
68
- abstractcore-2.4.3.dist-info/METADATA,sha256=Gd1Wqq6wEEsoL3zA2ezoWConAWpeMkh7KLRtcafgwmc,27596
69
- abstractcore-2.4.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
70
- abstractcore-2.4.3.dist-info/entry_points.txt,sha256=di-rxy4iD6A3j8DmQHeVmXYlncgCJKZGamyjrQXEfcU,357
71
- abstractcore-2.4.3.dist-info/top_level.txt,sha256=DiNHBI35SIawW3N9Z-z0y6cQYNbXd32pvBkW0RLfScs,13
72
- abstractcore-2.4.3.dist-info/RECORD,,
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