abstractcore 2.4.0__py3-none-any.whl → 2.4.2__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/core/factory.py +7 -56
- abstractcore/exceptions/__init__.py +125 -0
- abstractcore/media/__init__.py +151 -0
- abstractcore/providers/__init__.py +27 -0
- abstractcore/providers/registry.py +406 -0
- abstractcore/server/app.py +47 -75
- abstractcore/utils/version.py +1 -1
- {abstractcore-2.4.0.dist-info → abstractcore-2.4.2.dist-info}/METADATA +1 -1
- {abstractcore-2.4.0.dist-info → abstractcore-2.4.2.dist-info}/RECORD +13 -10
- {abstractcore-2.4.0.dist-info → abstractcore-2.4.2.dist-info}/WHEEL +0 -0
- {abstractcore-2.4.0.dist-info → abstractcore-2.4.2.dist-info}/entry_points.txt +0 -0
- {abstractcore-2.4.0.dist-info → abstractcore-2.4.2.dist-info}/licenses/LICENSE +0 -0
- {abstractcore-2.4.0.dist-info → abstractcore-2.4.2.dist-info}/top_level.txt +0 -0
abstractcore/core/factory.py
CHANGED
|
@@ -64,59 +64,10 @@ def create_llm(provider: str, model: Optional[str] = None, **kwargs) -> Abstract
|
|
|
64
64
|
elif (".gguf" in model.lower() or "-gguf" in model.lower()) and provider.lower() == "mlx":
|
|
65
65
|
provider = "huggingface"
|
|
66
66
|
|
|
67
|
-
#
|
|
68
|
-
|
|
69
|
-
from ..providers.
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
try:
|
|
75
|
-
from ..providers.openai_provider import OpenAIProvider
|
|
76
|
-
return OpenAIProvider(model=model or "gpt-5-nano-2025-08-07", **kwargs)
|
|
77
|
-
except ImportError:
|
|
78
|
-
raise ImportError("OpenAI dependencies not installed. Install with: pip install abstractcore[openai]")
|
|
79
|
-
except (ModelNotFoundError, AuthenticationError, ProviderAPIError) as e:
|
|
80
|
-
# Re-raise provider exceptions cleanly
|
|
81
|
-
raise e
|
|
82
|
-
|
|
83
|
-
elif provider.lower() == "anthropic":
|
|
84
|
-
try:
|
|
85
|
-
from ..providers.anthropic_provider import AnthropicProvider
|
|
86
|
-
return AnthropicProvider(model=model or "claude-3-5-haiku-latest", **kwargs)
|
|
87
|
-
except ImportError:
|
|
88
|
-
raise ImportError("Anthropic dependencies not installed. Install with: pip install abstractcore[anthropic]")
|
|
89
|
-
except (ModelNotFoundError, AuthenticationError, ProviderAPIError) as e:
|
|
90
|
-
# Re-raise provider exceptions cleanly
|
|
91
|
-
raise e
|
|
92
|
-
|
|
93
|
-
elif provider.lower() == "ollama":
|
|
94
|
-
try:
|
|
95
|
-
from ..providers.ollama_provider import OllamaProvider
|
|
96
|
-
return OllamaProvider(model=model or "qwen3-coder:30b", **kwargs)
|
|
97
|
-
except ImportError:
|
|
98
|
-
raise ImportError("Ollama dependencies not installed. Install with: pip install abstractcore[ollama]")
|
|
99
|
-
|
|
100
|
-
elif provider.lower() == "huggingface":
|
|
101
|
-
try:
|
|
102
|
-
from ..providers.huggingface_provider import HuggingFaceProvider
|
|
103
|
-
return HuggingFaceProvider(model=model or "Qwen/Qwen3-4B/", **kwargs)
|
|
104
|
-
except ImportError:
|
|
105
|
-
raise ImportError("HuggingFace dependencies not installed. Install with: pip install abstractcore[huggingface]")
|
|
106
|
-
|
|
107
|
-
elif provider.lower() == "mlx":
|
|
108
|
-
try:
|
|
109
|
-
from ..providers.mlx_provider import MLXProvider
|
|
110
|
-
return MLXProvider(model=model or "mlx-community/Qwen3-4B", **kwargs)
|
|
111
|
-
except ImportError:
|
|
112
|
-
raise ImportError("MLX dependencies not installed. Install with: pip install abstractcore[mlx]")
|
|
113
|
-
|
|
114
|
-
elif provider.lower() == "lmstudio":
|
|
115
|
-
try:
|
|
116
|
-
from ..providers.lmstudio_provider import LMStudioProvider
|
|
117
|
-
return LMStudioProvider(model=model or "qwen/qwen3-4b-2507", **kwargs)
|
|
118
|
-
except ImportError:
|
|
119
|
-
raise ImportError("LM Studio provider not available")
|
|
120
|
-
|
|
121
|
-
else:
|
|
122
|
-
raise ValueError(f"Unknown provider: {provider}. Available providers: openai, anthropic, ollama, huggingface, mlx, lmstudio, mock")
|
|
67
|
+
# Use centralized provider registry for all provider creation
|
|
68
|
+
try:
|
|
69
|
+
from ..providers.registry import create_provider
|
|
70
|
+
return create_provider(provider, model, **kwargs)
|
|
71
|
+
except (ModelNotFoundError, AuthenticationError, ProviderAPIError) as e:
|
|
72
|
+
# Re-raise provider exceptions cleanly
|
|
73
|
+
raise e
|
|
@@ -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
|
|
@@ -9,7 +9,22 @@ from .huggingface_provider import HuggingFaceProvider
|
|
|
9
9
|
from .mlx_provider import MLXProvider
|
|
10
10
|
from .mock_provider import MockProvider
|
|
11
11
|
|
|
12
|
+
# Provider registry for centralized provider discovery and management
|
|
13
|
+
from .registry import (
|
|
14
|
+
ProviderRegistry,
|
|
15
|
+
ProviderInfo,
|
|
16
|
+
get_provider_registry,
|
|
17
|
+
list_available_providers,
|
|
18
|
+
get_provider_info,
|
|
19
|
+
is_provider_available,
|
|
20
|
+
get_all_providers_with_models,
|
|
21
|
+
get_all_providers_status,
|
|
22
|
+
create_provider,
|
|
23
|
+
get_available_models_for_provider
|
|
24
|
+
)
|
|
25
|
+
|
|
12
26
|
__all__ = [
|
|
27
|
+
# Provider classes
|
|
13
28
|
'BaseProvider',
|
|
14
29
|
'OpenAIProvider',
|
|
15
30
|
'AnthropicProvider',
|
|
@@ -18,4 +33,16 @@ __all__ = [
|
|
|
18
33
|
'HuggingFaceProvider',
|
|
19
34
|
'MLXProvider',
|
|
20
35
|
'MockProvider',
|
|
36
|
+
|
|
37
|
+
# Provider registry
|
|
38
|
+
'ProviderRegistry',
|
|
39
|
+
'ProviderInfo',
|
|
40
|
+
'get_provider_registry',
|
|
41
|
+
'list_available_providers',
|
|
42
|
+
'get_provider_info',
|
|
43
|
+
'is_provider_available',
|
|
44
|
+
'get_all_providers_with_models',
|
|
45
|
+
'get_all_providers_status',
|
|
46
|
+
'create_provider',
|
|
47
|
+
'get_available_models_for_provider',
|
|
21
48
|
]
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provider Registry - Centralized provider discovery and metadata management.
|
|
3
|
+
|
|
4
|
+
This module provides a single source of truth for all AbstractCore providers,
|
|
5
|
+
eliminating the need for manual synchronization across factory.py, server/app.py,
|
|
6
|
+
and __init__.py files.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import List, Dict, Any, Optional, Type, Callable
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from abc import ABC
|
|
12
|
+
import logging
|
|
13
|
+
from ..utils.structured_logging import get_logger
|
|
14
|
+
|
|
15
|
+
logger = get_logger("provider_registry")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ProviderInfo:
|
|
20
|
+
"""Information about a registered provider."""
|
|
21
|
+
name: str
|
|
22
|
+
display_name: str
|
|
23
|
+
provider_class: Type
|
|
24
|
+
description: str
|
|
25
|
+
provider_type: str = "llm"
|
|
26
|
+
default_model: Optional[str] = None
|
|
27
|
+
supported_features: List[str] = field(default_factory=list)
|
|
28
|
+
authentication_required: bool = True
|
|
29
|
+
local_provider: bool = False
|
|
30
|
+
installation_extras: Optional[str] = None
|
|
31
|
+
import_path: str = ""
|
|
32
|
+
|
|
33
|
+
def __post_init__(self):
|
|
34
|
+
"""Set default values after initialization."""
|
|
35
|
+
if not self.import_path:
|
|
36
|
+
self.import_path = f"..providers.{self.name}_provider"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ProviderRegistry:
|
|
40
|
+
"""
|
|
41
|
+
Centralized registry for all AbstractCore providers.
|
|
42
|
+
|
|
43
|
+
This registry serves as the single source of truth for provider discovery,
|
|
44
|
+
metadata, and instantiation across the entire AbstractCore system.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self):
|
|
48
|
+
self._providers: Dict[str, ProviderInfo] = {}
|
|
49
|
+
self._logger = get_logger("ProviderRegistry")
|
|
50
|
+
self._register_all_providers()
|
|
51
|
+
|
|
52
|
+
def _register_all_providers(self):
|
|
53
|
+
"""Register all available providers with their metadata."""
|
|
54
|
+
|
|
55
|
+
# OpenAI Provider
|
|
56
|
+
self.register_provider(ProviderInfo(
|
|
57
|
+
name="openai",
|
|
58
|
+
display_name="OpenAI",
|
|
59
|
+
provider_class=None, # Will be set during lazy loading
|
|
60
|
+
description="Commercial API with GPT-4, GPT-3.5, and embedding models",
|
|
61
|
+
default_model="gpt-5-nano-2025-08-07",
|
|
62
|
+
supported_features=["chat", "completion", "embeddings", "native_tools", "streaming", "structured_output"],
|
|
63
|
+
authentication_required=True,
|
|
64
|
+
local_provider=False,
|
|
65
|
+
installation_extras="openai",
|
|
66
|
+
import_path="..providers.openai_provider"
|
|
67
|
+
))
|
|
68
|
+
|
|
69
|
+
# Anthropic Provider
|
|
70
|
+
self.register_provider(ProviderInfo(
|
|
71
|
+
name="anthropic",
|
|
72
|
+
display_name="Anthropic",
|
|
73
|
+
provider_class=None,
|
|
74
|
+
description="Commercial API with Claude 3 family models",
|
|
75
|
+
default_model="claude-3-5-haiku-latest",
|
|
76
|
+
supported_features=["chat", "completion", "native_tools", "streaming", "structured_output"],
|
|
77
|
+
authentication_required=True,
|
|
78
|
+
local_provider=False,
|
|
79
|
+
installation_extras="anthropic",
|
|
80
|
+
import_path="..providers.anthropic_provider"
|
|
81
|
+
))
|
|
82
|
+
|
|
83
|
+
# Ollama Provider
|
|
84
|
+
self.register_provider(ProviderInfo(
|
|
85
|
+
name="ollama",
|
|
86
|
+
display_name="Ollama",
|
|
87
|
+
provider_class=None,
|
|
88
|
+
description="Local LLM server for running open-source models",
|
|
89
|
+
default_model="qwen3-coder:30b",
|
|
90
|
+
supported_features=["chat", "completion", "embeddings", "prompted_tools", "streaming"],
|
|
91
|
+
authentication_required=False,
|
|
92
|
+
local_provider=True,
|
|
93
|
+
installation_extras="ollama",
|
|
94
|
+
import_path="..providers.ollama_provider"
|
|
95
|
+
))
|
|
96
|
+
|
|
97
|
+
# LMStudio Provider
|
|
98
|
+
self.register_provider(ProviderInfo(
|
|
99
|
+
name="lmstudio",
|
|
100
|
+
display_name="LMStudio",
|
|
101
|
+
provider_class=None,
|
|
102
|
+
description="Local model development and testing platform",
|
|
103
|
+
default_model="qwen/qwen3-4b-2507",
|
|
104
|
+
supported_features=["chat", "completion", "embeddings", "prompted_tools", "streaming"],
|
|
105
|
+
authentication_required=False,
|
|
106
|
+
local_provider=True,
|
|
107
|
+
installation_extras=None,
|
|
108
|
+
import_path="..providers.lmstudio_provider"
|
|
109
|
+
))
|
|
110
|
+
|
|
111
|
+
# MLX Provider
|
|
112
|
+
self.register_provider(ProviderInfo(
|
|
113
|
+
name="mlx",
|
|
114
|
+
display_name="MLX",
|
|
115
|
+
provider_class=None,
|
|
116
|
+
description="Apple Silicon optimized local inference",
|
|
117
|
+
default_model="mlx-community/Qwen3-4B",
|
|
118
|
+
supported_features=["chat", "completion", "prompted_tools", "streaming", "apple_silicon"],
|
|
119
|
+
authentication_required=False,
|
|
120
|
+
local_provider=True,
|
|
121
|
+
installation_extras="mlx",
|
|
122
|
+
import_path="..providers.mlx_provider"
|
|
123
|
+
))
|
|
124
|
+
|
|
125
|
+
# HuggingFace Provider
|
|
126
|
+
self.register_provider(ProviderInfo(
|
|
127
|
+
name="huggingface",
|
|
128
|
+
display_name="HuggingFace",
|
|
129
|
+
provider_class=None,
|
|
130
|
+
description="Access to HuggingFace models (transformers and embeddings)",
|
|
131
|
+
default_model="Qwen/Qwen3-4B/",
|
|
132
|
+
supported_features=["chat", "completion", "embeddings", "prompted_tools", "local_models"],
|
|
133
|
+
authentication_required=False, # Optional for public models
|
|
134
|
+
local_provider=True,
|
|
135
|
+
installation_extras="huggingface",
|
|
136
|
+
import_path="..providers.huggingface_provider"
|
|
137
|
+
))
|
|
138
|
+
|
|
139
|
+
# Mock Provider
|
|
140
|
+
self.register_provider(ProviderInfo(
|
|
141
|
+
name="mock",
|
|
142
|
+
display_name="Mock",
|
|
143
|
+
provider_class=None,
|
|
144
|
+
description="Testing provider for development and unit tests",
|
|
145
|
+
default_model="mock-model",
|
|
146
|
+
supported_features=["chat", "completion", "embeddings", "prompted_tools", "streaming", "testing"],
|
|
147
|
+
authentication_required=False,
|
|
148
|
+
local_provider=True,
|
|
149
|
+
installation_extras=None,
|
|
150
|
+
import_path="..providers.mock_provider"
|
|
151
|
+
))
|
|
152
|
+
|
|
153
|
+
def register_provider(self, provider_info: ProviderInfo):
|
|
154
|
+
"""Register a provider in the registry."""
|
|
155
|
+
self._providers[provider_info.name] = provider_info
|
|
156
|
+
self._logger.debug(f"Registered provider: {provider_info.name}")
|
|
157
|
+
|
|
158
|
+
def get_provider_info(self, provider_name: str) -> Optional[ProviderInfo]:
|
|
159
|
+
"""Get information about a specific provider."""
|
|
160
|
+
return self._providers.get(provider_name.lower())
|
|
161
|
+
|
|
162
|
+
def list_provider_names(self) -> List[str]:
|
|
163
|
+
"""Get list of all registered provider names."""
|
|
164
|
+
return list(self._providers.keys())
|
|
165
|
+
|
|
166
|
+
def is_provider_available(self, provider_name: str) -> bool:
|
|
167
|
+
"""Check if a provider is registered."""
|
|
168
|
+
return provider_name.lower() in self._providers
|
|
169
|
+
|
|
170
|
+
def get_provider_class(self, provider_name: str):
|
|
171
|
+
"""Get the provider class, loading it lazily if needed."""
|
|
172
|
+
provider_info = self.get_provider_info(provider_name)
|
|
173
|
+
if not provider_info:
|
|
174
|
+
raise ValueError(f"Unknown provider: {provider_name}")
|
|
175
|
+
|
|
176
|
+
# Lazy loading of provider class
|
|
177
|
+
if provider_info.provider_class is None:
|
|
178
|
+
provider_info.provider_class = self._load_provider_class(provider_info)
|
|
179
|
+
|
|
180
|
+
return provider_info.provider_class
|
|
181
|
+
|
|
182
|
+
def _load_provider_class(self, provider_info: ProviderInfo):
|
|
183
|
+
"""Dynamically load a provider class."""
|
|
184
|
+
try:
|
|
185
|
+
if provider_info.name == "mock":
|
|
186
|
+
from ..providers.mock_provider import MockProvider
|
|
187
|
+
return MockProvider
|
|
188
|
+
elif provider_info.name == "openai":
|
|
189
|
+
from ..providers.openai_provider import OpenAIProvider
|
|
190
|
+
return OpenAIProvider
|
|
191
|
+
elif provider_info.name == "anthropic":
|
|
192
|
+
from ..providers.anthropic_provider import AnthropicProvider
|
|
193
|
+
return AnthropicProvider
|
|
194
|
+
elif provider_info.name == "ollama":
|
|
195
|
+
from ..providers.ollama_provider import OllamaProvider
|
|
196
|
+
return OllamaProvider
|
|
197
|
+
elif provider_info.name == "lmstudio":
|
|
198
|
+
from ..providers.lmstudio_provider import LMStudioProvider
|
|
199
|
+
return LMStudioProvider
|
|
200
|
+
elif provider_info.name == "mlx":
|
|
201
|
+
from ..providers.mlx_provider import MLXProvider
|
|
202
|
+
return MLXProvider
|
|
203
|
+
elif provider_info.name == "huggingface":
|
|
204
|
+
from ..providers.huggingface_provider import HuggingFaceProvider
|
|
205
|
+
return HuggingFaceProvider
|
|
206
|
+
else:
|
|
207
|
+
raise ImportError(f"No import logic for provider: {provider_info.name}")
|
|
208
|
+
except ImportError as e:
|
|
209
|
+
self._logger.warning(f"Failed to load provider {provider_info.name}: {e}")
|
|
210
|
+
raise ImportError(
|
|
211
|
+
f"{provider_info.display_name} dependencies not installed. "
|
|
212
|
+
f"Install with: pip install abstractcore[{provider_info.installation_extras}]"
|
|
213
|
+
) from e
|
|
214
|
+
|
|
215
|
+
def get_available_models(self, provider_name: str, **kwargs) -> List[str]:
|
|
216
|
+
"""
|
|
217
|
+
Get available models for a specific provider.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
provider_name: Name of the provider
|
|
221
|
+
**kwargs: Provider-specific parameters (e.g., api_key, base_url)
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
List of available model names
|
|
225
|
+
"""
|
|
226
|
+
try:
|
|
227
|
+
provider_class = self.get_provider_class(provider_name)
|
|
228
|
+
|
|
229
|
+
# Handle providers that need instance for model listing
|
|
230
|
+
if provider_name in ["anthropic", "ollama", "lmstudio"]:
|
|
231
|
+
provider_info = self.get_provider_info(provider_name)
|
|
232
|
+
# Create minimal instance for API access
|
|
233
|
+
instance = provider_class(model=provider_info.default_model, **kwargs)
|
|
234
|
+
return instance.list_available_models(**kwargs)
|
|
235
|
+
else:
|
|
236
|
+
# Handle providers with static method or class method
|
|
237
|
+
try:
|
|
238
|
+
# First try as static/class method
|
|
239
|
+
return provider_class.list_available_models(**kwargs)
|
|
240
|
+
except TypeError:
|
|
241
|
+
# If that fails (method needs 'self'), create temporary instance
|
|
242
|
+
provider_info = self.get_provider_info(provider_name)
|
|
243
|
+
instance = provider_class(model=provider_info.default_model, **kwargs)
|
|
244
|
+
return instance.list_available_models(**kwargs)
|
|
245
|
+
|
|
246
|
+
except Exception as e:
|
|
247
|
+
self._logger.debug(f"Failed to get models from provider {provider_name}: {e}")
|
|
248
|
+
return []
|
|
249
|
+
|
|
250
|
+
def get_provider_status(self, provider_name: str) -> Dict[str, Any]:
|
|
251
|
+
"""
|
|
252
|
+
Get detailed status information for a provider.
|
|
253
|
+
|
|
254
|
+
Returns provider information including availability, model count, etc.
|
|
255
|
+
This is used by the server /providers endpoint.
|
|
256
|
+
"""
|
|
257
|
+
provider_info = self.get_provider_info(provider_name)
|
|
258
|
+
if not provider_info:
|
|
259
|
+
return {
|
|
260
|
+
"name": provider_name,
|
|
261
|
+
"status": "unknown",
|
|
262
|
+
"error": "Provider not registered"
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
# Try to get models to test availability
|
|
267
|
+
models = self.get_available_models(provider_name)
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
"name": provider_info.name,
|
|
271
|
+
"display_name": provider_info.display_name,
|
|
272
|
+
"type": provider_info.provider_type,
|
|
273
|
+
"model_count": len(models),
|
|
274
|
+
"status": "available" if models else "no_models",
|
|
275
|
+
"description": provider_info.description,
|
|
276
|
+
"local_provider": provider_info.local_provider,
|
|
277
|
+
"authentication_required": provider_info.authentication_required,
|
|
278
|
+
"supported_features": provider_info.supported_features,
|
|
279
|
+
"installation_extras": provider_info.installation_extras,
|
|
280
|
+
"models": models
|
|
281
|
+
}
|
|
282
|
+
except Exception as e:
|
|
283
|
+
return {
|
|
284
|
+
"name": provider_info.name,
|
|
285
|
+
"display_name": provider_info.display_name,
|
|
286
|
+
"type": provider_info.provider_type,
|
|
287
|
+
"model_count": 0,
|
|
288
|
+
"status": "error",
|
|
289
|
+
"description": provider_info.description,
|
|
290
|
+
"error": str(e),
|
|
291
|
+
"local_provider": provider_info.local_provider,
|
|
292
|
+
"authentication_required": provider_info.authentication_required,
|
|
293
|
+
"supported_features": provider_info.supported_features,
|
|
294
|
+
"installation_extras": provider_info.installation_extras
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
def get_all_providers_status(self) -> List[Dict[str, Any]]:
|
|
298
|
+
"""Get status information for all registered providers."""
|
|
299
|
+
return [
|
|
300
|
+
self.get_provider_status(provider_name)
|
|
301
|
+
for provider_name in self.list_provider_names()
|
|
302
|
+
]
|
|
303
|
+
|
|
304
|
+
def get_providers_with_models(self) -> List[Dict[str, Any]]:
|
|
305
|
+
"""Get only providers that have available models."""
|
|
306
|
+
all_providers = self.get_all_providers_status()
|
|
307
|
+
return [
|
|
308
|
+
provider for provider in all_providers
|
|
309
|
+
if provider.get("status") == "available" and provider.get("model_count", 0) > 0
|
|
310
|
+
]
|
|
311
|
+
|
|
312
|
+
def create_provider_instance(self, provider_name: str, model: Optional[str] = None, **kwargs):
|
|
313
|
+
"""
|
|
314
|
+
Create a provider instance using the registry.
|
|
315
|
+
|
|
316
|
+
This is used by the factory to create provider instances.
|
|
317
|
+
"""
|
|
318
|
+
provider_info = self.get_provider_info(provider_name)
|
|
319
|
+
if not provider_info:
|
|
320
|
+
available_providers = ", ".join(self.list_provider_names())
|
|
321
|
+
raise ValueError(f"Unknown provider: {provider_name}. Available providers: {available_providers}")
|
|
322
|
+
|
|
323
|
+
provider_class = self.get_provider_class(provider_name)
|
|
324
|
+
model = model or provider_info.default_model
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
return provider_class(model=model, **kwargs)
|
|
328
|
+
except ImportError as e:
|
|
329
|
+
# Re-raise import errors with helpful message
|
|
330
|
+
if provider_info.installation_extras:
|
|
331
|
+
raise ImportError(
|
|
332
|
+
f"{provider_info.display_name} dependencies not installed. "
|
|
333
|
+
f"Install with: pip install abstractcore[{provider_info.installation_extras}]"
|
|
334
|
+
) from e
|
|
335
|
+
else:
|
|
336
|
+
raise ImportError(f"{provider_info.display_name} provider not available") from e
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
# Global registry instance
|
|
340
|
+
_registry = None
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def get_provider_registry() -> ProviderRegistry:
|
|
344
|
+
"""Get the global provider registry instance."""
|
|
345
|
+
global _registry
|
|
346
|
+
if _registry is None:
|
|
347
|
+
_registry = ProviderRegistry()
|
|
348
|
+
return _registry
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
# Convenience functions for external use
|
|
352
|
+
def list_available_providers() -> List[str]:
|
|
353
|
+
"""Get list of all available provider names."""
|
|
354
|
+
return get_provider_registry().list_provider_names()
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def get_provider_info(provider_name: str) -> Optional[ProviderInfo]:
|
|
358
|
+
"""Get information about a specific provider."""
|
|
359
|
+
return get_provider_registry().get_provider_info(provider_name)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def is_provider_available(provider_name: str) -> bool:
|
|
363
|
+
"""Check if a provider is available."""
|
|
364
|
+
return get_provider_registry().is_provider_available(provider_name)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def get_all_providers_with_models() -> List[Dict[str, Any]]:
|
|
368
|
+
"""
|
|
369
|
+
Get comprehensive information about all providers with available models.
|
|
370
|
+
|
|
371
|
+
This is the main function that should be used throughout AbstractCore
|
|
372
|
+
for provider discovery and information. It replaces the manual provider
|
|
373
|
+
lists in factory.py and server/app.py.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
List of provider dictionaries with comprehensive metadata including:
|
|
377
|
+
- name, display_name, type, description
|
|
378
|
+
- model_count, status, supported_features
|
|
379
|
+
- local_provider, authentication_required
|
|
380
|
+
- installation_extras, sample models
|
|
381
|
+
"""
|
|
382
|
+
return get_provider_registry().get_providers_with_models()
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def get_all_providers_status() -> List[Dict[str, Any]]:
|
|
386
|
+
"""
|
|
387
|
+
Get status information for all registered providers.
|
|
388
|
+
|
|
389
|
+
This includes providers that may not have models available,
|
|
390
|
+
useful for debugging and comprehensive provider listing.
|
|
391
|
+
"""
|
|
392
|
+
return get_provider_registry().get_all_providers_status()
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def create_provider(provider_name: str, model: Optional[str] = None, **kwargs):
|
|
396
|
+
"""
|
|
397
|
+
Create a provider instance using the centralized registry.
|
|
398
|
+
|
|
399
|
+
This replaces the factory logic and provides better error messages.
|
|
400
|
+
"""
|
|
401
|
+
return get_provider_registry().create_provider_instance(provider_name, model, **kwargs)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def get_available_models_for_provider(provider_name: str, **kwargs) -> List[str]:
|
|
405
|
+
"""Get available models for a specific provider."""
|
|
406
|
+
return get_provider_registry().get_available_models(provider_name, **kwargs)
|
abstractcore/server/app.py
CHANGED
|
@@ -109,50 +109,14 @@ def is_embedding_model(model_name: str) -> bool:
|
|
|
109
109
|
return any(pattern in model_lower for pattern in embedding_patterns)
|
|
110
110
|
|
|
111
111
|
# ============================================================================
|
|
112
|
-
# Provider Model Discovery
|
|
112
|
+
# Provider Model Discovery (Using Centralized Registry)
|
|
113
113
|
# ============================================================================
|
|
114
114
|
|
|
115
115
|
def get_models_from_provider(provider_name: str) -> List[str]:
|
|
116
|
-
"""Get available models from a specific provider using
|
|
116
|
+
"""Get available models from a specific provider using the centralized provider registry."""
|
|
117
117
|
try:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return OpenAIProvider.list_available_models()
|
|
121
|
-
elif provider_name == "anthropic":
|
|
122
|
-
from ..providers.anthropic_provider import AnthropicProvider
|
|
123
|
-
# Need minimal instance for API key access
|
|
124
|
-
try:
|
|
125
|
-
provider = AnthropicProvider(model="claude-3-haiku-20240307")
|
|
126
|
-
return provider.list_available_models()
|
|
127
|
-
except Exception:
|
|
128
|
-
return []
|
|
129
|
-
elif provider_name == "ollama":
|
|
130
|
-
from ..providers.ollama_provider import OllamaProvider
|
|
131
|
-
# Need minimal instance for HTTP client
|
|
132
|
-
try:
|
|
133
|
-
provider = OllamaProvider(model="llama2")
|
|
134
|
-
return provider.list_available_models()
|
|
135
|
-
except Exception:
|
|
136
|
-
return []
|
|
137
|
-
elif provider_name == "lmstudio":
|
|
138
|
-
from ..providers.lmstudio_provider import LMStudioProvider
|
|
139
|
-
# Need minimal instance for HTTP client
|
|
140
|
-
try:
|
|
141
|
-
provider = LMStudioProvider(model="local-model")
|
|
142
|
-
return provider.list_available_models()
|
|
143
|
-
except Exception:
|
|
144
|
-
return []
|
|
145
|
-
elif provider_name == "mlx":
|
|
146
|
-
from ..providers.mlx_provider import MLXProvider
|
|
147
|
-
return MLXProvider.list_available_models()
|
|
148
|
-
elif provider_name == "huggingface":
|
|
149
|
-
from ..providers.huggingface_provider import HuggingFaceProvider
|
|
150
|
-
return HuggingFaceProvider.list_available_models()
|
|
151
|
-
elif provider_name == "mock":
|
|
152
|
-
# Mock provider for testing
|
|
153
|
-
return ["mock-model-1", "mock-model-2", "mock-embedding-1"]
|
|
154
|
-
else:
|
|
155
|
-
return []
|
|
118
|
+
from ..providers.registry import get_available_models_for_provider
|
|
119
|
+
return get_available_models_for_provider(provider_name)
|
|
156
120
|
except Exception as e:
|
|
157
121
|
logger.debug(f"Failed to get models from provider {provider_name}: {e}")
|
|
158
122
|
return []
|
|
@@ -508,7 +472,7 @@ async def list_models(
|
|
|
508
472
|
continue # Skip non-embedding models
|
|
509
473
|
if type == ModelType.TEXT_GENERATION and is_embedding:
|
|
510
474
|
continue # Skip embedding models
|
|
511
|
-
|
|
475
|
+
|
|
512
476
|
model_id = f"{provider.lower()}/{model}"
|
|
513
477
|
models_data.append({
|
|
514
478
|
"id": model_id,
|
|
@@ -517,12 +481,13 @@ async def list_models(
|
|
|
517
481
|
"created": int(time.time()),
|
|
518
482
|
"permission": [{"allow_create_engine": False, "allow_sampling": True}]
|
|
519
483
|
})
|
|
520
|
-
|
|
484
|
+
|
|
521
485
|
filter_msg = f" (type={type.value})" if type else ""
|
|
522
486
|
logger.info(f"Listed {len(models_data)} models for provider {provider}{filter_msg}")
|
|
523
487
|
else:
|
|
524
|
-
# Get models from all providers
|
|
525
|
-
providers
|
|
488
|
+
# Get models from all providers using centralized registry
|
|
489
|
+
from ..providers.registry import list_available_providers
|
|
490
|
+
providers = list_available_providers()
|
|
526
491
|
for prov in providers:
|
|
527
492
|
models = get_models_from_provider(prov)
|
|
528
493
|
for model in models:
|
|
@@ -533,7 +498,7 @@ async def list_models(
|
|
|
533
498
|
continue # Skip non-embedding models
|
|
534
499
|
if type == ModelType.TEXT_GENERATION and is_embedding:
|
|
535
500
|
continue # Skip embedding models
|
|
536
|
-
|
|
501
|
+
|
|
537
502
|
model_id = f"{prov}/{model}"
|
|
538
503
|
models_data.append({
|
|
539
504
|
"id": model_id,
|
|
@@ -542,7 +507,7 @@ async def list_models(
|
|
|
542
507
|
"created": int(time.time()),
|
|
543
508
|
"permission": [{"allow_create_engine": False, "allow_sampling": True}]
|
|
544
509
|
})
|
|
545
|
-
|
|
510
|
+
|
|
546
511
|
filter_msg = f" (type={type.value})" if type else ""
|
|
547
512
|
logger.info(f"Listed {len(models_data)} models from all providers{filter_msg}")
|
|
548
513
|
|
|
@@ -562,13 +527,15 @@ async def list_models(
|
|
|
562
527
|
async def list_providers():
|
|
563
528
|
"""
|
|
564
529
|
List all available AbstractCore providers and their capabilities.
|
|
565
|
-
|
|
566
|
-
Returns information about all registered LLM providers, including:
|
|
567
|
-
- Provider name and type
|
|
568
|
-
- Number of available models
|
|
569
|
-
- Current availability status
|
|
570
|
-
- Provider description
|
|
571
|
-
|
|
530
|
+
|
|
531
|
+
Returns comprehensive information about all registered LLM providers, including:
|
|
532
|
+
- Provider name, display name, and type
|
|
533
|
+
- Number of available models and sample models
|
|
534
|
+
- Current availability status and detailed error information
|
|
535
|
+
- Provider description and supported features
|
|
536
|
+
- Authentication requirements and installation instructions
|
|
537
|
+
- Local vs. cloud provider designation
|
|
538
|
+
|
|
572
539
|
**Supported Providers:**
|
|
573
540
|
- **OpenAI**: Commercial API with GPT-4, GPT-3.5, and embedding models
|
|
574
541
|
- **Anthropic**: Commercial API with Claude 3 family models
|
|
@@ -577,42 +544,47 @@ async def list_providers():
|
|
|
577
544
|
- **MLX**: Apple Silicon optimized local inference
|
|
578
545
|
- **HuggingFace**: Access to HuggingFace models (transformers and embeddings)
|
|
579
546
|
- **Mock**: Testing provider for development
|
|
580
|
-
|
|
547
|
+
|
|
581
548
|
**Use Cases:**
|
|
582
549
|
- Discover available providers before making requests
|
|
583
550
|
- Check provider availability and model counts
|
|
584
551
|
- Build dynamic provider selection UIs
|
|
585
|
-
- Monitor provider status
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
552
|
+
- Monitor provider status and troubleshoot issues
|
|
553
|
+
- Get installation instructions for missing dependencies
|
|
554
|
+
|
|
555
|
+
**Enhanced Information:**
|
|
556
|
+
This endpoint now uses the centralized provider registry to provide
|
|
557
|
+
comprehensive information including supported features, authentication
|
|
558
|
+
requirements, and detailed status information.
|
|
559
|
+
|
|
560
|
+
**Returns:** A list of provider objects with comprehensive metadata.
|
|
590
561
|
"""
|
|
591
562
|
try:
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
"type": "llm", # Could be extended to include "embedding" type
|
|
601
|
-
"model_count": len(models),
|
|
602
|
-
"status": "available",
|
|
603
|
-
"description": f"{provider_name.title()} provider with {len(models)} available models"
|
|
604
|
-
})
|
|
563
|
+
from ..providers.registry import get_all_providers_with_models, get_all_providers_status
|
|
564
|
+
|
|
565
|
+
# Get providers with models (available providers)
|
|
566
|
+
available_providers = get_all_providers_with_models()
|
|
567
|
+
|
|
568
|
+
# Optionally include all providers (even those with issues) for debugging
|
|
569
|
+
# Uncomment the next line if you want to see providers with errors too:
|
|
570
|
+
# all_providers = get_all_providers_status()
|
|
605
571
|
|
|
606
|
-
logger.info(f"Listed {len(
|
|
572
|
+
logger.info(f"Listed {len(available_providers)} available providers with models")
|
|
607
573
|
|
|
608
574
|
return {
|
|
609
|
-
"providers":
|
|
575
|
+
"providers": available_providers,
|
|
576
|
+
"total_providers": len(available_providers),
|
|
577
|
+
"registry_version": "2.0", # Indicate this is using the new registry system
|
|
578
|
+
"note": "Provider information from centralized AbstractCore registry"
|
|
610
579
|
}
|
|
611
580
|
|
|
612
581
|
except Exception as e:
|
|
613
582
|
logger.error(f"Failed to list providers: {e}")
|
|
614
583
|
return {
|
|
615
|
-
"providers": []
|
|
584
|
+
"providers": [],
|
|
585
|
+
"total_providers": 0,
|
|
586
|
+
"error": str(e),
|
|
587
|
+
"registry_version": "2.0"
|
|
616
588
|
}
|
|
617
589
|
|
|
618
590
|
@app.post("/v1/responses")
|
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.2"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: abstractcore
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.2
|
|
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>
|
|
@@ -12,7 +12,7 @@ abstractcore/assets/model_capabilities.json,sha256=mSfP5C68I1c4GNLgL0sCDjpSZA3ks
|
|
|
12
12
|
abstractcore/assets/session_schema.json,sha256=b6HTAWxRVlVhAzA7FqaKpunK1yO6jilBOsD5sQkqJTo,10580
|
|
13
13
|
abstractcore/core/__init__.py,sha256=2h-86U4QkCQ4gzZ4iRusSTMlkODiUS6tKjZHiEXz6rM,684
|
|
14
14
|
abstractcore/core/enums.py,sha256=BhkVnHC-X1_377JDmqd-2mnem9GdBLqixWlYzlP_FJU,695
|
|
15
|
-
abstractcore/core/factory.py,sha256=
|
|
15
|
+
abstractcore/core/factory.py,sha256=UdrNwQAvifvFS3LMjF5KO87m-2n1bJBryTs9pvesYcI,2804
|
|
16
16
|
abstractcore/core/interface.py,sha256=DIA46gR7JN33kLyKbtbAkoME7l7rixtKbFGAQgt7ilk,13693
|
|
17
17
|
abstractcore/core/retry.py,sha256=wNlUAxfmvdO_uVWb4iqkhTqd7O1oRwXxqvVQaLXQOw0,14538
|
|
18
18
|
abstractcore/core/session.py,sha256=Zjy_oo_Ha_Kufrf4ffE9Sxl2gVXcdpBaYl-IssqoBe4,35200
|
|
@@ -21,11 +21,13 @@ 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
|
|
27
29
|
abstractcore/processing/basic_summarizer.py,sha256=XHNxMQ_8aLStTeUo6_2JaThlct12Htpz7ORmm0iuJsg,25495
|
|
28
|
-
abstractcore/providers/__init__.py,sha256=
|
|
30
|
+
abstractcore/providers/__init__.py,sha256=t8Kp4flH5GvZEC6dx-iYJSPeSxMODa2spXb8MqtlPy4,1282
|
|
29
31
|
abstractcore/providers/anthropic_provider.py,sha256=BM8Vu89c974yicvFwlsJ5C3N0wR9Kkt1pOszViWCwAQ,19694
|
|
30
32
|
abstractcore/providers/base.py,sha256=AJa9KFJGLJvrNrhI-EHaVSzaF1ocOOYc98GnnCjLuag,42824
|
|
31
33
|
abstractcore/providers/huggingface_provider.py,sha256=aucyfGFHi67NRU7_5vWp2TEXdBBjb710zz0lQjC_rWo,42283
|
|
@@ -34,9 +36,10 @@ abstractcore/providers/mlx_provider.py,sha256=eANGeexmJIS4KWn77fRBOJRkXvwgh7irAt
|
|
|
34
36
|
abstractcore/providers/mock_provider.py,sha256=tIjA57Hlwu3vNODOZShNn0tY9HWvz1p4z-HyD_bsvbo,5741
|
|
35
37
|
abstractcore/providers/ollama_provider.py,sha256=SkXD5gjuzeu9Thqnt4pRPSi-cjWxwuZGV2x5YMm26jo,19340
|
|
36
38
|
abstractcore/providers/openai_provider.py,sha256=xGFrSkbCrsBnWnho1U2aMCBdzfCqf121wU1EFGmU3YQ,21678
|
|
39
|
+
abstractcore/providers/registry.py,sha256=c0hxp9RRa-uipGotaAu48fHXc_HGlLcOxC1k763mzhU,16596
|
|
37
40
|
abstractcore/providers/streaming.py,sha256=VnffBV_CU9SAKzghL154OoFyEdDsiLwUNXPahyU41Bw,31342
|
|
38
41
|
abstractcore/server/__init__.py,sha256=1DSAz_YhQtnKv7sNi5TMQV8GFujctDOabgvAdilQE0o,249
|
|
39
|
-
abstractcore/server/app.py,sha256=
|
|
42
|
+
abstractcore/server/app.py,sha256=DCzKEe2hzvIfdq3N410boU0kRocFipRoiFDnABEGkYs,43350
|
|
40
43
|
abstractcore/structured/__init__.py,sha256=VXRQHGcm-iaYnLOBPin2kyhvhhQA0kaGt_pcNDGsE_8,339
|
|
41
44
|
abstractcore/structured/handler.py,sha256=Vb15smiR81JGDXX2RLkY2Exuj67J7a6C-xwVrZoXp0I,17134
|
|
42
45
|
abstractcore/structured/retry.py,sha256=BN_PvrWybyU1clMy2cult1-TVxFSMaVqiCPmmXvA5aI,3805
|
|
@@ -53,10 +56,10 @@ abstractcore/utils/cli.py,sha256=8ua5Lu4bKSs9y1JB5W3kJ0OA-_dFpUHk6prH1U684xc,568
|
|
|
53
56
|
abstractcore/utils/self_fixes.py,sha256=QEDwNTW80iQM4ftfEY3Ghz69F018oKwLM9yeRCYZOvw,5886
|
|
54
57
|
abstractcore/utils/structured_logging.py,sha256=Y7TVkf1tP9BCOPNbBY1rQubBxcAxhhUOYMbrV2k50ZM,15830
|
|
55
58
|
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.
|
|
59
|
+
abstractcore/utils/version.py,sha256=5z25ZawOKXC7_TsMUhliL7I_XUADAb5ums58hgtaXEc,605
|
|
60
|
+
abstractcore-2.4.2.dist-info/licenses/LICENSE,sha256=PI2v_4HMvd6050uDD_4AY_8PzBnu2asa3RKbdDjowTA,1078
|
|
61
|
+
abstractcore-2.4.2.dist-info/METADATA,sha256=AMCwg1STsdh8FmhmHw05GNK6ANM8nN1VDvusq7lFEFM,19684
|
|
62
|
+
abstractcore-2.4.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
63
|
+
abstractcore-2.4.2.dist-info/entry_points.txt,sha256=Ocy403YwzaOBT7D_vf7w6YFiIQ4nTbp0htjXfeI5IOo,315
|
|
64
|
+
abstractcore-2.4.2.dist-info/top_level.txt,sha256=DiNHBI35SIawW3N9Z-z0y6cQYNbXd32pvBkW0RLfScs,13
|
|
65
|
+
abstractcore-2.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|