vortex123 commited on
Commit
cb994ee
·
verified ·
1 Parent(s): 42344a2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +262 -72
app.py CHANGED
@@ -1,119 +1,309 @@
 
1
  import gradio as gr
2
  import google.generativeai as genai
3
- import os
4
- import asyncio # Import для асинхронности
 
 
 
5
 
6
- # Безопасное получение API ключа
7
- GEMINI_API_KEY = "AIzaSyBoqoPX-9uzvXyxzse0gRwH8_P9xO6O3Bc"
 
8
  if not GEMINI_API_KEY:
9
- print("Error: GEMINI_API_KEY environment variable not set.")
10
  exit()
11
 
12
  genai.configure(api_key=GEMINI_API_KEY)
13
 
14
- AVAILABLE_MODELS = ["gemini-1.5-flash", "gemini-1.5-pro", "gemini-2.0-flash-thinking-exp"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- # Инициализация моделей один раз при запуске приложения
17
- MODELS = {model_name: genai.GenerativeModel(model_name=model_name) for model_name in AVAILABLE_MODELS}
 
 
 
 
 
 
 
 
 
18
 
19
- async def respond(message, history, selected_model):
20
- model = MODELS.get(selected_model)
21
- if not model:
22
- yield {"role": "assistant", "content": "Error: Selected model not available."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  return
24
 
 
 
 
25
  try:
26
- chat = model.start_chat(history=history)
27
- response_stream = chat.send_message(message, stream=True)
28
- full_response = ""
 
 
29
  for chunk in response_stream:
30
- full_response += (chunk.text or "")
31
- yield {"role": "assistant", "content": full_response}
 
 
32
  except Exception as e:
33
- yield {"role": "assistant", "content": f"Error during API call: {e}"}
34
 
35
- async def respond_thinking(message, history, selected_model):
36
- if "thinking" not in selected_model:
37
- yield {"role": "assistant", "content": "Thinking model не выбрана."}, ""
38
- return
39
 
40
- model = MODELS.get(selected_model)
41
- if not model:
42
- yield {"role": "assistant", "content": "Error: Selected model not available."}, ""
43
- return
 
 
 
 
 
44
 
45
- yield {"role": "assistant", "content": "Думаю..."}
 
 
 
 
46
 
47
  try:
48
- response = model.generate_content(message)
49
- thinking_process_text = ""
50
- model_response_text = ""
51
 
 
 
 
52
  if response.candidates:
53
  for part in response.candidates[0].content.parts:
54
- if hasattr(part, 'thought') and part.thought == True:
55
- thinking_process_text += f"Model Thought:\n{part.text}\n\n"
 
56
  else:
57
- model_response_text += (part.text or "")
 
 
 
58
 
59
- yield {"role": "assistant", "content": model_response_text}, thinking_process_text
60
  except Exception as e:
61
- yield {"role": "assistant", "content": f"Error during API call: {e}"}, f"Error during API call: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
- async def process_message(message, history, model_name):
64
- if "thinking" in model_name:
65
- async for response, thinking in respond_thinking(message, history, model_name):
66
- yield (response, thinking)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  else:
68
- async for response in respond(message, history, model_name):
69
- yield (response, "")
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
- def clear_thinking():
72
- return ""
 
 
 
 
 
 
 
 
 
73
 
74
  with gr.Blocks() as demo:
75
- gr.Markdown("# Gemini Chatbot с режимом размышления")
 
 
 
 
 
 
76
 
77
  with gr.Row():
78
- model_selection = gr.Dropdown(
79
- AVAILABLE_MODELS, value="gemini-1.5-flash", label="Выберите модель Gemini"
 
 
80
  )
 
81
 
82
- thinking_output = gr.Code(label="Процесс размышления (для моделей с размышлением)")
 
83
 
84
- chatbot = gr.ChatInterface(
85
- process_message, # Функция обработки сообщений передается сюда
86
- additional_inputs=[model_selection],
87
- title="Gemini Chat",
88
- description="Общайтесь с моделями Gemini от Google.",
89
- type="messages"
90
  )
91
 
92
- with gr.Row():
93
- clear_button = gr.Button("Очистить")
94
-
95
- def change_chatbot(model_name):
96
- return gr.ChatInterface.update()
97
 
98
- async def update_thinking(history):
99
- if history and history[-1][0]["role"] == "assistant" and len(history[-1]) > 1:
100
- return history[-1][1]
101
- return ""
102
 
103
- model_selection.change(
104
- change_chatbot,
105
- inputs=[model_selection],
 
 
 
 
 
 
 
 
 
 
 
 
106
  outputs=[chatbot],
 
 
 
 
 
 
 
107
  )
108
 
109
- chatbot.change(
110
- update_thinking,
111
- inputs=[chatbot],
112
- outputs=[thinking_output]
 
 
 
 
 
 
 
 
 
 
 
 
113
  )
114
 
115
- clear_button.click(lambda: None, None, chatbot, queue=False)
116
- clear_button.click(clear_thinking, outputs=[thinking_output], queue=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
 
 
 
118
  if __name__ == "__main__":
119
- demo.launch()
 
1
+ import os
2
  import gradio as gr
3
  import google.generativeai as genai
4
+ import asyncio
5
+
6
+ ###############################################################################
7
+ # 1. Настройка окружения и инициализация модели
8
+ ###############################################################################
9
 
10
+ # Задайте свой ключ через переменную окружения, например:
11
+ # export GEMINI_API_KEY="ваш-ключ"
12
+ GEMINI_API_KEY = "AIzaSyBoqoPX-9uzvXyxzse0gRwH8_P9xO6O3Bc"
13
  if not GEMINI_API_KEY:
14
+ print("Error: GEMINI_API_KEY is not set.")
15
  exit()
16
 
17
  genai.configure(api_key=GEMINI_API_KEY)
18
 
19
+ AVAILABLE_MODELS = [
20
+ "gemini-1.5-flash",
21
+ "gemini-1.5-pro",
22
+ "gemini-2.0-flash-thinking-exp"
23
+ ]
24
+
25
+ # Создаём объекты-модели и храним их в словаре для быстрого доступа
26
+ MODELS = {}
27
+ for model_name in AVAILABLE_MODELS:
28
+ try:
29
+ MODELS[model_name] = genai.GenerativeModel(model_name=model_name)
30
+ except Exception as e:
31
+ print(f"Не удалось инициализировать модель {model_name}: {e}")
32
+
33
+ ###############################################################################
34
+ # 2. Функции для генерации ответов
35
+ ###############################################################################
36
+
37
+ def _convert_history_to_genai_format(history):
38
+ """
39
+ Gradio-хранит чат как список кортежей: [(user_msg, bot_msg), (user_msg, bot_msg), ...]
40
+ Для Gemini же нужна история вида [{'role': 'user', 'content': ...}, {'role': 'assistant', 'content': ...}, ...].
41
+
42
+ Возвращает список dict, где role = user/assistant, content = текст.
43
+ """
44
+ genai_history = []
45
+ for user_text, bot_text in history:
46
+ if user_text: # сообщение от пользователя
47
+ genai_history.append({"role": "user", "content": user_text})
48
+ if bot_text: # сообщение от ассистента
49
+ genai_history.append({"role": "assistant", "content": bot_text})
50
+ return genai_history
51
+
52
 
53
+ def _convert_genai_to_chatbot_format(history):
54
+ """
55
+ Обратное преобразование:
56
+ [{'role': 'user', 'content': ...}, {'role': 'assistant', 'content': ...}]
57
+ -> [(user_msg, bot_msg), ...]
58
+
59
+ Упрощённо полагаем, что роли будут идти поочерёдно user-assistant.
60
+ Если вдруг несколько подряд от одного и того же role, объединяем по очереди.
61
+ """
62
+ display_history = []
63
+ user_buffer = None
64
 
65
+ for turn in history:
66
+ role = turn["role"]
67
+ content = turn["content"]
68
+ if role == "user":
69
+ # Запускаем новую пару
70
+ user_buffer = content
71
+ elif role == "assistant":
72
+ if user_buffer is not None:
73
+ display_history.append((user_buffer, content))
74
+ user_buffer = None
75
+ else:
76
+ # На случай, если идёт assistant без user
77
+ display_history.append((None, content))
78
+
79
+ # Если вдруг остался user_buffer без ответа (редко бывает)
80
+ if user_buffer is not None:
81
+ display_history.append((user_buffer, None))
82
+
83
+ return display_history
84
+
85
+
86
+ async def _respond_stream(model_name, user_message, history):
87
+ """
88
+ Генерация ОТВЕТА С ПОСТЕПЕННЫМ стримингом (для моделей без "thinking").
89
+ Возвращает генератор, чтобы Gradio мог пошагово обновлять вывод.
90
+ """
91
+ if model_name not in MODELS:
92
+ yield "Ошибка: модель не найдена."
93
  return
94
 
95
+ model = MODELS[model_name]
96
+ genai_history = _convert_history_to_genai_format(history)
97
+
98
  try:
99
+ chat = model.start_chat(history=genai_history)
100
+ # Запрашиваем стриминг-ответ
101
+ response_stream = chat.send_message(user_message, stream=True)
102
+
103
+ partial_text = ""
104
  for chunk in response_stream:
105
+ chunk_text = chunk.text or ""
106
+ partial_text += chunk_text
107
+ # Возвращаем накопленный ответ
108
+ yield partial_text
109
  except Exception as e:
110
+ yield f"Error during API call: {e}"
111
 
 
 
 
 
112
 
113
+ async def _respond_thinking(model_name, user_message, history):
114
+ """
115
+ Генерация ответа для моделей с "thinking" (gemini-2.0-flash-thinking-exp).
116
+ 1) Выдаём "Думаю..."
117
+ 2) По завершении запроса возвращаем итоговый ответ
118
+ 3) При желании можно вернуть размышления в отдельную переменную
119
+ """
120
+ if model_name not in MODELS:
121
+ return "Ошибка: модель не найдена.", ""
122
 
123
+ model = MODELS[model_name]
124
+ genai_history = _convert_history_to_genai_format(history)
125
+
126
+ # 1) "Думаю..."
127
+ yield "Думаю...", "" # (ответ в чат, размышления)
128
 
129
  try:
130
+ chat = model.start_chat(history=genai_history)
131
+ response = chat.send_message(user_message, stream=False) # без stream
 
132
 
133
+ # Для thinking-моделей ответ приходит в виде parts
134
+ thinking_process_text = ""
135
+ final_text = ""
136
  if response.candidates:
137
  for part in response.candidates[0].content.parts:
138
+ # Если в part есть .thought == True, считаем это «размышлениями»
139
+ if getattr(part, "thought", False):
140
+ thinking_process_text += part.text or ""
141
  else:
142
+ final_text += part.text or ""
143
+
144
+ # Возвращаем итоговый ответ
145
+ yield final_text, thinking_process_text
146
 
 
147
  except Exception as e:
148
+ yield f"Error during API call: {e}", f"Error: {e}"
149
+
150
+
151
+ ###############################################################################
152
+ # 3. Основная функция (колбэк для Gradio), обрабатывающая один «шаг» диалога
153
+ ###############################################################################
154
+
155
+ async def user_send_message(user_message, history, model_name, thinking_store):
156
+ """
157
+ Эта функция вызывается при нажатии кнопки "Отправить" (или Enter).
158
+ Параметры:
159
+ user_message: текст от пользователя
160
+ history: текущее состояние чата [(user_msg, bot_msg), ...]
161
+ model_name: выбранная модель
162
+ thinking_store: текущее значение поля "Размышления"
163
 
164
+ Возвращаем:
165
+ (updated_history, updated_thinking_store)
166
+ """
167
+ if not user_message.strip():
168
+ # Пустая строка – ничего не делаем
169
+ return history, thinking_store
170
+
171
+ # Добавляем в историю шаг пользователя
172
+ history.append((user_message, None)) # Пока ассистента нет
173
+
174
+ # Если модель thinking
175
+ if "thinking" in model_name.lower():
176
+ # Асинхронный генератор: сначала "Думаю...", потом финальный ответ
177
+ async for (assistant_text, thinking_text) in _respond_thinking(model_name, user_message, history):
178
+ # Обновляем последний элемент истории (None -> assistant_text)
179
+ history[-1] = (user_message, assistant_text)
180
+ # Обновляем thinking_output
181
+ yield history, thinking_text
182
  else:
183
+ # Стриминговый ответ
184
+ partial_answer = ""
185
+ async for partial_chunk in _respond_stream(model_name, user_message, history):
186
+ partial_answer = partial_chunk
187
+ history[-1] = (user_message, partial_answer)
188
+ # В режиме без thinking просто очищаем thinking_store
189
+ yield history, ""
190
+
191
+ # Когда генерация закончена, возвращаем итог
192
+ return history, thinking_store
193
+
194
+
195
+ ###############################################################################
196
+ # 4. Колбэки для кнопки "Очистить"
197
+ ###############################################################################
198
 
199
+ def clear_all():
200
+ """
201
+ Полная очистка истории чата и поля «Размышления».
202
+ Возвращает пустые значения для State и компонентов.
203
+ """
204
+ return [], "" # history, thinking_store
205
+
206
+
207
+ ###############################################################################
208
+ # 5. Определяем Gradio-интерфейс
209
+ ###############################################################################
210
 
211
  with gr.Blocks() as demo:
212
+ gr.Markdown("## Gemini Chatbot с сохранением истории и «thinking»-режимом")
213
+
214
+ # Храним историю диалога (список кортежей) в gr.State
215
+ history_state = gr.State([])
216
+
217
+ # Храним «размышления» (thinking) в отдельном тексте
218
+ thinking_store = gr.State("")
219
 
220
  with gr.Row():
221
+ model_dropdown = gr.Dropdown(
222
+ choices=AVAILABLE_MODELS,
223
+ value="gemini-1.5-flash",
224
+ label="Выберите модель"
225
  )
226
+ clear_button = gr.Button("Очистить чат")
227
 
228
+ # Сам чат: компонент Chatbot для отображения диалога
229
+ chatbot = gr.Chatbot(label="Диалог с Gemini")
230
 
231
+ # Поле ввода сообщения
232
+ user_input = gr.Textbox(
233
+ label="Ваш вопрос/сообщение",
234
+ placeholder="Введите вопрос...",
 
 
235
  )
236
 
237
+ # Поле для показа «Размышлений» (если модель thinking)
238
+ thinking_output = gr.Textbox(
239
+ label="Размышления модели (только для gemini-2.0-flash-thinking-exp)",
240
+ interactive=False
241
+ )
242
 
243
+ # Кнопка «Отправить»
244
+ send_btn = gr.Button("Отправить")
 
 
245
 
246
+ ############################################################################
247
+ # Связываем действия пользователя с колбэками
248
+ ############################################################################
249
+ # 1. При нажатии «Отправить» запускаем user_send_message
250
+ # - обновляем историю и thinking_output
251
+ # - одновременно обновляем отображение чата
252
+ send_btn.click(
253
+ fn=user_send_message,
254
+ inputs=[user_input, history_state, model_dropdown, thinking_store],
255
+ outputs=[history_state, thinking_store],
256
+ queue=True,
257
+ ).then(
258
+ # После обновления history_state, пересчитываем отображение чата
259
+ fn=lambda h: h,
260
+ inputs=[history_state],
261
  outputs=[chatbot],
262
+ preprocess=False,
263
+ postprocess=True
264
+ ).then(
265
+ # После этого выводим текущее состояние thinking_store
266
+ fn=lambda t: t,
267
+ inputs=[thinking_store],
268
+ outputs=[thinking_output],
269
  )
270
 
271
+ # 2. При нажатии Enter в текстовом поле — та же логика, что и на кнопке
272
+ user_input.submit(
273
+ fn=user_send_message,
274
+ inputs=[user_input, history_state, model_dropdown, thinking_store],
275
+ outputs=[history_state, thinking_store],
276
+ queue=True,
277
+ ).then(
278
+ fn=lambda h: h,
279
+ inputs=[history_state],
280
+ outputs=[chatbot],
281
+ preprocess=False,
282
+ postprocess=True
283
+ ).then(
284
+ fn=lambda t: t,
285
+ inputs=[thinking_store],
286
+ outputs=[thinking_output],
287
  )
288
 
289
+ # 3. Кнопка «Очистить»
290
+ clear_button.click(
291
+ fn=clear_all,
292
+ inputs=[],
293
+ outputs=[history_state, thinking_store],
294
+ queue=False
295
+ ).then(
296
+ fn=lambda h: h,
297
+ inputs=[history_state],
298
+ outputs=chatbot,
299
+ ).then(
300
+ fn=lambda _: "",
301
+ inputs=[],
302
+ outputs=thinking_output
303
+ )
304
 
305
+ ###############################################################################
306
+ # 6. Запуск
307
+ ###############################################################################
308
  if __name__ == "__main__":
309
+ demo.launch()