abstractcore 2.4.1__tar.gz → 2.4.2__tar.gz

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 (124) hide show
  1. {abstractcore-2.4.1 → abstractcore-2.4.2}/PKG-INFO +1 -1
  2. abstractcore-2.4.2/abstractcore/core/factory.py +73 -0
  3. abstractcore-2.4.2/abstractcore/providers/__init__.py +48 -0
  4. abstractcore-2.4.2/abstractcore/providers/registry.py +406 -0
  5. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/server/app.py +47 -75
  6. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/utils/version.py +1 -1
  7. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore.egg-info/PKG-INFO +1 -1
  8. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore.egg-info/SOURCES.txt +1 -0
  9. abstractcore-2.4.1/abstractcore/core/factory.py +0 -122
  10. abstractcore-2.4.1/abstractcore/providers/__init__.py +0 -21
  11. {abstractcore-2.4.1 → abstractcore-2.4.2}/LICENSE +0 -0
  12. {abstractcore-2.4.1 → abstractcore-2.4.2}/README.md +0 -0
  13. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/__init__.py +0 -0
  14. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/apps/__init__.py +0 -0
  15. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/apps/__main__.py +0 -0
  16. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/apps/extractor.py +0 -0
  17. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/apps/judge.py +0 -0
  18. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/apps/summarizer.py +0 -0
  19. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/architectures/__init__.py +0 -0
  20. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/architectures/detection.py +0 -0
  21. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/architectures/enums.py +0 -0
  22. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/assets/architecture_formats.json +0 -0
  23. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/assets/model_capabilities.json +0 -0
  24. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/assets/session_schema.json +0 -0
  25. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/core/__init__.py +0 -0
  26. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/core/enums.py +0 -0
  27. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/core/interface.py +0 -0
  28. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/core/retry.py +0 -0
  29. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/core/session.py +0 -0
  30. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/core/types.py +0 -0
  31. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/embeddings/__init__.py +0 -0
  32. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/embeddings/manager.py +0 -0
  33. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/embeddings/models.py +0 -0
  34. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/events/__init__.py +0 -0
  35. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/exceptions/__init__.py +0 -0
  36. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/media/__init__.py +0 -0
  37. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/processing/__init__.py +0 -0
  38. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/processing/basic_extractor.py +0 -0
  39. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/processing/basic_judge.py +0 -0
  40. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/processing/basic_summarizer.py +0 -0
  41. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/providers/anthropic_provider.py +0 -0
  42. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/providers/base.py +0 -0
  43. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/providers/huggingface_provider.py +0 -0
  44. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/providers/lmstudio_provider.py +0 -0
  45. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/providers/mlx_provider.py +0 -0
  46. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/providers/mock_provider.py +0 -0
  47. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/providers/ollama_provider.py +0 -0
  48. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/providers/openai_provider.py +0 -0
  49. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/providers/streaming.py +0 -0
  50. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/server/__init__.py +0 -0
  51. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/structured/__init__.py +0 -0
  52. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/structured/handler.py +0 -0
  53. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/structured/retry.py +0 -0
  54. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/tools/__init__.py +0 -0
  55. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/tools/common_tools.py +0 -0
  56. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/tools/core.py +0 -0
  57. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/tools/handler.py +0 -0
  58. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/tools/parser.py +0 -0
  59. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/tools/registry.py +0 -0
  60. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/tools/syntax_rewriter.py +0 -0
  61. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/tools/tag_rewriter.py +0 -0
  62. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/utils/__init__.py +0 -0
  63. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/utils/cli.py +0 -0
  64. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/utils/self_fixes.py +0 -0
  65. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/utils/structured_logging.py +0 -0
  66. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore/utils/token_utils.py +0 -0
  67. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore.egg-info/dependency_links.txt +0 -0
  68. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore.egg-info/entry_points.txt +0 -0
  69. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore.egg-info/requires.txt +0 -0
  70. {abstractcore-2.4.1 → abstractcore-2.4.2}/abstractcore.egg-info/top_level.txt +0 -0
  71. {abstractcore-2.4.1 → abstractcore-2.4.2}/pyproject.toml +0 -0
  72. {abstractcore-2.4.1 → abstractcore-2.4.2}/setup.cfg +0 -0
  73. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_agentic_cli_compatibility.py +0 -0
  74. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_all_specified_providers.py +0 -0
  75. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_basic_session.py +0 -0
  76. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_basic_summarizer.py +0 -0
  77. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_complete_integration.py +0 -0
  78. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_comprehensive_events.py +0 -0
  79. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_core_components.py +0 -0
  80. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_critical_streaming_tool_fix.py +0 -0
  81. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_embeddings.py +0 -0
  82. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_embeddings_integration.py +0 -0
  83. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_embeddings_llm_integration.py +0 -0
  84. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_embeddings_matrix_operations.py +0 -0
  85. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_embeddings_no_mock.py +0 -0
  86. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_embeddings_real.py +0 -0
  87. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_embeddings_semantic_validation.py +0 -0
  88. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_embeddings_simple.py +0 -0
  89. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_environment_variable_tool_call_tags.py +0 -0
  90. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_factory.py +0 -0
  91. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_final_comprehensive.py +0 -0
  92. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_final_graceful_errors.py +0 -0
  93. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_graceful_fallback.py +0 -0
  94. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_integrated_functionality.py +0 -0
  95. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_ollama_tool_role_fix.py +0 -0
  96. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_openai_conversion_manual.py +0 -0
  97. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_openai_format_bug.py +0 -0
  98. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_openai_format_conversion.py +0 -0
  99. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_progressive_complexity.py +0 -0
  100. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_provider_basic_session.py +0 -0
  101. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_provider_connectivity.py +0 -0
  102. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_provider_simple_generation.py +0 -0
  103. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_provider_streaming.py +0 -0
  104. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_provider_token_translation.py +0 -0
  105. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_provider_tool_detection.py +0 -0
  106. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_providers.py +0 -0
  107. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_providers_comprehensive.py +0 -0
  108. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_providers_simple.py +0 -0
  109. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_real_models_comprehensive.py +0 -0
  110. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_retry_observability.py +0 -0
  111. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_retry_strategy.py +0 -0
  112. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_server_embeddings_real.py +0 -0
  113. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_stream_tool_calling.py +0 -0
  114. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_streaming_enhancements.py +0 -0
  115. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_streaming_tag_rewriting.py +0 -0
  116. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_structured_integration.py +0 -0
  117. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_structured_output.py +0 -0
  118. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_syntax_rewriter.py +0 -0
  119. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_tool_calling.py +0 -0
  120. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_tool_execution_separation.py +0 -0
  121. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_unified_streaming.py +0 -0
  122. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_unload_memory.py +0 -0
  123. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_user_scenario_validation.py +0 -0
  124. {abstractcore-2.4.1 → abstractcore-2.4.2}/tests/test_wrong_model_fallback.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: abstractcore
3
- Version: 2.4.1
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>
@@ -0,0 +1,73 @@
1
+ """
2
+ Factory for creating LLM providers.
3
+ """
4
+
5
+ from typing import Optional
6
+ from .interface import AbstractCoreInterface
7
+ from ..exceptions import ModelNotFoundError, AuthenticationError, ProviderAPIError
8
+
9
+
10
+ def create_llm(provider: str, model: Optional[str] = None, **kwargs) -> AbstractCoreInterface:
11
+ """
12
+ Create an LLM provider instance with unified token parameter support.
13
+
14
+ Args:
15
+ provider: Provider name (openai, anthropic, ollama, huggingface, mlx, lmstudio, mock)
16
+ model: Model name (optional, will use provider default)
17
+ **kwargs: Additional configuration including token parameters
18
+
19
+ Token Parameters (AbstractCore Unified Standard):
20
+ max_tokens: Total context window budget (input + output combined)
21
+ max_output_tokens: Maximum tokens reserved for generation (default: 2048)
22
+ max_input_tokens: Maximum tokens for input (auto-calculated if not specified)
23
+
24
+ Examples:
25
+ # Strategy 1: Budget + Output Reserve (Recommended)
26
+ llm = create_llm(
27
+ provider="openai",
28
+ model="gpt-4o",
29
+ max_tokens=8000, # Total budget
30
+ max_output_tokens=2000 # Reserve for output
31
+ )
32
+
33
+ # Strategy 2: Explicit Input + Output (Advanced)
34
+ llm = create_llm(
35
+ provider="anthropic",
36
+ model="claude-3.5-sonnet",
37
+ max_input_tokens=6000, # Explicit input limit
38
+ max_output_tokens=2000 # Explicit output limit
39
+ )
40
+
41
+ # Quick setup with defaults
42
+ llm = create_llm("ollama", "qwen3-coder:30b")
43
+
44
+ # Get configuration help
45
+ print(llm.get_token_configuration_summary())
46
+ warnings = llm.validate_token_constraints()
47
+
48
+ Returns:
49
+ Configured LLM provider instance with unified token management
50
+
51
+ Raises:
52
+ ImportError: If provider dependencies are not installed
53
+ ValueError: If provider is not supported
54
+ ModelNotFoundError: If specified model is not available
55
+ AuthenticationError: If API credentials are invalid
56
+ """
57
+
58
+ # Auto-detect provider from model name if needed
59
+ if model:
60
+ # MLX models should use MLX provider
61
+ if "mlx-community" in model.lower() and provider.lower() == "huggingface":
62
+ provider = "mlx"
63
+ # GGUF models should use HuggingFace GGUF backend
64
+ elif (".gguf" in model.lower() or "-gguf" in model.lower()) and provider.lower() == "mlx":
65
+ provider = "huggingface"
66
+
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,48 @@
1
+ # LLM provider implementations
2
+
3
+ from .base import BaseProvider
4
+ from .openai_provider import OpenAIProvider
5
+ from .anthropic_provider import AnthropicProvider
6
+ from .ollama_provider import OllamaProvider
7
+ from .lmstudio_provider import LMStudioProvider
8
+ from .huggingface_provider import HuggingFaceProvider
9
+ from .mlx_provider import MLXProvider
10
+ from .mock_provider import MockProvider
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
+
26
+ __all__ = [
27
+ # Provider classes
28
+ 'BaseProvider',
29
+ 'OpenAIProvider',
30
+ 'AnthropicProvider',
31
+ 'OllamaProvider',
32
+ 'LMStudioProvider',
33
+ 'HuggingFaceProvider',
34
+ 'MLXProvider',
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',
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)