@danhachuel/thunderbolt 0.3.18 → 0.3.19
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.
package/app/main.py
CHANGED
|
@@ -3853,10 +3853,6 @@ def _render_llm_card(settings: dict[str, Any], cards: list[dict[str, Any]], inde
|
|
|
3853
3853
|
test_result = test_llm_provider_card(edited)
|
|
3854
3854
|
edited["test_result"] = stamp_test_result(test_result)
|
|
3855
3855
|
_persist_llm_cards(settings, cards, str(settings.get(LLM_ACTIVE_CARD_KEY) or card_id))
|
|
3856
|
-
if test_result.get("ok"):
|
|
3857
|
-
st.success(f"Teste API: {test_result['message']}")
|
|
3858
|
-
else:
|
|
3859
|
-
st.error(f"Teste API: {test_result['message']}")
|
|
3860
3856
|
elif refresh_clicked:
|
|
3861
3857
|
try:
|
|
3862
3858
|
from integrations.openai_model_discovery import fetch_openai_compatible_models
|
|
@@ -3880,7 +3876,7 @@ def _render_llm_card(settings: dict[str, Any], cards: list[dict[str, Any]], inde
|
|
|
3880
3876
|
saved_test = edited.get("test_result") or card.get("test_result")
|
|
3881
3877
|
if isinstance(saved_test, dict) and saved_test.get("message"):
|
|
3882
3878
|
if saved_test.get("status") == "success":
|
|
3883
|
-
st.success(
|
|
3879
|
+
st.success("Último teste: API Key OK")
|
|
3884
3880
|
else:
|
|
3885
3881
|
st.error(f"Último teste: {saved_test['message']}")
|
|
3886
3882
|
|
|
@@ -9,12 +9,11 @@ from __future__ import annotations
|
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
from datetime import datetime, timezone
|
|
11
11
|
from typing import Any, Mapping
|
|
12
|
-
from urllib.parse import urlsplit, urlunsplit
|
|
13
|
-
|
|
14
12
|
from integrations.openai_model_discovery import (
|
|
15
13
|
DEFAULT_NVIDIA_NIM_BASE_URL,
|
|
16
|
-
|
|
14
|
+
OpenAICompatibleAPIError,
|
|
17
15
|
fetch_openai_compatible_models,
|
|
16
|
+
validate_openai_compatible_api_key,
|
|
18
17
|
)
|
|
19
18
|
|
|
20
19
|
|
|
@@ -357,14 +356,6 @@ def apply_llm_cards_to_settings(
|
|
|
357
356
|
return result
|
|
358
357
|
|
|
359
358
|
|
|
360
|
-
def _safe_url(value: str) -> str:
|
|
361
|
-
try:
|
|
362
|
-
parsed = urlsplit(str(value or ""))
|
|
363
|
-
return urlunsplit((parsed.scheme, parsed.netloc, parsed.path, "", ""))
|
|
364
|
-
except ValueError:
|
|
365
|
-
return "endpoint configurado"
|
|
366
|
-
|
|
367
|
-
|
|
368
359
|
def _redact(text: Any, secrets: tuple[str, ...]) -> str:
|
|
369
360
|
message = str(text or "")
|
|
370
361
|
for secret in secrets:
|
|
@@ -386,11 +377,12 @@ def test_llm_provider_card(card: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
386
377
|
return {"ok": False, "status": "error", "message": "Missing configuration — introduza a Base URL antes do teste."}
|
|
387
378
|
if not base_url:
|
|
388
379
|
return {"ok": False, "status": "error", "message": "Missing configuration — este provider não tem endpoint configurado."}
|
|
389
|
-
|
|
390
|
-
|
|
380
|
+
model = str(normalized.get("model") or "").strip()
|
|
381
|
+
if not model:
|
|
382
|
+
return {"ok": False, "status": "error", "message": "Missing configuration — introduza o modelo antes do teste."}
|
|
391
383
|
try:
|
|
392
|
-
|
|
393
|
-
except
|
|
384
|
+
validate_openai_compatible_api_key(api_key, base_url, model)
|
|
385
|
+
except OpenAICompatibleAPIError as exc:
|
|
394
386
|
return {
|
|
395
387
|
"ok": False,
|
|
396
388
|
"status": "error",
|
|
@@ -401,7 +393,7 @@ def test_llm_provider_card(card: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
401
393
|
return {
|
|
402
394
|
"ok": True,
|
|
403
395
|
"status": "success",
|
|
404
|
-
"message":
|
|
396
|
+
"message": "API Key OK",
|
|
405
397
|
}
|
|
406
398
|
|
|
407
399
|
|
|
@@ -16,6 +16,10 @@ class ModelDiscoveryError(ValueError):
|
|
|
16
16
|
"""Raised when an OpenAI-compatible model list cannot be loaded safely."""
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
class OpenAICompatibleAPIError(ValueError):
|
|
20
|
+
"""Raised when an authenticated OpenAI-compatible API check fails."""
|
|
21
|
+
|
|
22
|
+
|
|
19
23
|
def models_endpoint(base_url: str) -> str:
|
|
20
24
|
"""Return the ``/models`` endpoint for an OpenAI-compatible base URL."""
|
|
21
25
|
value = str(base_url or "").strip().rstrip("/")
|
|
@@ -26,6 +30,56 @@ def models_endpoint(base_url: str) -> str:
|
|
|
26
30
|
return f"{value}/models"
|
|
27
31
|
|
|
28
32
|
|
|
33
|
+
def chat_completions_endpoint(base_url: str) -> str:
|
|
34
|
+
"""Return the Chat Completions endpoint for an OpenAI-compatible base URL."""
|
|
35
|
+
value = str(base_url or "").strip().rstrip("/")
|
|
36
|
+
if not value:
|
|
37
|
+
raise OpenAICompatibleAPIError("Informe a Base URL antes do teste.")
|
|
38
|
+
if value.endswith("/chat/completions"):
|
|
39
|
+
return value
|
|
40
|
+
return f"{value}/chat/completions"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def validate_openai_compatible_api_key(
|
|
44
|
+
api_key: str,
|
|
45
|
+
base_url: str,
|
|
46
|
+
model: str,
|
|
47
|
+
*,
|
|
48
|
+
timeout: float = DEFAULT_TIMEOUT_SECONDS,
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Validate credentials with one minimal authenticated Chat Completions call."""
|
|
51
|
+
token = str(api_key or "").strip()
|
|
52
|
+
model_id = str(model or "").strip()
|
|
53
|
+
if not token:
|
|
54
|
+
raise OpenAICompatibleAPIError("Informe a API key antes do teste.")
|
|
55
|
+
if not model_id:
|
|
56
|
+
raise OpenAICompatibleAPIError("Informe o modelo antes do teste.")
|
|
57
|
+
|
|
58
|
+
endpoint = chat_completions_endpoint(base_url)
|
|
59
|
+
headers = {
|
|
60
|
+
"Accept": "application/json",
|
|
61
|
+
"Authorization": f"Bearer {token}",
|
|
62
|
+
"Content-Type": "application/json",
|
|
63
|
+
}
|
|
64
|
+
payload = {
|
|
65
|
+
"model": model_id,
|
|
66
|
+
"messages": [{"role": "user", "content": "Responda apenas OK."}],
|
|
67
|
+
"max_tokens": 1,
|
|
68
|
+
"temperature": 0,
|
|
69
|
+
}
|
|
70
|
+
try:
|
|
71
|
+
response = requests.post(endpoint, headers=headers, json=payload, timeout=timeout)
|
|
72
|
+
except requests.RequestException as exc:
|
|
73
|
+
raise OpenAICompatibleAPIError(f"Não foi possível testar {endpoint}: {exc}") from exc
|
|
74
|
+
|
|
75
|
+
if response.status_code >= 400:
|
|
76
|
+
if response.status_code in (401, 403):
|
|
77
|
+
raise OpenAICompatibleAPIError(
|
|
78
|
+
f"O endpoint recusou a API key (HTTP {response.status_code}). Verifique a API key."
|
|
79
|
+
)
|
|
80
|
+
raise OpenAICompatibleAPIError(f"O endpoint de teste devolveu HTTP {response.status_code}.")
|
|
81
|
+
|
|
82
|
+
|
|
29
83
|
def normalize_model_ids(payload: Any) -> list[str]:
|
|
30
84
|
"""Extract unique model IDs from the standard OpenAI response shape."""
|
|
31
85
|
if isinstance(payload, dict):
|
package/package.json
CHANGED