youssefreda9 commited on
Commit
7cd5afa
·
1 Parent(s): 2df4f2d

fix: Use huggingface_hub InferenceClient instead of raw urllib (fixes DNS inside HF Spaces)

Browse files
Files changed (2) hide show
  1. requirements.txt +1 -0
  2. src/hf_inference.py +95 -123
requirements.txt CHANGED
@@ -1,3 +1,4 @@
 
1
  transformers
2
  torch
3
  datasets
 
1
+ huggingface_hub>=0.20.0
2
  transformers
3
  torch
4
  datasets
src/hf_inference.py CHANGED
@@ -1,8 +1,8 @@
1
  """
2
  HuggingFace Inference API client for Bayan models.
3
 
4
- Instead of loading 500MB+ models into RAM locally, this module calls
5
- HuggingFace's free Inference API to run predictions remotely.
6
 
7
  Models:
8
  - bayan10/summarization-model (MBart, summarization pipeline)
@@ -12,74 +12,29 @@ Models:
12
  """
13
 
14
  import os
15
- import json
16
  import logging
17
  import time
18
- import urllib.request
19
- import urllib.error
20
- import ssl
21
 
22
  logger = logging.getLogger(__name__)
23
 
24
  HF_API_TOKEN = os.environ.get("HF_API_TOKEN", "")
25
- HF_API_BASE = "https://api-inference.huggingface.co/models/"
26
 
27
- # Timeout for inference calls (seconds)
28
- HF_TIMEOUT = 120 # 2 min — accounts for cold starts
29
 
30
 
31
- def _build_headers():
32
- """Build request headers with auth token."""
33
- headers = {"Content-Type": "application/json"}
34
- if HF_API_TOKEN:
35
- headers["Authorization"] = "Bearer " + HF_API_TOKEN
36
- return headers
37
-
38
-
39
- def _call_hf_api(repo_id, payload, timeout=HF_TIMEOUT):
40
- """
41
- Call HuggingFace Inference API.
42
-
43
- Sends wait_for_model=true to handle cold starts automatically
44
- (HF will wait up to ~2min for the model to load instead of 503).
45
- Returns parsed JSON response or raises Exception.
46
- """
47
- url = HF_API_BASE + repo_id
48
- headers = _build_headers()
49
-
50
- # Tell HF to wait for the model to load instead of returning 503
51
- if "options" not in payload:
52
- payload["options"] = {"wait_for_model": True}
53
- else:
54
- payload["options"]["wait_for_model"] = True
55
-
56
- data = json.dumps(payload).encode("utf-8")
57
- ctx = ssl.create_default_context()
58
-
59
- logger.info("Calling HF API: %s (payload keys: %s)", repo_id, list(payload.keys()))
60
-
61
- try:
62
- req = urllib.request.Request(url, data=data, headers=headers, method="POST")
63
- resp = urllib.request.urlopen(req, timeout=timeout, context=ctx)
64
- body = resp.read().decode("utf-8")
65
- result = json.loads(body)
66
- logger.info("HF API success for %s: response type=%s", repo_id, type(result).__name__)
67
- return result
68
-
69
- except urllib.error.HTTPError as e:
70
- body = e.read().decode("utf-8", errors="replace")
71
- logger.error("HF API error for %s: HTTP %d — %s", repo_id, e.code, body[:500])
72
- raise RuntimeError(
73
- "HF API error for {} (HTTP {}): {}".format(repo_id, e.code, body[:300])
74
- )
75
-
76
- except Exception as e:
77
- logger.error("HF API exception for %s: %s", repo_id, str(e))
78
- raise
79
 
80
 
81
  # ============================================================
82
- # Model-specific wrappers
83
  # ============================================================
84
 
85
  SUMMARIZATION_REPO = os.environ.get("SUMMARIZATION_REPO_ID", "bayan10/summarization-model")
@@ -88,101 +43,119 @@ PUNCTUATION_REPO = os.environ.get("PUNCTUATION_REPO_ID", "bayan10/PuncAra-v1")
88
  AUTOCOMPLETE_REPO = os.environ.get("AUTOCOMPLETE_REPO_ID", "bayan10/AutoComplete")
89
 
90
 
 
 
 
 
91
  def hf_summarize(text, max_length=128, min_length=30):
92
  """Summarize Arabic text via HF Inference API."""
93
- payload = {
94
- "inputs": text,
95
- "parameters": {
96
- "max_length": max_length,
97
- "min_length": min_length,
98
- }
99
- }
100
-
101
- result = _call_hf_api(SUMMARIZATION_REPO, payload)
102
-
103
- # HF summarization returns: [{"summary_text": "..."}]
104
- if isinstance(result, list) and len(result) > 0:
105
- return result[0].get("summary_text", result[0].get("generated_text", ""))
106
-
 
 
107
  if isinstance(result, dict):
108
  return result.get("summary_text", result.get("generated_text", str(result)))
109
-
110
  return str(result)
111
 
112
 
113
  def hf_correct_spelling(text):
114
  """Correct spelling in Arabic text via HF Inference API."""
115
- payload = {"inputs": text}
116
-
117
- result = _call_hf_api(SPELLING_REPO, payload)
118
-
119
- # Text2text / seq2seq models return: [{"generated_text": "..."}]
120
- if isinstance(result, list) and len(result) > 0:
121
- item = result[0]
122
- if isinstance(item, dict):
123
- return item.get("generated_text", item.get("translation_text", text))
124
- return str(item)
125
-
 
 
 
 
126
  if isinstance(result, dict):
127
- return result.get("generated_text", result.get("translation_text", text))
128
-
129
  return text
130
 
131
 
132
  def hf_add_punctuation(text):
133
  """Add punctuation to Arabic text via HF Inference API."""
134
- payload = {"inputs": text}
135
-
136
- result = _call_hf_api(PUNCTUATION_REPO, payload)
137
-
138
- if isinstance(result, list) and len(result) > 0:
139
- item = result[0]
140
- if isinstance(item, dict):
141
- return item.get("generated_text", item.get("translation_text", text))
142
- return str(item)
143
-
 
 
 
 
 
144
  if isinstance(result, dict):
145
- return result.get("generated_text", result.get("translation_text", text))
146
-
147
  return text
148
 
149
 
150
  def hf_autocomplete(text, n=5):
151
  """Get autocomplete suggestions for Arabic text via HF Inference API."""
152
- payload = {
153
- "inputs": text,
154
- "parameters": {
155
- "max_new_tokens": 20,
156
- }
157
- }
158
-
159
- result = _call_hf_api(AUTOCOMPLETE_REPO, payload)
160
-
161
- # Text generation returns: [{"generated_text": "..."}]
 
 
 
 
 
 
 
 
162
  if isinstance(result, list):
163
  suggestions = []
164
  for item in result:
165
- if isinstance(item, dict):
166
- gen = item.get("generated_text", "")
167
- # Remove the input prefix to get just the completion
168
- if gen.startswith(text):
169
- gen = gen[len(text):].strip()
170
- if gen:
171
- suggestions.append(gen)
172
  return suggestions if suggestions else [text]
173
-
174
  return [text]
175
 
176
 
177
  def check_hf_api_available():
178
  """Quick check if HF Inference API is reachable."""
179
  try:
180
- url = HF_API_BASE + SUMMARIZATION_REPO
181
- headers = _build_headers()
182
- req = urllib.request.Request(url, headers=headers, method="GET")
183
- ctx = ssl.create_default_context()
184
- resp = urllib.request.urlopen(req, timeout=10, context=ctx)
185
- return resp.status == 200
186
  except Exception:
187
  return False
188
 
@@ -217,4 +190,3 @@ def debug_test_all_models():
217
  }
218
 
219
  return results
220
-
 
1
  """
2
  HuggingFace Inference API client for Bayan models.
3
 
4
+ Uses huggingface_hub.InferenceClient which works from within HF Spaces
5
+ (handles internal routing / DNS automatically).
6
 
7
  Models:
8
  - bayan10/summarization-model (MBart, summarization pipeline)
 
12
  """
13
 
14
  import os
 
15
  import logging
16
  import time
 
 
 
17
 
18
  logger = logging.getLogger(__name__)
19
 
20
  HF_API_TOKEN = os.environ.get("HF_API_TOKEN", "")
 
21
 
22
+ # Lazy-initialized client (created on first use)
23
+ _client = None
24
 
25
 
26
+ def _get_client():
27
+ """Get or create the InferenceClient singleton."""
28
+ global _client
29
+ if _client is None:
30
+ from huggingface_hub import InferenceClient
31
+ _client = InferenceClient(token=HF_API_TOKEN if HF_API_TOKEN else None)
32
+ logger.info("HuggingFace InferenceClient initialized (token=%s)", "set" if HF_API_TOKEN else "not set")
33
+ return _client
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
 
36
  # ============================================================
37
+ # Repository IDs
38
  # ============================================================
39
 
40
  SUMMARIZATION_REPO = os.environ.get("SUMMARIZATION_REPO_ID", "bayan10/summarization-model")
 
43
  AUTOCOMPLETE_REPO = os.environ.get("AUTOCOMPLETE_REPO_ID", "bayan10/AutoComplete")
44
 
45
 
46
+ # ============================================================
47
+ # Model-specific wrappers
48
+ # ============================================================
49
+
50
  def hf_summarize(text, max_length=128, min_length=30):
51
  """Summarize Arabic text via HF Inference API."""
52
+ client = _get_client()
53
+ logger.info("Calling HF summarization: %s (max_length=%d)", SUMMARIZATION_REPO, max_length)
54
+
55
+ result = client.summarization(
56
+ text,
57
+ model=SUMMARIZATION_REPO,
58
+ parameters={"max_length": max_length, "min_length": min_length},
59
+ )
60
+
61
+ logger.info("Summarization result type: %s", type(result).__name__)
62
+
63
+ # Result is a SummarizationOutput with .summary_text
64
+ if hasattr(result, 'summary_text'):
65
+ return result.summary_text
66
+
67
+ # Fallback for dict responses
68
  if isinstance(result, dict):
69
  return result.get("summary_text", result.get("generated_text", str(result)))
70
+
71
  return str(result)
72
 
73
 
74
  def hf_correct_spelling(text):
75
  """Correct spelling in Arabic text via HF Inference API."""
76
+ client = _get_client()
77
+ logger.info("Calling HF spelling correction: %s", SPELLING_REPO)
78
+
79
+ # Use text_generation since model has no pipeline_tag
80
+ result = client.text_generation(
81
+ text,
82
+ model=SPELLING_REPO,
83
+ max_new_tokens=len(text) + 50,
84
+ )
85
+
86
+ logger.info("Spelling result type: %s, value: %s", type(result).__name__, str(result)[:100])
87
+
88
+ if isinstance(result, str):
89
+ return result if result.strip() else text
90
+
91
  if isinstance(result, dict):
92
+ return result.get("generated_text", text)
93
+
94
  return text
95
 
96
 
97
  def hf_add_punctuation(text):
98
  """Add punctuation to Arabic text via HF Inference API."""
99
+ client = _get_client()
100
+ logger.info("Calling HF punctuation: %s", PUNCTUATION_REPO)
101
+
102
+ # PuncAra is an encoder-decoder model
103
+ result = client.text_generation(
104
+ text,
105
+ model=PUNCTUATION_REPO,
106
+ max_new_tokens=len(text) + 50,
107
+ )
108
+
109
+ logger.info("Punctuation result type: %s, value: %s", type(result).__name__, str(result)[:100])
110
+
111
+ if isinstance(result, str):
112
+ return result if result.strip() else text
113
+
114
  if isinstance(result, dict):
115
+ return result.get("generated_text", text)
116
+
117
  return text
118
 
119
 
120
  def hf_autocomplete(text, n=5):
121
  """Get autocomplete suggestions for Arabic text via HF Inference API."""
122
+ client = _get_client()
123
+ logger.info("Calling HF autocomplete: %s", AUTOCOMPLETE_REPO)
124
+
125
+ result = client.text_generation(
126
+ text,
127
+ model=AUTOCOMPLETE_REPO,
128
+ max_new_tokens=20,
129
+ )
130
+
131
+ logger.info("Autocomplete result type: %s, value: %s", type(result).__name__, str(result)[:100])
132
+
133
+ if isinstance(result, str):
134
+ # Result includes the input + completion
135
+ completion = result
136
+ if completion.startswith(text):
137
+ completion = completion[len(text):].strip()
138
+ return [completion] if completion else [text]
139
+
140
  if isinstance(result, list):
141
  suggestions = []
142
  for item in result:
143
+ gen = item.get("generated_text", "") if isinstance(item, dict) else str(item)
144
+ if gen.startswith(text):
145
+ gen = gen[len(text):].strip()
146
+ if gen:
147
+ suggestions.append(gen)
 
 
148
  return suggestions if suggestions else [text]
149
+
150
  return [text]
151
 
152
 
153
  def check_hf_api_available():
154
  """Quick check if HF Inference API is reachable."""
155
  try:
156
+ client = _get_client()
157
+ # Just check if the client can be created
158
+ return client is not None
 
 
 
159
  except Exception:
160
  return False
161
 
 
190
  }
191
 
192
  return results