File size: 6,570 Bytes
2fc8dc5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import re
import json
from typing import List, Dict, Tuple
from knowledge_base import KnowledgeBase
from retriever import Retriever

class ITMOChatbot:
    def __init__(self):
        self.kb = KnowledgeBase()
        self.retriever = Retriever()
        self.max_history_turns = 3
        self.max_context_tokens = 1200
        self.relevance_threshold = 0.38
        
        try:
            from transformers import pipeline
            self.generator = pipeline('text2text-generation', model='cointegrated/rut5-base-multitask')
        except Exception as e:
            print(f'Генеративная модель не загружена: {e}')
            self.generator = None
    
    def chat(self, message: str, history: list) -> Tuple[str, float]:
        if not message.strip():
            return 'Пожалуйста, задайте вопрос.', 0.0
        
        if not self.kb.is_itmo_query(message):
            return self._get_irrelevant_response(), 0.0
        
        context = self._get_context(message)
        if not context:
            return 'К сожалению, не нашел релевантной информации в учебных планах ITMO.', 0.0
        
        response = self._generate_response(message, history, context)
        relevance_score = self._calculate_relevance_score(message, context)
        
        return response, relevance_score
    
    def recommend_courses(self, profile: dict) -> str:
        if not profile.get('semester'):
            return 'Пожалуйста, укажите целевой семестр для получения рекомендаций.'
        
        recommendations = self.kb.recommend(profile)
        if not recommendations:
            return 'К сожалению, не удалось найти подходящие курсы для вашего профиля.'
        
        result = '🎯 Рекомендуемые курсы (из официальных учебных планов ITMO):\n\n'
        for i, rec in enumerate(recommendations[:7], 1):
            result += f'{i}. {rec["name"]} ({rec["semester"]} семестр, {rec["credits"]} кредитов)\n'
            result += f'   {rec["why"]}\n\n'
        
        return result
    
    def _get_context(self, message: str) -> List[Dict]:
        try:
            results = self.retriever.retrieve(message, k=6, threshold=0.35)
            # Преобразуем результаты в нужный формат
            formatted_results = []
            for result in results:
                course_id = result.get('course_id')
                if course_id:
                    course = self.kb.get_course_by_id(course_id)
                    if course:
                        course['score'] = result.get('score', 0.0)
                        formatted_results.append(course)
            return formatted_results
        except Exception as e:
            print(f'Ошибка при получении контекста: {e}')
            return []
    
    def _generate_response(self, message: str, history: list, context: List[Dict]) -> str:
        if not context:
            return 'В предоставленных данных об этом не сказано.'
        
        prompt = self._build_prompt(message, history, context)
        
        if self.generator:
            try:
                response = self.generator(
                    prompt,
                    max_new_tokens=180,
                    temperature=0.4,
                    do_sample=True
                )[0]['generated_text']
                return response.strip()
            except Exception as e:
                print(f'Ошибка генерации: {e}')
        
        return self._fallback_response(context)
    
    def _build_prompt(self, message: str, history: list, context: List[Dict]) -> str:
        system_prompt = 'Отвечай только по контексту (ниже). Если недостаточно данных — прямо скажи: "в предоставленных данных об этом не сказано". Отвечай кратко и по делу.'
        
        history_text = ''
        if history:
            recent_history = history[-self.max_history_turns:]
            for turn in recent_history:
                history_text += f'Пользователь: {turn[0]}\nБот: {turn[1]}\n'
        
        context_text = 'Контекст:\n'
        for item in context:
            context_text += f'- {item["name"]} ({item["semester"]} семестр, {item["credits"]} кредитов): {item["short_desc"]}\n'
        
        prompt = f'{system_prompt}\n\n{history_text}Контекст:\n{context_text}\nВопрос: {message}'
        
        if len(prompt) > self.max_context_tokens * 4:
            prompt = prompt[:self.max_context_tokens * 4]
        
        return prompt
    
    def _fallback_response(self, context: List[Dict]) -> str:
        if not context:
            return 'В предоставленных данных об этом не сказано.'
        
        courses = []
        for item in context[:3]:
            courses.append(f'{item["name"]} ({item["semester"]} семестр, {item["credits"]} кредитов)')
        
        return f'Найденные курсы: {", ".join(courses)}. Для более подробной информации обратитесь к официальным учебным планам ITMO.'
    
    def _calculate_relevance_score(self, message: str, context: List[Dict]) -> float:
        if not context:
            return 0.0
        
        scores = [item.get('score', 0.0) for item in context]
        return sum(scores) / len(scores) if scores else 0.0
    
    def _get_irrelevant_response(self) -> str:
        return '''Похоже, вопрос не относится к магистратурам ITMO и их учебным планам. 



Попробуйте спросить, например:

• "Какие дисциплины по NLP в 1 семестре программы ИИ?"

• "Расскажи о программе AI Product"

• "Какие курсы по машинному обучению есть в программе ИИ?"

• "Сколько кредитов за дисциплину 'Глубокое обучение'?"'''