File size: 15,079 Bytes
e54a9ba
26471fd
f35bde9
e54a9ba
f35bde9
e54a9ba
 
 
 
 
 
 
 
 
 
 
 
 
899870f
e54a9ba
26471fd
e54a9ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26471fd
 
e54a9ba
26471fd
e54a9ba
 
26471fd
899870f
e54a9ba
a3dfced
899870f
a3dfced
 
26471fd
e54a9ba
 
899870f
26471fd
 
 
 
e54a9ba
 
26471fd
e54a9ba
 
 
 
 
 
 
26471fd
f35bde9
e54a9ba
a3dfced
 
 
 
 
 
 
 
e54a9ba
a3dfced
 
 
 
 
 
e54a9ba
 
 
 
a3dfced
 
e54a9ba
 
f35bde9
a3dfced
 
 
e54a9ba
a3dfced
e54a9ba
a3dfced
 
e54a9ba
 
 
a3dfced
e54a9ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26471fd
e54a9ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26471fd
e54a9ba
 
20ffed9
e54a9ba
a3dfced
e54a9ba
 
 
 
 
 
20ffed9
 
a3dfced
 
e54a9ba
20ffed9
 
e54a9ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20ffed9
a3dfced
e54a9ba
 
 
a3dfced
20ffed9
26471fd
e54a9ba
 
26471fd
e54a9ba
 
f35bde9
 
26471fd
20ffed9
f35bde9
a3dfced
20ffed9
e54a9ba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# 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)