Spaces:
Build error
Build error
| 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-сообщения | |
| (префикс "<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>: "): | |
| # Считаем это developer role | |
| dev_content = user_text.replace("<developer>: ", "", 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(( | |
| "<developer>: 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(( | |
| "<developer>: " + default_prompt, | |
| None | |
| )) | |
| return new_history, thinking_text | |
| ############################################################################### | |
| # 9. Функция конвертации истории с учётом developer role | |
| ############################################################################### | |
| def _history_to_genai_enhanced(history, model_name): | |
| """ | |
| Улучшенная версия, отличающая developer-сообщения | |
| (префикс "<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>: "): | |
| # Считаем это developer role | |
| dev_content = user_text.replace("<developer>: ", "", 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() |