DeepLearning101 commited on
Commit
03f0180
·
verified ·
1 Parent(s): 548611a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -44
app.py CHANGED
@@ -15,7 +15,6 @@ Jev-style 判別式決策互動 POC(繁體中文)
15
  import json
16
  import os
17
  import subprocess
18
- import sys
19
  import threading
20
  import time
21
  import urllib.request
@@ -35,17 +34,71 @@ API = "http://127.0.0.1:8017/v1/systemone"
35
  _server = None
36
  _lock = threading.Lock()
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  def _dl(url, dst):
 
 
40
  if os.path.exists(dst):
41
  return dst
42
- print(f"[poc] 下載 {url}", flush=True)
43
  tmp = dst + ".part"
44
- urllib.request.urlretrieve(url, tmp)
 
 
 
 
 
 
 
 
 
 
 
45
  os.rename(tmp, dst)
 
 
46
  return dst
47
 
48
 
 
 
 
 
 
 
 
 
 
 
 
49
  def ensure_backend():
50
  """下載、解壓、啟動 jevos(冪等,可重複呼叫)。"""
51
  global _server
@@ -58,45 +111,45 @@ def ensure_backend():
58
  def _ensure_backend_locked():
59
  global _server
60
  # 先看是不是已經有 server 在跑(例如本機手動啟動的)→ 有就直接用,不下載
61
- try:
62
- requests.post(API, json={"model": "jev-latest", "state": "ping",
63
- "questions": {"q": {"type": "noul", "instructions": "ok?"}}},
64
- timeout=5)
65
- print("[poc] jevos 已在運行", flush=True)
66
  return
67
- except Exception:
68
- pass
69
- jev_dir = os.path.join(DATA, "jev")
70
- binary = os.path.join(jev_dir, "jev")
71
- model_dir = os.path.join(jev_dir, "model")
72
- if not (os.path.exists(binary) and os.path.isdir(model_dir)):
73
- os.makedirs(DATA, exist_ok=True)
74
- tgz = _dl(BIN_URL, os.path.join(DATA, "jev-linux-x64.tar.gz"))
75
- with tarfile.open(tgz) as tf:
76
- tf.extractall(DATA)
77
- zf = _dl(MODEL_URL, os.path.join(DATA, "jevos-v3-openvino-int8.zip"))
78
- with zipfile.ZipFile(zf) as z:
79
- z.extractall(jev_dir) # 內含 model/
80
- os.chmod(binary, 0o755)
81
- print("[poc] 啟動 jevos…", flush=True)
82
- _server = subprocess.Popen([binary, "serve"], cwd=jev_dir,
83
- stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
84
- for _ in range(60):
85
- time.sleep(1)
86
- try:
87
- requests.post(API, json={"model": "jev-latest", "state": "ping",
88
- "questions": {"q": {"type": "noul", "instructions": "ok?"}}},
89
- timeout=5)
90
- print("[poc] jevos 就緒", flush=True)
91
- return
92
- except Exception:
93
- continue
94
- raise RuntimeError("jevos 啟動逾時")
95
 
96
 
97
  def decide(state, mode, question, options_text):
98
  if not (state or "").strip() or not (question or "").strip():
99
- return "請先輸入狀態文字與問題", "", ""
 
 
 
 
100
  ensure_backend()
101
  t0 = time.perf_counter()
102
  if mode == "是非題":
@@ -118,7 +171,7 @@ def decide(state, mode, question, options_text):
118
  desc = desc.strip() or name # 無描述時用名稱代替(choice 需要 criteria 描述)
119
  criteria[name] = desc
120
  if not criteria:
121
- return "請至少輸入一個候選選項", "", ""
122
  payload = {"model": "jev-latest", "state": state,
123
  "questions": {"q1": {"type": "choice", "instructions": question,
124
  "criteria": criteria}}}
@@ -127,12 +180,12 @@ def decide(state, mode, question, options_text):
127
  r.raise_for_status()
128
  data = r.json()
129
  except Exception as e:
130
- return f"後端呼叫失敗:{type(e).__name__}({e})", "", ""
131
  ms = (time.perf_counter() - t0) * 1000
132
  try:
133
  ans = data["answers"]["q1"]
134
  except (KeyError, TypeError):
135
- return f"後端回傳格式異常:{str(data)[:300]}", "", ""
136
  usage = data.get("usage", {})
137
  if ans["type"] == "noul":
138
  p = ans["noul"]
@@ -146,7 +199,13 @@ def decide(state, mode, question, options_text):
146
  f"各選項機率:{json.dumps(probs, ensure_ascii=False)}")
147
  meta = (f"延遲 {ms:.0f} ms | input_tokens={usage.get('input_tokens','?')} | "
148
  f"output_tokens={usage.get('output_tokens','?')}(零解碼)")
149
- return summary, bars, meta
 
 
 
 
 
 
150
 
151
 
152
  PRESETS = {
@@ -173,9 +232,12 @@ CSS = """
173
  with gr.Blocks(title="Jev-style 判別式決策 POC") as demo:
174
  gr.Markdown("# ⚡ Jev-style 判別式決策 POC\n"
175
  "輸入一段**狀態文字**+**問題**,模型直接輸出**機率分佈**——不生成任何文字(`output_tokens=0`)。"
176
- "這就是 System 1 判別引擎:傳統 LLM Agent 每步 1–3 秒還可能寫壞 JSON,這裡一次前向傳播就給出完整機率分佈。")
 
 
177
  with gr.Row():
178
  preset = gr.Dropdown(list(PRESETS.keys()), label="範例一鍵填入", value="客服訊息分類")
 
179
  state = gr.Textbox(label="狀態文字(state)", lines=3)
180
  mode = gr.Radio(["是非題", "多選題"], label="題型", value="多選題")
181
  question = gr.Textbox(label="問題(instructions)", lines=1)
@@ -186,12 +248,14 @@ with gr.Blocks(title="Jev-style 判別式決策 POC") as demo:
186
  out_meta = gr.Textbox(label="效能", lines=1)
187
 
188
  preset.change(load_preset, preset, [state, mode, question, options])
 
189
  btn.click(decide, [state, mode, question, options],
190
- [out_summary, out_bars, out_meta])
191
  demo.load(load_preset, preset, [state, mode, question, options])
 
192
 
193
  if __name__ == "__main__":
194
  # Spaces 啟動時先備好後端(背景執行,不擋 UI)
195
- threading.Thread(target=ensure_backend, daemon=True).start()
196
  demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)),
197
  css=CSS)
 
15
  import json
16
  import os
17
  import subprocess
 
18
  import threading
19
  import time
20
  import urllib.request
 
34
  _server = None
35
  _lock = threading.Lock()
36
 
37
+ # 後端狀態(給 UI 顯示用,避免首次下載時使用者以為當機)
38
+ _progress = {"phase": "idle", "pct": 0, "msg": ""}
39
+ _last_pct_shown = -1
40
+
41
+
42
+ def _set_phase(phase, msg="", pct=None):
43
+ global _last_pct_shown
44
+ _progress["phase"] = phase
45
+ _progress["msg"] = msg
46
+ if pct is not None:
47
+ _progress["pct"] = pct
48
+ print(f"[poc] {msg or phase}", flush=True)
49
+
50
+
51
+ def status_text():
52
+ ph = _progress["phase"]
53
+ if ph == "ready":
54
+ return "✅ 後端就緒(jevos 運行中)"
55
+ if ph == "downloading":
56
+ return f"⏳ 下載模型中 {_progress['pct']}%(首次啟動需下載約 660MB,請稍候)"
57
+ if ph == "extracting":
58
+ return "⏳ 解壓縮模型中…"
59
+ if ph == "starting":
60
+ return "⏳ 啟動 jevos 中(載入模型約需 10 秒)…"
61
+ if ph == "error":
62
+ return f"❌ 後端錯誤:{_progress['msg']}"
63
+ return "⏳ 後端準備中…"
64
+
65
 
66
  def _dl(url, dst):
67
+ """下載檔案,顯示進度百分比。"""
68
+ global _last_pct_shown
69
  if os.path.exists(dst):
70
  return dst
71
+ _set_phase("downloading", f"開始下載 {url.split('/')[-1]}")
72
  tmp = dst + ".part"
73
+ _last_pct_shown = -1
74
+
75
+ def hook(blocks, block_size, total):
76
+ global _last_pct_shown
77
+ if total and total > 0:
78
+ pct = min(100, blocks * block_size * 100 // total)
79
+ _progress["pct"] = pct
80
+ if pct // 10 != _last_pct_shown // 10:
81
+ _last_pct_shown = pct
82
+ print(f"[poc] 下載進度 {pct}%", flush=True)
83
+
84
+ urllib.request.urlretrieve(url, tmp, reporthook=hook)
85
  os.rename(tmp, dst)
86
+ _progress["pct"] = 100
87
+ print("[poc] 下載完成", flush=True)
88
  return dst
89
 
90
 
91
+ def backend_ready():
92
+ """不阻塞地檢查 jevos 是否已在服務。"""
93
+ try:
94
+ requests.post(API, json={"model": "jev-latest", "state": "ping",
95
+ "questions": {"q": {"type": "noul", "instructions": "ok?"}}},
96
+ timeout=3)
97
+ return True
98
+ except Exception:
99
+ return False
100
+
101
+
102
  def ensure_backend():
103
  """下載、解壓、啟動 jevos(冪等,可重複呼叫)。"""
104
  global _server
 
111
  def _ensure_backend_locked():
112
  global _server
113
  # 先看是不是已經有 server 在跑(例如本機手動啟動的)→ 有就直接用,不下載
114
+ if backend_ready():
115
+ _set_phase("ready", "jevos 已在運行")
 
 
 
116
  return
117
+ try:
118
+ jev_dir = os.path.join(DATA, "jev")
119
+ binary = os.path.join(jev_dir, "jev")
120
+ model_dir = os.path.join(jev_dir, "model")
121
+ if not (os.path.exists(binary) and os.path.isdir(model_dir)):
122
+ os.makedirs(DATA, exist_ok=True)
123
+ tgz = _dl(BIN_URL, os.path.join(DATA, "jev-linux-x64.tar.gz"))
124
+ _set_phase("extracting", "解壓縮 jevos 主程式…")
125
+ with tarfile.open(tgz) as tf:
126
+ tf.extractall(DATA)
127
+ zf = _dl(MODEL_URL, os.path.join(DATA, "jevos-v3-openvino-int8.zip"))
128
+ _set_phase("extracting", "解壓縮模型(約 630MB)…")
129
+ with zipfile.ZipFile(zf) as z:
130
+ z.extractall(jev_dir) # 內含 model/
131
+ os.chmod(binary, 0o755)
132
+ _set_phase("starting", "啟動 jevos…")
133
+ _server = subprocess.Popen([binary, "serve"], cwd=jev_dir,
134
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
135
+ for _ in range(90):
136
+ time.sleep(1)
137
+ if backend_ready():
138
+ _set_phase("ready", "jevos 就緒")
139
+ return
140
+ raise RuntimeError("jevos 啟動逾時(90 秒)")
141
+ except Exception as e:
142
+ _set_phase("error", f"{type(e).__name__}: {e}")
143
+ raise
 
144
 
145
 
146
  def decide(state, mode, question, options_text):
147
  if not (state or "").strip() or not (question or "").strip():
148
+ return "請先輸入狀態文字與問題", "", "", status_text()
149
+ # 後端還沒好就不要卡住:直接告訴使用者目前進度
150
+ if not backend_ready():
151
+ ensure_backend_if_needed()
152
+ return status_text() + ",請稍後再按一次「送出判別」。", "", "", status_text()
153
  ensure_backend()
154
  t0 = time.perf_counter()
155
  if mode == "是非題":
 
171
  desc = desc.strip() or name # 無描述時用名稱代替(choice 需要 criteria 描述)
172
  criteria[name] = desc
173
  if not criteria:
174
+ return "請至少輸入一個候選選項", "", "", status_text()
175
  payload = {"model": "jev-latest", "state": state,
176
  "questions": {"q1": {"type": "choice", "instructions": question,
177
  "criteria": criteria}}}
 
180
  r.raise_for_status()
181
  data = r.json()
182
  except Exception as e:
183
+ return f"後端呼叫失敗:{type(e).__name__}({e})", "", "", status_text()
184
  ms = (time.perf_counter() - t0) * 1000
185
  try:
186
  ans = data["answers"]["q1"]
187
  except (KeyError, TypeError):
188
+ return f"後端回傳格式異常:{str(data)[:300]}", "", "", status_text()
189
  usage = data.get("usage", {})
190
  if ans["type"] == "noul":
191
  p = ans["noul"]
 
199
  f"各選項機率:{json.dumps(probs, ensure_ascii=False)}")
200
  meta = (f"延遲 {ms:.0f} ms | input_tokens={usage.get('input_tokens','?')} | "
201
  f"output_tokens={usage.get('output_tokens','?')}(零解碼)")
202
+ return summary, bars, meta, status_text()
203
+
204
+
205
+ def ensure_backend_if_needed():
206
+ """確保背景下載執行緒有在跑(不阻塞)。"""
207
+ t = threading.Thread(target=ensure_backend, daemon=True)
208
+ t.start()
209
 
210
 
211
  PRESETS = {
 
232
  with gr.Blocks(title="Jev-style 判別式決策 POC") as demo:
233
  gr.Markdown("# ⚡ Jev-style 判別式決策 POC\n"
234
  "輸入一段**狀態文字**+**問題**,模型直接輸出**機率分佈**——不生成任何文字(`output_tokens=0`)。"
235
+ "這就是 System 1 判別引擎:傳統 LLM Agent 每步 1–3 秒還可能寫壞 JSON,這裡一次前向傳播就給出完整機率分佈。\n\n"
236
+ "> ⏰ **首次啟動**需要下載模型(約 660MB),請看下方「後端狀態」等它就緒再按送出,之後啟動都是秒開。")
237
+ status_box = gr.Textbox(label="後端狀態", value="⏳ 後端準備中…", interactive=False)
238
  with gr.Row():
239
  preset = gr.Dropdown(list(PRESETS.keys()), label="範例一鍵填入", value="客服訊息分類")
240
+ refresh_btn = gr.Button("重新整理狀態", size="sm")
241
  state = gr.Textbox(label="狀態文字(state)", lines=3)
242
  mode = gr.Radio(["是非題", "多選題"], label="題型", value="多選題")
243
  question = gr.Textbox(label="問題(instructions)", lines=1)
 
248
  out_meta = gr.Textbox(label="效能", lines=1)
249
 
250
  preset.change(load_preset, preset, [state, mode, question, options])
251
+ refresh_btn.click(lambda: status_text(), outputs=status_box)
252
  btn.click(decide, [state, mode, question, options],
253
+ [out_summary, out_bars, out_meta, status_box])
254
  demo.load(load_preset, preset, [state, mode, question, options])
255
+ demo.load(lambda: status_text(), outputs=status_box)
256
 
257
  if __name__ == "__main__":
258
  # Spaces 啟動時先備好後端(背景執行,不擋 UI)
259
+ ensure_backend_if_needed()
260
  demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)),
261
  css=CSS)