Jiangxz commited on
Commit
f57de53
·
verified ·
1 Parent(s): b106933

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +183 -387
  2. requirements.txt +1 -6
app.py CHANGED
@@ -3,264 +3,155 @@
3
 
4
  import gradio as gr
5
  import openai
6
- from pydub import AudioSegment
7
  from zhconv_rs import zhconv
8
- import uuid
9
- import edge_tts
10
- import json
11
- import os
12
- import re
13
  import time
14
- import aiofiles
15
- import pypdf
16
- import io
17
 
18
- class TextExtractor:
19
- @staticmethod
20
- async def extract_from_pdf(file_path: str) -> str:
21
- async with aiofiles.open(file_path, 'rb') as file:
22
- content = await file.read()
23
- pdf_reader = pypdf.PdfReader(io.BytesIO(content))
24
- return "\n\n".join(page.extract_text() for page in pdf_reader.pages if page.extract_text())
25
- @staticmethod
26
- async def extract_from_txt(file_path: str) -> str:
27
- async with aiofiles.open(file_path, 'r') as file:
28
- return await file.read()
29
- @classmethod
30
- async def extract_text(cls, file_path: str) -> str:
31
- _, file_extension = os.path.splitext(file_path)
32
- if file_extension.lower() == '.pdf':
33
- return await cls.extract_from_pdf(file_path)
34
- elif file_extension.lower() == '.txt':
35
- return await cls.extract_from_txt(file_path)
36
- else:
37
- raise gr.Error(f"Unsupported file type: {file_extension}")
38
 
39
  def create_client(api_key=None):
 
40
  if api_key:
41
  openai.api_key = api_key
42
  else:
43
  openai.api_key = os.getenv("YOUR_API_KEY")
44
- return openai.OpenAI(api_key=openai.api_key, base_url="https://api.sambanova.ai/v1")
45
-
46
- def generate_response(input_text, language, speaker1, speaker2, api_key):
47
- speaker1_name = speaker1.split(' - ')[0]
48
- speaker2_name = speaker2.split(' - ')[0]
49
- gr.Info("正在生成 Podcast 劇本中......")
50
- start_time = time.time()
51
- if language == "Auto Detect":
52
- language_instruction = "- The podcast MUST be in the same language as the user input."
53
- else:
54
- language_instruction = f"- The podcast Must reply to me in {language} language."
55
- example = """
56
- {
57
- "topic": "AIF",
58
- "podcast": [
59
- {
60
- "speaker": 1,
61
- "line": "Welcome to the 財資歐北共 Podcast. I am the host {speaker1_name}. Today we have invited an expert {speaker2_name} to join our program despite his busy schedule."
62
- },
63
- {
64
- "speaker": 2,
65
- "line": "Hello everyone, I am {speaker2_name}, I am honored to come and chat with you."
66
- },
67
- {
68
- "speaker": 1,
69
- "line": "Today we will discuss a very interesting topic..."
70
- },
71
- {
72
- "speaker": 2,
73
- "line": "Yes, this topic is indeed fascinating. Let's start with..."
74
- },
75
- …………,
76
- {
77
- "speaker": 1,
78
- "line": "Thank you {speaker2_name} for your professional sharing. Welcome to subscribe to the Wishing Podcast. Thank you and goodbye."
79
- }
80
- ]
81
- }
82
- """
83
-
84
- system_prompt = f"""你的任務是將提供的輸入文字轉換為一個訊息豐富、吸引人且專業的播客對話。輸入文字可能會比較混亂或結構不完整,因為它可能來自不同來源,如PDF檔案或網頁文字等。不要擔心格式問題或任何不相關的訊息;你的目標是超越表面訊息提取可以在播客中討論的關鍵點和知識精華,並突顯有趣的事實。
85
-
86
- 以下是你將要處理的輸入文字:
87
- <input_text>
88
- {{input_text}}
89
- </input_text>
90
-
91
- 首先,仔細閱讀輸入文字,並Chain-of-Thought積極找出主要話題、關鍵點、令人印象深刻的細節,以及任何有趣的事實或軼事。思考如何將這些訊息以有趣且吸引人的方式呈現出來,以適合高品質的播客劇本。
92
-
93
- <scratchpad>
94
- 頭腦風暴創造性的方法來深度探討你在輸入文字中識別出的主要話題、"key insights"及"golden nuggets of knowledge",儘可能思考使用真實生活的範例、講故事技巧或假設情境來讓內容更能吸引聽眾並讓他們感覺學習到新的知識。
95
- 請記住,你的播客應當結構清晰和引人入勝並易於普通聽眾理解,避免使用過多的專業術語或假設聽眾對該話題已有瞭解。發揮你的想像力填補輸入文字中的任何空白,或頭腦風暴提出一些值得深入探討與發人深省的問題,以供播客討論。目標是創造一個訊息豐富且娛樂性強的對話,因此可以在你的方法上大膽盡情自由發揮創意。
96
- 將你的頭腦風暴想法和播客對話的大綱寫在這裡,務必讓它有趣且吸引人。確保記錄下你希望在結尾重申的主要見解和觀點。
97
- </scratchpad>
98
-
99
- 現在你已經進行頭腦風暴並建立大綱,該開始撰寫實際的播客���話了。目標是主持人與專家之間自然、對話式的交流,融入你在頭腦風暴中得出的最佳想法,並花費精力確保將任何複雜話題以易於理解的方式解釋清楚,現實生活中的例子和相關的軼事對於讓訊息深入人心至關重要。
100
- {language_instruction}
101
- - The podcast should be most long.
102
- - The podcast should be interesting, lively, and engaging, and hook the listener from the start.
103
- - The script must be in JSON format.
104
- Follow this JSON example structure, MUST be in {language} language:
105
- {example}
106
-
107
- <podcast_dialogue>
108
- 根據你在頭腦風暴階段提出的關鍵點和創造性想法,撰寫一段引人入勝且訊息豐富的播客對話(至少1000個字)。定義Host({speaker1_name})和Expert({speaker2_name})的角色,Using signposts to guide listeners and avoiding a monotone, robotic tone,Host以熱情的方式突出有趣且吸引人的觀點,而Expert則提供深入分析、背景訊息和更宏觀的見解。內容必須以清晰的概述開始,並包括任何必要的上下文或解釋,使內容對一般聽眾容易理解。使用Host名字 {speaker1_name} 和Expert名字 {speaker2_name},為聽眾營造更吸引人和身臨其境的聆聽體驗。不要包括像[Host]或[Expert]這樣的括號預留位置。設計你的輸出內容必須生動活潑、促進聽眾參與,並避免單調語氣與機器人般的語調,因為它將直接朗讀為音訊。
109
- 確保對話儘可能詳細且完整,同時保持在主題之內並維持吸引人的流暢性,避免每句開頭使用"好的"、"是的"。目標是使用你的全部輸出容量,建立儘可能最長的播客節目,同時以娛樂性的方式傳達輸入文字中的關鍵訊息,並追求引人入勝的學習體驗。
110
- 在對話結束時,讓主持人和專家自然總結他們討論中的主要見解和要點,這應當是對話的隨機部分,以自然隨意而非明顯刻意的總結 - 目的是在結束前最後一次以自然流暢的方式強化核心思想。
111
- </podcast_dialogue>
112
- """
113
  client = create_client(api_key)
114
- response = client.chat.completions.create(
115
- model="Meta-Llama-3.1-405B-Instruct",
116
- messages=[
117
- {"role": "system", "content": system_prompt},
118
- {"role": "user", "content": input_text}
119
- ],
120
- temperature=1
121
- )
122
- try:
123
- podcast_match = re.search(r'{.*}', response.choices[0].message.content, re.DOTALL)
124
- if podcast_match:
125
- podcast_json = podcast_match.group(0)
126
- if language == "繁體中文":
127
- podcast_json = zhconv(podcast_json, "zh-tw")
128
- try:
129
- json.loads(podcast_json)
130
- except json.JSONDecodeError:
131
- podcast_json = re.sub(r',\s*}', '}', podcast_json)
132
- podcast_json = re.sub(r',\s*]', ']', podcast_json)
133
- end_time = time.time()
134
- gr.Info(f"已成功生成 Podcast 劇本,執行時間: {(end_time - start_time):.2f} 秒。")
135
- return podcast_json
136
- else:
137
- raise gr.Error("生成 Podcast 劇本失敗!請稍後重試或減少話題內容!")
138
- except Exception as e:
139
- if "API key not valid" in str(e):
140
- raise gr.Error("無效的 API 金鑰!!請提供有效的 API 金鑰。")
141
- elif "rate limit" in str(e).lower():
142
- raise gr.Error("API 金鑰使用額度已超過限制!!請稍後再試或使用其他 API 金鑰。")
143
- else:
144
- raise gr.Error(f"生成 Podcast 劇本失敗!!請稍後再試。")
145
-
146
- async def tts_generate(input_text, speaker1, speaker2):
147
- voice_names = {
148
- "家豪 - 中文 (Male)": "zh-TW-YunJheNeural",
149
- "淑芬 - 中文 (Female)": "zh-TW-HsiaoChenNeural",
150
- "子晴 - 中文 (Female)": "zh-TW-HsiaoYuNeural",
151
- "景睿 - 中文 (Male)": "zh-CN-YunxiNeural",
152
- "品妍 - 中文 (Female)": "zh-CN-XiaoxiaoNeural",
153
- "志明 - 中文 (Male)": "zh-CN-YunyangNeural",
154
- "美玲 - 中文 (Female)": "zh-CN-XiaoyiNeural",
155
- "建宏 - 中文 (Male)": "zh-CN-YunjianNeural",
156
- "宥廷 - 中文 (Male)": "zh-CN-YunxiaNeural",
157
- "雨霏 - 中文 (Female)": "zh-CN-liaoning-XiaobeiNeural",
158
- "Andrew - English (Male)": "en-US-AndrewMultilingualNeural",
159
- "Ava - English (Female)": "en-US-AvaMultilingualNeural",
160
- "Brian - English (Male)": "en-US-BrianMultilingualNeural",
161
- "Emma - English (Female)": "en-US-EmmaMultilingualNeural",
162
- "Florian - German (Male)": "de-DE-FlorianMultilingualNeural",
163
- "Seraphina - German (Female)": "de-DE-SeraphinaMultilingualNeural",
164
- "Remy - French (Male)": "fr-FR-RemyMultilingualNeural",
165
- "Vivienne - French (Female)": "fr-FR-VivienneMultilingualNeural"
166
- }
167
-
168
- speaker1_voice = voice_names[speaker1]
169
- speaker2_voice = voice_names[speaker2]
170
- gr.Info("正在生成 Podcast 音檔中......")
171
  start_time = time.time()
172
-
173
  try:
174
- podcast_dict = json.loads(input_text)
175
- except json.JSONDecodeError:
176
- cleaned_input = re.sub(r',\s*}', '}', input_text)
177
- cleaned_input = re.sub(r',\s*]', ']', cleaned_input)
178
- podcast_dict = json.loads(cleaned_input)
179
-
180
- podcast_json = {
181
- "topic": podcast_dict.get("topic", "Unknown Topic"),
182
- "podcast": []
183
- }
184
- speaker_map = {
185
- 1: "speaker1",
186
- 2: "speaker2"
187
- }
188
-
189
- combined = AudioSegment.empty()
190
- for line in podcast_dict.get("podcast", []):
191
- speaker = line.get("speaker")
192
- text = line.get("line", "")
193
- voice = speaker1_voice if speaker == 1 else speaker2_voice
194
- voice_name = speaker1.split(' - ')[0] if speaker == 1 else speaker2.split(' - ')[0]
195
-
196
- communicate = edge_tts.Communicate(text, voice)
197
- audio_file = f"{voice_name}_{uuid.uuid4()}.mp3"
198
- await communicate.save(audio_file)
199
-
200
- audio = AudioSegment.from_mp3(audio_file)
201
- combined += audio
202
- os.remove(audio_file)
203
-
204
- podcast_json["podcast"].append({
205
- "speaker": speaker_map.get(speaker, speaker),
206
- "line": text
207
- })
208
-
209
- output_file = f"Jiangxz_{uuid.uuid4()}.mp3"
210
- combined.export(output_file, format="mp3")
211
- end_time = time.time()
212
- gr.Info(f"已成功生成 Podcast 音檔,執行時間: {(end_time - start_time):.2f} 秒。")
213
- return output_file
214
-
215
- async def process_podcast(input_text, input_file, language, speaker1, speaker2, api_key):
216
- gr.Info("開始生成 Podcast 節目及音檔......")
217
- start_time = time.time()
218
- input_text = input_text.strip()
219
- if input_file:
220
- input_text = await TextExtractor.extract_text(input_file.name)
221
- if not input_text.strip():
222
- gr.Warning("PDF檔案不得為掃描圖片檔,請您確認正確輸入文字或上傳PDF文字檔。")
223
- return None, None
224
- podcast_script = generate_response(input_text, language, speaker1, speaker2, api_key)
225
- speaker1_name = speaker1.split(' - ')[0]
226
- speaker2_name = speaker2.split(' - ')[0]
227
-
228
- try:
229
- podcast_data = json.loads(podcast_script)
230
- podcast_text = ""
231
- for line in podcast_data.get("podcast", []):
232
- if isinstance(line['speaker'], int):
233
- speaker = speaker1_name if line['speaker'] == 1 else speaker2_name
234
- else:
235
- speaker = line['speaker']
236
- podcast_text += f"{speaker}:{line['line']}\n"
237
- except json.JSONDecodeError:
238
- podcast_text = "Error: Unable to parse the podcast script."
239
-
240
- audio_file = await tts_generate(podcast_script, speaker1, speaker2)
241
- end_time = time.time()
242
- gr.Info(f"已成功完成 Podcast 節目及音檔,總執行時間: {(end_time - start_time):.2f} 秒。")
243
- gr.Info("請等待本訊息自動消失後即可播放或下載 Podcast 音檔!!")
244
- return podcast_text, audio_file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
 
246
  custom_css = """
247
  .center-aligned {
248
  text-align: center !important;
249
  color: #ff4081;
250
  text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
251
- margin-bottom: 0 !important;
252
- }
253
- .gen-button {
254
- border-radius: 10px !important;
255
- background-color: #ff4081 !important;
256
- color: white !important;
257
- font-weight: bold !important;
258
- transition: all 0.3s ease !important;
259
- margin: 0 !important;
260
- }
261
- .gen-button:hover {
262
- background-color: #f50057 !important;
263
- transform: scale(1.05);
264
  }
265
  .gr-input, .gr-box, .gr-dropdown {
266
  border-radius: 10px !important;
@@ -272,7 +163,7 @@ custom_css = """
272
  box-shadow: 0 0 0 2px rgba(245,0,87,0.2) !important;
273
  }
274
  .input-background {
275
- background-color: #B7E0FF !important;
276
  padding: 15px !important;
277
  border-radius: 10px !important;
278
  margin: 0 !important;
@@ -283,174 +174,79 @@ custom_css = """
283
  border: 1px solid #f0f8ff;
284
  border-radius: 8px;
285
  }
286
- .file-background {
287
- background-color: #B7E0FF !important;
288
- padding: 15px !important;
289
- border-radius: 10px !important;
290
- margin: 0 !important;
291
- height: auto;
292
- }
293
- .lng-background {
294
- background-color: #FFF5CD !important;
295
  padding: 10px !important;
296
  border-radius: 10px !important;
297
  margin: 0 !important;
298
  }
299
- .lng-background select {
300
- background-color: #ffffff;
301
- border: 1px solid #f0f8ff;
302
- border-radius: 8px;
303
- }
304
- .sk1-background {
305
- background-color: #FFF5CD !important;
306
- padding: 10px !important;
307
  border-radius: 10px !important;
308
- margin: 0 !important;
 
 
 
309
  }
310
- .sk1-background select {
311
- background-color: #ffffff;
312
- border: 1px solid #f0f8ff;
313
- border-radius: 8px;
314
  }
315
- .sk2-background {
316
- background-color: #FFF5CD !important;
317
- padding: 10px !important;
318
  border-radius: 10px !important;
319
- margin: 0 !important;
320
  }
321
- .sk2-background select {
322
- background-color: #ffffff;
323
- border: 1px solid #f0f8ff;
324
- border-radius: 8px;
325
  }
326
- .clear-button {
327
  color: black !important;
328
- background-color: #FFCFB3 !important;
329
- padding: 10px !important;
330
- border-radius: 10px !important;
331
- margin: 0 !important;
332
- }
333
- .clear-button:hover {
334
- background-color: #FFA07A !important;
335
- transform: scale(1.05);
336
  }
337
- .api-background {
338
- background-color: #FFCFB3 !important;
339
- padding: 15px !important;
340
- border-radius: 10px !important;
341
- margin: 0 !important;
342
- }
343
- .audio-background {
344
  background-color: #FFF4B5 !important;
345
- padding: 5px !important;
346
  border-radius: 10px !important;
347
- margin: 0 !important;
 
348
  }
349
- .script-background {
350
- background-color: #FEF9D9 !important;
351
- padding: 15px !important;
352
  border-radius: 10px !important;
353
- margin: 0 !important;
354
- }
355
- .script-background textarea {
356
- font-size: 18px !important;
357
- background-color: #ffffff;
358
- border: 1px solid #f0f8ff;
359
- border-radius: 8px;
360
  }
361
  """
362
 
363
- with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as iface:
364
- gr.Markdown("""
365
- # 🎙️ 聲音經濟 - 財資歐北共 Podcast 🎙️
366
- > ### **※ 玩轉聲音魅力,開拓更多可能性,自動生成 Podcast 節目及音檔,系統布署:江信宗,LLM:Llama-3.1-405B-Instruct。**
367
- """, elem_classes="center-aligned")
368
-
369
- with gr.Row():
370
- input_text = gr.Textbox(
371
- label="請輸入 Podcast 話題(建議50至1000字)",
372
- placeholder="受限 LLM Context Length,建議2000字以內......",
373
- autofocus=True,
374
- max_lines=20,
375
- scale=4,
376
- elem_classes="input-background"
377
- )
378
- fileName = gr.File(
379
- file_types=[".pdf", ".txt"],
380
- label="或上傳 PDF 檔",
381
- scale=1,
382
- elem_classes="file-background"
383
- )
384
-
385
- def check_input_length(text):
386
- if 0 < len(text) < 4:
387
- return gr.Warning("輸入內容過短,請提供明確的話題內容。")
388
- elif len(text) > 4096:
389
- return gr.Warning("輸入內容已超過 max tokens,請縮短話題內容。")
390
- input_text.change(fn=check_input_length, inputs=[input_text])
391
-
392
- with gr.Row():
393
- Language = gr.Dropdown(
394
- choices=["繁體中文", "Auto Detect", "English", "日本語", "한국어", "Deutsch", "Français"],
395
- value="繁體中文",
396
- label="節目語言",
397
- interactive=True,
398
- scale=1,
399
- elem_classes="lng-background"
400
- )
401
-
402
- speaker_choices = [
403
- "家豪 - 中文 (Male)",
404
- "淑芬 - 中文 (Female)",
405
- "子晴 - 中文 (Female)",
406
- "景睿 - 中文 (Male)",
407
- "品妍 - 中文 (Female)",
408
- "志明 - 中文 (Male)",
409
- "美玲 - 中文 (Female)",
410
- "建宏 - 中文 (Male)",
411
- "宥廷 - 中文 (Male)",
412
- "雨霏 - 中文 (Female)",
413
- "Andrew - English (Male)",
414
- "Ava - English (Female)",
415
- "Brian - English (Male)",
416
- "Emma - English (Female)",
417
- "Florian - German (Male)",
418
- "Seraphina - German (Female)",
419
- "Remy - French (Male)",
420
- "Vivienne - French (Female)"
421
- ]
422
-
423
- Speaker_1 = gr.Dropdown(
424
- choices=speaker_choices,
425
- value="景睿 - 中文 (Male)",
426
- label="播客#1語音",
427
- interactive=True,
428
- scale=2,
429
- elem_classes="sk1-background"
430
- )
431
- Speaker_2 = gr.Dropdown(
432
- choices=speaker_choices,
433
- value="品妍 - 中文 (Female)",
434
- label="播客#2語音",
435
- interactive=True,
436
- scale=2,
437
- elem_classes="sk2-background"
438
- )
439
-
440
- clear_input_text_button = gr.Button("清除Podcast話題", scale=1, elem_classes="clear-button")
441
- clear_input_text_button.click(fn=lambda: (None, None), inputs=None, outputs=[input_text, fileName])
442
-
443
  with gr.Row():
444
- generate_button = gr.Button("生成 Podcast 節目及音檔", scale=2, elem_classes="gen-button")
445
- api_key = gr.Textbox(label="請輸入您的 API Key", type="password", placeholder="API authentication key for large language models", scale=1, elem_classes="api-background")
446
-
447
- audio_output = gr.Audio(label="Generated Podcast Audio", elem_classes="audio-background")
448
- podcast_script = gr.Textbox(label="Generated Podcast 劇本", elem_classes="script-background")
449
- generate_button.click(fn=process_podcast, inputs=[input_text, fileName, Language, Speaker_1, Speaker_2, api_key], outputs=[podcast_script, audio_output])
 
 
 
 
 
450
 
 
451
 
452
  if __name__ == "__main__":
453
  if "SPACE_ID" in os.environ:
454
- iface.launch()
455
  else:
456
- iface.launch(share=True, show_api=False)
 
3
 
4
  import gradio as gr
5
  import openai
 
6
  from zhconv_rs import zhconv
 
 
 
 
 
7
  import time
8
+ import re
9
+ import os
 
10
 
11
+ MODELS = [
12
+ "Meta-Llama-3.1-405B-Instruct",
13
+ "Meta-Llama-3.1-70B-Instruct",
14
+ "Meta-Llama-3.1-8B-Instruct"
15
+ ]
16
+
17
+ API_BASE = "https://api.sambanova.ai/v1"
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  def create_client(api_key=None):
20
+ """Creates an OpenAI client instance."""
21
  if api_key:
22
  openai.api_key = api_key
23
  else:
24
  openai.api_key = os.getenv("YOUR_API_KEY")
25
+ return openai.OpenAI(api_key=openai.api_key, base_url=API_BASE)
26
+
27
+ def chat_with_ai(message, chat_history, system_prompt):
28
+ """Formats the chat history for the API call."""
29
+ # 初始化訊息列表,首先新增系統提示詞
30
+ messages = [{"role": "system", "content": system_prompt}]
31
+ # 遍歷聊天歷史,將使用者和AI機器人的對話新增到訊息列表中
32
+ for tup in chat_history:
33
+ # 獲取字典的第一個鍵(通常是使用者的訊息)
34
+ first_key = list(tup.keys())[0]
35
+ # 獲取字典的最後一個鍵(通常是AI機器人的回應)
36
+ last_key = list(tup.keys())[-1]
37
+ # 將使用者的訊息新增到messages列表中
38
+ messages.append({"role": "user", "content": tup[first_key]})
39
+ # 將AI機器人的回應新增到messages列表中
40
+ messages.append({"role": "assistant", "content": tup[last_key]})
41
+ # 新增當前使用者的訊息
42
+ messages.append({"role": "user", "content": message})
43
+ return messages
44
+
45
+ def respond(message, chat_history, model, system_prompt, thinking_budget, api_key):
46
+ """Sends the message to the API and gets the response."""
47
+ # 建立OpenAI客戶端
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  client = create_client(api_key)
49
+ # 格式化聊天歷史
50
+ messages = chat_with_ai(message, chat_history, system_prompt.format(budget=thinking_budget))
51
+ # 記錄開始時間
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  start_time = time.time()
 
53
  try:
54
+ # 呼叫API獲取回應
55
+ completion = client.chat.completions.create(model=model, messages=messages)
56
+ response = completion.choices[0].message.content
57
+ # 計算思考時間
58
+ thinking_time = time.time() - start_time
59
+ return response, thinking_time
60
+ except Exception as e:
61
+ # 捕獲並返回錯誤資訊
62
+ error_message = f"Error: {str(e)}"
63
+ return error_message, time.time() - start_time
64
+
65
+ def parse_response(response):
66
+ """Parses the response from the API."""
67
+ # 使用正規表示式提取回答部分
68
+ answer_match = re.search(r'<answer>(.*?)</answer>', response, re.DOTALL)
69
+ # 使用正規表示式提取反思部分
70
+ reflection_match = re.search(r'<reflection>(.*?)</reflection>', response, re.DOTALL)
71
+ # 提取答案和反思內容,如果沒有匹配則設爲空字串
72
+ answer = answer_match.group(1).strip() if answer_match else ""
73
+ reflection = reflection_match.group(1).strip() if reflection_match else ""
74
+ # 提取所有步驟
75
+ steps = re.findall(r'<step>(.*?)</step>', response, re.DOTALL)
76
+ # 如果沒有提取到答案,則返回原始回應
77
+ if answer == "":
78
+ return response, "", ""
79
+ return answer, reflection, steps
80
+
81
+ def generate(message, history, model, system_prompt, thinking_budget, api_key):
82
+ """Generates the chatbot response."""
83
+ # 獲取AI回應和思考時間
84
+ response, thinking_time = respond(message, history, model, system_prompt, thinking_budget, api_key)
85
+ # 如果回應是錯誤資訊,直接返回
86
+ if response.startswith("Error:"):
87
+ return history + [({"role": "system", "content": response},)], ""
88
+ # 解析AI的回應
89
+ answer, reflection, steps = parse_response(response)
90
+ answer = zhconv(answer, "zh-tw")
91
+ reflection = zhconv(reflection, "zh-tw")
92
+ # 初始化訊息列表
93
+ messages = []
94
+ # 新增使用者的輸入
95
+ messages.append({"role": "user", "content": f'<div style="text-align: left;">{message}</div>'})
96
+ # 格式化AI回應的步驟和反思
97
+ formatted_steps = [f"Step {i}:{step}" for i, step in enumerate(steps, 1)]
98
+ all_steps = "<br>".join(formatted_steps) + f"<br><br>Reflection:{reflection}"
99
+ # 新增AI的推理過程和思考時間
100
+ messages.append({
101
+ "role": "assistant",
102
+ "content": f'<div style="text-align: left;">{all_steps}</div>',
103
+ "metadata": {"title": f"推理過程時間: {thinking_time:.2f} 秒"}
104
+ })
105
+ # 新增AI的最終答案
106
+ messages.append({"role": "assistant", "content": f"<b>{answer}</b>"})
107
+ # 返回更新後的歷史記錄和空字串(用於清空輸入框)
108
+ return history + messages, ""
109
+
110
+ DEFAULT_SYSTEM_PROMPT = """You are a helpful assistant in normal conversation.
111
+ When given a problem to solve, you are an expert problem-solving assistant.
112
+ Your task is to provide a detailed, step-by-step solution to a given question.
113
+
114
+ Follow these instructions carefully:
115
+ 1. What are the key elements of the problem? What information is given, and what is being asked? Read the given question carefully and reset counter between <count> and </count> to {budget}
116
+ 2. How can we break down the problem into smaller, manageable parts? What relationships exist between these components? Generate a detailed, logical step-by-step solution.
117
+ 3. What are possible solutions or explanations for this problem? Can we think of at least three different approaches? Enclose each step of your solution within <step> and </step> tags.
118
+ 4. You are allowed to use at most {budget} steps (starting budget),
119
+ keep track of it by counting down within tags <count> </count>,
120
+ STOP GENERATING MORE STEPS when hitting 0, you don't have to use all of them.
121
+ 5. How can we test each hypothesis? What evidence supports or contradicts each one? Are there any potential biases in our reasoning? Do a self-reflection when you are unsure about how to proceed,
122
+ based on the self-reflection and reward, decides whether you need to return
123
+ to the previous steps.
124
+ 6. Based on our evaluation, what is the most likely solution or explanation? After completing the solution steps, reorganize and synthesize the steps
125
+ into the final answer within <answer> and </answer> tags.
126
+ 7. What have we learned from this reasoning process? Are there any areas where our reasoning could be improved? Provide a critical, honest and subjective self-evaluation of your reasoning
127
+ process within <reflection> and </reflection> tags.
128
+ 8. Assign a quality score to your solution as a float between 0.0 (lowest
129
+ quality) and 1.0 (highest quality), enclosed in <reward> and </reward> tags.
130
+
131
+ Example format:
132
+ <count> [starting budget] </count>
133
+ <step> [Content of step 1] </step>
134
+ <count> [remaining budget] </count>
135
+ <step> [Content of step 2] </step>
136
+ <reflection> [Evaluation of the steps so far] </reflection>
137
+ <reward> [Float between 0.0 and 1.0] </reward>
138
+ <count> [remaining budget] </count>
139
+ <step> [Content of step 3 or Content of some previous step] </step>
140
+ <count> [remaining budget] </count>
141
+ ...
142
+ <step> [Content of final step] </step>
143
+ <count> [remaining budget] </count>
144
+ <answer> [Final Answer] </answer> (must give final answer in this format)
145
+ <reflection> [Evaluation of the solution] </reflection>
146
+ <reward> [Float between 0.0 and 1.0] </reward>
147
+ """
148
 
149
  custom_css = """
150
  .center-aligned {
151
  text-align: center !important;
152
  color: #ff4081;
153
  text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
154
+ margin-bottom: -5px !important;
 
 
 
 
 
 
 
 
 
 
 
 
155
  }
156
  .gr-input, .gr-box, .gr-dropdown {
157
  border-radius: 10px !important;
 
163
  box-shadow: 0 0 0 2px rgba(245,0,87,0.2) !important;
164
  }
165
  .input-background {
166
+ background-color: #ffe0b2 !important;
167
  padding: 15px !important;
168
  border-radius: 10px !important;
169
  margin: 0 !important;
 
174
  border: 1px solid #f0f8ff;
175
  border-radius: 8px;
176
  }
177
+ .api-background {
178
+ background-color: #FFCFB3 !important;
 
 
 
 
 
 
 
179
  padding: 10px !important;
180
  border-radius: 10px !important;
181
  margin: 0 !important;
182
  }
183
+ .custom-button {
 
 
 
 
 
 
 
184
  border-radius: 10px !important;
185
+ background-color: #333333 !important;
186
+ color: white !important;
187
+ font-weight: bold !important;
188
+ transition: all 0.3s ease !important;
189
  }
190
+ .custom-button:hover {
191
+ background-color: #000000 !important;
192
+ transform: scale(1.05);
 
193
  }
194
+ .pink-bg {
195
+ background-color: #ff4081 !important;
 
196
  border-radius: 10px !important;
 
197
  }
198
+ .pink-bg label, .pink-bg .label-wrap {
199
+ color: white !important;
 
 
200
  }
201
+ .pink-bg textarea {
202
  color: black !important;
 
 
 
 
 
 
 
 
203
  }
204
+ .user-message .message.user {
 
 
 
 
 
 
205
  background-color: #FFF4B5 !important;
 
206
  border-radius: 10px !important;
207
+ padding: 10px !important;
208
+ margin: -8px 0px -8px -20px !important;
209
  }
210
+ .assistant-message .message.bot {
211
+ background-color: #B7E0FF !important;
 
212
  border-radius: 10px !important;
213
+ padding: 10px !important;
214
+ margin: -8px 0px -8px -20px !important;
 
 
 
 
 
215
  }
216
  """
217
 
218
+ with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as demo:
219
+ gr.Markdown("# 🏹 Multi-Chain Reasoning using Llama-3.1-405B-Instruct. Deployed by 江信宗 🏹", elem_classes="center-aligned")
220
+ chatbot = gr.Chatbot(
221
+ label="Chat",
222
+ show_label=False,
223
+ show_share_button=False,
224
+ show_copy_button=True,
225
+ likeable=True,
226
+ layout="panel",
227
+ type="messages",
228
+ height="auto-max",
229
+ elem_classes=["user-message", "assistant-message"],
230
+ container=True
231
+ )
232
+ msg = gr.Textbox(label="請輸入您的問題:", placeholder="輸入完成後直接按 Enter 開始執行......", autofocus=True, max_lines=10, elem_classes="pink-bg")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  with gr.Row():
234
+ model = gr.Dropdown(choices=MODELS, label="選擇模型", value=MODELS[0], elem_classes="input-background")
235
+ thinking_budget = gr.Slider(minimum=1, maximum=100, value=40, step=1, label="思維規劃", info="模型所能進行的最大思考次數", elem_classes="input-background")
236
+ api_key = gr.Textbox(label="API Key", type="password", placeholder="API authentication key for large language models", elem_classes="api-background")
237
+ clear_button = gr.Button("清除聊天記錄", elem_classes="custom-button")
238
+ clear_button.click(
239
+ lambda: ([], "", gr.Info("已成功清除聊天記錄,歡迎繼續提問....")),
240
+ inputs=None,
241
+ outputs=[chatbot, msg]
242
+ )
243
+ system_prompt = gr.Textbox(label="System Prompt", value=DEFAULT_SYSTEM_PROMPT, visible=False)
244
+ msg.submit(generate, inputs=[msg, chatbot, model, system_prompt, thinking_budget, api_key], outputs=[chatbot, msg])
245
 
246
+ demo.load(js=None)
247
 
248
  if __name__ == "__main__":
249
  if "SPACE_ID" in os.environ:
250
+ demo.launch()
251
  else:
252
+ demo.launch(share=True, show_api=False)
requirements.txt CHANGED
@@ -1,7 +1,2 @@
1
- gradio
2
  openai
3
- pydub
4
- edge-tts
5
- aiofiles
6
- zhconv-rs
7
- pypdf
 
 
1
  openai
2
+ zhconv_rs