mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-12 07:10:09 +00:00
Vertex: countTokens verify, global-location host, honest region help
Model listing 403/404s under plain ADC; countTokens is free and proves project+location+API in one call. Verified live: Gemini (global), Qwen MaaS (us-south1).
This commit is contained in:
@@ -400,9 +400,10 @@ DESCRIPTORS: list[ProviderDescriptor] = [
|
|||||||
"location",
|
"location",
|
||||||
"Location",
|
"Location",
|
||||||
secret=False,
|
secret=False,
|
||||||
placeholder="us-east5",
|
placeholder="global",
|
||||||
help="The region your Vertex AI models are enabled in "
|
help="Use `global` for the newest Gemini and Claude models. Some models "
|
||||||
"(Claude models: us-east5 or europe-west1).",
|
"are regional — Model Garden lists each (Claude also: us-east5 / "
|
||||||
|
"europe-west1; Qwen3 Coder: us-south1).",
|
||||||
),
|
),
|
||||||
ProviderField(
|
ProviderField(
|
||||||
"auth_method",
|
"auth_method",
|
||||||
@@ -695,9 +696,16 @@ def _verify_bedrock(fields: dict[str, Any], timeout: float) -> dict[str, Any]:
|
|||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
|
# Verify probe: countTokens on a stable Gemini model — free (no generation), works with
|
||||||
|
# plain ADC (the model list/GET endpoints 403/404 under user credentials — checked live
|
||||||
|
# 2026-07-26), and exercises project + location + API enablement in one call.
|
||||||
|
_VERTEX_PROBE_MODEL = "gemini-2.5-flash"
|
||||||
|
_VERTEX_PROBE_BODY = {"contents": [{"role": "user", "parts": [{"text": "hi"}]}]}
|
||||||
|
|
||||||
|
|
||||||
def _verify_vertex(fields: dict[str, Any], timeout: float) -> dict[str, Any]:
|
def _verify_vertex(fields: dict[str, Any], timeout: float) -> dict[str, Any]:
|
||||||
"""One cheap read-only call (list Google's publisher models) through the SELECTED
|
"""One cheap call (countTokens) through the SELECTED auth method: ADC /
|
||||||
auth method: ADC / service-account bearer, or the express API key header."""
|
service-account bearer, or the express API key header."""
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from .vertex_provider import load_credentials
|
from .vertex_provider import load_credentials
|
||||||
@@ -713,9 +721,11 @@ def _verify_vertex(fields: dict[str, Any], timeout: float) -> dict[str, Any]:
|
|||||||
return {"ok": False, "error": "Enter a Vertex API key to test."}
|
return {"ok": False, "error": "Enter a Vertex API key to test."}
|
||||||
try:
|
try:
|
||||||
# Express mode is global — no region host, no project in the path.
|
# Express mode is global — no region host, no project in the path.
|
||||||
resp = httpx.get(
|
resp = httpx.post(
|
||||||
"https://aiplatform.googleapis.com/v1/publishers/google/models",
|
"https://aiplatform.googleapis.com/v1/publishers/google/models/"
|
||||||
|
f"{_VERTEX_PROBE_MODEL}:countTokens",
|
||||||
headers={"x-goog-api-key": key},
|
headers={"x-goog-api-key": key},
|
||||||
|
json=_VERTEX_PROBE_BODY,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -754,11 +764,15 @@ def _verify_vertex(fields: dict[str, Any], timeout: float) -> dict[str, Any]:
|
|||||||
if kind in ("RefreshError", "MalformedError", "JSONDecodeError", "ValueError"):
|
if kind in ("RefreshError", "MalformedError", "JSONDecodeError", "ValueError"):
|
||||||
return {"ok": False, "error": "Google rejected the credentials."}
|
return {"ok": False, "error": "Google rejected the credentials."}
|
||||||
return {"ok": False, "error": f"Couldn't load Google credentials ({kind})."}
|
return {"ok": False, "error": f"Couldn't load Google credentials ({kind})."}
|
||||||
|
from .vertex_provider import _regional_host
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp = httpx.get(
|
resp = httpx.post(
|
||||||
f"https://{location}-aiplatform.googleapis.com/v1/projects/{project}"
|
f"https://{_regional_host(location)}/v1/projects/{project}"
|
||||||
f"/locations/{location}/publishers/google/models",
|
f"/locations/{location}/publishers/google/models/"
|
||||||
|
f"{_VERTEX_PROBE_MODEL}:countTokens",
|
||||||
headers={"Authorization": f"Bearer {creds.token}"},
|
headers={"Authorization": f"Bearer {creds.token}"},
|
||||||
|
json=_VERTEX_PROBE_BODY,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|||||||
@@ -46,6 +46,14 @@ _SCOPES = ["https://www.googleapis.com/auth/cloud-platform"]
|
|||||||
_FAMILIES = ("gemini", "claude", "openweight")
|
_FAMILIES = ("gemini", "claude", "openweight")
|
||||||
|
|
||||||
|
|
||||||
|
def _regional_host(location: Optional[str]) -> str:
|
||||||
|
"""Vertex REST host for a location — `global` (newer Gemini models) has no region
|
||||||
|
prefix (checked live 2026-07-26)."""
|
||||||
|
if not location or location == "global":
|
||||||
|
return "aiplatform.googleapis.com"
|
||||||
|
return f"{location}-aiplatform.googleapis.com"
|
||||||
|
|
||||||
|
|
||||||
def load_credentials(service_account_json: Optional[str]) -> Any:
|
def load_credentials(service_account_json: Optional[str]) -> Any:
|
||||||
"""Explicit service-account JSON (content or path) → Credentials; blank → None (the
|
"""Explicit service-account JSON (content or path) → Credentials; blank → None (the
|
||||||
SDKs and the token path then fall back to Application Default Credentials)."""
|
SDKs and the token path then fall back to Application Default Credentials)."""
|
||||||
@@ -191,7 +199,7 @@ class VertexProvider(ProviderClient):
|
|||||||
client = self._clients.get("openweight")
|
client = self._clients.get("openweight")
|
||||||
if client is None:
|
if client is None:
|
||||||
base = (
|
base = (
|
||||||
f"https://{self._location}-aiplatform.googleapis.com/v1/projects/"
|
f"https://{_regional_host(self._location)}/v1/projects/"
|
||||||
f"{self._project}/locations/{self._location}/endpoints/openapi"
|
f"{self._project}/locations/{self._location}/endpoints/openapi"
|
||||||
)
|
)
|
||||||
client = OpenAIProvider(api_key=creds.token, base_url=base)
|
client = OpenAIProvider(api_key=creds.token, base_url=base)
|
||||||
|
|||||||
@@ -105,6 +105,16 @@ def test_openweight_builds_maas_endpoint_and_refreshes_bearer():
|
|||||||
assert rebuilt._api_key == "tok-3"
|
assert rebuilt._api_key == "tok-3"
|
||||||
|
|
||||||
|
|
||||||
|
def test_openweight_global_location_has_no_region_host():
|
||||||
|
creds = _FakeCreds()
|
||||||
|
p = VertexProvider(project="proj", location="global", credentials=creds)
|
||||||
|
client = p._openweight_client()
|
||||||
|
assert client._base_url == (
|
||||||
|
"https://aiplatform.googleapis.com/v1/projects/proj"
|
||||||
|
"/locations/global/endpoints/openapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# -- credentials ------------------------------------------------------------------------
|
# -- credentials ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@@ -240,7 +250,7 @@ def test_verify_vertex_api_key_method(monkeypatch):
|
|||||||
|
|
||||||
captured: dict = {}
|
captured: dict = {}
|
||||||
|
|
||||||
def fake_get(url, headers=None, timeout=None, **kw):
|
def fake_post(url, headers=None, json=None, timeout=None, **kw):
|
||||||
captured["url"] = url
|
captured["url"] = url
|
||||||
captured["headers"] = headers
|
captured["headers"] = headers
|
||||||
|
|
||||||
@@ -249,7 +259,7 @@ def test_verify_vertex_api_key_method(monkeypatch):
|
|||||||
|
|
||||||
return _Resp()
|
return _Resp()
|
||||||
|
|
||||||
monkeypatch.setattr(httpx, "get", fake_get)
|
monkeypatch.setattr(httpx, "post", fake_post)
|
||||||
out = verify_provider_key(
|
out = verify_provider_key(
|
||||||
"vertex",
|
"vertex",
|
||||||
fields={
|
fields={
|
||||||
@@ -262,7 +272,10 @@ def test_verify_vertex_api_key_method(monkeypatch):
|
|||||||
assert out == {"ok": True}
|
assert out == {"ok": True}
|
||||||
assert captured["headers"]["x-goog-api-key"] == "AQ.k"
|
assert captured["headers"]["x-goog-api-key"] == "AQ.k"
|
||||||
# Express mode is global — no region host, no project in the path.
|
# Express mode is global — no region host, no project in the path.
|
||||||
assert captured["url"] == "https://aiplatform.googleapis.com/v1/publishers/google/models"
|
assert captured["url"] == (
|
||||||
|
"https://aiplatform.googleapis.com/v1/publishers/google/models/"
|
||||||
|
"gemini-2.5-flash:countTokens"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_verify_vertex_service_account_requires_json():
|
def test_verify_vertex_service_account_requires_json():
|
||||||
@@ -306,7 +319,7 @@ def _patch_verify(monkeypatch, creds: Any, status_code: Optional[int]):
|
|||||||
monkeypatch.setattr(vp, "load_credentials", lambda raw: creds)
|
monkeypatch.setattr(vp, "load_credentials", lambda raw: creds)
|
||||||
captured: dict = {}
|
captured: dict = {}
|
||||||
|
|
||||||
def fake_get(url, headers=None, timeout=None, **kw):
|
def fake_post(url, headers=None, json=None, timeout=None, **kw):
|
||||||
captured["url"] = url
|
captured["url"] = url
|
||||||
captured["headers"] = headers
|
captured["headers"] = headers
|
||||||
|
|
||||||
@@ -317,7 +330,7 @@ def _patch_verify(monkeypatch, creds: Any, status_code: Optional[int]):
|
|||||||
resp.status_code = status_code
|
resp.status_code = status_code
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
monkeypatch.setattr(httpx, "get", fake_get)
|
monkeypatch.setattr(httpx, "post", fake_post)
|
||||||
return captured
|
return captured
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user