aiTOYAMA commited on
Commit
4e29e07
·
verified ·
1 Parent(s): 5e888a5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +287 -308
app.py CHANGED
@@ -1,329 +1,308 @@
1
  import os
2
- import shutil
3
- from datetime import datetime
 
4
  import gradio as gr
5
- from langchain_chroma import Chroma
6
- from langchain_community.document_loaders import PyPDFLoader
7
- from langchain_text_splitters import RecursiveCharacterTextSplitter
8
- from langchain_huggingface import HuggingFaceEmbeddings
9
- from langchain_google_genai import ChatGoogleGenerativeAI
10
- from huggingface_hub import snapshot_download
11
-
12
- # ==================================================
13
- # 設定
14
- # ==================================================
15
-
16
- PAPER_DIR = "papers"
17
- DB_DIR = "/tmp/chroma_db" # Read-onlyエラー回避のため、一時ディレクトリを指定
18
- NOTE_FILE = "research_notes.txt"
19
-
20
- os.makedirs(PAPER_DIR, exist_ok=True)
21
-
22
-
23
- # ==================================================
24
- # Gemini / Embedding モデル設定
25
- # ==================================================
26
 
 
 
 
27
  API_KEY = os.getenv("GEMINI_API_KEY")
 
28
 
29
- if API_KEY is None:
30
- raise ValueError(
31
- "GEMINI_API_KEY が設定されていません。環境変数を確認してください。"
32
- )
33
-
34
- # 埋め込みモデルの初期化
35
- embedding_model = HuggingFaceEmbeddings(
36
- model="sentence-transformers/all-MiniLM-L6-v2"
37
- )
38
-
39
- # Geminiモデルの初期化 (安定性の高い2.5-flashを採用)
40
- llm = ChatGoogleGenerativeAI(
41
- model="gemini-2.5-flash",
42
- temperature=0.3
43
- )
44
-
45
- # ==================================================
46
- # PDF → DB 登録・再構築
47
- # ==================================================
48
-
49
- def rebuild_database():
50
- pdf_files = [
51
- os.path.join(PAPER_DIR, f)
52
- for f in os.listdir(PAPER_DIR)
53
- if f.endswith(".pdf")
54
- ]
55
-
56
- if len(pdf_files) == 0:
57
- return "PDFがありません"
58
-
59
- all_docs = []
60
-
61
- for pdf in pdf_files:
62
- try:
63
- loader = PyPDFLoader(pdf)
64
- docs = loader.load()
65
- all_docs.extend(docs)
66
- except Exception as e:
67
- print(f"PDF読み込みエラー ({pdf}): {e}")
68
-
69
- if not all_docs:
70
- return "読み込み可能なPDFテキストがありませんでした"
71
-
72
- # テキストの最適化分割
73
- splitter = RecursiveCharacterTextSplitter(
74
- chunk_size=1000,
75
- chunk_overlap=200
76
- )
77
 
78
- chunks = splitter.split_documents(all_docs)
79
-
80
- # 既存の古いDBディレクトリを安全に削除して再初期化
81
- if os.path.exists(DB_DIR):
82
- try:
83
- shutil.rmtree(DB_DIR)
84
- except Exception as e:
85
- print(f"既存DBの削除に失敗しました: {e}")
86
-
87
- # ベクトルDBの構築
88
- Chroma.from_documents(
89
- documents=chunks,
90
- embedding=embedding_model,
91
- persist_directory=DB_DIR
92
- )
93
-
94
- return f"{len(pdf_files)}件のPDFを登録しました"
95
-
96
-
97
- # ==================================================
98
- # DB取得
99
- # ==================================================
100
-
101
- def get_db():
102
- if not os.path.exists(DB_DIR):
103
- return None
104
-
105
- return Chroma(
106
- persist_directory=DB_DIR,
107
- embedding_function=embedding_model
108
- )
109
-
110
-
111
- # ==================================================
112
- # PDF追加
113
- # ==================================================
114
-
115
- def upload_pdfs(files):
116
- if files is None:
117
- return "PDF未選択"
118
-
119
- count = 0
120
- for file in files:
121
- dst = os.path.join(
122
- PAPER_DIR,
123
- os.path.basename(file.name)
124
- )
125
- try:
126
- shutil.copy(file.name, dst)
127
- count += 1
128
- except Exception as e:
129
- print(f"ファイルコピーエラー: {e}")
130
-
131
- # 追加後にデータベースを再構築
132
- msg = rebuild_database()
133
-
134
- return f"{count}件アップロード\n{msg}"
135
-
136
-
137
- # ==================================================
138
- # 手法検索
139
- # ==================================================
140
-
141
- def search_method(question):
142
- db = get_db()
143
-
144
- if db is None:
145
- return "PDFを登録してください"
146
-
147
- # 関連ドキュメントの検索
148
- docs = db.similarity_search(question, k=5)
149
- context = "\n\n".join([d.page_content for d in docs])
150
-
151
- prompt = f"""
152
- あなたは研究指導教員です。
153
-
154
- 以下の論文内容を参考に、
155
- 手法や専門用語を初心者にも
156
- わかりやすく説明してください。
157
-
158
- 【論文情報】
159
- {context}
160
-
161
- 【質問】
162
- {question}
163
-
164
- 【回答】
165
- """
166
 
 
 
 
167
  try:
168
- response = llm.invoke(prompt)
169
- return response.content
 
 
170
  except Exception as e:
171
- return f"Geminiによる生成エラーが発生しました: {e}"
172
-
173
-
174
- # ==================================================
175
- # 初回データセット取得 & データベース初期化
176
- # ==================================================
177
-
178
- try:
179
- snapshot_download(
180
- repo_id="aiTOYAMA/papers",
181
- repo_type="dataset",
182
- local_dir=PAPER_DIR
183
- )
184
- print("データセットのダウンロードに成功しました")
185
- except Exception as e:
186
- print(f"データセットのダウンロードに失敗しました (手動での登録が必要です): {e}")
187
-
188
- if not os.path.exists(DB_DIR):
189
- print(rebuild_database())
190
-
191
-
192
- # ==================================================
193
- # 実験検索
194
- # ==================================================
195
-
196
- def search_experiment(question):
197
- db = get_db()
198
-
199
- if db is None:
200
- return "PDFを登録してください"
201
-
202
- docs = db.similarity_search(question, k=5)
203
- context = "\n\n".join([d.page_content for d in docs])
204
-
205
- prompt = f"""
206
- 以下の論文内容を参考に、
207
- 実験設定を整理して回答してください。
208
-
209
- 抽出項目
210
-
211
- ・データセット
212
- ・学習率
213
- ・Epoch数
214
- ・評価指標
215
- ・比較手法
216
- ・実験結果
217
-
218
- 【論文情報】
219
- {context}
220
-
221
- 【質問】
222
- {question}
223
-
224
- 【回答】
225
- """
226
 
 
 
 
227
  try:
228
- response = llm.invoke(prompt)
229
- return response.content
230
- except Exception as e:
231
- return f"Geminiによる生成エラーが発生しました: {e}"
232
-
233
-
234
- # ==================================================
235
- # 保存
236
- # ==================================================
237
-
238
- def save_note(question, answer):
239
- if not question or not answer:
240
- return "質問または回答が空のため保存できません"
241
-
242
- now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
243
-
 
 
 
 
 
 
 
 
 
 
244
  try:
245
- with open(NOTE_FILE, "a", encoding="utf-8") as f:
246
- f.write("\n")
247
- f.write("=" * 60)
248
- f.write("\n")
249
- f.write(f"日時: {now}\n\n")
250
- f.write(f"質問:\n{question}\n\n")
251
- f.write(f"回答:\n{answer}\n")
252
- f.write("\n")
253
- return "保存完了"
254
  except Exception as e:
255
- return f"ファイル保存エラー: {e}"
256
-
257
-
258
- # ==================================================
259
- # UI (Gradio) ※インターフェース構成は完全再現
260
- # ==================================================
261
-
262
- with gr.Blocks() as demo:
263
-
264
- gr.Markdown("# 研究支援RAGアプリ")
265
-
266
- # タブ1: 論文追加
267
- with gr.Tab("論文追加"):
268
- pdf_input = gr.File(
269
- file_count="multiple",
270
- file_types=[".pdf"],
271
- label="論文PDF"
272
- )
273
-
274
- upload_btn = gr.Button("登録")
275
-
276
- upload_status = gr.Textbox(label="状態")
277
-
278
- upload_btn.click(
279
- fn=upload_pdfs,
280
- inputs=pdf_input,
281
- outputs=upload_status
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  )
283
-
284
- # タブ2: 手法・専門用語検索
285
- with gr.Tab("手法・専門用語検索"):
286
- method_question = gr.Textbox(label="質問")
287
-
288
- method_answer = gr.Textbox(
289
- label="回答",
290
- lines=15
291
  )
292
 
293
- method_btn = gr.Button("検索")
 
 
294
 
295
- method_save = gr.Button("保存")
296
-
297
- method_status = gr.Textbox(label="保存状態")
298
-
299
- method_btn.click(
300
- fn=search_method,
301
- inputs=method_question,
302
- outputs=method_answer
303
- )
304
 
305
- method_save.click(
306
- fn=save_note,
307
- inputs=[method_question, method_answer],
308
- outputs=method_status
 
 
 
 
 
 
 
309
  )
 
 
 
 
310
 
311
- # タブ3: 実験内容検索
312
- with gr.Tab("実験内容検索"):
313
- exp_question = gr.Textbox(label="質問")
314
-
315
- exp_answer = gr.Textbox(
316
- label="回答",
317
- lines=15
318
  )
319
-
320
- exp_btn = gr.Button("検索")
321
-
322
- exp_save = gr.Button("保存") # 整合性を保つため gr.Button に修正
323
-
324
- exp_status = gr.Textbox(label="保存状態")
325
-
326
- exp_btn.click(
327
- fn=search_experiment,
328
- inputs=exp_question,
329
- outputs=exp_answer
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import tempfile
3
+ from google import genai
4
+ from google.genai import types
5
  import gradio as gr
6
+ import fitz # PyMuPDF
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
+ # =====================================================================
9
+ # 1. 初期設定と環境準備
10
+ # =====================================================================
11
  API_KEY = os.getenv("GEMINI_API_KEY")
12
+ client = genai.Client(api_key=API_KEY)
13
 
14
+ # ルート直下を指定
15
+ PDF_DIR = "."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
+ knowledge_base = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ def load_pdf_text(file_path):
20
+ """PDFファイルからテキストを抽出する関数"""
21
+ text = ""
22
  try:
23
+ doc = fitz.open(file_path)
24
+ for page in doc:
25
+ text += page.get_text()
26
+ return text
27
  except Exception as e:
28
+ print(f"Error loading {file_path}: {e}")
29
+ return ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ def update_knowledge_base():
32
+ """フォルダ内の全てのPDFから知識ベースを更新する関数"""
33
+ global knowledge_base
34
  try:
35
+ pdf_files = [f for f in os.listdir(PDF_DIR) if f.lower().endswith('.pdf') and os.path.isfile(os.path.join(PDF_DIR, f))]
36
+ except:
37
+ pdf_files = []
38
+
39
+ combined_text = ""
40
+ file_list_str = "【読み込み済みの論文一覧】\n"
41
+
42
+ if not pdf_files:
43
+ file_list_str += "・なし (論文PDFをアップロードしてください)\n"
44
+
45
+ for pdf in pdf_files:
46
+ file_list_str += f"・{pdf}\n"
47
+ combined_text += f"\n--- 論文: {pdf} ---\n"
48
+ combined_text += load_pdf_text(os.path.join(PDF_DIR, pdf))
49
+
50
+ knowledge_base = combined_text
51
+
52
+ warning = ""
53
+ if len(knowledge_base) > 200000:
54
+ warning = "\n⚠️ 注意: 読み込まれたテキスト量が非常に大きいため、回答の生成に時間がかかったり、API制限に達する可能性があります。"
55
+
56
+ status = f"現在、{len(pdf_files)} 個の論文PDFが読み込まれています。{warning}\n\n{file_list_str}"
57
+ return status
58
+
59
+ def clear_database():
60
+ """データベースフォルダ内のPDFファイルをすべて削除して初期化する関数"""
61
  try:
62
+ pdf_files = [f for f in os.listdir(PDF_DIR) if f.lower().endswith('.pdf') and os.path.isfile(os.path.join(PDF_DIR, f))]
63
+ for pdf in pdf_files:
64
+ os.remove(os.path.join(PDF_DIR, pdf))
 
 
 
 
 
 
65
  except Exception as e:
66
+ print(f"Error deleting {pdf}: {e}")
67
+ status = update_knowledge_base()
68
+ return status, None
69
+
70
+ init_status = update_knowledge_base()
71
+
72
+
73
+ # =====================================================================
74
+ # 2. RAGロジック(generate_content & ストリーミング対応)
75
+ # =====================================================================
76
+ def extract_pure_text(content):
77
+ """Gradio 6の複雑な内部データから純粋なテキストのみを抽���する関数"""
78
+ if isinstance(content, list):
79
+ text_list = []
80
+ for item in content:
81
+ if isinstance(item, dict) and "text" in item:
82
+ if isinstance(item["text"], list):
83
+ text_list.append(extract_pure_text(item["text"]))
84
+ else:
85
+ text_list.append(str(item["text"]))
86
+ elif isinstance(item, dict) and "content" in item:
87
+ text_list.append(extract_pure_text(item["content"]))
88
+ elif isinstance(item, str):
89
+ text_list.append(item)
90
+ return "".join(text_list)
91
+ elif isinstance(content, dict):
92
+ if "text" in content:
93
+ return extract_pure_text(content["text"])
94
+ elif "content" in content:
95
+ return extract_pure_text(content["content"])
96
+ return str(content)
97
+
98
+ def rag_chat_stream(message, history, mode):
99
+ global knowledge_base
100
+ pure_message = extract_pure_text(message)
101
+ if not pure_message.strip():
102
+ yield "ユーザーの質問を正しく読み取れませんでした。もう一度入力してください。"
103
+ return
104
+
105
+ if mode == "terms":
106
+ mode_instruction = (
107
+ "あなたは研究をサポートする優秀なAIアシスタントです。提供された論文資料に基づき、"
108
+ "専門用語の定義や、提案されている「手法・アルゴリズム・理論」について、背景を含めて数式や箇条書きを用いて分かりやすく解説してください。"
109
  )
110
+ else:
111
+ mode_instruction = (
112
+ "あなたは研究をサポートする優秀なAIアシスタントです。提供された論文資料に基づき、"
113
+ "「実験設定、データセット、評価指標、結果、比較グラフの内容、考察」に特化して詳しく分析・回答してください。"
 
 
 
 
114
  )
115
 
116
+ system_instruction = f"""
117
+ {mode_instruction}
118
+ ただし、資料に具体的な記述がない内容に関しては、必ず「提供された論文資料に該当する情報が見つからないため、一般的な知識に基づきGeminiが回答します。」と前置きした上で回答してください。
119
 
120
+ ===== 信頼できる論文資料 =====
121
+ {knowledge_base}
122
+ ============================
123
+ """
 
 
 
 
 
124
 
125
+ gemini_contents = []
126
+ recent_history = history[-12:] if len(history) > 12 else history
127
+
128
+ for msg in recent_history:
129
+ pure_text = extract_pure_text(msg["content"])
130
+ if not pure_text.strip() or "ただいま大変混雑しています" in pure_text:
131
+ continue
132
+
133
+ role = "user" if msg["role"] == "user" else "model"
134
+ gemini_contents.append(
135
+ types.Content(role=role, parts=[types.Part.from_text(text=pure_text)])
136
  )
137
+
138
+ gemini_contents.append(
139
+ types.Content(role="user", parts=[types.Part.from_text(text=pure_message)])
140
+ )
141
 
142
+ try:
143
+ response_stream = client.models.generate_content_stream(
144
+ model="gemini-2.5-flash",
145
+ contents=gemini_contents,
146
+ config=types.GenerateContentConfig(system_instruction=system_instruction)
 
 
147
  )
148
+
149
+ current_text = ""
150
+ for chunk in response_stream:
151
+ if chunk.text:
152
+ current_text += chunk.text
153
+ yield current_text
154
+
155
+ except Exception as e:
156
+ error_msg = str(e)
157
+ if "503" in error_msg or "UNAVAILABLE" in error_msg:
158
+ yield (
159
+ "⚠️【ただいま大変混雑しています】\n\n"
160
+ "Gemini APIを提供しているGoogleのサーバー側で一時的にアクセスが集中し、パンクしている状態です。\n"
161
+ "1〜2分ほど時間を置いてから、もう一度送信ボタンを押してみてください。"
162
+ )
163
+ else:
164
+ yield f"エラーが発生しました: {error_msg}"
165
+
166
+
167
+ # =====================================================================
168
+ # 3. アクション関数(ファイル追加・履歴ダウンロード用生成)
169
+ # =====================================================================
170
+ def upload_new_papers(file_objects):
171
+ if file_objects is None:
172
+ return "ファイルが選択されていません。", None
173
+
174
+ saved_count = 0
175
+ for file in file_objects:
176
+ orig_name = os.path.basename(file.name)
177
+ dest_path = os.path.join(PDF_DIR, orig_name)
178
+ with open(file.name, "rb") as src, open(dest_path, "wb") as dst:
179
+ dst.write(src.read())
180
+ saved_count += 1
181
+
182
+ status = update_knowledge_base()
183
+ return status, None
184
+
185
+ def prepare_terms_download(history):
186
+ if not history: return None # 🌟非表示切り替えを廃止し、値をクリアするだけに
187
+ return _generate_file(history, "用語・手法")
188
+
189
+ def prepare_exp_download(history):
190
+ if not history: return None
191
+ return _generate_file(history, "実験内容・結果")
192
+
193
+ def _generate_file(history, mode_label):
194
+ """共通のテキストファイル生成ロジック"""
195
+ custom_filename = f"chat_history_{mode_label}.txt"
196
+
197
+ temp_dir = tempfile.gettempdir()
198
+ file_path = os.path.join(temp_dir, custom_filename)
199
+
200
+ with open(file_path, "w", encoding="utf-8") as f:
201
+ f.write(f"==================================================\n")
202
+ f.write(f" 対話履歴ログ: {mode_label}\n")
203
+ f.write(f"==================================================\n\n")
204
+
205
+ for msg in history:
206
+ role_label = "【ユーザー】" if msg["role"] == "user" else "【AIアシスタント】"
207
+ pure_text = extract_pure_text(msg["content"])
208
+ f.write(f"{role_label}\n{pure_text}\n")
209
+ f.write("-" * 50 + "\n\n")
210
+
211
+ return file_path
212
+
213
+
214
+ # =====================================================================
215
+ # 4. Gradio UIの構築
216
+ # =====================================================================
217
+ with gr.Blocks(title="研究専用RAGアシスタント") as demo:
218
+ gr.Markdown("# 🎓 研究専用RAGアシスタント")
219
+ gr.Markdown("論文PDFの内容をベースに、Geminiがあなたの研究・論文読解をサポートします。")
220
+
221
+ with gr.Accordion("📚 論文データベース管理 (PDF追加・初期化)", open=True):
222
+ with gr.Row():
223
+ file_input = gr.File(label="論文PDFをアップロード", file_count="multiple", file_types=[".pdf"])
224
+ with gr.Column():
225
+ upload_btn = gr.Button("📂 選択した論文を追加", variant="primary")
226
+ clear_db_btn = gr.Button("🗑️ データベースを全削除して初期化", variant="stop")
227
+ status_output = gr.Textbox(value=init_status, label="現在のデータベースステータス", interactive=False, lines=6)
228
+
229
+ upload_btn.click(fn=upload_new_papers, inputs=file_input, outputs=[status_output, file_input])
230
+ clear_db_btn.click(fn=clear_database, inputs=None, outputs=[status_output, file_input])
231
+
232
+ gr.Markdown("### 🔍 論文調査")
233
+
234
+ with gr.Tabs():
235
+ # タブ1: 専門用語・手法用
236
+ with gr.TabItem("📖 専門用語・手法を調べる"):
237
+ gr.Markdown("*論文内の見慣れないキーワードや、提案手法のアルゴリズム、理論について質問してください。*")
238
+ chatbot_terms = gr.Chatbot(label="対話履歴(用語・手法)", elem_id="terms_chat")
239
+ msg_terms = gr.Textbox(placeholder="例: この論文で使われている○○という手法のメリットは何ですか?", label="質問を入力")
240
+
241
+ with gr.Row():
242
+ save_terms_btn = gr.Button("💾 対話履歴を保存 (TXT)", variant="primary")
243
+ clear_terms = gr.Button("🗑️ 対話履歴をクリア")
244
+
245
+ # 🌟修正:最初から visible=True (常に画面に枠を表示)
246
+ file_download_terms = gr.File(label="ダウンロード(保存ボタンを押すとここにファイルが出ます)", visible=True)
247
+
248
+ def user_terms(user_message, history):
249
+ return "", history + [{"role": "user", "content": user_message}]
250
+
251
+ def bot_terms(history):
252
+ user_msg = history[-1]["content"]
253
+ history_for_api = history[:-1]
254
+ history.append({"role": "assistant", "content": ""})
255
+
256
+ for current_response in rag_chat_stream(user_msg, history_for_api, mode="terms"):
257
+ history[-1]["content"] = current_response
258
+ yield history
259
+
260
+ msg_terms.submit(user_terms, [msg_terms, chatbot_terms], [msg_terms, chatbot_terms], queue=False).then(
261
+ bot_terms, chatbot_terms, chatbot_terms
262
+ )
263
+ clear_terms.click(lambda: ([], None), None, [chatbot_terms, file_download_terms], queue=False)
264
+
265
+ save_terms_btn.click(
266
+ fn=prepare_terms_download,
267
+ inputs=chatbot_terms,
268
+ outputs=file_download_terms
269
+ )
270
+
271
+ # タブ2: 実験内容用
272
+ with gr.TabItem("📊 実験内容・結果を調べる"):
273
+ gr.Markdown("*論文内の実験環境、データセット、グラフの解釈、評価結果について質問してください。*")
274
+ chatbot_exp = gr.Chatbot(label="対話履歴(実験内容)", elem_id="exp_chat")
275
+ msg_exp = gr.Textbox(placeholder="例: 実験で使用されたデータセットの規模と、Baselineとの精度の差は?", label="質問を入力")
276
+
277
+ with gr.Row():
278
+ save_exp_btn = gr.Button("💾 対話履歴を保存 (TXT)", variant="primary")
279
+ clear_exp = gr.Button("🗑️ 対話履歴をクリア")
280
+
281
+ # 🌟修正:最初から visible=True (常に画面に枠を表示)
282
+ file_download_exp = gr.File(label="ダウンロード(保存ボタンを押すとここにファイルが出ます)", visible=True)
283
+
284
+ def user_exp(user_message, history):
285
+ return "", history + [{"role": "user", "content": user_message}]
286
+
287
+ def bot_exp(history):
288
+ user_msg = history[-1]["content"]
289
+ history_for_api = history[:-1]
290
+ history.append({"role": "assistant", "content": ""})
291
+
292
+ for current_response in rag_chat_stream(user_msg, history_for_api, mode="experiments"):
293
+ history[-1]["content"] = current_response
294
+ yield history
295
+
296
+ msg_exp.submit(user_exp, [msg_exp, chatbot_exp], [msg_exp, chatbot_exp], queue=False).then(
297
+ bot_exp, chatbot_exp, chatbot_exp
298
+ )
299
+ clear_exp.click(lambda: ([], None), None, [chatbot_exp, file_download_exp], queue=False)
300
+
301
+ save_exp_btn.click(
302
+ fn=prepare_exp_download,
303
+ inputs=chatbot_exp,
304
+ outputs=file_download_exp
305
+ )
306
+
307
+ if __name__ == "__main__":
308
+ demo.launch(theme=gr.themes.Soft())