abstractcore 2.4.4__py3-none-any.whl → 2.4.6__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.
Files changed (31) hide show
  1. abstractcore/cli/__init__.py +9 -0
  2. abstractcore/cli/main.py +759 -0
  3. abstractcore/cli/vision_config.py +491 -0
  4. abstractcore/core/interface.py +7 -0
  5. abstractcore/core/session.py +27 -2
  6. abstractcore/media/handlers/__init__.py +16 -0
  7. abstractcore/media/handlers/anthropic_handler.py +326 -0
  8. abstractcore/media/handlers/local_handler.py +541 -0
  9. abstractcore/media/handlers/openai_handler.py +281 -0
  10. abstractcore/media/processors/__init__.py +13 -0
  11. abstractcore/media/processors/image_processor.py +610 -0
  12. abstractcore/media/processors/office_processor.py +490 -0
  13. abstractcore/media/processors/pdf_processor.py +485 -0
  14. abstractcore/media/processors/text_processor.py +557 -0
  15. abstractcore/media/utils/__init__.py +22 -0
  16. abstractcore/media/utils/image_scaler.py +306 -0
  17. abstractcore/providers/anthropic_provider.py +14 -2
  18. abstractcore/providers/base.py +24 -0
  19. abstractcore/providers/huggingface_provider.py +23 -9
  20. abstractcore/providers/lmstudio_provider.py +6 -1
  21. abstractcore/providers/mlx_provider.py +20 -7
  22. abstractcore/providers/ollama_provider.py +6 -1
  23. abstractcore/providers/openai_provider.py +6 -2
  24. abstractcore/tools/common_tools.py +651 -1
  25. abstractcore/utils/version.py +1 -1
  26. {abstractcore-2.4.4.dist-info → abstractcore-2.4.6.dist-info}/METADATA +59 -9
  27. {abstractcore-2.4.4.dist-info → abstractcore-2.4.6.dist-info}/RECORD +31 -17
  28. {abstractcore-2.4.4.dist-info → abstractcore-2.4.6.dist-info}/entry_points.txt +2 -0
  29. {abstractcore-2.4.4.dist-info → abstractcore-2.4.6.dist-info}/WHEEL +0 -0
  30. {abstractcore-2.4.4.dist-info → abstractcore-2.4.6.dist-info}/licenses/LICENSE +0 -0
  31. {abstractcore-2.4.4.dist-info → abstractcore-2.4.6.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."""