File size: 12,930 Bytes
6925fdd
adbdeb6
6925fdd
 
 
 
 
adbdeb6
6925fdd
 
 
 
adbdeb6
6925fdd
 
 
 
 
 
 
 
 
adbdeb6
6925fdd
 
 
 
 
 
 
 
 
 
 
 
adbdeb6
6925fdd
adbdeb6
6925fdd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
adbdeb6
6925fdd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
adbdeb6
 
 
6925fdd
adbdeb6
6925fdd
adbdeb6
 
 
 
 
 
 
 
 
6925fdd
 
adbdeb6
6925fdd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
adbdeb6
6925fdd
 
 
 
 
 
adbdeb6
6925fdd
adbdeb6
6925fdd
 
 
adbdeb6
6925fdd
 
 
 
 
 
 
 
3eaaefb
 
 
 
 
 
6925fdd
3eaaefb
2824061
3eaaefb
 
 
2824061
3eaaefb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6925fdd
 
adbdeb6
6925fdd
adbdeb6
 
6925fdd
adbdeb6
 
6925fdd
 
 
 
 
 
adbdeb6
 
 
 
 
 
6925fdd
 
 
 
 
 
 
 
 
 
 
 
adbdeb6
6925fdd
 
 
 
 
 
 
 
 
 
 
adbdeb6
6925fdd
 
adbdeb6
6925fdd
adbdeb6
 
6925fdd
 
adbdeb6
2824061
6925fdd
adbdeb6
6925fdd
adbdeb6
 
 
 
 
6925fdd
adbdeb6
 
 
6925fdd
adbdeb6
 
 
 
 
 
 
 
 
6925fdd
 
 
adbdeb6
 
6925fdd
adbdeb6
 
 
 
 
 
6925fdd
adbdeb6
 
 
 
6925fdd
 
 
 
 
 
adbdeb6
6925fdd
 
adbdeb6
6925fdd
 
3eaaefb
 
 
 
 
 
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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import gradio as gr
import os
import re

# Конфигурация модели
MODEL_NAME = "Qwen/Qwen2.5-Coder-14B-Instruct-GPTQ-Int4"
DEVICE = "cpu"  # Запуск на CPU

# Глобальная загрузка модели (один раз при запуске)
def load_model():
    print("🔄 Загружаем токенизатор...")
    tokenizer = AutoTokenizer.from_pretrained(
        MODEL_NAME,
        trust_remote_code=True
    )
    
    # Добавляем pad token если его нет
    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token
    
    print("🔄 Загружаем GPTQ-модель Qwen2.5-Coder-14B...")
    model = AutoModelForCausalLM.from_pretrained(
        MODEL_NAME,
        torch_dtype=torch.float32,  # Используем float32 для CPU
        device_map="cpu",           # Явно указываем CPU
        trust_remote_code=True
    )
    model.eval()  # Переводим модель в режим оценки
    return tokenizer, model

# Загружаем модель один раз при старте
try:
    tokenizer, model = load_model()
    print("✅ Qwen2.5-Coder-14B-Instruct-GPTQ-Int4 успешно загружена!")
except Exception as e:
    print(f"❌ Ошибка загрузки модели: {e}")
    tokenizer, model = None, None

def read_file_content(file_path):
    """Читает содержимое файла с обработкой различных кодировок"""
    try:
        # Пробуем разные кодировки
        encodings = ['utf-8', 'cp1251', 'iso-8859-1', 'windows-1251']
        
        for encoding in encodings:
            try:
                with open(file_path, 'r', encoding=encoding) as f:
                    content = f.read()
                return content
            except UnicodeDecodeError:
                continue
        
        # Если текстовые кодировки не работают, пробуем бинарный режим
        if file_path.endswith(('.py', '.txt', '.js', '.html', '.css', '.json', '.md')):
            with open(file_path, 'rb') as f:
                content = f.read()
            return content.decode('utf-8', errors='replace')
        else:
            return f"Файл {os.path.basename(file_path)} не является текстовым файлом"
            
    except Exception as e:
        return f"Ошибка чтения файла: {str(e)}"

def extract_search_terms(prompt):
    """Извлекает поисковые термины из промпта в формате [поиск: ...]"""
    search_pattern = r'\[поиск:\s*(.*?)\]'
    matches = re.findall(search_pattern, prompt, re.IGNORECASE)
    
    if matches:
        # Удаляем поисковую часть из промпта
        clean_prompt = re.sub(search_pattern, '', prompt).strip()
        return clean_prompt, matches[0].strip()
    
    return prompt, None

def generate_code_with_context(prompt, files, max_length=1024, temperature=0.7, top_p=0.9):
    """
    Генерирует код на основе промпта пользователя с учетом загруженных файлов.
    
    Эта функция автоматически станет доступна как MCP-инструмент для других приложений.
    
    Args:
        prompt (str): Запрос пользователя, может содержать [поиск: термин]
        files (list): Список загруженных файлов для анализа
        max_length (int): Максимальная длина ответа в токенах
        temperature (float): Параметр температуры для генерации
        top_p (float): Параметр top-p для генерации
        
    Returns:
        str: Сгенерированный код или сообщение об ошибке
    """
    if model is None or tokenizer is None:
        return "❌ Ошибка: модель не загружена. Проверьте:\n- Подключение к интернету\n- Достаточно ли оперативной памяти (рекомендуется 16+ ГБ)\n- Установлены ли зависимости: `pip install auto-gptq optimum`"
    
    try:
        # Извлекаем поисковые термины из промпта
        clean_prompt, search_term = extract_search_terms(prompt)
        
        # Обрабатываем загруженные файлы
        file_contexts = []
        
        if files:
            for file_info in files:
                if hasattr(file_info, 'name'):
                    file_path = file_info.name
                else:
                    file_path = file_info
                
                content = read_file_content(file_path)
                filename = os.path.basename(file_path)
                file_contexts.append(f"Файл: {filename}\n```\n{content[:2000]}\n```")
        
        # Формируем финальный промпт
        final_prompt = clean_prompt
        if file_contexts:
            files_context = "\n\n".join(file_contexts)
            final_prompt = f"""Контекст из загруженных файлов:
{files_context}

Запрос: {clean_prompt}"""
        
        # Форматируем сообщение для модели в соответствии с официальным форматом Qwen2.5 :cite[1]
        messages = [
            {"role": "system", "content": "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."},
            {"role": "user", "content": final_prompt}
        ]
        
        # Применяем шаблон чата :cite[1]
        text = tokenizer.apply_chat_template(
            messages,
            tokenize=False,
            add_generation_prompt=True
        )
        
        # Токенизируем входные данные с ограничением длины
        inputs = tokenizer(
            text, 
            return_tensors="pt", 
            truncation=True, 
            max_length=2048,
            padding=True,
            return_attention_mask=True
        )
    
        # Генерация с использованием обновленного кода и attention_mask
        with torch.no_grad():
            generated_ids = model.generate(
                inputs.input_ids,
                attention_mask=inputs.attention_mask,  # Добавляем attention_mask
                max_new_tokens=max_length,
                temperature=temperature,
                top_p=top_p,
                do_sample=True,
                pad_token_id=tokenizer.eos_token_id,
                repetition_penalty=1.1,
                no_repeat_ngram_size=3
            )
            
            # Декодируем результат
            generated_text = tokenizer.decode(generated_ids[0], skip_special_tokens=True)
            
            # Убираем оригинальный промпт из ответа
            if generated_text.startswith(text):
                response = generated_text[len(text):].strip()
            else:
                response = generated_text.strip()
            
            return response
        
    except Exception as e:
        return f"❌ Ошибка при генерации кода: {str(e)}"

# Создаем интерфейс Gradio
with gr.Blocks(title="Qwen2.5-Coder-14B with MCP", theme=gr.themes.Soft()) as demo:
    gr.Markdown("""
    # 🚀 Qwen2.5-Coder-14B-Instruct-GPTQ-Int4 + MCP
    **Профессиональный генератор кода с поддержкой Model Context Protocol**
    """)
    
    with gr.Row():
        with gr.Column():
            prompt_input = gr.Textbox(
                lines=4,
                placeholder="""Введите ваш запрос... Примеры:
- "Напиши функцию для быстрой сортировки на Python"
- "Создай REST API на FastAPI для управления пользователями"
- "Найди и исправь ошибку в загруженном коде [поиск: syntax error]" """,
                label="Запрос на генерацию кода",
                info="Используйте [поиск: ...] для поиска в файлах"
            )
            
            with gr.Accordion("📁 Загрузка файлов для анализа", open=True):
                file_input = gr.File(
                    label="Загрузите файлы для анализа",
                    file_count="multiple",
                    file_types=[".txt", ".py", ".js", ".html", ".css", ".json", ".md", ".java", ".cpp", ".c"],
                    type="filepath"
                )
            
            with gr.Accordion("⚙️ Параметры генерации", open=False):
                max_length_slider = gr.Slider(
                    minimum=100, maximum=2048, value=512,
                    label="Максимальная длина ответа (токены)"
                )
                temperature_slider = gr.Slider(
                    minimum=0.1, maximum=1.0, value=0.7,
                    label="Температура (креативность)"
                )
                top_p_slider = gr.Slider(
                    minimum=0.1, maximum=1.0, value=0.9,
                    label="Top-p (вероятностный отбор)"
                )
            
            generate_btn = gr.Button("🚀 Сгенерировать код", variant="primary")
            
        with gr.Column():
            response_output = gr.Textbox(
                label="Сгенерированный код",
                lines=18,
                show_copy_button=True
            )
    
    # Добавляем информацию о MCP
    with gr.Accordion("🔗 MCP Сервер - Подключение к другим приложениям", open=True):
        gr.Markdown("""
        **MCP (Model Context Protocol) сервер активирован!**
        
        Ваш генератор кода теперь доступен как MCP-инструмент для:
        - Claude Desktop
        - Cursor 
        - Cline
        - Других MCP-клиентов
        
        **URL для подключения:**
        - Основной MCP URL: `http://localhost:7860/gradio_api/mcp/`
        - SSE URL: `http://localhost:7860/gradio_api/mcp/sse`
        
        **Для подключения к Claude Desktop** добавьте в настройки (`claude_desktop_config.json`):
        ```json
        {
          "mcpServers": {
            "qwen-coder-generator": {
              "url": "http://localhost:7860/gradio_api/mcp/sse"
            }
          }
        }
        ```
        """)
    
    # Информация о модели
    with gr.Accordion("ℹ️ О модели Qwen2.5-Coder-14B", open=False):
        gr.Markdown("""
        **Qwen2.5-Coder-14B-Instruct** - это специализированная модель для программирования :cite[6]:
        - **Параметры**: 14.7 миллиардов (квантованные в INT4)
        - **Специализация**: Генерация кода, исправление ошибок, код-ризонинг
        - **Контекст**: До 128K токенов :cite[1]
        - **Языки программирования**: Поддержка 40+ языков :cite[6]
        - **Память**: ~4-6 ГБ RAM (благодаря GPTQ-квантованию)
        
        **Улучшения Qwen2.5 по сравнению с Qwen2** :cite[1]:
        - Значительно больше знаний и улучшенные возможности в программировании
        - Улучшенное следование инструкциям и генерация длинных текстов
        - Поддержка многоязычия (29+ языков)
        """)
    
    # Обработчики событий
    generate_btn.click(
        fn=generate_code_with_context,
        inputs=[prompt_input, file_input, max_length_slider, temperature_slider, top_p_slider],
        outputs=response_output
    )

# Запускаем приложение с MCP-сервером
if __name__ == "__main__":
    demo.launch(
        server_name="0.0.0.0",
        server_port=7860,
        share=False,
        ssr_mode=False,  # Явно отключаем SSR
        mcp_server=True
    )