kaburia commited on
Commit
da4d8cf
·
1 Parent(s): 0c9a0a8
Files changed (1) hide show
  1. app.py +108 -105
app.py CHANGED
@@ -5,10 +5,9 @@ import time
5
  import json
6
  import requests
7
  import gradio as gr
8
- import time
9
-
10
 
11
- # Import helpers module so we can set helpers.session_id for the uploader
 
12
  import utils.helpers as helpers
13
  from utils.helpers import retrieve_context, log_interaction_hf, upload_log_to_hf
14
 
@@ -16,43 +15,57 @@ from utils.helpers import retrieve_context, log_interaction_hf, upload_log_to_hf
16
  with open("config.json") as f:
17
  config = json.load(f)
18
 
19
- DO_API_KEY = config["do_token"] # DigitalOcean Model Access Key (serverless inference)
20
- token_ = config['token']
21
- HF_TOKEN = 'hf_'+token_ # Hugging Face token for dataset uploads
22
 
23
- # Provide a stable session_id for the whole app lifetime so logs land under a unique folder
24
  session_id = f"{int(time.time())}-{uuid.uuid4().hex[:8]}"
25
- helpers.session_id = session_id # <-- required by your upload_log_to_hf implementation
26
 
27
  BASE_URL = "https://inference.do-ai.run/v1"
28
- UPLOAD_INTERVAL = 5 # upload logs to HF every N turns
29
-
 
30
 
31
- # ========= Inference Utilities =========
32
  def _auth_headers():
33
- return {"Authorization": f"Bearer {DO_API_KEY}", "Content-Type": "application/json"}
 
 
 
 
34
 
35
  def list_models():
36
- """Fetch live model IDs from DO; fall back to a sane default on failure."""
 
 
 
37
  try:
38
- r = requests.get(f"{BASE_URL}/models", headers=_auth_headers(), timeout=15)
39
- r.raise_for_status()
40
- data = r.json().get("data", [])
41
- ids = [m["id"] for m in data]
42
  if ids:
43
  return ids
44
  except Exception as e:
45
  print(f"⚠️ list_models failed: {e}")
46
- # Fallback to common public model
47
  return ["llama3.3-70b-instruct"]
48
 
 
 
 
 
 
 
49
  def gradient_request(model_id, prompt, max_tokens=512, temperature=0.7, top_p=0.95):
50
- """Non-streaming call (used by lightweight tasks like intent detection)."""
 
 
 
51
  url = f"{BASE_URL}/chat/completions"
52
- if not model_id:
53
- model_id = list_models()[0]
54
  payload = {
55
- "model": model_id,
56
  "messages": [{"role": "user", "content": prompt}],
57
  "max_tokens": max_tokens,
58
  "temperature": temperature,
@@ -60,31 +73,33 @@ def gradient_request(model_id, prompt, max_tokens=512, temperature=0.7, top_p=0.
60
  }
61
  for attempt in range(3):
62
  try:
63
- resp = requests.post(url, headers=_auth_headers(), json=payload, timeout=30)
64
- # If model not found, try the first available model (self-heal)
65
  if resp.status_code == 404:
 
66
  ids = list_models()
67
- if model_id not in ids and ids:
68
  payload["model"] = ids[0]
69
  continue
70
  resp.raise_for_status()
71
  j = resp.json()
72
  return j["choices"][0]["message"]["content"].strip()
73
  except requests.HTTPError as e:
74
- msg = getattr(e.response, "text", str(e))
75
- raise RuntimeError(f"Inference error ({e.response.status_code}): {msg}") from e
76
  except requests.RequestException as e:
77
  if attempt == 2:
78
  raise
 
79
  raise RuntimeError("Exhausted retries")
80
 
81
-
82
  def gradient_stream(model_id, prompt, max_tokens=512, temperature=0.7, top_p=0.95):
 
 
 
 
83
  url = f"{BASE_URL}/chat/completions"
84
- if not model_id:
85
- model_id = list_models()[0]
86
  payload = {
87
- "model": model_id,
88
  "messages": [{"role": "user", "content": prompt}],
89
  "max_tokens": max_tokens,
90
  "temperature": temperature,
@@ -92,13 +107,9 @@ def gradient_stream(model_id, prompt, max_tokens=512, temperature=0.7, top_p=0.9
92
  "stream": True,
93
  }
94
 
95
- # Immediate heartbeat so the user sees a bubble populate
96
- yield "" # noop to render assistant bubble
97
-
98
  try:
99
- with requests.post(url, headers=_auth_headers(), json=payload, stream=True, timeout=120) as r:
100
  if r.status_code != 200:
101
- # Surface the exact server message
102
  try:
103
  err_txt = r.text
104
  except Exception:
@@ -107,11 +118,10 @@ def gradient_stream(model_id, prompt, max_tokens=512, temperature=0.7, top_p=0.9
107
 
108
  last_token_ts = time.time()
109
  for raw in r.iter_lines(decode_unicode=True):
110
- if not raw:
111
- # if no tokens for 3s, yield a dot to show liveness
112
  if time.time() - last_token_ts > 3:
113
  last_token_ts = time.time()
114
- yield "" # visual keepalive
115
  continue
116
  if not raw.startswith("data: "):
117
  continue
@@ -128,28 +138,29 @@ def gradient_stream(model_id, prompt, max_tokens=512, temperature=0.7, top_p=0.9
128
  except Exception:
129
  continue
130
  except Exception as e:
131
- # Bubble the failure up so caller can fall back to non-streaming
132
  raise
133
 
134
  def gradient_complete(model_id, prompt, max_tokens=512, temperature=0.7, top_p=0.95):
135
  url = f"{BASE_URL}/chat/completions"
136
  payload = {
137
- "model": model_id,
138
  "messages": [{"role": "user", "content": prompt}],
139
  "max_tokens": max_tokens,
140
  "temperature": temperature,
141
  "top_p": top_p,
142
  }
143
- r = requests.post(url, headers=_auth_headers(), json=payload, timeout=60)
144
  if r.status_code != 200:
145
  raise RuntimeError(f"HTTP {r.status_code}: {r.text}")
146
  j = r.json()
147
  return j["choices"][0]["message"]["content"].strip()
148
 
149
-
150
  # ========= Lightweight Intent Detection =========
151
  def detect_intent(model_id, message: str) -> str:
152
- """Fail-open to 'info_query' on any issue."""
 
 
 
153
  try:
154
  out = gradient_request(
155
  model_id,
@@ -163,29 +174,26 @@ def detect_intent(model_id, message: str) -> str:
163
  print(f"⚠️ detect_intent failed: {e}")
164
  return "info_query"
165
 
166
-
167
- # ========= App Logic (Gradio Blocks) =========
168
  with gr.Blocks(title="Gradient AI Chat") as demo:
169
- # Keep a reactive turn counter in session state
170
  turn_counter = gr.State(0)
171
 
172
  gr.Markdown("## Gradient AI Chat")
173
  gr.Markdown("Select a model and ask your question.")
174
 
175
- # Model dropdown will be populated at runtime with live IDs
176
  with gr.Row():
177
  model_drop = gr.Dropdown(choices=[], label="Select Model")
178
  system_msg = gr.Textbox(
179
- value="You are a faithful assistant. Use only the provided context.",
180
  label="System message"
181
  )
182
 
183
  with gr.Row():
184
  max_tokens_slider = gr.Slider(minimum=1, maximum=4096, value=512, step=1, label="Max new tokens")
185
  temperature_slider = gr.Slider(minimum=0.0, maximum=2.0, value=0.7, step=0.1, label="Temperature")
186
- top_p_slider = gr.Slider(minimum=0.1, maximum=1.0, value=0.95, step=0.05, label="Top-p")
187
 
188
- # Use tuples to silence deprecation warning in current Gradio
189
  chatbot = gr.Chatbot(height=500, type="tuples")
190
  msg = gr.Textbox(label="Your message")
191
 
@@ -205,9 +213,8 @@ with gr.Blocks(title="Gradient AI Chat") as demo:
205
  # --- Load models into dropdown at startup
206
  def load_models():
207
  ids = list_models()
208
- default = ids[0] if ids else None
209
- return gr.Dropdown.update(choices=ids, value=default)
210
-
211
 
212
  demo.load(load_models, outputs=[model_drop])
213
 
@@ -222,78 +229,75 @@ with gr.Blocks(title="Gradient AI Chat") as demo:
222
 
223
  # --- Event handlers
224
  def user(user_message, chat_history):
225
- # Seed a new assistant message for streaming
226
- return "", (chat_history + [[user_message, ""]])
 
 
227
 
228
  def bot(chat_history, current_turn_count, model_id, system_message, max_tokens, temperature, top_p):
 
 
 
 
 
 
 
 
229
  user_message = chat_history[-1][0]
230
 
231
- # Build prompt
232
  intent = detect_intent(model_id, user_message)
233
- if intent == "small_talk":
234
- full_prompt = f"[System]: Friendly chat.\n[User]: {user_message}\n[Assistant]: "
235
- else:
 
236
  try:
237
- context = retrieve_context(user_message, p=5, threshold=0.5)
238
  except Exception as e:
239
  print(f"⚠️ retrieve_context failed: {e}")
240
  context = ""
241
- full_prompt = (
242
- f"[System]: {system_message}\n"
243
- "Use only the provided context. Quote verbatim; no inference.\n\n"
244
- f"Context:\n{context}\n\nQuestion: {user_message}\n"
245
- )
246
 
247
- # Seed assistant message so user sees the bubble
248
- chat_history[-1][1] = ""
249
- yield chat_history, current_turn_count
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
  # Stream with fallback
252
  try:
253
  received_any = False
 
 
254
  for token in gradient_stream(model_id, full_prompt, max_tokens, temperature, top_p):
255
  if token:
256
  received_any = True
257
- chat_history[-1][1] += token
258
- yield chat_history, current_turn_count
 
 
259
  if not received_any:
260
- # Dead stream: fall back to non-streaming
261
  text = gradient_complete(model_id, full_prompt, max_tokens, temperature, top_p)
262
- chat_history[-1][1] = text
263
- yield chat_history, current_turn_count
264
- except Exception as e:
265
- # Explicit error surfaced to the user
266
- chat_history[-1][1] = f"⚠️ Inference failed: {e}"
267
- yield chat_history, current_turn_count
268
- return
269
-
270
- # Logging & periodic upload
271
- try:
272
- log_interaction_hf(user_message, chat_history[-1][1])
273
- except Exception as e:
274
- print(f"⚠️ log_interaction_hf failed: {e}")
275
-
276
- new_turn_count = (current_turn_count or 0) + 1
277
- if new_turn_count % UPLOAD_INTERVAL == 0:
278
- try:
279
- upload_log_to_hf(HF_TOKEN) # IMPORTANT: HF token, not DO
280
- except Exception as e:
281
- print(f"❌ Log upload failed: {e}")
282
-
283
- yield chat_history, new_turn_count
284
-
285
 
286
- # 2) Stream
287
- try:
288
- for token in gradient_stream(model_id, full_prompt, max_tokens, temperature, top_p):
289
- chat_history[-1][1] += token
290
- yield chat_history, current_turn_count
291
  except Exception as e:
292
- chat_history[-1][1] = f"⚠️ Inference failed: {e}"
293
- yield chat_history, current_turn_count
294
  return
295
 
296
- # 3) Logging & periodic upload
297
  try:
298
  log_interaction_hf(user_message, chat_history[-1][1])
299
  except Exception as e:
@@ -302,8 +306,7 @@ with gr.Blocks(title="Gradient AI Chat") as demo:
302
  new_turn_count = (current_turn_count or 0) + 1
303
  if new_turn_count % UPLOAD_INTERVAL == 0:
304
  try:
305
- # IMPORTANT: pass HF token, not DO token
306
- upload_log_to_hf(HF_TOKEN)
307
  except Exception as e:
308
  print(f"❌ Log upload failed: {e}")
309
 
 
5
  import json
6
  import requests
7
  import gradio as gr
 
 
8
 
9
+ # ========= Helpers & Context =========
10
+ # Ensure your local utils module exposes: session_id, retrieve_context, log_interaction_hf, upload_log_to_hf
11
  import utils.helpers as helpers
12
  from utils.helpers import retrieve_context, log_interaction_hf, upload_log_to_hf
13
 
 
15
  with open("config.json") as f:
16
  config = json.load(f)
17
 
18
+ DO_API_KEY = config["do_token"] # DigitalOcean Model Access Key (serverless inference)
19
+ HF_TOKEN = "hf_" + config["token"] # Hugging Face token for dataset uploads
 
20
 
21
+ # Stable session id for the whole app lifetime so logs land under a unique folder
22
  session_id = f"{int(time.time())}-{uuid.uuid4().hex[:8]}"
23
+ helpers.session_id = session_id # used by your upload_log_to_hf implementation
24
 
25
  BASE_URL = "https://inference.do-ai.run/v1"
26
+ UPLOAD_INTERVAL = 5 # upload logs to HF every N turns
27
+ REQUEST_TIMEOUT = 60
28
+ STREAM_TIMEOUT = 120
29
 
30
+ # ========= Network Utils =========
31
  def _auth_headers():
32
+ return {
33
+ "Authorization": f"Bearer {DO_API_KEY}",
34
+ "Content-Type": "application/json",
35
+ "Accept": "application/json",
36
+ }
37
 
38
  def list_models():
39
+ """
40
+ Fetch live model IDs from DO; fall back to a deterministic default on failure.
41
+ Always return a non-empty list.
42
+ """
43
  try:
44
+ resp = requests.get(f"{BASE_URL}/models", headers=_auth_headers(), timeout=REQUEST_TIMEOUT)
45
+ resp.raise_for_status()
46
+ data = resp.json().get("data", [])
47
+ ids = [m.get("id") for m in data if m.get("id")]
48
  if ids:
49
  return ids
50
  except Exception as e:
51
  print(f"⚠️ list_models failed: {e}")
52
+ # Deterministic fallback
53
  return ["llama3.3-70b-instruct"]
54
 
55
+ def _normalize_model_id(model_id: str | None) -> str:
56
+ if model_id:
57
+ return model_id
58
+ return list_models()[0]
59
+
60
+ # ========= Inference (non-stream + stream) =========
61
  def gradient_request(model_id, prompt, max_tokens=512, temperature=0.7, top_p=0.95):
62
+ """
63
+ Non-streaming completion (used by lightweight tasks like intent detection).
64
+ Self-heals if model_id is not found by retrying with the first available model.
65
+ """
66
  url = f"{BASE_URL}/chat/completions"
 
 
67
  payload = {
68
+ "model": _normalize_model_id(model_id),
69
  "messages": [{"role": "user", "content": prompt}],
70
  "max_tokens": max_tokens,
71
  "temperature": temperature,
 
73
  }
74
  for attempt in range(3):
75
  try:
76
+ resp = requests.post(url, headers=_auth_headers(), json=payload, timeout=REQUEST_TIMEOUT)
 
77
  if resp.status_code == 404:
78
+ # Model not found → pick first available model and retry once
79
  ids = list_models()
80
+ if ids and payload["model"] not in ids:
81
  payload["model"] = ids[0]
82
  continue
83
  resp.raise_for_status()
84
  j = resp.json()
85
  return j["choices"][0]["message"]["content"].strip()
86
  except requests.HTTPError as e:
87
+ body = getattr(e.response, "text", str(e))
88
+ raise RuntimeError(f"Inference error ({e.response.status_code}): {body}") from e
89
  except requests.RequestException as e:
90
  if attempt == 2:
91
  raise
92
+ time.sleep(0.5)
93
  raise RuntimeError("Exhausted retries")
94
 
 
95
  def gradient_stream(model_id, prompt, max_tokens=512, temperature=0.7, top_p=0.95):
96
+ """
97
+ Streaming generator yielding content chunks.
98
+ Emits keepalives if the server is quiet for >3s.
99
+ """
100
  url = f"{BASE_URL}/chat/completions"
 
 
101
  payload = {
102
+ "model": _normalize_model_id(model_id),
103
  "messages": [{"role": "user", "content": prompt}],
104
  "max_tokens": max_tokens,
105
  "temperature": temperature,
 
107
  "stream": True,
108
  }
109
 
 
 
 
110
  try:
111
+ with requests.post(url, headers=_auth_headers(), json=payload, stream=True, timeout=STREAM_TIMEOUT) as r:
112
  if r.status_code != 200:
 
113
  try:
114
  err_txt = r.text
115
  except Exception:
 
118
 
119
  last_token_ts = time.time()
120
  for raw in r.iter_lines(decode_unicode=True):
121
+ if raw is None or raw == b"" or raw == "":
 
122
  if time.time() - last_token_ts > 3:
123
  last_token_ts = time.time()
124
+ yield "" # visual keepalive (no-op for UI)
125
  continue
126
  if not raw.startswith("data: "):
127
  continue
 
138
  except Exception:
139
  continue
140
  except Exception as e:
 
141
  raise
142
 
143
  def gradient_complete(model_id, prompt, max_tokens=512, temperature=0.7, top_p=0.95):
144
  url = f"{BASE_URL}/chat/completions"
145
  payload = {
146
+ "model": _normalize_model_id(model_id),
147
  "messages": [{"role": "user", "content": prompt}],
148
  "max_tokens": max_tokens,
149
  "temperature": temperature,
150
  "top_p": top_p,
151
  }
152
+ r = requests.post(url, headers=_auth_headers(), json=payload, timeout=REQUEST_TIMEOUT)
153
  if r.status_code != 200:
154
  raise RuntimeError(f"HTTP {r.status_code}: {r.text}")
155
  j = r.json()
156
  return j["choices"][0]["message"]["content"].strip()
157
 
 
158
  # ========= Lightweight Intent Detection =========
159
  def detect_intent(model_id, message: str) -> str:
160
+ """
161
+ Classify as 'small_talk' or 'info_query'.
162
+ Fail-open to 'info_query' on any issue.
163
+ """
164
  try:
165
  out = gradient_request(
166
  model_id,
 
174
  print(f"⚠️ detect_intent failed: {e}")
175
  return "info_query"
176
 
177
+ # ========= Gradio App =========
 
178
  with gr.Blocks(title="Gradient AI Chat") as demo:
 
179
  turn_counter = gr.State(0)
180
 
181
  gr.Markdown("## Gradient AI Chat")
182
  gr.Markdown("Select a model and ask your question.")
183
 
 
184
  with gr.Row():
185
  model_drop = gr.Dropdown(choices=[], label="Select Model")
186
  system_msg = gr.Textbox(
187
+ value="You are a faithful assistant. Prefer provided context, but answer helpfully if none is available.",
188
  label="System message"
189
  )
190
 
191
  with gr.Row():
192
  max_tokens_slider = gr.Slider(minimum=1, maximum=4096, value=512, step=1, label="Max new tokens")
193
  temperature_slider = gr.Slider(minimum=0.0, maximum=2.0, value=0.7, step=0.1, label="Temperature")
194
+ top_p_slider = gr.Slider(minimum=0.1, maximum=1.0, value=0.95, step=0.05, label="Topp")
195
 
196
+ # IMPORTANT: tuples mode we must pass and replace tuples, not mutate them
197
  chatbot = gr.Chatbot(height=500, type="tuples")
198
  msg = gr.Textbox(label="Your message")
199
 
 
213
  # --- Load models into dropdown at startup
214
  def load_models():
215
  ids = list_models()
216
+ # value must be in choices; guarantee both
217
+ return gr.Dropdown.update(choices=ids, value=ids[0])
 
218
 
219
  demo.load(load_models, outputs=[model_drop])
220
 
 
229
 
230
  # --- Event handlers
231
  def user(user_message, chat_history):
232
+ chat_history = chat_history or []
233
+ # Append a tuple and return
234
+ chat_history = list(chat_history) + [(user_message, "")]
235
+ return "", chat_history
236
 
237
  def bot(chat_history, current_turn_count, model_id, system_message, max_tokens, temperature, top_p):
238
+ """
239
+ Single, clean streaming pass. Replace tuples; never mutate in place.
240
+ """
241
+ if not chat_history:
242
+ # Shouldn't happen, but stay defensive
243
+ yield chat_history, (current_turn_count or 0)
244
+ return
245
+
246
  user_message = chat_history[-1][0]
247
 
248
+ # Intent (optional; keeps your original flow)
249
  intent = detect_intent(model_id, user_message)
250
+
251
+ # Build prompt with a safe fallback when RAG returns nothing
252
+ context = ""
253
+ if intent != "small_talk":
254
  try:
255
+ context = retrieve_context(user_message, p=5, threshold=0.5) or ""
256
  except Exception as e:
257
  print(f"⚠️ retrieve_context failed: {e}")
258
  context = ""
 
 
 
 
 
259
 
260
+ if intent == "small_talk":
261
+ full_prompt = f"[System]: Friendly chat.\n[User]: {user_message}\n[Assistant]: "
262
+ else:
263
+ if context.strip():
264
+ full_prompt = (
265
+ f"[System]: {system_message}\n"
266
+ "Use the provided context verbatim; if context is insufficient, answer directly.\n\n"
267
+ f"Context:\n{context}\n\nQuestion: {user_message}\n"
268
+ )
269
+ else:
270
+ # No context → do not block the model
271
+ full_prompt = f"[System]: {system_message}\nQuestion: {user_message}\n"
272
+
273
+ # Seed assistant bubble (replace tuple, don’t mutate)
274
+ chat_history = list(chat_history)
275
+ chat_history[-1] = (chat_history[-1][0], "")
276
+ yield chat_history, (current_turn_count or 0)
277
 
278
  # Stream with fallback
279
  try:
280
  received_any = False
281
+ buffer = ""
282
+
283
  for token in gradient_stream(model_id, full_prompt, max_tokens, temperature, top_p):
284
  if token:
285
  received_any = True
286
+ buffer += token
287
+ chat_history[-1] = (chat_history[-1][0], buffer)
288
+ yield chat_history, (current_turn_count or 0)
289
+
290
  if not received_any:
 
291
  text = gradient_complete(model_id, full_prompt, max_tokens, temperature, top_p)
292
+ chat_history[-1] = (chat_history[-1][0], text)
293
+ yield chat_history, (current_turn_count or 0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
 
 
 
 
 
 
295
  except Exception as e:
296
+ chat_history[-1] = (chat_history[-1][0], f"⚠️ Inference failed: {e}")
297
+ yield chat_history, (current_turn_count or 0)
298
  return
299
 
300
+ # Logging & periodic upload (once per turn)
301
  try:
302
  log_interaction_hf(user_message, chat_history[-1][1])
303
  except Exception as e:
 
306
  new_turn_count = (current_turn_count or 0) + 1
307
  if new_turn_count % UPLOAD_INTERVAL == 0:
308
  try:
309
+ upload_log_to_hf(HF_TOKEN) # IMPORTANT: HF token, not DO
 
310
  except Exception as e:
311
  print(f"❌ Log upload failed: {e}")
312