Gemini-2.0 / app.py
vortex123's picture
Update app.py
52215c5 verified
raw
history blame
13.3 kB
import os
import gradio as gr
import google.generativeai as genai
import asyncio
###############################################################################
# 1. Настройка окружения и моделей
###############################################################################
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "ВАШ_КЛЮЧ_ЗДЕСЬ")
if not GEMINI_API_KEY:
print("Error: GEMINI_API_KEY is not set.")
exit()
genai.configure(api_key=GEMINI_API_KEY)
# Список моделей
AVAILABLE_MODELS = [
"gemini-2.0-flash-exp",
"gemini-exp-1206",
"gemini-2.0-flash-thinking-exp-1219",
]
# Инициализация моделей
MODELS = {}
for model_name in AVAILABLE_MODELS:
try:
MODELS[model_name] = genai.GenerativeModel(model_name=model_name)
except Exception as e:
print(f"[!] Не удалось инициализировать {model_name}: {e}")
###############################################################################
# 2. Промпты по умолчанию (system message) для каждой модели
###############################################################################
MODEL_SYSTEM_PROMPTS = {
# Пример: для обычной модели
"gemini-2.0-flash-exp": (
"You are a helpful assistant. "
"You respond in Markdown. "
"You do NOT wrap your final answer in {output: ...}."
),
# Допустим, это тоже обычная модель
"gemini-exp-1206": (
"You are an experimental Gemini model. "
"You respond in Markdown. "
"You do NOT wrap your final answer in {output: ...}."
),
# Думающая модель
"gemini-2.0-flash-thinking-exp-1219": (
"You are a thinking model. "
"You respond with thoughts internally, but produce the **final** answer in JSON format, like `{output: (final text)}`."
),
}
###############################################################################
# 3. Определяем, какую роль использовать: 'assistant' или 'model'
###############################################################################
def _assistant_role(model_name: str) -> str:
"""
Для некоторых моделей Gemini требуется role='model', а не 'assistant'.
Если столкнетесь с ошибкой "Please use a valid role: user, model",
укажите здесь 'model'.
"""
# Условие — подстройте под свои модели:
# Предположим, gemini-exp-1206 и gemini-2.0-flash-thinking-exp-1219
# требуют role='model', а остальные — 'assistant'
if model_name in ["gemini-exp-1206", "gemini-2.0-flash-thinking-exp-1219"]:
return "model"
return "assistant"
###############################################################################
# 4. Утилиты для конвертации истории
###############################################################################
def _history_gradio_to_genai(history, model_name):
"""
Gradio: [(user_msg, bot_msg), (user_msg, bot_msg), ...]
→ Gemini: [{'role': 'user'|'assistant'|'model'|'system', 'parts': ...}, ...]
Добавляем system-сообщение (промпт по умолчанию) в начало.
Далее все user-реплики = {'role': 'user'},
все ответы ассистента = {'role': <assistant_role>}.
"""
genai_history = []
# Системное сообщение (промпт по умолчанию)
system_prompt = MODEL_SYSTEM_PROMPTS.get(model_name, "")
if system_prompt:
genai_history.append({"role": "system", "parts": system_prompt})
asst_role = _assistant_role(model_name)
for user_text, bot_text in history:
# Сообщение пользователя
if user_text:
genai_history.append({"role": "user", "parts": user_text})
# Сообщение "ассистента"
if bot_text:
genai_history.append({"role": asst_role, "parts": bot_text})
return genai_history
###############################################################################
# 5. Функции-генераторы для ответа
###############################################################################
async def _respond_stream(model_name, user_message, history):
"""
Обычная модель, stream=True: выдаём ответ порциями.
"""
if model_name not in MODELS:
yield "Error: model not found."
return
model = MODELS[model_name]
genai_history = _history_gradio_to_genai(history, model_name)
try:
chat = model.start_chat(history=genai_history)
response_stream = chat.send_message(user_message, stream=True)
partial_text = ""
for chunk in response_stream:
partial_text += (chunk.text or "")
yield partial_text
return
except Exception as e:
yield f"Ошибка при запросе к API: {e}"
return
async def _respond_thinking(model_name, user_message, history):
"""
Думающая модель:
1) "Думаю..."
2) По завершении:
- собираем "мысли" (part.thought)
- итоговый ответ оборачиваем в {output: ...} (согласно ТЗ)
"""
if model_name not in MODELS:
yield "Error: model not found.", ""
return
model = MODELS[model_name]
genai_history = _history_gradio_to_genai(history, model_name)
# Шаг 1: "Думаю..."
yield "Думаю...", ""
try:
chat = model.start_chat(history=genai_history)
response = chat.send_message(user_message, stream=False)
thinking_process_text = ""
final_text = ""
if response.candidates:
parts = response.candidates[0].content.parts
for p in parts:
if getattr(p, "thought", False):
thinking_process_text += p.text or ""
else:
final_text += p.text or ""
# По ТЗ: оборачиваем финальный ответ в {output: ...}
final_text_json = f"{{output: ({final_text})}}"
yield final_text_json, thinking_process_text
return
except Exception as e:
yield f"Ошибка при запросе к API: {e}", ""
return
###############################################################################
# 6. Основная функция для одного шага диалога
###############################################################################
async def user_send_message(user_message, history, model_name, thinking_text):
"""
Пользователь ввёл user_message; на основе history + model_name генерируем ответ.
Возвращаем (history, thinking_text) через yield на каждом шаге.
"""
# Если пустая строка — ничего не делаем
if not user_message.strip():
yield history, thinking_text
return
# Добавляем ход в history, пока ответ ассистента None
history.append((user_message, None))
# Проверяем, является ли модель "думающей"
if "thinking" in model_name.lower():
async for (assistant_text, thought_text) in _respond_thinking(model_name, user_message, history):
# Обновляем ответ в истории
history[-1] = (user_message, assistant_text)
# Обновляем блок размышлений
yield history, thought_text
return
else:
# Обычная модель
partial_answer = ""
async for chunk in _respond_stream(model_name, user_message, history):
partial_answer = chunk
history[-1] = (user_message, partial_answer)
yield history, ""
return
###############################################################################
# 7. Логика переключения модели, чтобы «сообщать» о переключении
###############################################################################
def switch_model(old_model, new_model, history):
"""
При смене модели добавляем в историю user-сообщение:
"I’m switching from {old_model} to {new_model}."
Сохраняем новое имя модели в state (prev_model_state).
"""
if old_model and new_model and old_model != new_model:
switch_message = f"I’m switching from {old_model} to {new_model}."
history.append((switch_message, None)) # user_msg, None
return new_model, history
###############################################################################
# 8. Функция очистки
###############################################################################
def clear_all():
"""Сбросить историю и размышления."""
return [], ""
###############################################################################
# 9. Сборка Gradio-интерфейса
###############################################################################
with gr.Blocks() as demo:
gr.Markdown("## Chat with Gemini (Thinking & Non-Thinking Models)")
# Храним «предыдущую модель», чтобы отследить переключение
prev_model_state = gr.State("")
# Храним историю [(user, assistant), ...]
history_state = gr.State([])
# Храним размышления (только для думающей модели)
thinking_store = gr.State("")
with gr.Row():
model_dropdown = gr.Dropdown(
choices=AVAILABLE_MODELS,
value="gemini-2.0-flash-exp",
label="Choose a model"
)
clear_button = gr.Button("Очистить чат")
chatbot = gr.Chatbot(
label="Диалог с Gemini",
markdown=True # Включаем поддержку Markdown
)
user_input = gr.Textbox(
label="Ваш вопрос",
placeholder="Введите текст...",
)
thinking_output = gr.Textbox(
label="Размышления (если модель думающая)",
interactive=False
)
send_btn = gr.Button("Отправить")
# --- Обработка переключения модели ---
model_dropdown.change(
fn=switch_model,
inputs=[prev_model_state, model_dropdown, history_state],
outputs=[prev_model_state, history_state],
queue=False
).then(
# После смены модели обновим чат (чтобы увидеть user-сообщение о переключении)
fn=lambda h: h,
inputs=[history_state],
outputs=[chatbot],
queue=False
)
# --- При нажатии кнопки «Отправить» ---
send_chain = send_btn.click(
fn=user_send_message,
inputs=[user_input, history_state, model_dropdown, thinking_store],
outputs=[history_state, thinking_store],
queue=True
)
# Обновляем чат
send_chain.then(
fn=lambda h: h,
inputs=[history_state],
outputs=[chatbot],
queue=True
)
# Обновляем размышления
send_chain.then(
fn=lambda t: t,
inputs=[thinking_store],
outputs=[thinking_output],
queue=True
)
# Очищаем поле ввода
send_chain.then(
fn=lambda: "",
inputs=[],
outputs=[user_input],
queue=True
)
# --- При нажатии Enter в поле ввода ---
submit_chain = user_input.submit(
fn=user_send_message,
inputs=[user_input, history_state, model_dropdown, thinking_store],
outputs=[history_state, thinking_store],
queue=True
)
submit_chain.then(
fn=lambda h: h,
inputs=[history_state],
outputs=[chatbot],
queue=True
)
submit_chain.then(
fn=lambda t: t,
inputs=[thinking_store],
outputs=[thinking_output],
queue=True
)
# Очищаем поле
submit_chain.then(
fn=lambda: "",
inputs=[],
outputs=[user_input],
queue=True
)
# --- Кнопка «Очистить» ---
clear_chain = clear_button.click(
fn=clear_all,
inputs=[],
outputs=[history_state, thinking_store],
queue=False
)
clear_chain.then(
fn=lambda h: h,
inputs=[history_state],
outputs=[chatbot]
)
clear_chain.then(
fn=lambda _: "",
inputs=[],
outputs=[thinking_output]
)
clear_chain.then(
fn=lambda: "",
inputs=[],
outputs=[user_input]
)
if __name__ == "__main__":
demo.launch()