abstractcore 2.4.4__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/utils/version.py +1 -1
- {abstractcore-2.4.4.dist-info → abstractcore-2.4.5.dist-info}/METADATA +1 -1
- {abstractcore-2.4.4.dist-info → abstractcore-2.4.5.dist-info}/RECORD +21 -7
- {abstractcore-2.4.4.dist-info → abstractcore-2.4.5.dist-info}/entry_points.txt +2 -0
- {abstractcore-2.4.4.dist-info → abstractcore-2.4.5.dist-info}/WHEEL +0 -0
- {abstractcore-2.4.4.dist-info → abstractcore-2.4.5.dist-info}/licenses/LICENSE +0 -0
- {abstractcore-2.4.4.dist-info → abstractcore-2.4.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Anthropic-specific media handler.
|
|
3
|
+
|
|
4
|
+
This module provides media formatting capabilities specifically for Anthropic's API,
|
|
5
|
+
including support for Claude Vision and document processing.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict, Any, List, Optional, Union
|
|
10
|
+
|
|
11
|
+
from ..base import BaseProviderMediaHandler, MediaProcessingError
|
|
12
|
+
from ..types import MediaContent, MediaType, ContentFormat
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AnthropicMediaHandler(BaseProviderMediaHandler):
|
|
16
|
+
"""
|
|
17
|
+
Media handler for Anthropic API formatting.
|
|
18
|
+
|
|
19
|
+
Formats media content according to Anthropic's API specifications for
|
|
20
|
+
Claude Vision and other multimodal capabilities.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, model_capabilities: Optional[Dict[str, Any]] = None, **kwargs):
|
|
24
|
+
"""
|
|
25
|
+
Initialize Anthropic media handler.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
model_capabilities: Model capabilities from model_capabilities.json
|
|
29
|
+
**kwargs: Additional configuration
|
|
30
|
+
"""
|
|
31
|
+
super().__init__("anthropic", model_capabilities, **kwargs)
|
|
32
|
+
|
|
33
|
+
# Anthropic-specific configuration
|
|
34
|
+
self.max_image_size = kwargs.get('max_image_size', 5 * 1024 * 1024) # 5MB
|
|
35
|
+
self.max_images_per_message = kwargs.get('max_images_per_message', 20) # Claude supports up to 20 images
|
|
36
|
+
|
|
37
|
+
self.logger.debug(f"Initialized Anthropic media handler with capabilities: {self.capabilities}")
|
|
38
|
+
|
|
39
|
+
def _process_internal(self, file_path: Path, media_type: MediaType, **kwargs) -> MediaContent:
|
|
40
|
+
"""
|
|
41
|
+
Process file using appropriate processor and return Anthropic-formatted content.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
file_path: Path to the file
|
|
45
|
+
media_type: Type of media
|
|
46
|
+
**kwargs: Processing options
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
MediaContent formatted for Anthropic
|
|
50
|
+
"""
|
|
51
|
+
# Use appropriate processor based on media type
|
|
52
|
+
if media_type == MediaType.IMAGE:
|
|
53
|
+
from ..processors import ImageProcessor
|
|
54
|
+
# Ensure maximum resolution for best Anthropic vision results
|
|
55
|
+
if 'prefer_max_resolution' not in kwargs:
|
|
56
|
+
kwargs['prefer_max_resolution'] = True
|
|
57
|
+
processor = ImageProcessor(**kwargs)
|
|
58
|
+
elif media_type == MediaType.DOCUMENT:
|
|
59
|
+
if file_path.suffix.lower() == '.pdf':
|
|
60
|
+
from ..processors import PDFProcessor
|
|
61
|
+
processor = PDFProcessor(**kwargs)
|
|
62
|
+
else:
|
|
63
|
+
from ..processors import TextProcessor
|
|
64
|
+
processor = TextProcessor(**kwargs)
|
|
65
|
+
else:
|
|
66
|
+
from ..processors import TextProcessor
|
|
67
|
+
processor = TextProcessor(**kwargs)
|
|
68
|
+
|
|
69
|
+
# Process the file
|
|
70
|
+
result = processor.process_file(file_path, **kwargs)
|
|
71
|
+
|
|
72
|
+
if not result.success:
|
|
73
|
+
raise MediaProcessingError(f"Failed to process {file_path}: {result.error_message}")
|
|
74
|
+
|
|
75
|
+
return result.media_content
|
|
76
|
+
|
|
77
|
+
def format_for_provider(self, media_content: MediaContent) -> Dict[str, Any]:
|
|
78
|
+
"""
|
|
79
|
+
Format media content for Anthropic API.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
media_content: MediaContent to format
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Dictionary formatted for Anthropic API
|
|
86
|
+
"""
|
|
87
|
+
if media_content.media_type == MediaType.IMAGE:
|
|
88
|
+
return self._format_image_for_anthropic(media_content)
|
|
89
|
+
elif media_content.media_type in [MediaType.DOCUMENT, MediaType.TEXT]:
|
|
90
|
+
return self._format_text_for_anthropic(media_content)
|
|
91
|
+
else:
|
|
92
|
+
raise MediaProcessingError(f"Unsupported media type for Anthropic: {media_content.media_type}")
|
|
93
|
+
|
|
94
|
+
def _format_image_for_anthropic(self, media_content: MediaContent) -> Dict[str, Any]:
|
|
95
|
+
"""
|
|
96
|
+
Format image content for Anthropic's image format.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
media_content: Image MediaContent
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Anthropic-compatible image object
|
|
103
|
+
"""
|
|
104
|
+
if media_content.content_format != ContentFormat.BASE64:
|
|
105
|
+
raise MediaProcessingError("Anthropic image formatting requires base64 content")
|
|
106
|
+
|
|
107
|
+
# Create Anthropic image object
|
|
108
|
+
return {
|
|
109
|
+
"type": "image",
|
|
110
|
+
"source": {
|
|
111
|
+
"type": "base64",
|
|
112
|
+
"media_type": media_content.mime_type,
|
|
113
|
+
"data": media_content.content
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
def _format_text_for_anthropic(self, media_content: MediaContent) -> Dict[str, Any]:
|
|
118
|
+
"""
|
|
119
|
+
Format text/document content for Anthropic API.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
media_content: Text/Document MediaContent
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Anthropic-compatible text object
|
|
126
|
+
"""
|
|
127
|
+
if isinstance(media_content.content, bytes):
|
|
128
|
+
content = media_content.content.decode('utf-8')
|
|
129
|
+
else:
|
|
130
|
+
content = str(media_content.content)
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
"type": "text",
|
|
134
|
+
"text": content
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
def create_multimodal_message(self, text: str, media_contents: List[MediaContent]) -> Dict[str, Any]:
|
|
138
|
+
"""
|
|
139
|
+
Create a multimodal message for Anthropic API.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
text: Text content
|
|
143
|
+
media_contents: List of media contents
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Anthropic-compatible message object
|
|
147
|
+
"""
|
|
148
|
+
content = []
|
|
149
|
+
|
|
150
|
+
# Add text content first (Anthropic prefers text before images)
|
|
151
|
+
if text.strip():
|
|
152
|
+
content.append({
|
|
153
|
+
"type": "text",
|
|
154
|
+
"text": text
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
# Add media contents
|
|
158
|
+
image_count = 0
|
|
159
|
+
for media_content in media_contents:
|
|
160
|
+
if self.can_handle_media(media_content):
|
|
161
|
+
# Check image limits
|
|
162
|
+
if media_content.media_type == MediaType.IMAGE:
|
|
163
|
+
if image_count >= self.max_images_per_message:
|
|
164
|
+
self.logger.warning(f"Skipping image - exceeded max images per message ({self.max_images_per_message})")
|
|
165
|
+
continue
|
|
166
|
+
image_count += 1
|
|
167
|
+
|
|
168
|
+
formatted_content = self.format_for_provider(media_content)
|
|
169
|
+
content.append(formatted_content)
|
|
170
|
+
else:
|
|
171
|
+
self.logger.warning(f"Skipping unsupported media type: {media_content.media_type}")
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
"role": "user",
|
|
175
|
+
"content": content
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
def validate_media_for_model(self, media_content: MediaContent, model: str) -> bool:
|
|
179
|
+
"""
|
|
180
|
+
Validate if media content is compatible with specific Anthropic model.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
media_content: MediaContent to validate
|
|
184
|
+
model: Anthropic model name
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
True if compatible, False otherwise
|
|
188
|
+
"""
|
|
189
|
+
model_lower = model.lower()
|
|
190
|
+
|
|
191
|
+
# Vision model validation
|
|
192
|
+
if media_content.media_type == MediaType.IMAGE:
|
|
193
|
+
# Check if model supports vision
|
|
194
|
+
if not self.model_capabilities.get('vision_support', False):
|
|
195
|
+
return False
|
|
196
|
+
|
|
197
|
+
# Check image size
|
|
198
|
+
if hasattr(media_content, 'metadata'):
|
|
199
|
+
file_size = media_content.metadata.get('file_size', 0)
|
|
200
|
+
if file_size > self.max_image_size:
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
# Model-specific checks
|
|
204
|
+
if 'claude-3' in model_lower:
|
|
205
|
+
return True # All Claude 3 models support vision
|
|
206
|
+
elif 'claude-3.5' in model_lower:
|
|
207
|
+
return True # All Claude 3.5 models support vision
|
|
208
|
+
elif 'claude-4' in model_lower:
|
|
209
|
+
return True # Future Claude 4 models
|
|
210
|
+
|
|
211
|
+
# Text/document validation
|
|
212
|
+
elif media_content.media_type in [MediaType.TEXT, MediaType.DOCUMENT]:
|
|
213
|
+
# All Anthropic models support text
|
|
214
|
+
return True
|
|
215
|
+
|
|
216
|
+
# Audio validation (future support)
|
|
217
|
+
elif media_content.media_type == MediaType.AUDIO:
|
|
218
|
+
return self.model_capabilities.get('audio_support', False)
|
|
219
|
+
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
def estimate_tokens_for_media(self, media_content: MediaContent) -> int:
|
|
223
|
+
"""
|
|
224
|
+
Estimate token usage for media content.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
media_content: MediaContent to estimate
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Estimated token count
|
|
231
|
+
"""
|
|
232
|
+
if media_content.media_type == MediaType.IMAGE:
|
|
233
|
+
# Anthropic image token estimation
|
|
234
|
+
# Roughly ~1600 tokens per image for most cases
|
|
235
|
+
# This varies based on image content and complexity
|
|
236
|
+
return 1600
|
|
237
|
+
|
|
238
|
+
elif media_content.media_type in [MediaType.TEXT, MediaType.DOCUMENT]:
|
|
239
|
+
# Rough estimation: 3.5 characters per token (slightly better than GPT)
|
|
240
|
+
content_length = len(str(media_content.content))
|
|
241
|
+
return int(content_length / 3.5)
|
|
242
|
+
|
|
243
|
+
return 0
|
|
244
|
+
|
|
245
|
+
def get_model_media_limits(self, model: str) -> Dict[str, Any]:
|
|
246
|
+
"""
|
|
247
|
+
Get media-specific limits for Anthropic model.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
model: Anthropic model name
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Dictionary of limits
|
|
254
|
+
"""
|
|
255
|
+
limits = {
|
|
256
|
+
'max_images_per_message': self.max_images_per_message,
|
|
257
|
+
'max_image_size_bytes': self.max_image_size,
|
|
258
|
+
'supported_image_formats': ['png', 'jpeg', 'jpg', 'gif', 'webp'],
|
|
259
|
+
'max_image_resolution': '1568x1568' # Anthropic's documented limit
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
model_lower = model.lower()
|
|
263
|
+
|
|
264
|
+
# Model-specific adjustments
|
|
265
|
+
if 'claude-3.5' in model_lower:
|
|
266
|
+
limits.update({
|
|
267
|
+
'parallel_tool_use': self.model_capabilities.get('parallel_tools', False),
|
|
268
|
+
'disable_parallel_tool_use_option': True
|
|
269
|
+
})
|
|
270
|
+
elif 'claude-3' in model_lower and 'opus' in model_lower:
|
|
271
|
+
limits.update({
|
|
272
|
+
'parallel_tool_use': False, # Claude 3 Opus doesn't support parallel tools
|
|
273
|
+
'max_tools_per_request': 1
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
return limits
|
|
277
|
+
|
|
278
|
+
def create_document_analysis_prompt(self, media_contents: List[MediaContent],
|
|
279
|
+
analysis_type: str = "general") -> str:
|
|
280
|
+
"""
|
|
281
|
+
Create a specialized prompt for document analysis with Claude.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
media_contents: List of media contents (documents/images)
|
|
285
|
+
analysis_type: Type of analysis ('general', 'summary', 'extract', 'qa')
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Optimized prompt for document analysis
|
|
289
|
+
"""
|
|
290
|
+
document_count = len([mc for mc in media_contents if mc.media_type in [MediaType.DOCUMENT, MediaType.TEXT]])
|
|
291
|
+
image_count = len([mc for mc in media_contents if mc.media_type == MediaType.IMAGE])
|
|
292
|
+
|
|
293
|
+
if analysis_type == "summary":
|
|
294
|
+
return f"""Please analyze and summarize the provided {'documents' if document_count > 0 else 'images'}.
|
|
295
|
+
Provide a clear, concise summary that captures the key information, main points, and important details.
|
|
296
|
+
|
|
297
|
+
{"Documents provided: " + str(document_count) if document_count > 0 else ""}
|
|
298
|
+
{"Images provided: " + str(image_count) if image_count > 0 else ""}"""
|
|
299
|
+
|
|
300
|
+
elif analysis_type == "extract":
|
|
301
|
+
return f"""Please extract all relevant information from the provided {'documents' if document_count > 0 else 'images'}.
|
|
302
|
+
Focus on:
|
|
303
|
+
- Key facts and data points
|
|
304
|
+
- Important names, dates, and numbers
|
|
305
|
+
- Structured information like tables or lists
|
|
306
|
+
- Any actionable items or conclusions
|
|
307
|
+
|
|
308
|
+
Present the information in a clear, organized format."""
|
|
309
|
+
|
|
310
|
+
elif analysis_type == "qa":
|
|
311
|
+
return f"""I will ask questions about the provided {'documents' if document_count > 0 else 'images'}.
|
|
312
|
+
Please read through all the content carefully and be prepared to answer detailed questions about:
|
|
313
|
+
- Specific information contained in the materials
|
|
314
|
+
- Relationships between different pieces of information
|
|
315
|
+
- Analysis and interpretation of the content
|
|
316
|
+
|
|
317
|
+
Please confirm that you have reviewed all the provided materials."""
|
|
318
|
+
|
|
319
|
+
else: # general
|
|
320
|
+
return f"""Please analyze the provided {'documents' if document_count > 0 else 'images'} and provide:
|
|
321
|
+
1. A brief overview of what is contained in each item
|
|
322
|
+
2. Key insights or important information
|
|
323
|
+
3. Any notable patterns, relationships, or conclusions
|
|
324
|
+
4. Suggestions for how this information might be used or what actions might be taken
|
|
325
|
+
|
|
326
|
+
Be thorough but concise in your analysis."""
|