import torch from transformers import AutoModelForCausalLM, AutoTokenizer from typing import List, Dict, Optional import re class LLMEngine: """Движок для работы с локальной языковой моделью""" def __init__(self, model_name: str = "mistralai/Mistral-7B-Instruct-v0.2", use_gpu: bool = None): """ Инициализация LLM model_name: название модели (можно заменить на русскую Saiga) use_gpu: использовать ли GPU (автоопределение если None) """ self.model_name = model_name self.use_gpu = use_gpu if use_gpu is not None else torch.cuda.is_available() self.model = None self.tokenizer = None self.is_loaded = False # Альтернативные русские модели self.russian_models = [ "IlyaGusev/saiga_mistral_7b", "IlyaGusev/saiga_llama3_8b", "mistralai/Mistral-7B-Instruct-v0.2" ] def load_model(self): """Загружает модель в память""" if self.is_loaded: return True print(f"🔄 Загрузка модели {self.model_name}...") try: # Загрузка токенизатора self.tokenizer = AutoTokenizer.from_pretrained( self.model_name, trust_remote_code=True ) # Настройка паддинга if self.tokenizer.pad_token is None: self.tokenizer.pad_token = self.tokenizer.eos_token # Загрузка модели self.model = AutoModelForCausalLM.from_pretrained( self.model_name, torch_dtype=torch.float16 if self.use_gpu else torch.float32, device_map="auto" if self.use_gpu else None, trust_remote_code=True, low_cpu_mem_usage=True ) if not self.use_gpu: self.model = self.model.to("cpu") self.is_loaded = True device = "GPU" if self.use_gpu else "CPU" print(f"✅ Модель загружена на {device}") return True except Exception as e: print(f"❌ Ошибка загрузки модели: {e}") return False def generate(self, prompt: str, context: str = None, max_new_tokens: int = 500, temperature: float = 0.7, top_p: float = 0.95) -> str: """ Генерация ответа на основе промпта и контекста """ if not self.is_loaded: if not self.load_model(): return "Ошибка: модель не загружена" # Формирование полного промпта if context: full_prompt = f"""Ты — эксперт по строительной документации, специализирующийся на СП 61.13330.2012 "Тепловая изоляция оборудования и трубопроводов". Контекст из документации: {context} Вопрос: {prompt} Ответ (кратко, четко, со ссылками на нормативы):""" else: full_prompt = f"""Ты — эксперт по строительной документации. Отвечай кратко и по делу. Вопрос: {prompt} Ответ:""" # Токенизация inputs = self.tokenizer( full_prompt, return_tensors="pt", truncation=True, max_length=2048 ) if self.use_gpu: inputs = {k: v.to("cuda") for k, v in inputs.items()} # Генерация with torch.no_grad(): outputs = self.model.generate( **inputs, max_new_tokens=max_new_tokens, temperature=temperature, top_p=top_p, do_sample=True, pad_token_id=self.tokenizer.eos_token_id ) # Декодирование response = self.tokenizer.decode(outputs[0], skip_special_tokens=True) # Извлекаем только ответ (после "Ответ:") if "Ответ:" in response: response = response.split("Ответ:")[-1].strip() # Очистка от повторений response = self._clean_response(response) return response def _clean_response(self, text: str) -> str: """Очистка ответа от мусора""" # Убираем повторяющиеся строки lines = text.split('\n') unique_lines = [] seen = set() for line in lines: line = line.strip() if line and line not in seen and len(line) > 5: seen.add(line) unique_lines.append(line) return '\n'.join(unique_lines[:20]) def answer_with_context(self, question: str, context: str, sources: List[Dict] = None) -> Dict: """ Генерация ответа с использованием контекста из документации """ if not self.is_loaded: self.load_model() # Формируем контекст context_text = context if sources: context_text += "\n\nИсточники:\n" for src in sources[:3]: context_text += f"- {src['doc_name']} (релевантность: {src['score']:.2f})\n" context_text += f" {src['text'][:200]}...\n" # Генерируем ответ answer = self.generate( prompt=question, context=context_text, max_new_tokens=500, temperature=0.3 ) return { 'question': question, 'answer': answer, 'sources': sources or [], 'model': self.model_name } def unload(self): """Выгружает модель из памяти""" if self.model is not None: del self.model del self.tokenizer torch.cuda.empty_cache() self.is_loaded = False print("✅ Модель выгружена")