lianghsun commited on
Commit
6121bfa
·
1 Parent(s): 760a2df

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +143 -72
app.py CHANGED
@@ -33,61 +33,58 @@ uploaded_at = datetime.now(tz_utc8).isoformat()
33
  tab_jsonl, tab_pdf = st.tabs(["對話資料 (.jsonl)", "預訓練 PDF"])
34
 
35
 
 
36
  # ---------- Tab 1: JSONL ----------
37
  with tab_jsonl:
38
  st.subheader("上傳對話資料")
39
 
40
- sample_prompt = """你現在是一個資料標註助手,請幫我產生一組適合用來微調聊天模型的對話資料,輸出格式必須是 `.jsonl`。
 
41
 
42
  格式要求:
43
 
44
  - 每一行是一個獨立的 JSON 物件。
45
- - 每個 JSON 物件必須包含一個 `messages` 欄位。
46
- - `messages` 是一個陣列,元素為依照 OpenAI Chat API 格式的訊息物件:
47
- - `{"role": "system" | "user" | "assistant", "content": "文字內容"}`
48
- - `content` 一律使用純文字字串(不要使用多段 content / 不要使用 function_call)。
49
- - 不要輸出程式碼區塊標記 ```,只輸出純文字內容。
50
  - 不要在檔案中加入註解或說明文字,每一行只能是 JSON。
51
 
52
  範例(僅供格式參考):
53
 
54
- {"messages": [
55
  {"role": "system", "content": "你是一個友善的客服人員。"},
56
  {"role": "user", "content": "請問我要如何申請退貨?"},
57
- {"role": "assistant", "content": "您好,若您要申請退貨,請先登入會員中心,在「訂單管理」中選擇欲退貨的訂單,點選「申請退貨」,依指示填寫原因並送出。"}
58
- ]}
59
- {"messages": [
60
- {"role": "system", "content": "你是一個勞動法規諮詢助手。"},
61
- {"role": "user", "content": "加班費要怎麼算?"},
62
- {"role": "assistant", "content": "依據勞動基準法第24條,加班工資應依平日或休息日的不同,分別以正常工資的一又三分之一、二又三分之一等倍數計算。實務上請再確認公司內部規章。"}
63
- ]}
64
 
65
- 請依照以上規格輸出多行 `.jsonl` 對話資料。"""
 
66
 
67
  st.markdown("##### 請將以下的 prompt 貼到你的對話生成模型中,產生符合格式的對話資料:")
68
  st.code(sample_prompt, language="markdown")
69
 
70
- jsonl_file = st.file_uploader(
71
- "上傳對話資料 `.jsonl` 檔",
72
- type=["jsonl"],
73
- accept_multiple_files=False
 
74
  )
75
 
76
- jsonl_valid = False
77
- parsed_lines = []
78
-
79
- if jsonl_file is not None:
80
- st.markdown("#### 檔案檢查結果")
81
- content = jsonl_file.read().decode("utf-8")
82
- f = StringIO(content)
83
-
84
  errors = []
85
  allowed_roles = {"system", "user", "assistant"}
86
 
87
- for idx, line in enumerate(f, start=1):
88
  line = line.strip()
89
  if not line:
90
  continue
 
 
 
 
 
 
91
  try:
92
  obj = json.loads(line)
93
  except json.JSONDecodeError as e:
@@ -109,54 +106,128 @@ with tab_jsonl:
109
  if not isinstance(msg_content, str):
110
  errors.append(f"第 {idx} 行第 {m_idx+1} 則 content 需為字串。")
111
 
112
- parsed_lines.append(obj)
113
 
114
- if errors:
115
- st.error("格式檢查失敗,請修正後重新上傳:")
116
- for e in errors[:20]:
117
- st.write("- " + e)
118
- if len(errors) > 20:
119
- st.write(f"... 還有 {len(errors) - 20} 筆錯誤未顯示")
120
- else:
121
- jsonl_valid = True
122
- st.success(f"檢查通過!共 {len(parsed_lines)} 筆對話。")
123
- st.markdown("#### 範例預覽(前 2 筆)")
124
- for i, obj in enumerate(parsed_lines[:2], start=1):
125
- st.json(obj)
126
-
127
- # 上傳按鈕:會在送出前幫每一筆加上 metadata
128
- if st.button("上傳對話資料", disabled=not (jsonl_file and jsonl_valid or BACKEND_URL is None)):
129
- if BACKEND_URL is None:
130
- st.warning("尚未設定 BACKEND_URL,無法實際送出,請在 `st.secrets` 中配置。")
131
- else:
132
- with st.spinner("正在上傳對話資料並檢查,請稍候…"):
133
- # 準備 metadata(會附加在每一行 JSON 物件上)
134
- meta = {
135
- "uploaded_at": uploaded_at, # UTC+8 ISO 字串
136
- "contributor_email": contributor_email if contributor_email.strip() else None,
137
- "share_permission": bool(share_permission),
138
- }
139
-
140
- # 重新組一份帶 metadata 的 jsonl 內容
141
- enriched_lines = []
142
- for obj in parsed_lines:
143
- obj_with_meta = {
144
- **obj,
145
- "metadata": meta,
 
 
 
 
 
 
 
 
 
 
 
146
  }
147
- enriched_lines.append(json.dumps(obj_with_meta, ensure_ascii=False))
148
 
149
- payload = "\n".join(enriched_lines).encode("utf-8")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
- files = {"file": ("contrib.jsonl", payload, "application/jsonl")}
152
- try:
153
- resp = requests.post(f"{BACKEND_URL}/upload-jsonl", files=files)
154
- if resp.ok:
155
- st.success("已成功送交後端伺服器,等待後端進一步檢查與處理。")
156
- else:
157
- st.error(f"後端回傳錯誤:{resp.status_code} {resp.text}")
158
- except Exception as e:
159
- st.error(f"送出時發生錯誤:{e}")
 
 
 
 
 
 
 
160
 
161
 
162
  # ---------- Tab 2: PDF ----------
 
33
  tab_jsonl, tab_pdf = st.tabs(["對話資料 (.jsonl)", "預訓練 PDF"])
34
 
35
 
36
+ # ---------- Tab 1: JSONL ----------
37
  # ---------- Tab 1: JSONL ----------
38
  with tab_jsonl:
39
  st.subheader("上傳對話資料")
40
 
41
+ sample_prompt = """
42
+ 請將我們上述對話的內容(但不包含本問題),整理成 OpenAI Messages Format,輸出格式必須是 .jsonl。
43
 
44
  格式要求:
45
 
46
  - 每一行是一個獨立的 JSON 物件。
47
+ - 每個 JSON 物件必須包含一個 messages 欄位。
 
 
 
 
48
  - 不要在檔案中加入註解或說明文字,每一行只能是 JSON。
49
 
50
  範例(僅供格式參考):
51
 
52
+ [{"messages": [
53
  {"role": "system", "content": "你是一個友善的客服人員。"},
54
  {"role": "user", "content": "請問我要如何申請退貨?"},
55
+ {"role": "assistant", "content": "您好,若您要申請退貨,請先登入會員中心,在「訂單管理」中選擇欲退貨的訂單,點選「申請退貨」,依指示填寫原因並送出。"}, {"role": "user", "content": "..."},
56
+ {"role": "assistant", "content": "..."},]
 
 
 
 
 
57
 
58
+ 請依照以上規格輸出一筆 .jsonl 對話資料(保持為一列,好讓我可以方便貼上),並用 markdown 表示。
59
+ """
60
 
61
  st.markdown("##### 請將以下的 prompt 貼到你的對話生成模型中,產生符合格式的對話資料:")
62
  st.code(sample_prompt, language="markdown")
63
 
64
+ st.markdown("#### 選擇輸入方式")
65
+ input_mode = st.radio(
66
+ "選擇要如何提供 `.jsonl` 內容",
67
+ ["上傳檔案", "貼上文字"],
68
+ horizontal=True,
69
  )
70
 
71
+ # 共用的檢查函式:給「檔案模式」和「貼上模式」共用
72
+ def validate_jsonl_lines(lines):
73
+ """lines: list[str] (每一行一個 JSON) → 回傳 (parsed_objs, errors)"""
74
+ parsed = []
 
 
 
 
75
  errors = []
76
  allowed_roles = {"system", "user", "assistant"}
77
 
78
+ for idx, line in enumerate(lines, start=1):
79
  line = line.strip()
80
  if not line:
81
  continue
82
+ # 如果使用者是從 ChatGPT 貼出來的,有可能含 ``` 之類的標記,先跳過
83
+ if line.startswith("```") and line.endswith("```"):
84
+ continue
85
+ if line.startswith("```") or line == "```":
86
+ continue
87
+
88
  try:
89
  obj = json.loads(line)
90
  except json.JSONDecodeError as e:
 
106
  if not isinstance(msg_content, str):
107
  errors.append(f"第 {idx} 行第 {m_idx+1} 則 content 需為字串。")
108
 
109
+ parsed.append(obj)
110
 
111
+ return parsed, errors
112
+
113
+ # ---------- 模式 A:上傳檔案 ----------
114
+ if input_mode == "上傳檔案":
115
+ jsonl_file = st.file_uploader(
116
+ "上傳對話資料 `.jsonl` ",
117
+ type=["jsonl"],
118
+ accept_multiple_files=False
119
+ )
120
+
121
+ file_jsonl_valid = False
122
+ file_parsed_lines = []
123
+
124
+ if jsonl_file is not None:
125
+ st.markdown("#### 檔案檢查結果")
126
+ content = jsonl_file.read().decode("utf-8")
127
+ lines = content.splitlines()
128
+
129
+ file_parsed_lines, errors = validate_jsonl_lines(lines)
130
+
131
+ if errors:
132
+ st.error("格式檢查失敗,請修正後重新上傳:")
133
+ for e in errors[:20]:
134
+ st.write("- " + e)
135
+ if len(errors) > 20:
136
+ st.write(f"... 還有 {len(errors) - 20} 筆錯誤未顯示")
137
+ st.info("若多次調整仍無法通過檢查,建議先在本地編輯好 `.jsonl` 檔,再重新上傳。")
138
+ else:
139
+ file_jsonl_valid = True
140
+ st.success(f"檢查通過!共 {len(file_parsed_lines)} 筆對話。")
141
+ st.markdown("#### 範例預覽(前 2 筆)")
142
+ for i, obj in enumerate(file_parsed_lines[:2], start=1):
143
+ st.json(obj)
144
+
145
+ if st.button("上傳對話資料(檔案)", disabled=not (BACKEND_URL and file_jsonl_valid)):
146
+ if BACKEND_URL is None:
147
+ st.warning("尚未設定 BACKEND_URL,無法實際送出,請在 `st.secrets` 中配置。")
148
+ else:
149
+ with st.spinner("正在上傳對話資料並檢查,請稍候…"):
150
+ meta = {
151
+ "uploaded_at": uploaded_at, # UTC+8 ISO 字串
152
+ "contributor_email": contributor_email if contributor_email.strip() else None,
153
+ "share_permission": bool(share_permission),
154
  }
 
155
 
156
+ enriched_lines = []
157
+ for obj in file_parsed_lines:
158
+ obj_with_meta = {**obj, "metadata": meta}
159
+ enriched_lines.append(json.dumps(obj_with_meta, ensure_ascii=False))
160
+
161
+ payload = "\n".join(enriched_lines).encode("utf-8")
162
+ files = {"file": ("contrib.jsonl", payload, "application/jsonl")}
163
+
164
+ try:
165
+ resp = requests.post(f"{BACKEND_URL}/upload-jsonl", files=files)
166
+ if resp.ok:
167
+ st.success("已成功送交後端伺服器,等待後端進一步檢查與處理。")
168
+ else:
169
+ st.error(f"後端回傳錯誤:{resp.status_code} {resp.text}")
170
+ except Exception as e:
171
+ st.error(f"送出時發生錯誤:{e}")
172
+
173
+ # ---------- 模式 B:貼上文字 ----------
174
+ else:
175
+ st.markdown("請將 `.jsonl` 內容貼在下方,每一行必須是一個 JSON 物件:")
176
+ pasted_text = st.text_area(
177
+ "貼上 `.jsonl` 內容",
178
+ placeholder='例如:\n{"messages": [...]}',
179
+ height=240,
180
+ )
181
+
182
+ pasted_jsonl_valid = False
183
+ pasted_parsed_lines = []
184
+
185
+ if pasted_text.strip():
186
+ st.markdown("#### 貼上內容檢查結果")
187
+ lines = pasted_text.splitlines()
188
+ pasted_parsed_lines, errors = validate_jsonl_lines(lines)
189
+
190
+ if errors:
191
+ st.error("格式檢查失敗,請依錯誤訊息調整貼上的內容:")
192
+ for e in errors[:20]:
193
+ st.write("- " + e)
194
+ if len(errors) > 20:
195
+ st.write(f"... 還有 {len(errors) - 20} 筆錯誤未顯示")
196
+ st.info("若多次調整仍無法通過檢查,建議先在本地編輯好 `.jsonl` 檔案,再使用「上傳檔案」模式上傳。")
197
+ else:
198
+ pasted_jsonl_valid = True
199
+ st.success(f"檢查通過!共 {len(pasted_parsed_lines)} 筆對話。")
200
+ st.markdown("#### 範例預覽(前 2 筆)")
201
+ for i, obj in enumerate(pasted_parsed_lines[:2], start=1):
202
+ st.json(obj)
203
+
204
+ if st.button("上傳對話資料(貼上內容)", disabled=not (BACKEND_URL and pasted_jsonl_valid)):
205
+ if BACKEND_URL is None:
206
+ st.warning("尚未設定 BACKEND_URL,無法實際送出,請在 `st.secrets` 中配置。")
207
+ else:
208
+ with st.spinner("正在上傳貼上內容並檢查,請稍候…"):
209
+ meta = {
210
+ "uploaded_at": uploaded_at, # UTC+8 ISO 字串
211
+ "contributor_email": contributor_email if contributor_email.strip() else None,
212
+ "share_permission": bool(share_permission),
213
+ }
214
 
215
+ enriched_lines = []
216
+ for obj in pasted_parsed_lines:
217
+ obj_with_meta = {**obj, "metadata": meta}
218
+ enriched_lines.append(json.dumps(obj_with_meta, ensure_ascii=False))
219
+
220
+ payload = "\n".join(enriched_lines).encode("utf-8")
221
+ files = {"file": ("contrib_pasted.jsonl", payload, "application/jsonl")}
222
+
223
+ try:
224
+ resp = requests.post(f"{BACKEND_URL}/upload-jsonl", files=files)
225
+ if resp.ok:
226
+ st.success("已成功送交後端伺服器,等待後端進一步檢查與處理。")
227
+ else:
228
+ st.error(f"後端回傳錯誤:{resp.status_code} {resp.text}")
229
+ except Exception as e:
230
+ st.error(f"送出時發生錯誤:{e}")
231
 
232
 
233
  # ---------- Tab 2: PDF ----------