Chat / app.py
HeavensHackDev's picture
Update app.py
e54a9ba verified
raw
history blame
15.1 kB
# app.py
import gradio as gr
from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM # Используем Auto классы для большей гибкости
import random
import re
import torch # Убедимся, что torch импортирован
# --- Выбор Модели ---
# Заменяем на самую маленькую DeepSeek Coder Instruct
# !!! ПРЕДУПРЕЖДЕНИЕ: МОЖЕТ НЕ ЗАПУСТИТЬСЯ ИЛИ БЫТЬ ОЧЕНЬ МЕДЛЕННОЙ НА FREE CPU !!!
CODE_MODEL_NAME = "deepseek-ai/deepseek-coder-1.3b-instruct"
# --- Загрузка моделей ---
GENERATOR_LOADED = False
QA_LOADED = False
generator = None
qa_pipeline = None
# 1. Модель для кодирования/генерации
try:
# Для моделей кода часто лучше использовать AutoModelForCausalLM и AutoTokenizer напрямую
# Вместо pipeline('text-generation') это дает больше контроля
print(f"Загрузка токенизатора: {CODE_MODEL_NAME}")
tokenizer = AutoTokenizer.from_pretrained(CODE_MODEL_NAME, trust_remote_code=True)
print(f"Загрузка модели: {CODE_MODEL_NAME}")
# Загружаем с torch_dtype=torch.float16 для экономии памяти, если возможно
try:
generator_model = AutoModelForCausalLM.from_pretrained(
CODE_MODEL_NAME,
trust_remote_code=True,
torch_dtype=torch.float16 # Пытаемся загрузить в половинной точности
)
except Exception as e_half: # Если float16 не поддерживается/вызывает ошибку, пробуем float32
print(f"Не удалось загрузить в float16 ({e_half}), пробую float32...")
generator_model = AutoModelForCausalLM.from_pretrained(
CODE_MODEL_NAME,
trust_remote_code=True
)
# Создаем pipeline вручную
generator = pipeline('text-generation', model=generator_model, tokenizer=tokenizer, device=-1) # device=-1 значит CPU
print(f"Генератор кода/текста ({CODE_MODEL_NAME}) загружен.")
GENERATOR_LOADED = True
except Exception as e:
print(f"ОШИБКА: Не удалось загрузить модель {CODE_MODEL_NAME}! {e}")
generator = None
generator_model = None
tokenizer = None
GENERATOR_LOADED = False
# 2. QA Модель (оставляем как есть или можно убрать, если не нужна)
QA_MODEL_NAME = 'timpal0l/mdeberta-v3-base-squad2'
try:
qa_pipeline = pipeline('question-answering', model=QA_MODEL_NAME)
print(f"QA модель ({QA_MODEL_NAME}) загружена.")
QA_LOADED = True
# !!! ВНИМАНИЕ: Две модели могут потребовать слишком много RAM !!!
# !!! Возможно, стоит закомментировать загрузку QA, если возникают проблемы с памятью !!!
except Exception as e:
print(f"ОШИБКА: Не удалось загрузить QA модель! {e}")
qa_pipeline = None
QA_LOADED = False
# --- Встроенные знания (можно сократить, т.к. модель сама умнее) ---
knowledge_base = {
"кто ты": f"Я Nova (использую модель {CODE_MODEL_NAME}), работающая на Hugging Face Spaces. Я специализируюсь на помощи с кодом и ответами на вопросы по программированию.",
"что ты умеешь": "Я могу пытаться генерировать код, объяснять его, находить ошибки (в определенной степени), отвечать на вопросы по программированию и поддерживать диалог.",
"как дела": "Работаю над кодом! А если серьезно - функционирую нормально.",
"помощь": "Спросите меня о программировании, попросите написать фрагмент кода (например, 'напиши функцию на python для суммирования списка'), или просто пообщаемся.",
"python": "Python - отличный высокоуровневый язык, известный своей читаемостью. Хорошо подходит для веб-разработки, анализа данных, ИИ и автоматизации.",
"javascript": "JavaScript - основной язык веб-фронтенда, позволяет делать страницы интерактивными. С Node.js используется и на бэкенде.",
# Убрали много базовых вещей, т.к. модель должна знать их сама
}
# --- Утилиты (clean_text, clean_generated_output - как раньше) ---
def clean_text(text):
if not isinstance(text, str): return ""
text = text.lower().strip()
text = re.sub(r"^[?!.,\s]+|[?!.,\s]+$", "", text)
text = re.sub(r"\s+", " ", text)
return text
def clean_generated_output(generated_text, prompt):
# Простая очистка - убираем промпт если он есть в начале
if not generated_text or not isinstance(generated_text, str): return ""
if not prompt or not isinstance(prompt, str): prompt = ""
cleaned = generated_text
prompt_clean = prompt.strip()
if prompt_clean and cleaned.startswith(prompt_clean):
cleaned = cleaned[len(prompt_clean):].strip()
# Попробуем убрать стандартные фразы, которые модель может добавить
cleaned = re.sub(r'^\s*(ответ|бот|вот код|конечно|here is the code)\s*[:\-]?\s*', '', cleaned, flags=re.IGNORECASE).strip()
return cleaned
# --- Основная Функция Обработки (Упрощенная, больше полагаемся на модель) ---
def respond(message, chat_history):
print("-" * 30)
print(f"ВХОД: '{message}'")
user_message_clean = clean_text(message)
bot_response = ""
# 1. Проверка на точное совпадение в knowledge_base (ОСТАВЛЯЕМ для мета-вопросов)
if user_message_clean in knowledge_base:
bot_response = knowledge_base[user_message_clean]
print(f"Ответ [Точный]: {bot_response[:100]}...")
# 2. Основная генерация (используем модель DeepSeek Coder)
if not bot_response and GENERATOR_LOADED and generator:
print("Генерация ответа с помощью модели кода...")
# Формируем промпт в формате, который модель ожидает (часто нужен специальный формат для Instruct-моделей)
# Для DeepSeek Coder Instruct формат может быть таким:
prompt_list = []
prompt_list.append("### Instruction:")
# Добавим контекст из чата (если есть)
for user_msg, bot_msg in chat_history[-2:]: # Последние 2 обмена для контекста
prompt_list.append(f"User: {user_msg}")
if bot_msg: prompt_list.append(f"Assistant: {bot_msg}") # Используем Assistant
prompt_list.append(f"{message}") # Текущее сообщение как инструкция/вопрос
prompt_list.append("\n### Response:") # Приглашение для ответа
full_prompt = "\n".join(prompt_list)
print(f"Промпт для DeepSeek: ...{full_prompt[-600:]}") # Логируем конец
try:
# Параметры генерации для кода
# `max_new_tokens` - ограничивает длину *сгенерированного* текста
# `temperature` - контроль случайности (ниже = более предсказуемо)
# `top_p`, `top_k` - другие методы семплирования
# `eos_token_id` - ID токена конца последовательности (может помочь модели остановиться)
generated_output = generator(
full_prompt,
max_new_tokens=250, # Ограничим генерацию
temperature=0.7,
top_k=50,
top_p=0.95,
# eos_token_id=tokenizer.eos_token_id if tokenizer else None, # Помогает модели остановиться
pad_token_id=tokenizer.eos_token_id if tokenizer else 50256, # Часто нужно указать
num_return_sequences=1,
do_sample=True
)[0]['generated_text']
print(f"Сырой ответ DeepSeek: {generated_output}")
# Очистка - убираем весь промпт
bot_response = clean_generated_output(generated_output, full_prompt)
print(f"Очищенный ответ DeepSeek: {bot_response}")
# Дополнительно убираем инструкции, которые модель могла повторить
bot_response = bot_response.replace("### Instruction:", "").replace("### Response:", "").strip()
if not bot_response or len(bot_response) < 5:
bot_response = knowledge_base.get("не найдено", "Извините, не смог сгенерировать ответ.")
print("Генерация DeepSeek не удалась или слишком короткая.")
except Exception as e:
print(f"Ошибка при генерации DeepSeek: {e}")
bot_response = knowledge_base.get("ошибка генерации", f"Произошла ошибка при генерации: {e}")
# 3. Ответ по умолчанию
if not bot_response:
# Если генератор не загружен или ничего не получилось
if not GENERATOR_LOADED:
bot_response = "Модель для генерации кода не загружена."
else:
bot_response = knowledge_base.get("не найдено", "К сожалению, я пока не знаю, как на это ответить.")
print("Ответ [Заглушка/Ошибка]")
# Добавляем в историю и возвращаем
chat_history.append((message, bot_response))
return "", chat_history
# --- Gradio Интерфейс (CSS тот же) ---
custom_css = """
.gradio-container { background-color: #f4f7f9; border-radius: 15px; padding: 25px; color: #333; }
h1 { color: #1a237e; text-align: center; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin-bottom: 15px; font-weight: 600; }
#title-markdown p { text-align: center; color: #546e7a; margin-top: -10px; margin-bottom: 25px;}
#chatbot { background-color: #eceff1; border-radius: 12px; border: 1px solid #cfd8dc; padding: 10px; box-shadow: inset 0 1px 3px rgba(0,0,0,0.05); }
#chatbot .user-message .message-bubble-border { border: none !important; }
#chatbot .user-message .message-bubble { background: #007bff !important; color: white !important; border-radius: 18px 18px 5px 18px !important; padding: 12px 18px !important; margin: 8px 5px 8px 0 !important; align-self: flex-end !important; max-width: 80% !important; box-shadow: 0 2px 4px rgba(0, 123, 255, 0.2); word-wrap: break-word; text-align: left;}
#chatbot .bot-message .message-bubble-border { border: none !important; }
#chatbot .bot-message .message-bubble { background: #ffffff !important; color: #212529 !important; border: 1px solid #dee2e6 !important; border-radius: 18px 18px 18px 5px !important; padding: 12px 18px !important; margin: 8px 0 8px 5px !important; align-self: flex-start !important; max-width: 80% !important; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); word-wrap: break-word; text-align: left;}
#chatbot .bot-message .message-bubble pre { background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 6px; padding: 10px; margin: 8px 0 5px 0; overflow-x: auto; word-wrap: normal; }
#chatbot .bot-message .message-bubble pre code { background-color: transparent !important; color: #2d3748; font-family: 'Consolas', 'Monaco', 'Courier New', monospace; font-size: 0.9em; padding: 0; white-space: pre; }
textarea { border: 1px solid #ced4da !important; border-radius: 10px !important; padding: 12px 15px !important; background-color: #ffffff; transition: border-color 0.3s ease, box-shadow 0.3s ease; font-size: 1rem; }
textarea:focus { border-color: #80bdff !important; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); outline: none; }
button { border-radius: 10px !important; padding: 10px 15px !important; transition: background-color 0.2s ease, transform 0.1s ease !important; font-weight: 500 !important; border: none !important; }
button:active { transform: translateY(1px); }
button.primary { background-color: #007bff !important; color: white !important; }
button.primary:hover { background-color: #0056b3 !important; box-shadow: 0 2px 5px rgba(0, 123, 255, 0.3); }
button.secondary { background-color: #6c757d !important; color: white !important; }
button.secondary:hover { background-color: #5a6268 !important; box-shadow: 0 2px 5px rgba(108, 117, 125, 0.3); }
"""
with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue=gr.themes.colors.blue, secondary_hue=gr.themes.colors.slate)) as demo:
gr.Markdown("# 🤖 Nova (DeepSeek Test) 💡 <br> Используем модель для кода. Ответы могут быть ОЧЕНЬ медленными!")
chatbot = gr.Chatbot(
label="Диалог", height=550, elem_id="chatbot",
show_copy_button=True, bubble_full_width=False,
avatar_images=(None, "https://img.icons8.com/plasticine/100/bot.png")
)
with gr.Row():
msg = gr.Textbox(
label="Ваше сообщение", placeholder="Напиши функцию python для...",
scale=4, show_label=False, container=False
)
submit_btn = gr.Button("➤ Отправить", variant="primary", scale=1, min_width=120)
clear_btn = gr.Button("🗑️ Очистить", variant="secondary", scale=1, min_width=120)
msg.submit(respond, [msg, chatbot], [msg, chatbot])
submit_btn.click(respond, [msg, chatbot], [msg, chatbot])
clear_btn.click(lambda: (None, []), None, [msg, chatbot], queue=False)
# Запуск
demo.queue()
demo.launch(debug=True)