import os import gradio as gr import google.generativeai as genai import asyncio ############################################################################### # 1. Настройка окружения и инициализация моделей ############################################################################### # Подставьте свой ключ или берите из окружения GEMINI_API_KEY = "AIzaSyBoqoPX-9uzvXyxzse0gRwH8_P9xO6O3Bc" 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. Дефолтные промпты (developer role) для каждой модели ############################################################################### # Когда пользователь переключается на модель, мы добавляем это сообщение в историю. DEFAULT_DEVELOPER_PROMPTS = { "gemini-2.0-flash-exp": ( "You are a normal model (developer role). " "Provide direct answers with no JSON wrapping." ), "gemini-exp-1206": ( "You are an experimental normal model (developer role). " "Provide direct answers with no JSON wrapping." ), "gemini-2.0-flash-thinking-exp-1219": ( "You are a thinking model (developer role). " "Please provide your final answer in the format {output: ...}. " "You may use internal thoughts but do not show them directly to the user." ), } ############################################################################### # 3. Функция для определения роли ассистента (assistant vs model) ############################################################################### def _assistant_role(model_name: str) -> str: """ Некоторые новые модели не принимают 'assistant', а требуют 'model'. """ # Допустим "gemini-exp-1206" и "gemini-2.0-flash-thinking-exp-1219" хотят "model" if model_name in ["gemini-exp-1206", "gemini-2.0-flash-thinking-exp-1219"]: return "model" return "assistant" ############################################################################### # 4. Преобразование истории из Gradio в формат Generative AI ############################################################################### def _history_to_genai_enhanced(history, model_name): """ Улучшенная версия, отличающая developer-сообщения (префикс ": ") от user-сообщений. """ asst_role = _assistant_role(model_name) genai_history = [] for user_text, bot_text in history: if user_text: if user_text.startswith(": "): # Считаем это developer role dev_content = user_text.replace(": ", "", 1) genai_history.append({"role": "system", "parts": dev_content}) else: # Обычный пользователь genai_history.append({"role": "user", "parts": user_text}) if bot_text: # Ответ ассистента / модель genai_history.append({"role": asst_role, "parts": bot_text}) return genai_history ############################################################################### # 5. Генераторы для стрима обычных моделей и "thinking" моделей ############################################################################### async def _respond_stream_enh(model_name, user_message, history): """ Стриминговый ответ для обычных моделей: - Кусочек за кусочком (partial_text). """ if model_name not in MODELS: yield "Ошибка: модель не найдена." return model = MODELS[model_name] genai_history = _history_to_genai_enhanced(history, model_name) try: chat = model.start_chat(history=genai_history) stream = chat.send_message(user_message, stream=True) partial_text = "" async for chunk in stream: partial_text += (chunk.text or "") yield partial_text return except Exception as e: yield f"Ошибка при запросе к API: {e}" return async def _respond_thinking_enh(model_name, user_message, history): """ Для thinking-моделей: 1) Выводим "Думаю..." 2) После завершения — финальный ответ в формате {output: ...} + размышления. """ if model_name not in MODELS: yield "Ошибка: модель не найдена.", "" return model = MODELS[model_name] genai_history = _history_to_genai_enhanced(history, model_name) # Сначала "Думаю..." 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 hasattr(p, "thought") and p.thought: thinking_process_text += p.text or "" else: final_text += p.text or "" # Для thinking-моделей просили итоговый ответ в {output: ...} final_text_formatted = f"{{output: {final_text}}}" yield final_text_formatted, thinking_process_text return except Exception as e: yield f"Ошибка при запросе к API: {e}", "" return ############################################################################### # 6. Основная функция для ввода пользователя ############################################################################### async def user_send_message( user_message: str, history: list[tuple[str, str]], model_name: str, thinking_text: str ): """ Колбэк, когда пользователь отправляет запрос. Добавляем в history новый (user_msg, None), затем генерируем ответ. """ # Пустой ввод if not user_message.strip(): yield history, thinking_text return # Добавляем (user_message, None) history.append((user_message, None)) # Если модель — thinking if "thinking" in model_name.lower(): async for (assistant_text, thought_text) in _respond_thinking_enh(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_enh(model_name, user_message, history): partial_answer = chunk history[-1] = (user_message, partial_answer) yield history, "" return ############################################################################### # 7. Очистка диалога ############################################################################### def clear_all(): """Сброс истории и размышлений.""" return [], "" ############################################################################### # 8. Когда меняем модель в Dropdown ############################################################################### def on_model_change(selected_model, history, thinking_text): """ При переключении модели добавляем в историю developer-сообщение, + добавляем дефолтный промпт этой модели (тоже developer). """ new_history = history.copy() # Cообщаем модели, что переключились (developer role) new_history.append(( ": Switched to model: " + selected_model, None )) # Добавляем дефолтный промпт (developer role) default_prompt = DEFAULT_DEVELOPER_PROMPTS.get( selected_model, "No default prompt for this model." ) new_history.append(( ": " + default_prompt, None )) return new_history, thinking_text ############################################################################### # 9. Функция конвертации истории с учётом developer role ############################################################################### def _history_to_genai_enhanced(history, model_name): """ Улучшенная версия, отличающая developer-сообщения (префикс ": ") от user-сообщений. """ asst_role = _assistant_role(model_name) genai_history = [] for user_text, bot_text in history: if user_text: if user_text.startswith(": "): # Считаем это developer role dev_content = user_text.replace(": ", "", 1) genai_history.append({"role": "system", "parts": dev_content}) else: # Обычный пользователь genai_history.append({"role": "user", "parts": user_text}) if bot_text: # Ответ ассистента / модель genai_history.append({"role": asst_role, "parts": bot_text}) return genai_history ############################################################################### # 10. Построение интерфейса Gradio ############################################################################### with gr.Blocks() as demo: gr.Markdown("## Chat с Gemini. Поддержка developer role, переключения моделей, JSON-ответа для thinking") with gr.Row(): model_dropdown = gr.Dropdown( choices=AVAILABLE_MODELS, value="gemini-2.0-flash-exp", label="Выберите модель" ) clear_button = gr.Button("Очистить чат") history_state = gr.State([]) thinking_store = gr.State("") chatbot = gr.Chatbot(label="Диалог с Gemini") user_input = gr.Textbox(label="Ваш вопрос", placeholder="Введите текст...") thinking_output = gr.Textbox(label="Размышления", interactive=False) send_btn = gr.Button("Отправить") ################################################ # (A) Обработка переключения модели ################################################ def handle_model_change(selected_model, history, thinking): new_history, new_thinking = on_model_change(selected_model, history, thinking) return new_history, new_thinking # Когда пользователь меняет модель: model_change = model_dropdown.change( fn=handle_model_change, inputs=[model_dropdown, history_state, thinking_store], outputs=[history_state, thinking_store], queue=False ).then( # После добавления developer-сообщений в историю → обновляем чат fn=lambda h: h, inputs=[history_state], outputs=[chatbot], queue=False ) ################################################ # (B) При нажатии «Отправить» ################################################ 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=False ) ################################################ # (C) При нажатии Enter в textbox ################################################ 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=False ) ################################################ # (D) Кнопка «Очистить» ################################################ 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()