AQIT 0.1.0
Loading...
Searching...
No Matches
hf_auth.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2"""Hugging Face auth — local token/env, or Aquin web proxy (like OpenAI)."""
3from __future__ import annotations
4
5import json
6import os
7from pathlib import Path
8from typing import Any
9
10import httpx
11
12_CONFIG_PATH = Path.home() / ".aquin" / "config.json"
13_ENGINE_STATE = Path.home() / ".aquin" / "engine.json"
14_HF_CACHE_TOKEN = Path.home() / ".cache" / "huggingface" / "token"
15
16_proxy_token: str | None = None
17_last_proxy_error: str | None = None
19
20def _read_json(path: Path) -> dict[str, str]:
21 if not path.exists():
22 return {}
23 try:
24 data = json.loads(path.read_text(encoding="utf-8"))
25 return data if isinstance(data, dict) else {}
26 except Exception:
27 return {}
28
29
30def _local_hf_token() -> str:
31 return (
32 os.environ.get("HF_TOKEN", "").strip()
33 or os.environ.get("HUGGINGFACE_HUB_TOKEN", "").strip()
34 )
35
36
37def _cached_hf_token() -> str:
38 if _HF_CACHE_TOKEN.exists():
39 return _HF_CACHE_TOKEN.read_text(encoding="utf-8").strip()
40 return ""
42
43def _aquin_api_key() -> str:
44 from aquin.auth_config import resolve_api_key
45 return resolve_api_key(allow_missing=True)
46
48def _aquin_base_url() -> str:
49 return (
50 os.environ.get("AQUIN_BASE_URL", "").strip()
51 or _read_json(_ENGINE_STATE).get("base_url", "").strip()
52 or "https://api.aquin.app"
53 ).rstrip("/")
54
55
56def _aquin_logged_in() -> bool:
57 return bool(_aquin_api_key())
58
59
60def _apply_hf_token(token: str) -> None:
61 """Expose token to huggingface_hub / transformers via env only (no interactive login)."""
62 if not token:
63 return
65 return
66 os.environ["HF_TOKEN"] = token
67 os.environ["HUGGINGFACE_HUB_TOKEN"] = token
68
69
70def _fetch_proxy_token() -> str:
71 """Fetch HF hub token from Aquin API (Bearer aquin API key)."""
72 global _proxy_token, _last_proxy_error
73 if _proxy_token:
74 return _proxy_token
75
76 api_key = _aquin_api_key()
77 if not api_key:
78 _last_proxy_error = "No HF credentials — set HF_TOKEN"
79 return ""
80
81 url = f"{_aquin_base_url()}/api/sync/huggingface/token"
82 try:
83 resp = httpx.get(
84 url,
85 headers={"Authorization": f"Bearer {api_key}"},
86 timeout=30.0,
87 )
88 except Exception as exc:
89 _last_proxy_error = f"Could not reach Aquin HF proxy at {url}: {exc}"
90 return ""
91
92 if resp.status_code == 401:
93 _last_proxy_error = "Aquin API key rejected — set AQUIN_API_KEY or use HF_TOKEN"
94 return ""
95
96 if resp.status_code == 503:
97 try:
98 detail = str(resp.json().get("error") or "").strip()
99 except Exception:
100 detail = resp.text[:200]
101 _last_proxy_error = detail or "Hugging Face not configured on Aquin server (HF_TOKEN missing)"
102 return ""
103
104 if resp.status_code != 200:
105 _last_proxy_error = f"Aquin HF proxy returned {resp.status_code}: {resp.text[:200]}"
106 return ""
107
108 try:
109 token = str(resp.json().get("token") or "").strip()
110 except Exception:
111 _last_proxy_error = "Aquin HF proxy returned invalid JSON"
112 return ""
113
114 if not token:
115 _last_proxy_error = "Aquin HF proxy returned an empty token"
116 return ""
117
118 _proxy_token = token
119 _last_proxy_error = None
120 return token
121
122
123def resolve_hf_token() -> str:
124 """Return an HF hub token from env, local cache, or Aquin web proxy."""
125 local = _local_hf_token() or _cached_hf_token()
126 if local:
127 return local
128 if _aquin_logged_in():
129 return _fetch_proxy_token()
130 return ""
131
132
133def hf_hub_kwargs() -> dict[str, Any]:
134 """Ensure HF auth is configured. Returns {} — env-based auth avoids duplicate token= in TL."""
135 token = resolve_hf_token()
136 if token:
138 return {}
139
140
141def ensure_hf_login(*, quiet: bool = False) -> bool:
142 """
143 Authenticate huggingface_hub for gated downloads.
144 Prefers HF_TOKEN locally; otherwise optional AQUIN_API_KEY proxy if configured.
145 """
146 token = resolve_hf_token()
147 if not token:
148 return False
149 _apply_hf_token(token)
150 if not quiet:
151 source = "local" if _local_hf_token() or _cached_hf_token() else "Aquin proxy"
152 print(f"[hf] authenticated ({source})", flush=True)
153 return True
154
155
156def _proxy_unavailable_hint() -> str:
157 detail = _last_proxy_error or "No Hugging Face token from Aquin server"
158 return (
159 "Hugging Face access via Aquin proxy failed.\n"
160 f" • {detail}\n"
161 " • After aquin login, gated models (e.g. Llama) should work without huggingface-cli login.\n"
162 " • Server ops: set HF_TOKEN on the Aquin API host (same as OPENAI_API_KEY)."
163 )
164
165
166def require_hf_hub_auth(*, hf_repo: str) -> None:
167 """
168 Resolve HF auth before model download. Raises if Aquin login is set but
169 the server proxy cannot supply a token.
170 """
171 token = resolve_hf_token()
172 if token:
173 _apply_hf_token(token)
174 return
175
176 if _aquin_logged_in():
177 raise RuntimeError(_proxy_unavailable_hint())
178
179
180def gated_repo_hint(hf_repo: str) -> str:
181 if _aquin_logged_in() and not resolve_hf_token():
182 return (
183 f"Cannot access gated Hugging Face repo '{hf_repo}'.\n"
185 )
186 if resolve_hf_token():
187 return (
188 f"Cannot access gated Hugging Face repo '{hf_repo}'.\n"
189 " • The Aquin HF token may lack access to this model on huggingface.co"
190 )
191 return (
192 f"Cannot access gated Hugging Face repo '{hf_repo}'.\n"
193 " • Run: aquin login\n"
194 " • Or set HF_TOKEN locally / huggingface-cli login"
195 )
dict[str, str] _read_json(Path path)
Definition hf_auth.py:24
str gated_repo_hint(str hf_repo)
Definition hf_auth.py:184
None _apply_hf_token(str token)
Definition hf_auth.py:64
dict[str, Any] hf_hub_kwargs()
Definition hf_auth.py:137
None require_hf_hub_auth(*, str hf_repo)
Definition hf_auth.py:170
bool ensure_hf_login(*, bool quiet=False)
Definition hf_auth.py:145
str _proxy_unavailable_hint()
Definition hf_auth.py:160
bool _aquin_logged_in()
Definition hf_auth.py:60