stratifyai 0.1.0__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.
- cli/__init__.py +5 -0
- cli/stratifyai_cli.py +1753 -0
- stratifyai/__init__.py +113 -0
- stratifyai/api_key_helper.py +372 -0
- stratifyai/caching.py +279 -0
- stratifyai/chat/__init__.py +54 -0
- stratifyai/chat/builder.py +366 -0
- stratifyai/chat/stratifyai_anthropic.py +194 -0
- stratifyai/chat/stratifyai_bedrock.py +200 -0
- stratifyai/chat/stratifyai_deepseek.py +194 -0
- stratifyai/chat/stratifyai_google.py +194 -0
- stratifyai/chat/stratifyai_grok.py +194 -0
- stratifyai/chat/stratifyai_groq.py +195 -0
- stratifyai/chat/stratifyai_ollama.py +201 -0
- stratifyai/chat/stratifyai_openai.py +209 -0
- stratifyai/chat/stratifyai_openrouter.py +201 -0
- stratifyai/chunking.py +158 -0
- stratifyai/client.py +292 -0
- stratifyai/config.py +1273 -0
- stratifyai/cost_tracker.py +257 -0
- stratifyai/embeddings.py +245 -0
- stratifyai/exceptions.py +91 -0
- stratifyai/models.py +59 -0
- stratifyai/providers/__init__.py +5 -0
- stratifyai/providers/anthropic.py +330 -0
- stratifyai/providers/base.py +183 -0
- stratifyai/providers/bedrock.py +634 -0
- stratifyai/providers/deepseek.py +39 -0
- stratifyai/providers/google.py +39 -0
- stratifyai/providers/grok.py +39 -0
- stratifyai/providers/groq.py +39 -0
- stratifyai/providers/ollama.py +43 -0
- stratifyai/providers/openai.py +344 -0
- stratifyai/providers/openai_compatible.py +372 -0
- stratifyai/providers/openrouter.py +39 -0
- stratifyai/py.typed +2 -0
- stratifyai/rag.py +381 -0
- stratifyai/retry.py +185 -0
- stratifyai/router.py +643 -0
- stratifyai/summarization.py +179 -0
- stratifyai/utils/__init__.py +11 -0
- stratifyai/utils/bedrock_validator.py +136 -0
- stratifyai/utils/code_extractor.py +327 -0
- stratifyai/utils/csv_extractor.py +197 -0
- stratifyai/utils/file_analyzer.py +192 -0
- stratifyai/utils/json_extractor.py +219 -0
- stratifyai/utils/log_extractor.py +267 -0
- stratifyai/utils/model_selector.py +324 -0
- stratifyai/utils/provider_validator.py +442 -0
- stratifyai/utils/token_counter.py +186 -0
- stratifyai/vectordb.py +344 -0
- stratifyai-0.1.0.dist-info/METADATA +263 -0
- stratifyai-0.1.0.dist-info/RECORD +57 -0
- stratifyai-0.1.0.dist-info/WHEEL +5 -0
- stratifyai-0.1.0.dist-info/entry_points.txt +2 -0
- stratifyai-0.1.0.dist-info/licenses/LICENSE +21 -0
- stratifyai-0.1.0.dist-info/top_level.txt +2 -0
stratifyai/__init__.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""StratifyAI - Unified Intelligence Across Every Model Layer.
|
|
2
|
+
|
|
3
|
+
A production-ready Python module providing a unified, abstracted interface for
|
|
4
|
+
accessing multiple frontier LLM providers through a consistent API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
|
|
9
|
+
from .caching import (
|
|
10
|
+
ResponseCache,
|
|
11
|
+
cache_response,
|
|
12
|
+
clear_cache,
|
|
13
|
+
generate_cache_key,
|
|
14
|
+
get_cache_stats,
|
|
15
|
+
)
|
|
16
|
+
from .client import LLMClient, ProviderType
|
|
17
|
+
from .exceptions import (
|
|
18
|
+
AuthenticationError,
|
|
19
|
+
BudgetExceededError,
|
|
20
|
+
InsufficientBalanceError,
|
|
21
|
+
InvalidModelError,
|
|
22
|
+
InvalidProviderError,
|
|
23
|
+
LLMAbstractionError,
|
|
24
|
+
MaxRetriesExceededError,
|
|
25
|
+
ProviderAPIError,
|
|
26
|
+
ProviderError,
|
|
27
|
+
RateLimitError,
|
|
28
|
+
ValidationError,
|
|
29
|
+
)
|
|
30
|
+
from .models import ChatRequest, ChatResponse, Message, Usage
|
|
31
|
+
from .providers.base import BaseProvider
|
|
32
|
+
from .providers.openai import OpenAIProvider
|
|
33
|
+
from .cost_tracker import CostTracker, CostEntry
|
|
34
|
+
from .retry import RetryConfig, with_retry
|
|
35
|
+
from .providers.anthropic import AnthropicProvider
|
|
36
|
+
from .providers.google import GoogleProvider
|
|
37
|
+
from .providers.deepseek import DeepSeekProvider
|
|
38
|
+
from .providers.groq import GroqProvider
|
|
39
|
+
from .providers.grok import GrokProvider
|
|
40
|
+
from .providers.ollama import OllamaProvider
|
|
41
|
+
from .providers.openrouter import OpenRouterProvider
|
|
42
|
+
from .providers.bedrock import BedrockProvider
|
|
43
|
+
from .router import Router, RoutingStrategy, ModelMetadata
|
|
44
|
+
from .embeddings import (
|
|
45
|
+
EmbeddingProvider,
|
|
46
|
+
EmbeddingResult,
|
|
47
|
+
OpenAIEmbeddingProvider,
|
|
48
|
+
create_embedding_provider,
|
|
49
|
+
)
|
|
50
|
+
from .vectordb import VectorDBClient, SearchResult
|
|
51
|
+
from .rag import RAGClient, RAGResponse, IndexingResult
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
# Core client
|
|
55
|
+
"LLMClient",
|
|
56
|
+
"ProviderType",
|
|
57
|
+
# Data models
|
|
58
|
+
"Message",
|
|
59
|
+
"ChatRequest",
|
|
60
|
+
"ChatResponse",
|
|
61
|
+
"Usage",
|
|
62
|
+
# Providers
|
|
63
|
+
"BaseProvider",
|
|
64
|
+
"OpenAIProvider",
|
|
65
|
+
"AnthropicProvider",
|
|
66
|
+
"GoogleProvider",
|
|
67
|
+
"DeepSeekProvider",
|
|
68
|
+
"GroqProvider",
|
|
69
|
+
"GrokProvider",
|
|
70
|
+
"OllamaProvider",
|
|
71
|
+
"OpenRouterProvider",
|
|
72
|
+
"BedrockProvider",
|
|
73
|
+
# Caching
|
|
74
|
+
"ResponseCache",
|
|
75
|
+
"cache_response",
|
|
76
|
+
"generate_cache_key",
|
|
77
|
+
"get_cache_stats",
|
|
78
|
+
"clear_cache",
|
|
79
|
+
# Cost Tracking
|
|
80
|
+
"CostTracker",
|
|
81
|
+
"CostEntry",
|
|
82
|
+
# Retry
|
|
83
|
+
"RetryConfig",
|
|
84
|
+
"with_retry",
|
|
85
|
+
# Router
|
|
86
|
+
"Router",
|
|
87
|
+
"RoutingStrategy",
|
|
88
|
+
"ModelMetadata",
|
|
89
|
+
# Embeddings
|
|
90
|
+
"EmbeddingProvider",
|
|
91
|
+
"EmbeddingResult",
|
|
92
|
+
"OpenAIEmbeddingProvider",
|
|
93
|
+
"create_embedding_provider",
|
|
94
|
+
# Vector Database
|
|
95
|
+
"VectorDBClient",
|
|
96
|
+
"SearchResult",
|
|
97
|
+
# RAG
|
|
98
|
+
"RAGClient",
|
|
99
|
+
"RAGResponse",
|
|
100
|
+
"IndexingResult",
|
|
101
|
+
# Exceptions
|
|
102
|
+
"LLMAbstractionError",
|
|
103
|
+
"ProviderError",
|
|
104
|
+
"InvalidProviderError",
|
|
105
|
+
"ProviderAPIError",
|
|
106
|
+
"AuthenticationError",
|
|
107
|
+
"InsufficientBalanceError",
|
|
108
|
+
"RateLimitError",
|
|
109
|
+
"InvalidModelError",
|
|
110
|
+
"BudgetExceededError",
|
|
111
|
+
"MaxRetriesExceededError",
|
|
112
|
+
"ValidationError",
|
|
113
|
+
]
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"""API key management and validation helpers."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Dict, Optional, Tuple
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class APIKeyHelper:
|
|
9
|
+
"""Helper class for managing API keys with user-friendly error messages."""
|
|
10
|
+
|
|
11
|
+
# Map of provider names to their environment variable keys
|
|
12
|
+
PROVIDER_ENV_KEYS: Dict[str, str] = {
|
|
13
|
+
"openai": "OPENAI_API_KEY",
|
|
14
|
+
"anthropic": "ANTHROPIC_API_KEY",
|
|
15
|
+
"google": "GOOGLE_API_KEY",
|
|
16
|
+
"deepseek": "DEEPSEEK_API_KEY",
|
|
17
|
+
"groq": "GROQ_API_KEY",
|
|
18
|
+
"grok": "GROK_API_KEY",
|
|
19
|
+
"openrouter": "OPENROUTER_API_KEY",
|
|
20
|
+
"ollama": "OLLAMA_API_KEY",
|
|
21
|
+
"bedrock": "AWS_BEARER_TOKEN_BEDROCK", # Bedrock bearer token (or AWS_ACCESS_KEY_ID)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Map of provider names to their API key signup URLs
|
|
25
|
+
PROVIDER_SIGNUP_URLS: Dict[str, str] = {
|
|
26
|
+
"openai": "https://platform.openai.com/api-keys",
|
|
27
|
+
"anthropic": "https://console.anthropic.com/settings/keys",
|
|
28
|
+
"google": "https://makersuite.google.com/app/apikey",
|
|
29
|
+
"deepseek": "https://platform.deepseek.com/api-docs/",
|
|
30
|
+
"groq": "https://console.groq.com/keys",
|
|
31
|
+
"grok": "https://x.ai/api",
|
|
32
|
+
"openrouter": "https://openrouter.ai/keys",
|
|
33
|
+
"ollama": "https://ollama.ai/download",
|
|
34
|
+
"bedrock": "https://docs.aws.amazon.com/bedrock/",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Friendly provider names for error messages
|
|
38
|
+
PROVIDER_FRIENDLY_NAMES: Dict[str, str] = {
|
|
39
|
+
"openai": "OpenAI",
|
|
40
|
+
"anthropic": "Anthropic",
|
|
41
|
+
"google": "Google Gemini",
|
|
42
|
+
"deepseek": "DeepSeek",
|
|
43
|
+
"groq": "Groq",
|
|
44
|
+
"grok": "Grok (X.AI)",
|
|
45
|
+
"openrouter": "OpenRouter",
|
|
46
|
+
"ollama": "Ollama",
|
|
47
|
+
"bedrock": "AWS Bedrock",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def get_api_key(
|
|
52
|
+
cls,
|
|
53
|
+
provider: str,
|
|
54
|
+
api_key: Optional[str] = None
|
|
55
|
+
) -> Optional[str]:
|
|
56
|
+
"""
|
|
57
|
+
Get API key for a provider from parameter or environment.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
provider: Provider name (e.g., "openai", "anthropic")
|
|
61
|
+
api_key: Optional API key to use instead of environment variable
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
API key string or None if not found
|
|
65
|
+
"""
|
|
66
|
+
# Use provided key if available
|
|
67
|
+
if api_key:
|
|
68
|
+
return api_key
|
|
69
|
+
|
|
70
|
+
# Get from environment
|
|
71
|
+
env_key = cls.PROVIDER_ENV_KEYS.get(provider)
|
|
72
|
+
if not env_key:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
return os.getenv(env_key)
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def validate_api_key(
|
|
79
|
+
cls,
|
|
80
|
+
provider: str,
|
|
81
|
+
api_key: Optional[str] = None
|
|
82
|
+
) -> Tuple[bool, Optional[str]]:
|
|
83
|
+
"""
|
|
84
|
+
Validate that an API key is available for a provider.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
provider: Provider name
|
|
88
|
+
api_key: Optional API key to validate
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Tuple of (is_valid, error_message)
|
|
92
|
+
If valid, error_message is None
|
|
93
|
+
If invalid, error_message contains helpful guidance
|
|
94
|
+
"""
|
|
95
|
+
key = cls.get_api_key(provider, api_key)
|
|
96
|
+
|
|
97
|
+
if key:
|
|
98
|
+
return True, None
|
|
99
|
+
|
|
100
|
+
# Generate helpful error message
|
|
101
|
+
friendly_name = cls.PROVIDER_FRIENDLY_NAMES.get(provider, provider)
|
|
102
|
+
env_key = cls.PROVIDER_ENV_KEYS.get(provider, "API_KEY")
|
|
103
|
+
signup_url = cls.PROVIDER_SIGNUP_URLS.get(provider)
|
|
104
|
+
|
|
105
|
+
# Special handling for Bedrock (multiple auth methods)
|
|
106
|
+
if provider == "bedrock":
|
|
107
|
+
error_parts = [
|
|
108
|
+
f"❌ Missing API key for {friendly_name}",
|
|
109
|
+
"",
|
|
110
|
+
"AWS Bedrock supports multiple authentication methods:",
|
|
111
|
+
"",
|
|
112
|
+
"Option 1: Bearer token (simplest):",
|
|
113
|
+
f" export AWS_BEARER_TOKEN_BEDROCK=your-token-here",
|
|
114
|
+
"",
|
|
115
|
+
"Option 2: Access key + secret key:",
|
|
116
|
+
" export AWS_ACCESS_KEY_ID=your-access-key",
|
|
117
|
+
" export AWS_SECRET_ACCESS_KEY=your-secret-key",
|
|
118
|
+
"",
|
|
119
|
+
"Option 3: IAM roles (when running on AWS infrastructure)",
|
|
120
|
+
"Option 4: Configure ~/.aws/credentials",
|
|
121
|
+
"",
|
|
122
|
+
f"Get credentials: {signup_url}" if signup_url else "",
|
|
123
|
+
]
|
|
124
|
+
else:
|
|
125
|
+
error_parts = [
|
|
126
|
+
f"❌ Missing API key for {friendly_name}",
|
|
127
|
+
"",
|
|
128
|
+
"To use this provider, you need to:",
|
|
129
|
+
f"1. Get an API key from: {signup_url}" if signup_url else "",
|
|
130
|
+
f"2. Set the {env_key} environment variable",
|
|
131
|
+
"",
|
|
132
|
+
"Quick setup:",
|
|
133
|
+
f" export {env_key}=your-api-key-here",
|
|
134
|
+
"",
|
|
135
|
+
"Or add to .env file:",
|
|
136
|
+
f" {env_key}=your-api-key-here",
|
|
137
|
+
"",
|
|
138
|
+
"Alternative: Pass api_key parameter:",
|
|
139
|
+
f" client = LLMClient(provider='{provider}', api_key='your-key')",
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
# Remove empty strings from parts that had no signup URL
|
|
143
|
+
error_message = "\n".join([p for p in error_parts if p])
|
|
144
|
+
|
|
145
|
+
return False, error_message
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def check_available_providers(cls) -> Dict[str, bool]:
|
|
149
|
+
"""
|
|
150
|
+
Check which providers have API keys configured.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Dictionary mapping provider names to availability (True/False)
|
|
154
|
+
"""
|
|
155
|
+
available = {}
|
|
156
|
+
for provider in cls.PROVIDER_ENV_KEYS.keys():
|
|
157
|
+
key = cls.get_api_key(provider)
|
|
158
|
+
available[provider] = key is not None and len(key) > 0
|
|
159
|
+
|
|
160
|
+
return available
|
|
161
|
+
|
|
162
|
+
@classmethod
|
|
163
|
+
def get_setup_instructions(cls) -> str:
|
|
164
|
+
"""
|
|
165
|
+
Get comprehensive setup instructions for all providers.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Formatted setup instructions string
|
|
169
|
+
"""
|
|
170
|
+
available = cls.check_available_providers()
|
|
171
|
+
|
|
172
|
+
lines = [
|
|
173
|
+
"🔑 StratifyAI API Key Setup",
|
|
174
|
+
"=" * 50,
|
|
175
|
+
"",
|
|
176
|
+
"Available Providers:",
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
for provider in sorted(cls.PROVIDER_ENV_KEYS.keys()):
|
|
180
|
+
is_available = available.get(provider, False)
|
|
181
|
+
status = "✓" if is_available else "✗"
|
|
182
|
+
friendly_name = cls.PROVIDER_FRIENDLY_NAMES.get(provider, provider)
|
|
183
|
+
env_key = cls.PROVIDER_ENV_KEYS.get(provider)
|
|
184
|
+
|
|
185
|
+
lines.append(f" [{status}] {friendly_name} ({env_key})")
|
|
186
|
+
|
|
187
|
+
lines.extend([
|
|
188
|
+
"",
|
|
189
|
+
"Setup Instructions:",
|
|
190
|
+
" 1. Copy .env.example to .env: cp .env.example .env",
|
|
191
|
+
" 2. Add your API keys to .env file",
|
|
192
|
+
" 3. Test with: python -m cli.stratifyai_cli chat -p openai -m gpt-4o-mini -t 'Hello'",
|
|
193
|
+
"",
|
|
194
|
+
"Get API Keys:",
|
|
195
|
+
])
|
|
196
|
+
|
|
197
|
+
for provider in sorted(cls.PROVIDER_SIGNUP_URLS.keys()):
|
|
198
|
+
friendly_name = cls.PROVIDER_FRIENDLY_NAMES.get(provider, provider)
|
|
199
|
+
url = cls.PROVIDER_SIGNUP_URLS.get(provider)
|
|
200
|
+
lines.append(f" • {friendly_name}: {url}")
|
|
201
|
+
|
|
202
|
+
lines.extend([
|
|
203
|
+
"",
|
|
204
|
+
"Note: You only need keys for providers you plan to use.",
|
|
205
|
+
])
|
|
206
|
+
|
|
207
|
+
return "\n".join(lines)
|
|
208
|
+
|
|
209
|
+
@classmethod
|
|
210
|
+
def suggest_alternative_providers(
|
|
211
|
+
cls,
|
|
212
|
+
original_provider: str
|
|
213
|
+
) -> Optional[str]:
|
|
214
|
+
"""
|
|
215
|
+
Suggest alternative providers that have API keys configured.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
original_provider: The provider that failed
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Formatted suggestion string or None if no alternatives
|
|
222
|
+
"""
|
|
223
|
+
available = cls.check_available_providers()
|
|
224
|
+
|
|
225
|
+
# Find available alternatives
|
|
226
|
+
alternatives = [
|
|
227
|
+
p for p in available.keys()
|
|
228
|
+
if available[p] and p != original_provider
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
if not alternatives:
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
friendly_alternatives = [
|
|
235
|
+
cls.PROVIDER_FRIENDLY_NAMES.get(p, p)
|
|
236
|
+
for p in alternatives
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
suggestion = [
|
|
240
|
+
"",
|
|
241
|
+
"💡 Tip: You have API keys configured for these providers:",
|
|
242
|
+
" " + ", ".join(friendly_alternatives),
|
|
243
|
+
"",
|
|
244
|
+
"Try using one of them instead, or get an API key for",
|
|
245
|
+
f"{cls.PROVIDER_FRIENDLY_NAMES.get(original_provider, original_provider)}.",
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
return "\n".join(suggestion)
|
|
249
|
+
|
|
250
|
+
@classmethod
|
|
251
|
+
def create_env_file_if_missing(cls) -> bool:
|
|
252
|
+
"""
|
|
253
|
+
Create .env file from .env.example if it doesn't exist.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
True if created, False if already exists or creation failed
|
|
257
|
+
"""
|
|
258
|
+
env_path = Path(".env")
|
|
259
|
+
env_example_path = Path(".env.example")
|
|
260
|
+
|
|
261
|
+
# Skip if .env already exists
|
|
262
|
+
if env_path.exists():
|
|
263
|
+
return False
|
|
264
|
+
|
|
265
|
+
# Skip if .env.example doesn't exist
|
|
266
|
+
if not env_example_path.exists():
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
try:
|
|
270
|
+
# Copy .env.example to .env
|
|
271
|
+
content = env_example_path.read_text()
|
|
272
|
+
env_path.write_text(content)
|
|
273
|
+
return True
|
|
274
|
+
except Exception:
|
|
275
|
+
return False
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# Convenience functions for common use cases
|
|
279
|
+
|
|
280
|
+
def get_api_key_or_error(provider: str, api_key: Optional[str] = None) -> str:
|
|
281
|
+
"""
|
|
282
|
+
Get API key for a provider or raise helpful error.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
provider: Provider name
|
|
286
|
+
api_key: Optional API key to use
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
API key string
|
|
290
|
+
|
|
291
|
+
Raises:
|
|
292
|
+
ValueError: With helpful error message if key not found
|
|
293
|
+
"""
|
|
294
|
+
is_valid, error_message = APIKeyHelper.validate_api_key(provider, api_key)
|
|
295
|
+
|
|
296
|
+
if not is_valid:
|
|
297
|
+
# Add suggestion for alternative providers
|
|
298
|
+
suggestion = APIKeyHelper.suggest_alternative_providers(provider)
|
|
299
|
+
if suggestion:
|
|
300
|
+
error_message += suggestion
|
|
301
|
+
|
|
302
|
+
raise ValueError(error_message)
|
|
303
|
+
|
|
304
|
+
return APIKeyHelper.get_api_key(provider, api_key)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def print_setup_instructions() -> None:
|
|
308
|
+
"""Print API key setup instructions to console with rich formatting."""
|
|
309
|
+
from rich.console import Console
|
|
310
|
+
from rich.table import Table
|
|
311
|
+
|
|
312
|
+
console = Console()
|
|
313
|
+
available = APIKeyHelper.check_available_providers()
|
|
314
|
+
|
|
315
|
+
# Count configured providers
|
|
316
|
+
configured_count = sum(1 for v in available.values() if v)
|
|
317
|
+
total_count = len(available)
|
|
318
|
+
|
|
319
|
+
# API Key Status Table
|
|
320
|
+
status_table = Table(show_header=True, header_style="bold magenta", title="API Key Status")
|
|
321
|
+
status_table.add_column("Provider", style="cyan")
|
|
322
|
+
status_table.add_column("Status", justify="center")
|
|
323
|
+
status_table.add_column("Environment Variable", style="dim")
|
|
324
|
+
|
|
325
|
+
for provider in sorted(available.keys()):
|
|
326
|
+
is_available = available[provider]
|
|
327
|
+
status = "[green]✓ Configured[/green]" if is_available else "[yellow]⚠ Missing[/yellow]"
|
|
328
|
+
friendly_name = APIKeyHelper.PROVIDER_FRIENDLY_NAMES.get(provider, provider)
|
|
329
|
+
env_key = APIKeyHelper.PROVIDER_ENV_KEYS.get(provider, "N/A")
|
|
330
|
+
|
|
331
|
+
status_table.add_row(friendly_name, status, env_key)
|
|
332
|
+
|
|
333
|
+
console.print(status_table)
|
|
334
|
+
|
|
335
|
+
# Summary
|
|
336
|
+
if configured_count == 0:
|
|
337
|
+
console.print(f"\n[yellow]⚠ No providers configured[/yellow]")
|
|
338
|
+
elif configured_count == total_count:
|
|
339
|
+
console.print(f"\n[green]✓ All {total_count} providers configured![/green]")
|
|
340
|
+
else:
|
|
341
|
+
console.print(f"\n[cyan]{configured_count}/{total_count} providers configured[/cyan]")
|
|
342
|
+
|
|
343
|
+
# Provider signup URLs table
|
|
344
|
+
console.print("\n[bold magenta]Available Providers[/bold magenta]")
|
|
345
|
+
|
|
346
|
+
providers_table = Table(show_header=True, header_style="bold magenta")
|
|
347
|
+
providers_table.add_column("Provider", style="cyan")
|
|
348
|
+
providers_table.add_column("Get API Key", style="blue")
|
|
349
|
+
|
|
350
|
+
for provider in sorted(APIKeyHelper.PROVIDER_SIGNUP_URLS.keys()):
|
|
351
|
+
friendly_name = APIKeyHelper.PROVIDER_FRIENDLY_NAMES.get(provider, provider)
|
|
352
|
+
url = APIKeyHelper.PROVIDER_SIGNUP_URLS.get(provider)
|
|
353
|
+
providers_table.add_row(friendly_name, url)
|
|
354
|
+
|
|
355
|
+
console.print(providers_table)
|
|
356
|
+
|
|
357
|
+
# Help tip
|
|
358
|
+
console.print("\n[dim]💡 Tip: You only need to configure providers you plan to use[/dim]")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def check_provider_available(provider: str) -> bool:
|
|
362
|
+
"""
|
|
363
|
+
Check if a provider has an API key configured.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
provider: Provider name
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
True if API key is available, False otherwise
|
|
370
|
+
"""
|
|
371
|
+
available = APIKeyHelper.check_available_providers()
|
|
372
|
+
return available.get(provider, False)
|