vortex123 commited on
Commit
4962f84
·
verified ·
1 Parent(s): 52215c5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +130 -119
app.py CHANGED
@@ -2,113 +2,118 @@ import os
2
  import gradio as gr
3
  import google.generativeai as genai
4
  import asyncio
 
5
 
6
  ###############################################################################
7
- # 1. Настройка окружения и моделей
8
  ###############################################################################
9
 
10
- GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "ВАШ_КЛЮЧ_ЗДЕСЬ")
 
11
  if not GEMINI_API_KEY:
12
  print("Error: GEMINI_API_KEY is not set.")
13
  exit()
14
 
15
  genai.configure(api_key=GEMINI_API_KEY)
16
 
17
- # Список моделей
18
  AVAILABLE_MODELS = [
19
- "gemini-2.0-flash-exp",
20
- "gemini-exp-1206",
21
- "gemini-2.0-flash-thinking-exp-1219",
22
  ]
23
 
24
- # Инициализация моделей
25
  MODELS = {}
26
  for model_name in AVAILABLE_MODELS:
27
  try:
28
  MODELS[model_name] = genai.GenerativeModel(model_name=model_name)
29
  except Exception as e:
30
- print(f"[!] Не удалось инициализировать {model_name}: {e}")
 
31
 
32
  ###############################################################################
33
- # 2. Промпты по умолчанию (system message) для каждой модели
34
  ###############################################################################
35
- MODEL_SYSTEM_PROMPTS = {
36
- # Пример: для обычной модели
37
  "gemini-2.0-flash-exp": (
38
- "You are a helpful assistant. "
39
- "You respond in Markdown. "
40
- "You do NOT wrap your final answer in {output: ...}."
41
  ),
42
- # Допустим, это тоже обычная модель
43
  "gemini-exp-1206": (
44
- "You are an experimental Gemini model. "
45
- "You respond in Markdown. "
46
- "You do NOT wrap your final answer in {output: ...}."
47
  ),
48
- # Думающая модель
49
  "gemini-2.0-flash-thinking-exp-1219": (
50
  "You are a thinking model. "
51
- "You respond with thoughts internally, but produce the **final** answer in JSON format, like `{output: (final text)}`."
 
52
  ),
53
  }
54
 
 
55
  ###############################################################################
56
- # 3. Определяем, какую роль использовать: 'assistant' или 'model'
57
  ###############################################################################
 
58
  def _assistant_role(model_name: str) -> str:
59
  """
60
- Для некоторых моделей Gemini требуется role='model', а не 'assistant'.
61
- Если столкнетесь с ошибкой "Please use a valid role: user, model",
62
- укажите здесь 'model'.
63
  """
64
- # Условие подстройте под свои модели:
65
- # Предположим, gemini-exp-1206 и gemini-2.0-flash-thinking-exp-1219
66
- # требуют role='model', а остальные — 'assistant'
67
- if model_name in ["gemini-exp-1206", "gemini-2.0-flash-thinking-exp-1219"]:
68
  return "model"
69
  return "assistant"
70
 
 
71
  ###############################################################################
72
- # 4. Утилиты для конвертации истории
73
  ###############################################################################
 
74
  def _history_gradio_to_genai(history, model_name):
75
  """
76
- Gradio: [(user_msg, bot_msg), (user_msg, bot_msg), ...]
77
- Gemini: [{'role': 'user'|'assistant'|'model'|'system', 'parts': ...}, ...]
78
-
79
- Добавляем system-сообщение (промпт по умолчанию) в начало.
80
- Далее все user-реплики = {'role': 'user'},
81
- все ответы ассистента = {'role': <assistant_role>}.
82
  """
83
  genai_history = []
84
-
85
- # Системное сообщение (промпт по умолчанию)
86
- system_prompt = MODEL_SYSTEM_PROMPTS.get(model_name, "")
87
- if system_prompt:
 
 
 
 
 
 
 
 
 
88
  genai_history.append({"role": "system", "parts": system_prompt})
89
 
90
- asst_role = _assistant_role(model_name)
91
-
92
  for user_text, bot_text in history:
93
- # Сообщение пользователя
94
  if user_text:
95
  genai_history.append({"role": "user", "parts": user_text})
96
- # Сообщение "ассистента"
97
  if bot_text:
98
- genai_history.append({"role": asst_role, "parts": bot_text})
99
 
100
  return genai_history
101
 
 
102
  ###############################################################################
103
- # 5. Функции-генераторы для ответа
104
  ###############################################################################
105
 
106
  async def _respond_stream(model_name, user_message, history):
107
  """
108
- Обычная модель, stream=True: выдаём ответ порциями.
109
  """
110
  if model_name not in MODELS:
111
- yield "Error: model not found."
112
  return
113
 
114
  model = MODELS[model_name]
@@ -117,10 +122,12 @@ async def _respond_stream(model_name, user_message, history):
117
  try:
118
  chat = model.start_chat(history=genai_history)
119
  response_stream = chat.send_message(user_message, stream=True)
 
120
  partial_text = ""
121
  for chunk in response_stream:
122
  partial_text += (chunk.text or "")
123
  yield partial_text
 
124
  return
125
  except Exception as e:
126
  yield f"Ошибка при запросе к API: {e}"
@@ -129,14 +136,13 @@ async def _respond_stream(model_name, user_message, history):
129
 
130
  async def _respond_thinking(model_name, user_message, history):
131
  """
132
- Думающая модель:
133
- 1) "Думаю..."
134
- 2) По завершении:
135
- - собираем "мысли" (part.thought)
136
- - итоговый ответ оборачиваем в {output: ...} (согласно ТЗ)
137
  """
138
  if model_name not in MODELS:
139
- yield "Error: model not found.", ""
140
  return
141
 
142
  model = MODELS[model_name]
@@ -151,49 +157,59 @@ async def _respond_thinking(model_name, user_message, history):
151
 
152
  thinking_process_text = ""
153
  final_text = ""
 
154
  if response.candidates:
155
  parts = response.candidates[0].content.parts
156
  for p in parts:
 
157
  if getattr(p, "thought", False):
158
  thinking_process_text += p.text or ""
159
  else:
160
  final_text += p.text or ""
161
 
162
- # По ТЗ: оборачиваем финальный ответ в {output: ...}
163
- final_text_json = f"{{output: ({final_text})}}"
 
 
 
164
 
165
- yield final_text_json, thinking_process_text
166
  return
 
167
  except Exception as e:
168
  yield f"Ошибка при запросе к API: {e}", ""
169
  return
170
 
 
171
  ###############################################################################
172
- # 6. Основная функция для одного шага диалога
173
  ###############################################################################
 
174
  async def user_send_message(user_message, history, model_name, thinking_text):
175
  """
176
- Пользователь ввёл user_message; на основе history + model_name генерируем ответ.
177
- Возвращаем (history, thinking_text) через yield на каждом шаге.
 
 
 
 
178
  """
179
- # Если пустая строка — ничего не делаем
180
  if not user_message.strip():
 
181
  yield history, thinking_text
182
  return
183
 
184
- # Добавляем ход в history, пока ответ ассистента None
185
  history.append((user_message, None))
186
 
187
- # Проверяем, является ли модель "думающей"
188
  if "thinking" in model_name.lower():
189
  async for (assistant_text, thought_text) in _respond_thinking(model_name, user_message, history):
190
- # Обновляем ответ в истории
191
  history[-1] = (user_message, assistant_text)
192
- # Обновляем блок размышлений
193
  yield history, thought_text
194
  return
195
  else:
196
- # Обычная модель
197
  partial_answer = ""
198
  async for chunk in _respond_stream(model_name, user_message, history):
199
  partial_answer = chunk
@@ -201,77 +217,62 @@ async def user_send_message(user_message, history, model_name, thinking_text):
201
  yield history, ""
202
  return
203
 
 
204
  ###############################################################################
205
- # 7. Логика переключения модели, чтобы «сообщать» о переключении
206
  ###############################################################################
207
- def switch_model(old_model, new_model, history):
 
208
  """
209
- При смене модели добавляем в историю user-сообщение:
210
- "I’m switching from {old_model} to {new_model}."
211
- Сохраняем новое имя модели в state (prev_model_state).
212
  """
213
- if old_model and new_model and old_model != new_model:
214
- switch_message = f"I’m switching from {old_model} to {new_model}."
215
- history.append((switch_message, None)) # user_msg, None
216
- return new_model, history
 
217
 
218
  ###############################################################################
219
- # 8. Функция очистки
220
  ###############################################################################
 
221
  def clear_all():
222
- """Сбросить историю и размышления."""
223
  return [], ""
224
 
 
225
  ###############################################################################
226
- # 9. Сборка Gradio-интерфейса
227
  ###############################################################################
228
- with gr.Blocks() as demo:
229
- gr.Markdown("## Chat with Gemini (Thinking & Non-Thinking Models)")
230
 
231
- # Храним «предыдущую модель», чтобы отследить переключение
232
- prev_model_state = gr.State("")
233
- # Храним историю [(user, assistant), ...]
234
- history_state = gr.State([])
235
- # Храним размышления (только для думающей модели)
236
- thinking_store = gr.State("")
237
 
238
  with gr.Row():
239
  model_dropdown = gr.Dropdown(
240
  choices=AVAILABLE_MODELS,
241
  value="gemini-2.0-flash-exp",
242
- label="Choose a model"
243
  )
244
  clear_button = gr.Button("Очистить чат")
245
 
246
- chatbot = gr.Chatbot(
247
- label="Диалог с Gemini",
248
- markdown=True # Включаем поддержку Markdown
249
- )
250
- user_input = gr.Textbox(
251
- label="Ваш вопрос",
252
- placeholder="Введите текст...",
253
- )
254
  thinking_output = gr.Textbox(
255
- label="Размышления (если модель думающая)",
256
  interactive=False
257
  )
258
- send_btn = gr.Button("Отправить")
259
 
260
- # --- Обработка переключения модели ---
261
- model_dropdown.change(
262
- fn=switch_model,
263
- inputs=[prev_model_state, model_dropdown, history_state],
264
- outputs=[prev_model_state, history_state],
265
- queue=False
266
- ).then(
267
- # После смены модели обновим чат (чтобы увидеть user-сообщение о переключении)
268
- fn=lambda h: h,
269
- inputs=[history_state],
270
- outputs=[chatbot],
271
- queue=False
272
- )
273
 
274
- # --- При нажатии кнопки «Отправить» ---
275
  send_chain = send_btn.click(
276
  fn=user_send_message,
277
  inputs=[user_input, history_state, model_dropdown, thinking_store],
@@ -285,22 +286,22 @@ with gr.Blocks() as demo:
285
  outputs=[chatbot],
286
  queue=True
287
  )
288
- # Обновляем размышления
289
  send_chain.then(
290
  fn=lambda t: t,
291
  inputs=[thinking_store],
292
  outputs=[thinking_output],
293
  queue=True
294
  )
295
- # Очищаем поле ввода
296
  send_chain.then(
297
  fn=lambda: "",
298
  inputs=[],
299
  outputs=[user_input],
300
- queue=True
301
  )
302
 
303
- # --- При нажатии Enter в поле ввода ---
304
  submit_chain = user_input.submit(
305
  fn=user_send_message,
306
  inputs=[user_input, history_state, model_dropdown, thinking_store],
@@ -319,15 +320,26 @@ with gr.Blocks() as demo:
319
  outputs=[thinking_output],
320
  queue=True
321
  )
322
- # Очищаем поле
323
  submit_chain.then(
324
  fn=lambda: "",
325
  inputs=[],
326
  outputs=[user_input],
327
- queue=True
 
 
 
 
 
 
 
 
 
 
 
 
328
  )
329
 
330
- # --- Кнопка «Очистить» ---
331
  clear_chain = clear_button.click(
332
  fn=clear_all,
333
  inputs=[],
@@ -343,8 +355,7 @@ with gr.Blocks() as demo:
343
  fn=lambda _: "",
344
  inputs=[],
345
  outputs=[thinking_output]
346
- )
347
- clear_chain.then(
348
  fn=lambda: "",
349
  inputs=[],
350
  outputs=[user_input]
 
2
  import gradio as gr
3
  import google.generativeai as genai
4
  import asyncio
5
+ import json
6
 
7
  ###############################################################################
8
+ # 1. Настройка окружения и инициализация моделей
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
+ # Список доступных моделей
20
  AVAILABLE_MODELS = [
21
+ "gemini-2.0-flash-exp", # Обычная (не-thinking)
22
+ "gemini-exp-1206", # Обычная (не-thinking)
23
+ "gemini-2.0-flash-thinking-exp-1219", # Thinking
24
  ]
25
 
26
+ # Инициализация
27
  MODELS = {}
28
  for model_name in AVAILABLE_MODELS:
29
  try:
30
  MODELS[model_name] = genai.GenerativeModel(model_name=model_name)
31
  except Exception as e:
32
+ print(f"[Предупреждение] Не удалось инициализировать модель {model_name}: {e}")
33
+
34
 
35
  ###############################################################################
36
+ # 2. Словарь с «дефолтными» системными промптами для каждой модели
37
  ###############################################################################
38
+
39
+ DEFAULT_SYSTEM_PROMPTS = {
40
  "gemini-2.0-flash-exp": (
41
+ "You are a normal model. "
42
+ "Just respond directly to the user without any JSON or extra formatting."
 
43
  ),
 
44
  "gemini-exp-1206": (
45
+ "You are also a normal model. "
46
+ "Please answer in plain text, with no JSON formatting."
 
47
  ),
 
48
  "gemini-2.0-flash-thinking-exp-1219": (
49
  "You are a thinking model. "
50
+ "Generate your final answer in the JSON format: {\"output\": \"...\"}. "
51
+ "You may include your chain-of-thought internally, but the user should only see the final JSON."
52
  ),
53
  }
54
 
55
+
56
  ###############################################################################
57
+ # 3. Определение ролей для ассистента в зависимости от модели
58
  ###############################################################################
59
+
60
  def _assistant_role(model_name: str) -> str:
61
  """
62
+ Пример: пусть для gemini-2.0-flash-thinking-exp-1219 требуется role="model",
63
+ а для остальных role="assistant".
64
+ Настраивайте эту логику под нужды своих моделей.
65
  """
66
+ if model_name == "gemini-2.0-flash-thinking-exp-1219":
 
 
 
67
  return "model"
68
  return "assistant"
69
 
70
+
71
  ###############################################################################
72
+ # 4. Утилиты для преобразования истории между Gradio-форматом и Gemini
73
  ###############################################################################
74
+
75
  def _history_gradio_to_genai(history, model_name):
76
  """
77
+ Gradio хранит историю как [(user_msg, bot_msg), (user_msg, bot_msg), ...].
78
+ Для моделей Gemini нужен формат [{'role': 'user'|'assistant'|'model', 'parts': ...}, ...].
79
+ Плюс добавляем в начало «дефолтный системный промпт» (если он ещё не добавлен).
 
 
 
80
  """
81
  genai_history = []
82
+ role_for_assistant = _assistant_role(model_name)
83
+
84
+ # 1) Добавляем «системное» сообщение, если его ещё нет
85
+ # (Положим, он будет первым в истории, со специальной role="system").
86
+ # Некоторые модели могут не поддерживать role="system".
87
+ # Если так, придётся хранить это как user.
88
+ system_prompt = DEFAULT_SYSTEM_PROMPTS.get(model_name, "")
89
+ # Проверим, добавляли ли мы уже системный промпт (для простоты — ищем по role="system")
90
+ has_system = any(h.get("role") == "system" for h in genai_history)
91
+
92
+ # Если нет в genai_history, добавим
93
+ if system_prompt and not has_system:
94
+ # Вставляем первым сообщением
95
  genai_history.append({"role": "system", "parts": system_prompt})
96
 
97
+ # 2) Далее преобразуем основную историю
 
98
  for user_text, bot_text in history:
 
99
  if user_text:
100
  genai_history.append({"role": "user", "parts": user_text})
 
101
  if bot_text:
102
+ genai_history.append({"role": role_for_assistant, "parts": bot_text})
103
 
104
  return genai_history
105
 
106
+
107
  ###############################################################################
108
+ # 5. Стримовый ответ и «thinking» ответ
109
  ###############################################################################
110
 
111
  async def _respond_stream(model_name, user_message, history):
112
  """
113
+ Обычные модели: стримим ответ.
114
  """
115
  if model_name not in MODELS:
116
+ yield "Ошибка: модель не найдена."
117
  return
118
 
119
  model = MODELS[model_name]
 
122
  try:
123
  chat = model.start_chat(history=genai_history)
124
  response_stream = chat.send_message(user_message, stream=True)
125
+
126
  partial_text = ""
127
  for chunk in response_stream:
128
  partial_text += (chunk.text or "")
129
  yield partial_text
130
+
131
  return
132
  except Exception as e:
133
  yield f"Ошибка при запросе к API: {e}"
 
136
 
137
  async def _respond_thinking(model_name, user_message, history):
138
  """
139
+ Thinking-модель:
140
+ - Сначала "Думаю..."
141
+ - Потом итог (обёрнутый в JSON, согласно системному промпту).
142
+ - Выделяем 'размышления' (if p.thought == True) для thinking_output.
 
143
  """
144
  if model_name not in MODELS:
145
+ yield "Ошибка: модель не найдена.", ""
146
  return
147
 
148
  model = MODELS[model_name]
 
157
 
158
  thinking_process_text = ""
159
  final_text = ""
160
+
161
  if response.candidates:
162
  parts = response.candidates[0].content.parts
163
  for p in parts:
164
+ # Если это "размышления"
165
  if getattr(p, "thought", False):
166
  thinking_process_text += p.text or ""
167
  else:
168
  final_text += p.text or ""
169
 
170
+ # Если системный промпт прописывает формат {"output": "..."},
171
+ # то (скорее всего) модель сама уже это подставит.
172
+ # Но вдруг нужно «подстраховаться» вручную.
173
+ # В зависимости от задачи:
174
+ # final_text = json.dumps({"output": final_text}, ensure_ascii=False)
175
 
176
+ yield final_text, thinking_process_text
177
  return
178
+
179
  except Exception as e:
180
  yield f"Ошибка при запросе к API: {e}", ""
181
  return
182
 
183
+
184
  ###############################################################################
185
+ # 6. Основная функция: user_send_message
186
  ###############################################################################
187
+
188
  async def user_send_message(user_message, history, model_name, thinking_text):
189
  """
190
+ Gradio будет вызывать эту функцию при нажатии «Отправить» или Enter.
191
+ Параметры:
192
+ user_message : новая реплика пользователя
193
+ history : [(user, assistant), ...]
194
+ model_name : выбранная модель
195
+ thinking_text: текущее «размышление» (в отдельном Textbox)
196
  """
197
+ # Если пользователь ничего не ввёл
198
  if not user_message.strip():
199
+ # Просто ничего не делаем
200
  yield history, thinking_text
201
  return
202
 
203
+ # Добавляем (user_message, None) в историю
204
  history.append((user_message, None))
205
 
206
+ # Если модель thinking
207
  if "thinking" in model_name.lower():
208
  async for (assistant_text, thought_text) in _respond_thinking(model_name, user_message, history):
 
209
  history[-1] = (user_message, assistant_text)
 
210
  yield history, thought_text
211
  return
212
  else:
 
213
  partial_answer = ""
214
  async for chunk in _respond_stream(model_name, user_message, history):
215
  partial_answer = chunk
 
217
  yield history, ""
218
  return
219
 
220
+
221
  ###############################################################################
222
+ # 7. Доп. колбэк для смены модели
223
  ###############################################################################
224
+
225
+ def switch_model(new_model_name, history):
226
  """
227
+ Когда пользователь меняет выпадающий список (Dropdown) с моделью,
228
+ мы добавляем в историю специальное «системное» сообщение,
229
+ чтобы модель знала о переключении.
230
  """
231
+ history.append(
232
+ ("System notice", f"User switched to a different model: {new_model_name}.")
233
+ )
234
+ return history
235
+
236
 
237
  ###############################################################################
238
+ # 8. Очистка
239
  ###############################################################################
240
+
241
  def clear_all():
 
242
  return [], ""
243
 
244
+
245
  ###############################################################################
246
+ # 9. Gradio-интерфейс
247
  ###############################################################################
 
 
248
 
249
+ with gr.Blocks() as demo:
250
+ gr.Markdown("## Chat с Gemini (несколько моделей, «thinking»-режим, JSON-ответы)")
 
 
 
 
251
 
252
  with gr.Row():
253
  model_dropdown = gr.Dropdown(
254
  choices=AVAILABLE_MODELS,
255
  value="gemini-2.0-flash-exp",
256
+ label="Выберите модель"
257
  )
258
  clear_button = gr.Button("Очистить чат")
259
 
260
+ # Состояние для хранения истории
261
+ history_state = gr.State([])
262
+ # Состояние для «размышлений»
263
+ thinking_store = gr.State("")
264
+
265
+ chatbot = gr.Chatbot(label="Диалог с Gemini")
266
+ user_input = gr.Textbox(label="Ваш вопрос", placeholder="Введите текст...")
267
+
268
  thinking_output = gr.Textbox(
269
+ label="Размышления (для thinking-моделей)",
270
  interactive=False
271
  )
 
272
 
273
+ send_btn = gr.Button("Отправить")
 
 
 
 
 
 
 
 
 
 
 
 
274
 
275
+ # 1) Логика при нажатии «Отправить»
276
  send_chain = send_btn.click(
277
  fn=user_send_message,
278
  inputs=[user_input, history_state, model_dropdown, thinking_store],
 
286
  outputs=[chatbot],
287
  queue=True
288
  )
289
+ # Обновляем поле «Размышления»
290
  send_chain.then(
291
  fn=lambda t: t,
292
  inputs=[thinking_store],
293
  outputs=[thinking_output],
294
  queue=True
295
  )
296
+ # Очищаем поле ввода (user_input)
297
  send_chain.then(
298
  fn=lambda: "",
299
  inputs=[],
300
  outputs=[user_input],
301
+ queue=False
302
  )
303
 
304
+ # 2) При нажатии Enter в поле ввода
305
  submit_chain = user_input.submit(
306
  fn=user_send_message,
307
  inputs=[user_input, history_state, model_dropdown, thinking_store],
 
320
  outputs=[thinking_output],
321
  queue=True
322
  )
 
323
  submit_chain.then(
324
  fn=lambda: "",
325
  inputs=[],
326
  outputs=[user_input],
327
+ queue=False
328
+ )
329
+
330
+ # 3) Колбэк при смене модели: добавляем системное сообщение в историю
331
+ model_dropdown.change(
332
+ fn=switch_model,
333
+ inputs=[model_dropdown, history_state],
334
+ outputs=[history_state],
335
+ queue=False
336
+ ).then(
337
+ fn=lambda h: h, # обновим Chatbot
338
+ inputs=[history_state],
339
+ outputs=[chatbot]
340
  )
341
 
342
+ # 4) Кнопка «Очистить чат»
343
  clear_chain = clear_button.click(
344
  fn=clear_all,
345
  inputs=[],
 
355
  fn=lambda _: "",
356
  inputs=[],
357
  outputs=[thinking_output]
358
+ ).then(
 
359
  fn=lambda: "",
360
  inputs=[],
361
  outputs=[user_input]