DeepFabric 4.4.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.
- deepfabric/__init__.py +70 -0
- deepfabric/__main__.py +6 -0
- deepfabric/auth.py +382 -0
- deepfabric/builders.py +303 -0
- deepfabric/builders_agent.py +1304 -0
- deepfabric/cli.py +1288 -0
- deepfabric/config.py +899 -0
- deepfabric/config_manager.py +251 -0
- deepfabric/constants.py +94 -0
- deepfabric/dataset_manager.py +534 -0
- deepfabric/error_codes.py +581 -0
- deepfabric/evaluation/__init__.py +47 -0
- deepfabric/evaluation/backends/__init__.py +32 -0
- deepfabric/evaluation/backends/ollama_backend.py +137 -0
- deepfabric/evaluation/backends/tool_call_parsers.py +409 -0
- deepfabric/evaluation/backends/transformers_backend.py +326 -0
- deepfabric/evaluation/evaluator.py +845 -0
- deepfabric/evaluation/evaluators/__init__.py +13 -0
- deepfabric/evaluation/evaluators/base.py +104 -0
- deepfabric/evaluation/evaluators/builtin/__init__.py +5 -0
- deepfabric/evaluation/evaluators/builtin/tool_calling.py +93 -0
- deepfabric/evaluation/evaluators/registry.py +66 -0
- deepfabric/evaluation/inference.py +155 -0
- deepfabric/evaluation/metrics.py +397 -0
- deepfabric/evaluation/parser.py +304 -0
- deepfabric/evaluation/reporters/__init__.py +13 -0
- deepfabric/evaluation/reporters/base.py +56 -0
- deepfabric/evaluation/reporters/cloud_reporter.py +195 -0
- deepfabric/evaluation/reporters/file_reporter.py +61 -0
- deepfabric/evaluation/reporters/multi_reporter.py +56 -0
- deepfabric/exceptions.py +67 -0
- deepfabric/factory.py +26 -0
- deepfabric/generator.py +1084 -0
- deepfabric/graph.py +545 -0
- deepfabric/hf_hub.py +214 -0
- deepfabric/kaggle_hub.py +219 -0
- deepfabric/llm/__init__.py +41 -0
- deepfabric/llm/api_key_verifier.py +534 -0
- deepfabric/llm/client.py +1206 -0
- deepfabric/llm/errors.py +105 -0
- deepfabric/llm/rate_limit_config.py +262 -0
- deepfabric/llm/rate_limit_detector.py +278 -0
- deepfabric/llm/retry_handler.py +270 -0
- deepfabric/metrics.py +212 -0
- deepfabric/progress.py +262 -0
- deepfabric/prompts.py +290 -0
- deepfabric/schemas.py +1000 -0
- deepfabric/spin/__init__.py +6 -0
- deepfabric/spin/client.py +263 -0
- deepfabric/spin/models.py +26 -0
- deepfabric/stream_simulator.py +90 -0
- deepfabric/tools/__init__.py +5 -0
- deepfabric/tools/defaults.py +85 -0
- deepfabric/tools/loader.py +87 -0
- deepfabric/tools/mcp_client.py +677 -0
- deepfabric/topic_manager.py +303 -0
- deepfabric/topic_model.py +20 -0
- deepfabric/training/__init__.py +35 -0
- deepfabric/training/api_key_prompt.py +302 -0
- deepfabric/training/callback.py +363 -0
- deepfabric/training/metrics_sender.py +301 -0
- deepfabric/tree.py +438 -0
- deepfabric/tui.py +1267 -0
- deepfabric/update_checker.py +166 -0
- deepfabric/utils.py +150 -0
- deepfabric/validation.py +143 -0
- deepfabric-4.4.0.dist-info/METADATA +702 -0
- deepfabric-4.4.0.dist-info/RECORD +71 -0
- deepfabric-4.4.0.dist-info/WHEEL +4 -0
- deepfabric-4.4.0.dist-info/entry_points.txt +2 -0
- deepfabric-4.4.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
"""API key verification for LLM providers.
|
|
2
|
+
|
|
3
|
+
This module provides functionality to verify that API keys are valid and working
|
|
4
|
+
by making lightweight test API calls to each provider.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import anthropic
|
|
15
|
+
import openai
|
|
16
|
+
|
|
17
|
+
from google import genai
|
|
18
|
+
from google.api_core import exceptions as google_exceptions
|
|
19
|
+
|
|
20
|
+
from .client import PROVIDER_API_KEY_MAP
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VerificationStatus(Enum):
|
|
24
|
+
"""Status of an API key verification check."""
|
|
25
|
+
|
|
26
|
+
VALID = "valid"
|
|
27
|
+
INVALID = "invalid"
|
|
28
|
+
MISSING = "missing"
|
|
29
|
+
CONNECTION_ERROR = "connection_error"
|
|
30
|
+
RATE_LIMITED = "rate_limited"
|
|
31
|
+
UNKNOWN_ERROR = "unknown_error"
|
|
32
|
+
NOT_APPLICABLE = "not_applicable" # For providers like Ollama that don't need keys
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class VerificationResult:
|
|
37
|
+
"""Result of an API key verification check."""
|
|
38
|
+
|
|
39
|
+
provider: str
|
|
40
|
+
status: VerificationStatus
|
|
41
|
+
message: str
|
|
42
|
+
api_key_env_var: str | None = None
|
|
43
|
+
details: dict[str, Any] = field(default_factory=dict)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def is_valid(self) -> bool:
|
|
47
|
+
"""Check if the verification passed."""
|
|
48
|
+
return self.status in (VerificationStatus.VALID, VerificationStatus.NOT_APPLICABLE)
|
|
49
|
+
|
|
50
|
+
def __str__(self) -> str:
|
|
51
|
+
return f"{self.provider}: {self.status.value} - {self.message}"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def verify_openai_api_key(api_key: str | None = None) -> VerificationResult:
|
|
55
|
+
"""Verify an OpenAI API key by making a lightweight API call.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
api_key: Optional API key to verify. If not provided, uses OPENAI_API_KEY env var.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
VerificationResult with status and details
|
|
62
|
+
"""
|
|
63
|
+
env_var = "OPENAI_API_KEY"
|
|
64
|
+
|
|
65
|
+
if api_key is None:
|
|
66
|
+
api_key = os.getenv(env_var)
|
|
67
|
+
|
|
68
|
+
if not api_key:
|
|
69
|
+
return VerificationResult(
|
|
70
|
+
provider="openai",
|
|
71
|
+
status=VerificationStatus.MISSING,
|
|
72
|
+
message=f"{env_var} environment variable is not set",
|
|
73
|
+
api_key_env_var=env_var,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
client = openai.OpenAI(api_key=api_key)
|
|
78
|
+
# Use the models list endpoint - it's lightweight and verifies auth
|
|
79
|
+
models = client.models.list()
|
|
80
|
+
# Just check we can iterate (don't need to consume all)
|
|
81
|
+
model_count = sum(1 for _ in models)
|
|
82
|
+
|
|
83
|
+
return VerificationResult(
|
|
84
|
+
provider="openai",
|
|
85
|
+
status=VerificationStatus.VALID,
|
|
86
|
+
message="API key is valid",
|
|
87
|
+
api_key_env_var=env_var,
|
|
88
|
+
details={"models_available": model_count},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
except openai.AuthenticationError as e:
|
|
92
|
+
return VerificationResult(
|
|
93
|
+
provider="openai",
|
|
94
|
+
status=VerificationStatus.INVALID,
|
|
95
|
+
message="Invalid API key",
|
|
96
|
+
api_key_env_var=env_var,
|
|
97
|
+
details={"error": str(e)},
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
except openai.RateLimitError as e:
|
|
101
|
+
return VerificationResult(
|
|
102
|
+
provider="openai",
|
|
103
|
+
status=VerificationStatus.RATE_LIMITED,
|
|
104
|
+
message="Rate limit exceeded (key may be valid but quota exhausted)",
|
|
105
|
+
api_key_env_var=env_var,
|
|
106
|
+
details={"error": str(e)},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
except openai.APIConnectionError as e:
|
|
110
|
+
return VerificationResult(
|
|
111
|
+
provider="openai",
|
|
112
|
+
status=VerificationStatus.CONNECTION_ERROR,
|
|
113
|
+
message="Failed to connect to OpenAI API",
|
|
114
|
+
api_key_env_var=env_var,
|
|
115
|
+
details={"error": str(e)},
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
return VerificationResult(
|
|
120
|
+
provider="openai",
|
|
121
|
+
status=VerificationStatus.UNKNOWN_ERROR,
|
|
122
|
+
message=f"Unexpected error: {e}",
|
|
123
|
+
api_key_env_var=env_var,
|
|
124
|
+
details={"error": str(e), "error_type": type(e).__name__},
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def verify_anthropic_api_key(api_key: str | None = None) -> VerificationResult:
|
|
129
|
+
"""Verify an Anthropic API key by making a lightweight API call.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
api_key: Optional API key to verify. If not provided, uses ANTHROPIC_API_KEY env var.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
VerificationResult with status and details
|
|
136
|
+
"""
|
|
137
|
+
env_var = "ANTHROPIC_API_KEY"
|
|
138
|
+
|
|
139
|
+
if api_key is None:
|
|
140
|
+
api_key = os.getenv(env_var)
|
|
141
|
+
|
|
142
|
+
if not api_key:
|
|
143
|
+
return VerificationResult(
|
|
144
|
+
provider="anthropic",
|
|
145
|
+
status=VerificationStatus.MISSING,
|
|
146
|
+
message=f"{env_var} environment variable is not set",
|
|
147
|
+
api_key_env_var=env_var,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
client = anthropic.Anthropic(api_key=api_key)
|
|
152
|
+
# Use a minimal message call - this is the simplest way to verify auth
|
|
153
|
+
# The API doesn't have a models list endpoint like OpenAI
|
|
154
|
+
# Using count_tokens is a read-only operation that verifies the key
|
|
155
|
+
_result = client.messages.count_tokens(
|
|
156
|
+
model="claude-3-haiku-20240307",
|
|
157
|
+
messages=[{"role": "user", "content": "test"}],
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return VerificationResult(
|
|
161
|
+
provider="anthropic",
|
|
162
|
+
status=VerificationStatus.VALID,
|
|
163
|
+
message="API key is valid",
|
|
164
|
+
api_key_env_var=env_var,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
except anthropic.AuthenticationError as e:
|
|
168
|
+
return VerificationResult(
|
|
169
|
+
provider="anthropic",
|
|
170
|
+
status=VerificationStatus.INVALID,
|
|
171
|
+
message="Invalid API key",
|
|
172
|
+
api_key_env_var=env_var,
|
|
173
|
+
details={"error": str(e)},
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
except anthropic.RateLimitError as e:
|
|
177
|
+
return VerificationResult(
|
|
178
|
+
provider="anthropic",
|
|
179
|
+
status=VerificationStatus.RATE_LIMITED,
|
|
180
|
+
message="Rate limit exceeded (key may be valid but quota exhausted)",
|
|
181
|
+
api_key_env_var=env_var,
|
|
182
|
+
details={"error": str(e)},
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
except anthropic.APIConnectionError as e:
|
|
186
|
+
return VerificationResult(
|
|
187
|
+
provider="anthropic",
|
|
188
|
+
status=VerificationStatus.CONNECTION_ERROR,
|
|
189
|
+
message="Failed to connect to Anthropic API",
|
|
190
|
+
api_key_env_var=env_var,
|
|
191
|
+
details={"error": str(e)},
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
except Exception as e:
|
|
195
|
+
return VerificationResult(
|
|
196
|
+
provider="anthropic",
|
|
197
|
+
status=VerificationStatus.UNKNOWN_ERROR,
|
|
198
|
+
message=f"Unexpected error: {e}",
|
|
199
|
+
api_key_env_var=env_var,
|
|
200
|
+
details={"error": str(e), "error_type": type(e).__name__},
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def verify_gemini_api_key(api_key: str | None = None) -> VerificationResult:
|
|
205
|
+
"""Verify a Google Gemini API key by making a lightweight API call.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
api_key: Optional API key to verify. If not provided, uses GOOGLE_API_KEY
|
|
209
|
+
or GEMINI_API_KEY env var.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
VerificationResult with status and details
|
|
213
|
+
"""
|
|
214
|
+
env_vars = ["GOOGLE_API_KEY", "GEMINI_API_KEY"]
|
|
215
|
+
env_var_used = None
|
|
216
|
+
|
|
217
|
+
if api_key is None:
|
|
218
|
+
for env_var in env_vars:
|
|
219
|
+
api_key = os.getenv(env_var)
|
|
220
|
+
if api_key:
|
|
221
|
+
env_var_used = env_var
|
|
222
|
+
break
|
|
223
|
+
|
|
224
|
+
if not api_key:
|
|
225
|
+
return VerificationResult(
|
|
226
|
+
provider="gemini",
|
|
227
|
+
status=VerificationStatus.MISSING,
|
|
228
|
+
message="GOOGLE_API_KEY or GEMINI_API_KEY environment variable is not set",
|
|
229
|
+
api_key_env_var="GOOGLE_API_KEY or GEMINI_API_KEY",
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
client = genai.Client(api_key=api_key)
|
|
234
|
+
# List models - lightweight operation that verifies auth
|
|
235
|
+
models = list(client.models.list())
|
|
236
|
+
model_count = len(models)
|
|
237
|
+
|
|
238
|
+
return VerificationResult(
|
|
239
|
+
provider="gemini",
|
|
240
|
+
status=VerificationStatus.VALID,
|
|
241
|
+
message="API key is valid",
|
|
242
|
+
api_key_env_var=env_var_used or "GOOGLE_API_KEY",
|
|
243
|
+
details={"models_available": model_count},
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
except google_exceptions.PermissionDenied as e:
|
|
247
|
+
return VerificationResult(
|
|
248
|
+
provider="gemini",
|
|
249
|
+
status=VerificationStatus.INVALID,
|
|
250
|
+
message="Invalid API key",
|
|
251
|
+
api_key_env_var=env_var_used or "GOOGLE_API_KEY",
|
|
252
|
+
details={"error": str(e)},
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
except google_exceptions.ResourceExhausted as e:
|
|
256
|
+
return VerificationResult(
|
|
257
|
+
provider="gemini",
|
|
258
|
+
status=VerificationStatus.RATE_LIMITED,
|
|
259
|
+
message="Rate limit exceeded (key may be valid but quota exhausted)",
|
|
260
|
+
api_key_env_var=env_var_used or "GOOGLE_API_KEY",
|
|
261
|
+
details={"error": str(e)},
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
except google_exceptions.GoogleAPICallError as e:
|
|
265
|
+
return VerificationResult(
|
|
266
|
+
provider="gemini",
|
|
267
|
+
status=VerificationStatus.CONNECTION_ERROR,
|
|
268
|
+
message="Failed to connect to Gemini API",
|
|
269
|
+
api_key_env_var=env_var_used or "GOOGLE_API_KEY",
|
|
270
|
+
details={"error": str(e)},
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
except Exception as e:
|
|
274
|
+
return VerificationResult(
|
|
275
|
+
provider="gemini",
|
|
276
|
+
status=VerificationStatus.UNKNOWN_ERROR,
|
|
277
|
+
message=f"Unexpected error: {e}",
|
|
278
|
+
api_key_env_var=env_var_used or "GOOGLE_API_KEY",
|
|
279
|
+
details={"error": str(e), "error_type": type(e).__name__},
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def verify_openrouter_api_key(api_key: str | None = None) -> VerificationResult:
|
|
284
|
+
"""Verify an OpenRouter API key by making a lightweight API call.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
api_key: Optional API key to verify. If not provided, uses OPENROUTER_API_KEY env var.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
VerificationResult with status and details
|
|
291
|
+
"""
|
|
292
|
+
env_var = "OPENROUTER_API_KEY"
|
|
293
|
+
|
|
294
|
+
if api_key is None:
|
|
295
|
+
api_key = os.getenv(env_var)
|
|
296
|
+
|
|
297
|
+
if not api_key:
|
|
298
|
+
return VerificationResult(
|
|
299
|
+
provider="openrouter",
|
|
300
|
+
status=VerificationStatus.MISSING,
|
|
301
|
+
message=f"{env_var} environment variable is not set",
|
|
302
|
+
api_key_env_var=env_var,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
try:
|
|
306
|
+
# OpenRouter uses OpenAI-compatible API
|
|
307
|
+
client = openai.OpenAI(
|
|
308
|
+
api_key=api_key,
|
|
309
|
+
base_url="https://openrouter.ai/api/v1",
|
|
310
|
+
)
|
|
311
|
+
# List models to verify auth
|
|
312
|
+
models = client.models.list()
|
|
313
|
+
model_count = sum(1 for _ in models)
|
|
314
|
+
|
|
315
|
+
return VerificationResult(
|
|
316
|
+
provider="openrouter",
|
|
317
|
+
status=VerificationStatus.VALID,
|
|
318
|
+
message="API key is valid",
|
|
319
|
+
api_key_env_var=env_var,
|
|
320
|
+
details={"models_available": model_count},
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
except openai.AuthenticationError as e:
|
|
324
|
+
return VerificationResult(
|
|
325
|
+
provider="openrouter",
|
|
326
|
+
status=VerificationStatus.INVALID,
|
|
327
|
+
message="Invalid API key",
|
|
328
|
+
api_key_env_var=env_var,
|
|
329
|
+
details={"error": str(e)},
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
except openai.RateLimitError as e:
|
|
333
|
+
return VerificationResult(
|
|
334
|
+
provider="openrouter",
|
|
335
|
+
status=VerificationStatus.RATE_LIMITED,
|
|
336
|
+
message="Rate limit exceeded (key may be valid but quota exhausted)",
|
|
337
|
+
api_key_env_var=env_var,
|
|
338
|
+
details={"error": str(e)},
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
except openai.APIConnectionError as e:
|
|
342
|
+
return VerificationResult(
|
|
343
|
+
provider="openrouter",
|
|
344
|
+
status=VerificationStatus.CONNECTION_ERROR,
|
|
345
|
+
message="Failed to connect to OpenRouter API",
|
|
346
|
+
api_key_env_var=env_var,
|
|
347
|
+
details={"error": str(e)},
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
except Exception as e:
|
|
351
|
+
return VerificationResult(
|
|
352
|
+
provider="openrouter",
|
|
353
|
+
status=VerificationStatus.UNKNOWN_ERROR,
|
|
354
|
+
message=f"Unexpected error: {e}",
|
|
355
|
+
api_key_env_var=env_var,
|
|
356
|
+
details={"error": str(e), "error_type": type(e).__name__},
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def verify_ollama_connection(base_url: str | None = None) -> VerificationResult:
|
|
361
|
+
"""Verify Ollama is running and accessible.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
base_url: Optional base URL for Ollama server. Defaults to http://localhost:11434.
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
VerificationResult with status and details
|
|
368
|
+
"""
|
|
369
|
+
if base_url is None:
|
|
370
|
+
base_url = os.getenv("OLLAMA_HOST", "http://localhost:11434")
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
# Ensure base_url ends with /v1 for OpenAI-compatible API
|
|
374
|
+
api_base = base_url.rstrip("/")
|
|
375
|
+
if not api_base.endswith("/v1"):
|
|
376
|
+
api_base = f"{api_base}/v1"
|
|
377
|
+
|
|
378
|
+
client = openai.OpenAI(
|
|
379
|
+
api_key="ollama", # Dummy key, not needed for Ollama
|
|
380
|
+
base_url=api_base,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# List models to verify connection
|
|
384
|
+
models = client.models.list()
|
|
385
|
+
model_names = [m.id for m in models]
|
|
386
|
+
|
|
387
|
+
return VerificationResult(
|
|
388
|
+
provider="ollama",
|
|
389
|
+
status=VerificationStatus.VALID,
|
|
390
|
+
message="Ollama is running and accessible",
|
|
391
|
+
details={"available_models": model_names, "base_url": base_url},
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
except openai.APIConnectionError as e:
|
|
395
|
+
return VerificationResult(
|
|
396
|
+
provider="ollama",
|
|
397
|
+
status=VerificationStatus.CONNECTION_ERROR,
|
|
398
|
+
message=f"Cannot connect to Ollama server at {base_url}. Is Ollama running?",
|
|
399
|
+
details={"error": str(e), "base_url": base_url},
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
except Exception as e:
|
|
403
|
+
return VerificationResult(
|
|
404
|
+
provider="ollama",
|
|
405
|
+
status=VerificationStatus.UNKNOWN_ERROR,
|
|
406
|
+
message=f"Unexpected error: {e}",
|
|
407
|
+
details={"error": str(e), "error_type": type(e).__name__, "base_url": base_url},
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def verify_provider_api_key(
|
|
412
|
+
provider: str,
|
|
413
|
+
api_key: str | None = None,
|
|
414
|
+
**kwargs,
|
|
415
|
+
) -> VerificationResult:
|
|
416
|
+
"""Verify an API key for a specific provider.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
provider: Provider name (openai, anthropic, gemini, openrouter, ollama)
|
|
420
|
+
api_key: Optional API key to verify. If not provided, uses environment variables.
|
|
421
|
+
**kwargs: Additional provider-specific options (e.g., base_url for ollama)
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
VerificationResult with status and details
|
|
425
|
+
"""
|
|
426
|
+
provider = provider.lower()
|
|
427
|
+
|
|
428
|
+
# Dispatch to provider-specific verification functions
|
|
429
|
+
verifiers = {
|
|
430
|
+
"openai": lambda: verify_openai_api_key(api_key),
|
|
431
|
+
"anthropic": lambda: verify_anthropic_api_key(api_key),
|
|
432
|
+
"gemini": lambda: verify_gemini_api_key(api_key),
|
|
433
|
+
"openrouter": lambda: verify_openrouter_api_key(api_key),
|
|
434
|
+
"ollama": lambda: verify_ollama_connection(kwargs.get("base_url")),
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if provider in verifiers:
|
|
438
|
+
return verifiers[provider]()
|
|
439
|
+
|
|
440
|
+
if provider in ("test", "override"):
|
|
441
|
+
return VerificationResult(
|
|
442
|
+
provider=provider,
|
|
443
|
+
status=VerificationStatus.NOT_APPLICABLE,
|
|
444
|
+
message="Test provider, no verification needed",
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
return VerificationResult(
|
|
448
|
+
provider=provider,
|
|
449
|
+
status=VerificationStatus.UNKNOWN_ERROR,
|
|
450
|
+
message=f"Unknown provider: {provider}",
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _get_providers_to_check(include_optional: bool = False) -> list[str]:
|
|
455
|
+
"""Get list of providers to check based on configured API keys.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
include_optional: If True, include providers without keys set.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
List of provider names to verify.
|
|
462
|
+
"""
|
|
463
|
+
providers_to_check = []
|
|
464
|
+
for provider, env_vars in PROVIDER_API_KEY_MAP.items():
|
|
465
|
+
if provider in ("test", "override"):
|
|
466
|
+
continue
|
|
467
|
+
|
|
468
|
+
if not env_vars:
|
|
469
|
+
# Ollama - always include if checking optional
|
|
470
|
+
if include_optional:
|
|
471
|
+
providers_to_check.append(provider)
|
|
472
|
+
continue
|
|
473
|
+
|
|
474
|
+
# Check if any env var is set
|
|
475
|
+
has_key = any(os.getenv(env_var) for env_var in env_vars)
|
|
476
|
+
if has_key or include_optional:
|
|
477
|
+
providers_to_check.append(provider)
|
|
478
|
+
|
|
479
|
+
return providers_to_check
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def verify_all_api_keys(
|
|
483
|
+
providers: list[str] | None = None,
|
|
484
|
+
include_optional: bool = False,
|
|
485
|
+
) -> dict[str, VerificationResult]:
|
|
486
|
+
"""Verify API keys for multiple providers.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
providers: List of providers to verify. If None, verifies all configured providers
|
|
490
|
+
(those with API keys set in environment).
|
|
491
|
+
include_optional: If True and providers is None, also check providers without
|
|
492
|
+
keys set to show which are missing.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
Dictionary mapping provider names to VerificationResult
|
|
496
|
+
"""
|
|
497
|
+
if providers is None:
|
|
498
|
+
providers = _get_providers_to_check(include_optional)
|
|
499
|
+
|
|
500
|
+
results = {}
|
|
501
|
+
for provider in providers:
|
|
502
|
+
results[provider] = verify_provider_api_key(provider)
|
|
503
|
+
|
|
504
|
+
return results
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
async def verify_provider_api_key_async(
|
|
508
|
+
provider: str,
|
|
509
|
+
api_key: str | None = None,
|
|
510
|
+
**kwargs,
|
|
511
|
+
) -> VerificationResult:
|
|
512
|
+
"""Async version of verify_provider_api_key.
|
|
513
|
+
|
|
514
|
+
Runs the synchronous verification in a thread pool to avoid blocking.
|
|
515
|
+
"""
|
|
516
|
+
return await asyncio.to_thread(verify_provider_api_key, provider, api_key, **kwargs)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
async def verify_all_api_keys_async(
|
|
520
|
+
providers: list[str] | None = None,
|
|
521
|
+
include_optional: bool = False,
|
|
522
|
+
) -> dict[str, VerificationResult]:
|
|
523
|
+
"""Async version of verify_all_api_keys.
|
|
524
|
+
|
|
525
|
+
Verifies all providers concurrently for faster results.
|
|
526
|
+
"""
|
|
527
|
+
if providers is None:
|
|
528
|
+
providers = _get_providers_to_check(include_optional)
|
|
529
|
+
|
|
530
|
+
# Run all verifications concurrently
|
|
531
|
+
tasks = [verify_provider_api_key_async(provider) for provider in providers]
|
|
532
|
+
results_list = await asyncio.gather(*tasks)
|
|
533
|
+
|
|
534
|
+
return dict(zip(providers, results_list, strict=True))
|