aciang commited on
Commit
2f5b4b9
·
verified ·
1 Parent(s): 7fa4429

Patch app.py: fix messages format + YAML + queue

Browse files
Files changed (1) hide show
  1. app.py +47 -76
app.py CHANGED
@@ -2,11 +2,11 @@
2
  import os, torch, gradio as gr
3
  from transformers import AutoTokenizer, AutoModelForCausalLM
4
 
5
- TITLE = os.getenv("SPACE_TITLE", "LanguageBridge — Multimodal Chatbot (Mistral-7B)")
6
- MODEL_ID = os.getenv("MODEL_ID", "aciang/mistral7b-tk-sft-20251019-merged")
 
7
  SYSTEM_PROMPT = os.getenv("SYSTEM_PROMPT",
8
- "你是語言橋助教,回答需:條理清晰、步驟簡潔、可連結部落知識與科學方法;必要時列點。"
9
- )
10
 
11
  USE_IMAGE = os.getenv("USE_IMAGE", "0") in ("1","true","True")
12
  USE_AUDIO = os.getenv("USE_AUDIO", "0") in ("1","true","True")
@@ -14,99 +14,70 @@ USE_AUDIO = os.getenv("USE_AUDIO", "0") in ("1","true","True")
14
  def load_llm():
15
  dtype = torch.float16 if torch.cuda.is_available() else torch.float32
16
  try:
17
- model = AutoModelForCausalLM.from_pretrained(
18
- MODEL_ID, torch_dtype=dtype, device_map="auto"
19
- )
20
  except Exception as e:
21
- print(f"[Fallback CPU] {e}")
22
- model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=torch.float32)
23
  tok = AutoTokenizer.from_pretrained(MODEL_ID, use_fast=True)
24
- if tok.pad_token is None:
25
- tok.pad_token = tok.eos_token
26
  tok.padding_side = "left"
27
  return tok, model
28
 
29
- tokenizer, llm = load_llm(); llm.eval()
30
-
31
- def norm_messages(msgs):
32
- """保證為 [{'role': 'user'|'assistant', 'content': '文字'}]"""
33
- out = []
34
- if not msgs:
35
- return out
36
- for m in msgs:
37
- if isinstance(m, dict) and 'role' in m and 'content' in m:
38
- # Gradio 可能把 content 做成 {'text':...} 或 list;這裡統一成 str
39
- c = m['content']
40
- if isinstance(c, dict) and 'text' in c:
41
- c = c['text']
42
- elif isinstance(c, list) and len(c)>0:
43
- first = c[0]
44
- c = first.get('text', str(first))
45
- out.append({'role': m['role'], 'content': str(c)})
46
- elif isinstance(m, tuple) and len(m)==2:
47
- out.append({'role': m[0], 'content': str(m[1])})
48
- else:
49
- # 無法辨識就丟棄
50
- continue
51
- return out
52
-
53
- def build_prompt(messages):
54
- """把歷史訊息組成簡潔 prompt"""
55
- msgs = norm_messages(messages)
56
- parts = [SYSTEM_PROMPT, ""]
57
- for m in msgs:
58
- who = "使用者" if m['role']=="user" else "助教"
59
- parts.append(f"{who}:{m['content']}")
60
- parts.append("助教:")
61
- return "\n".join(parts)
62
 
63
  @torch.inference_mode()
64
- def respond(history, user_text, mx, temp, top_p):
65
- # history 為 messages 格式
66
- history = norm_messages(history)
67
- if user_text and user_text.strip():
68
- history.append({'role':'user', 'content': user_text.strip()})
69
- prompt = build_prompt(history)
 
 
 
70
  inputs = tokenizer(prompt, return_tensors="pt").to(llm.device)
71
  out = llm.generate(
72
  **inputs,
73
- max_new_tokens=int(mx),
74
- temperature=float(temp),
75
  top_p=float(top_p),
76
- do_sample=True if float(temp) > 0 else False,
77
  eos_token_id=tokenizer.eos_token_id,
78
- pad_token_id=tokenizer.pad_token_id
79
  )
80
  text = tokenizer.decode(out[0], skip_special_tokens=True)
81
- if "助教:" in text:
82
- text = text.split("助教:",1)[-1].strip()
83
- # 回填 assistant 訊息
84
- history.append({'role':'assistant', 'content': text})
85
- # gr.Chatbot(type="messages") 需要 messages(而不是 HTML)
86
- # 清空輸入框
87
- return history, ""
88
 
89
  with gr.Blocks(title=TITLE) as demo:
90
- gr.Markdown(f"## {TITLE}\n- 模型:`{MODEL_ID}`\n- 介面已修正為 Gradio v4 messages 格式")
91
 
92
- chat = gr.Chatbot(label="Chatbot", type="messages", height=480)
93
  with gr.Row():
94
- user_txt = gr.Textbox(label="你的問題 / 指令", placeholder="請輸入文字…", scale=4)
95
- send = gr.Button("送出 🚀", variant="primary", scale=1)
96
- clear = gr.Button("清除", scale=1)
 
 
 
 
 
97
 
98
- with gr.Row():
99
- mx = gr.Slider(64, 1024, value=512, step=32, label="max_new_tokens")
100
- temp = gr.Slider(0.0, 1.0, value=0.4, step=0.05, label="temperature")
101
- top = gr.Slider(0.5, 1.0, value=0.95, step=0.01, label="top_p")
 
 
 
 
 
102
 
103
- send.click(respond, inputs=[chat, user_txt, mx, temp, top], outputs=[chat, user_txt])
104
- clear.click(lambda: ([], ""), outputs=[chat, user_txt])
105
 
106
- # 不要傳入不支援的 queue 參數(concurrency_count 等)
107
- demo.queue()
108
- # 在 Space 執行時由服務端啟動,這裡不呼叫 launch()
109
 
110
  if __name__ == "__main__":
111
- # 在本機測試才會啟動;Space 端由 HF 自行啟動
112
  demo.launch(share=False, server_name="0.0.0.0", server_port=7860, show_error=True)
 
2
  import os, torch, gradio as gr
3
  from transformers import AutoTokenizer, AutoModelForCausalLM
4
 
5
+ TITLE = os.getenv("SPACE_TITLE", "LanguageBridge — Multimodal Chatbot (Mistral-7B)")
6
+ MODEL_ID = os.getenv("MODEL_ID", "aciang/mistral7b-tk-sft-20251019-merged")
7
+
8
  SYSTEM_PROMPT = os.getenv("SYSTEM_PROMPT",
9
+ "你是語言橋助教,回答務必:條列、準確、可引用部落知識與科學方法;必要時提供清楚步驟。")
 
10
 
11
  USE_IMAGE = os.getenv("USE_IMAGE", "0") in ("1","true","True")
12
  USE_AUDIO = os.getenv("USE_AUDIO", "0") in ("1","true","True")
 
14
  def load_llm():
15
  dtype = torch.float16 if torch.cuda.is_available() else torch.float32
16
  try:
17
+ model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=dtype, device_map="auto")
 
 
18
  except Exception as e:
19
+ print(f"[LLM fallback->CPU fp32] {e}")
20
+ model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=torch.float32, device_map=None)
21
  tok = AutoTokenizer.from_pretrained(MODEL_ID, use_fast=True)
22
+ if tok.pad_token is None: tok.pad_token = tok.eos_token
 
23
  tok.padding_side = "left"
24
  return tok, model
25
 
26
+ tokenizer, llm = load_llm()
27
+ llm.eval()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
  @torch.inference_mode()
30
+ def generate_reply(history, image, audio, max_new_tokens, temperature, top_p):
31
+ # history 需為 list[ (role:str, {"content": str}) ]
32
+ user_text = ""
33
+ if history:
34
+ for role, msg in history[::-1]:
35
+ if role == "user":
36
+ user_text = msg.get("content","") if isinstance(msg, dict) else str(msg)
37
+ break
38
+ prompt = f"{SYSTEM_PROMPT}\n\n使用者:{user_text}\n助教:"
39
  inputs = tokenizer(prompt, return_tensors="pt").to(llm.device)
40
  out = llm.generate(
41
  **inputs,
42
+ max_new_tokens=int(max_new_tokens),
43
+ temperature=float(temperature),
44
  top_p=float(top_p),
45
+ do_sample=True,
46
  eos_token_id=tokenizer.eos_token_id,
47
+ pad_token_id=tokenizer.pad_token_id,
48
  )
49
  text = tokenizer.decode(out[0], skip_special_tokens=True)
50
+ if "助教:" in text: text = text.split("助教:",1)[-1].strip()
51
+ return text
 
 
 
 
 
52
 
53
  with gr.Blocks(title=TITLE) as demo:
54
+ gr.Markdown(f"## {TITLE}\n模型:`{MODEL_ID}`(messages 格式已修正)")
55
 
 
56
  with gr.Row():
57
+ chat = gr.Chatbot(height=460, type="messages", show_copy_button=True)
58
+ with gr.Column(scale=0):
59
+ user_txt = gr.Textbox(label="你的問題 / 指令", placeholder="請輸入文字…", interactive=True)
60
+ mx = gr.Slider(64, 1024, value=256, step=32, label="max_new_tokens")
61
+ tp = gr.Slider(0.0, 1.0, value=0.2, step=0.05, label="temperature")
62
+ top = gr.Slider(0.5, 1.0, value=0.95, step=0.01, label="top_p")
63
+ go = gr.Button("送出 🚀", variant="primary")
64
+ clr = gr.Button("清除")
65
 
66
+ def respond(history, text, mx, tp, top):
67
+ history = history or []
68
+ history.append(("user", {"content": text}))
69
+ try:
70
+ ans = generate_reply(history, None, None, mx, tp, top)
71
+ except Exception as e:
72
+ ans = f"(推理失敗:{e})"
73
+ history.append(("assistant", {"content": ans}))
74
+ return history, ""
75
 
76
+ go.click(respond, inputs=[chat, user_txt, mx, tp, top], outputs=[chat, user_txt])
77
+ clr.click(lambda: ([], ""), outputs=[chat, user_txt])
78
 
79
+ # 重要:不要使用舊參數 concurrency_count(會 Runtime error)
80
+ demo.queue(max_size=32, api_open=False)
 
81
 
82
  if __name__ == "__main__":
 
83
  demo.launch(share=False, server_name="0.0.0.0", server_port=7860, show_error=True)