abstractcore 2.4.0__py3-none-any.whl → 2.4.1__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/exceptions/__init__.py +125 -0
- abstractcore/media/__init__.py +151 -0
- abstractcore/utils/version.py +1 -1
- {abstractcore-2.4.0.dist-info → abstractcore-2.4.1.dist-info}/METADATA +1 -1
- {abstractcore-2.4.0.dist-info → abstractcore-2.4.1.dist-info}/RECORD +9 -7
- {abstractcore-2.4.0.dist-info → abstractcore-2.4.1.dist-info}/WHEEL +0 -0
- {abstractcore-2.4.0.dist-info → abstractcore-2.4.1.dist-info}/entry_points.txt +0 -0
- {abstractcore-2.4.0.dist-info → abstractcore-2.4.1.dist-info}/licenses/LICENSE +0 -0
- {abstractcore-2.4.0.dist-info → abstractcore-2.4.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom exceptions for AbstractCore.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AbstractCoreError(Exception):
|
|
7
|
+
"""Base exception for AbstractCore"""
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProviderError(AbstractCoreError):
|
|
12
|
+
"""Base exception for provider-related errors"""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ProviderAPIError(ProviderError):
|
|
17
|
+
"""API call to provider failed"""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AuthenticationError(ProviderError):
|
|
22
|
+
"""Authentication with provider failed"""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Alias for backward compatibility with old AbstractCore
|
|
27
|
+
Authentication = AuthenticationError
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RateLimitError(ProviderError):
|
|
31
|
+
"""Rate limit exceeded"""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class InvalidRequestError(ProviderError):
|
|
36
|
+
"""Invalid request to provider"""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class UnsupportedFeatureError(AbstractCoreError):
|
|
41
|
+
"""Feature not supported by provider"""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class FileProcessingError(AbstractCoreError):
|
|
46
|
+
"""Error processing file or media"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ToolExecutionError(AbstractCoreError):
|
|
51
|
+
"""Error executing tool"""
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class SessionError(AbstractCoreError):
|
|
56
|
+
"""Error with session management"""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ConfigurationError(AbstractCoreError):
|
|
61
|
+
"""Invalid configuration"""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ModelNotFoundError(ProviderError):
|
|
66
|
+
"""Model not found or invalid model name"""
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def format_model_error(provider: str, invalid_model: str, available_models: list) -> str:
|
|
71
|
+
"""
|
|
72
|
+
Format a helpful error message for model not found errors.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
provider: Provider name (e.g., "OpenAI", "Anthropic")
|
|
76
|
+
invalid_model: The model name that was not found
|
|
77
|
+
available_models: List of available model names
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Formatted error message string
|
|
81
|
+
"""
|
|
82
|
+
message = f"❌ Model '{invalid_model}' not found for {provider} provider.\n"
|
|
83
|
+
|
|
84
|
+
if available_models:
|
|
85
|
+
message += f"\n✅ Available models ({len(available_models)}):\n"
|
|
86
|
+
for model in available_models[:30]: # Show max 30
|
|
87
|
+
message += f" • {model}\n"
|
|
88
|
+
if len(available_models) > 30:
|
|
89
|
+
message += f" ... and {len(available_models) - 30} more\n"
|
|
90
|
+
else:
|
|
91
|
+
# Show provider documentation when we can't fetch models
|
|
92
|
+
doc_links = {
|
|
93
|
+
"anthropic": "https://docs.anthropic.com/en/docs/about-claude/models",
|
|
94
|
+
"openai": "https://platform.openai.com/docs/models",
|
|
95
|
+
"ollama": "https://ollama.com/library",
|
|
96
|
+
"huggingface": "https://huggingface.co/models",
|
|
97
|
+
"mlx": "https://huggingface.co/mlx-community"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
provider_lower = provider.lower()
|
|
101
|
+
if provider_lower in doc_links:
|
|
102
|
+
message += f"\n📚 See available models: {doc_links[provider_lower]}\n"
|
|
103
|
+
else:
|
|
104
|
+
message += f"\n⚠️ Could not fetch available models for {provider}.\n"
|
|
105
|
+
|
|
106
|
+
return message.rstrip()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# Export all exceptions for easy importing
|
|
110
|
+
__all__ = [
|
|
111
|
+
'AbstractCoreError',
|
|
112
|
+
'ProviderError',
|
|
113
|
+
'ProviderAPIError',
|
|
114
|
+
'AuthenticationError',
|
|
115
|
+
'Authentication', # Backward compatibility alias
|
|
116
|
+
'RateLimitError',
|
|
117
|
+
'InvalidRequestError',
|
|
118
|
+
'UnsupportedFeatureError',
|
|
119
|
+
'FileProcessingError',
|
|
120
|
+
'ToolExecutionError',
|
|
121
|
+
'SessionError',
|
|
122
|
+
'ConfigurationError',
|
|
123
|
+
'ModelNotFoundError',
|
|
124
|
+
'format_model_error'
|
|
125
|
+
]
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Media handling for different providers.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Union, Dict, Any, Optional
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MediaType(Enum):
|
|
12
|
+
"""Supported media types"""
|
|
13
|
+
IMAGE = "image"
|
|
14
|
+
AUDIO = "audio"
|
|
15
|
+
VIDEO = "video"
|
|
16
|
+
DOCUMENT = "document"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MediaHandler:
|
|
20
|
+
"""Base class for media handling"""
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def encode_image(image_path: Union[str, Path]) -> str:
|
|
24
|
+
"""
|
|
25
|
+
Encode an image file to base64.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
image_path: Path to the image file
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Base64 encoded string
|
|
32
|
+
"""
|
|
33
|
+
with open(image_path, "rb") as image_file:
|
|
34
|
+
return base64.b64encode(image_file.read()).decode('utf-8')
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def format_for_openai(image_path: Union[str, Path]) -> Dict[str, Any]:
|
|
38
|
+
"""
|
|
39
|
+
Format image for OpenAI API.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
image_path: Path to the image
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Formatted content for OpenAI
|
|
46
|
+
"""
|
|
47
|
+
base64_image = MediaHandler.encode_image(image_path)
|
|
48
|
+
return {
|
|
49
|
+
"type": "image_url",
|
|
50
|
+
"image_url": {
|
|
51
|
+
"url": f"data:image/jpeg;base64,{base64_image}"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def format_for_anthropic(image_path: Union[str, Path]) -> Dict[str, Any]:
|
|
57
|
+
"""
|
|
58
|
+
Format image for Anthropic API.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
image_path: Path to the image
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Formatted content for Anthropic
|
|
65
|
+
"""
|
|
66
|
+
base64_image = MediaHandler.encode_image(image_path)
|
|
67
|
+
|
|
68
|
+
# Detect image type
|
|
69
|
+
path = Path(image_path)
|
|
70
|
+
media_type = "image/jpeg"
|
|
71
|
+
if path.suffix.lower() == ".png":
|
|
72
|
+
media_type = "image/png"
|
|
73
|
+
elif path.suffix.lower() == ".gif":
|
|
74
|
+
media_type = "image/gif"
|
|
75
|
+
elif path.suffix.lower() == ".webp":
|
|
76
|
+
media_type = "image/webp"
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
"type": "image",
|
|
80
|
+
"source": {
|
|
81
|
+
"type": "base64",
|
|
82
|
+
"media_type": media_type,
|
|
83
|
+
"data": base64_image
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def format_for_provider(image_path: Union[str, Path], provider: str) -> Optional[Dict[str, Any]]:
|
|
89
|
+
"""
|
|
90
|
+
Format media for a specific provider.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
image_path: Path to the media file
|
|
94
|
+
provider: Provider name
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Formatted content or None if not supported
|
|
98
|
+
"""
|
|
99
|
+
provider_lower = provider.lower()
|
|
100
|
+
|
|
101
|
+
if provider_lower == "openai":
|
|
102
|
+
return MediaHandler.format_for_openai(image_path)
|
|
103
|
+
elif provider_lower == "anthropic":
|
|
104
|
+
return MediaHandler.format_for_anthropic(image_path)
|
|
105
|
+
else:
|
|
106
|
+
# Local providers typically don't support images directly
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def is_image_file(path: Union[str, Path]) -> bool:
|
|
111
|
+
"""
|
|
112
|
+
Check if a file is an image.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
path: Path to check
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
True if the file is an image
|
|
119
|
+
"""
|
|
120
|
+
image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.ico', '.tiff'}
|
|
121
|
+
return Path(path).suffix.lower() in image_extensions
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def get_media_type(path: Union[str, Path]) -> MediaType:
|
|
125
|
+
"""
|
|
126
|
+
Determine the media type of a file.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
path: Path to the file
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
MediaType enum value
|
|
133
|
+
"""
|
|
134
|
+
path = Path(path)
|
|
135
|
+
extension = path.suffix.lower()
|
|
136
|
+
|
|
137
|
+
image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'}
|
|
138
|
+
audio_extensions = {'.mp3', '.wav', '.m4a', '.ogg', '.flac'}
|
|
139
|
+
video_extensions = {'.mp4', '.avi', '.mov', '.mkv', '.webm'}
|
|
140
|
+
document_extensions = {'.pdf', '.doc', '.docx', '.txt', '.md'}
|
|
141
|
+
|
|
142
|
+
if extension in image_extensions:
|
|
143
|
+
return MediaType.IMAGE
|
|
144
|
+
elif extension in audio_extensions:
|
|
145
|
+
return MediaType.AUDIO
|
|
146
|
+
elif extension in video_extensions:
|
|
147
|
+
return MediaType.VIDEO
|
|
148
|
+
elif extension in document_extensions:
|
|
149
|
+
return MediaType.DOCUMENT
|
|
150
|
+
else:
|
|
151
|
+
return MediaType.DOCUMENT # Default to document
|
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.1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: abstractcore
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.1
|
|
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>
|
|
@@ -21,6 +21,8 @@ abstractcore/embeddings/__init__.py,sha256=hR3xZyqcRm4c2pq1dIa5lxj_-Bk70Zad802JQ
|
|
|
21
21
|
abstractcore/embeddings/manager.py,sha256=QzDtSna4FDCPg1il7GGe_7p1VknuUHjXAFQa98PgU9A,50048
|
|
22
22
|
abstractcore/embeddings/models.py,sha256=bsPAzL6gv57AVii8O15PT0kxfwRkOml3f3njJN4UDi4,4874
|
|
23
23
|
abstractcore/events/__init__.py,sha256=UtbdTOeL05kvi7YP91yo4OEqs5UAbKylBvOOEkrUL5E,11065
|
|
24
|
+
abstractcore/exceptions/__init__.py,sha256=h6y3sdZR6uFMh0iq7z85DfJi01zGQvjVOm1W7l86iVQ,3224
|
|
25
|
+
abstractcore/media/__init__.py,sha256=BjWR8OIN2uxoOJBAlbM83VkyUtyDtiXM4AerYzhI9fU,4241
|
|
24
26
|
abstractcore/processing/__init__.py,sha256=t6hiakQjcZROT4pw9ZFt2q6fF3vf5VpdMKG2EWlsFd8,540
|
|
25
27
|
abstractcore/processing/basic_extractor.py,sha256=3x-3BdIHgLvqLnLF6K1-P4qVaLIpAnNIIutaJi7lDQM,49832
|
|
26
28
|
abstractcore/processing/basic_judge.py,sha256=tKWJrg_tY4vCHzWgXxz0ZjgLXBYYfpMcpG7vl03hJcM,32218
|
|
@@ -53,10 +55,10 @@ abstractcore/utils/cli.py,sha256=8ua5Lu4bKSs9y1JB5W3kJ0OA-_dFpUHk6prH1U684xc,568
|
|
|
53
55
|
abstractcore/utils/self_fixes.py,sha256=QEDwNTW80iQM4ftfEY3Ghz69F018oKwLM9yeRCYZOvw,5886
|
|
54
56
|
abstractcore/utils/structured_logging.py,sha256=Y7TVkf1tP9BCOPNbBY1rQubBxcAxhhUOYMbrV2k50ZM,15830
|
|
55
57
|
abstractcore/utils/token_utils.py,sha256=eLwFmJ68p9WMFD_MHLMmeJRW6Oqx_4hKELB8FNQ2Mnk,21097
|
|
56
|
-
abstractcore/utils/version.py,sha256=
|
|
57
|
-
abstractcore-2.4.
|
|
58
|
-
abstractcore-2.4.
|
|
59
|
-
abstractcore-2.4.
|
|
60
|
-
abstractcore-2.4.
|
|
61
|
-
abstractcore-2.4.
|
|
62
|
-
abstractcore-2.4.
|
|
58
|
+
abstractcore/utils/version.py,sha256=JrNRwMH_7TeJA9dClL5VSUDlkmqpSyPb0K4nPEHGWhM,605
|
|
59
|
+
abstractcore-2.4.1.dist-info/licenses/LICENSE,sha256=PI2v_4HMvd6050uDD_4AY_8PzBnu2asa3RKbdDjowTA,1078
|
|
60
|
+
abstractcore-2.4.1.dist-info/METADATA,sha256=BT2bJNHwvtFL842P1tWRYtjIaO0fIHiMiFn1QwJUviE,19684
|
|
61
|
+
abstractcore-2.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
62
|
+
abstractcore-2.4.1.dist-info/entry_points.txt,sha256=Ocy403YwzaOBT7D_vf7w6YFiIQ4nTbp0htjXfeI5IOo,315
|
|
63
|
+
abstractcore-2.4.1.dist-info/top_level.txt,sha256=DiNHBI35SIawW3N9Z-z0y6cQYNbXd32pvBkW0RLfScs,13
|
|
64
|
+
abstractcore-2.4.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|