""" Модуль для генерации описаний маршрутов и ответов на вопросы пользователей. Использует Gemini-2.5-flash API через requests библиотеку. """ import json import requests from typing import List, Dict, Optional class RouteDescriber: """ Класс для работы с описанием маршрутов и ответами на вопросы пользователей. """ def __init__(self): """ Инициализация с API ключом Gemini. Args: api_key: API ключ для доступа к Gemini API """ self.api_key = 'AIzaSyBXGBGH5NDY8L_jVmq2zb4i8xYEV2qN-48' self.base_url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent" self.conversation_history = [] self.route_points = [] self.route_description = "" self.parser_chat_context = "" def _call_gemini(self, prompt: str, system_instruction: Optional[str] = None) -> str: """ Выполняет запрос к Gemini API. Args: prompt: Текст промпта system_instruction: Системная инструкция для модели Returns: Ответ модели """ url = f"{self.base_url}?key={self.api_key}" headers = { "Content-Type": "application/json" } # Формируем тело запроса contents = [] # Добавляем историю разговора for msg in self.conversation_history: contents.append(msg) # Добавляем текущий промпт contents.append({ "role": "user", "parts": [{"text": prompt}] }) payload = { "contents": contents } # Добавляем системную инструкцию если есть if system_instruction: payload["system_instruction"] = { "parts": [{"text": system_instruction}] } try: response = requests.post(url, headers=headers, json=payload, timeout=30) response.raise_for_status() result = response.json() # Извлекаем текст ответа if "candidates" in result and len(result["candidates"]) > 0: candidate = result["candidates"][0] if "content" in candidate and "parts" in candidate["content"]: answer = candidate["content"]["parts"][0]["text"] # Сохраняем в историю self.conversation_history.append({ "role": "user", "parts": [{"text": prompt}] }) self.conversation_history.append({ "role": "model", "parts": [{"text": answer}] }) return answer return "Ошибка при получении ответа" except requests.exceptions.RequestException as e: return f"Ошибка запроса: {str(e)}" def generate_route_description(self, route_points: List[Dict], parser_chat_history: Optional[List[Dict]] = None) -> str: """ Генерирует описание маршрута на основе точек. Args: route_points: Список точек маршрута с полями 'name' и 'description' parser_chat_history: История чата с парсером в формате [{"role": "user"/"model", "text": "..."}] Returns: Описание маршрута (1-2 предложения на точку) """ self.route_points = route_points self.conversation_history = [] # Очищаем историю для нового маршрута # Обрабатываем историю чата с парсером if parser_chat_history: parser_messages = [] for msg in parser_chat_history: role = msg.get('role', 'user') text = msg.get('text', '') parser_messages.append(f"{role.upper()}: {text}") self.parser_chat_context = "\n".join(parser_messages) else: self.parser_chat_context = "" # Формируем промпт points_info = [] for i, point in enumerate(route_points, 1): name = point.get('name', f'Точка {i}') description = point.get('description', '') points_info.append(f"{i}. {name}\nОписание: {description}") prompt = f"""Создай краткое описание маршрута из {len(route_points)} точек. Для каждой точки напиши 1-2 предложения, которые заинтересуют посетителя. Используй только информацию из описаний точек. Точки маршрута: {chr(10).join(points_info)} Формат ответа: 1. [Название точки]: [1-2 предложения описания] 2. [Название точки]: [1-2 предложения описания] ...""" # Добавляем контекст парсера если есть if self.parser_chat_context: prompt = f"""История запроса пользователя: {self.parser_chat_context} {prompt}""" system_instruction = """Ты — помощник для описания туристических маршрутов. Твоя задача — создавать краткие, привлекательные описания точек маршрута (1-2 предложения на точку). Используй только информацию, представленную в описаниях точек. Пиши на русском языке.""" description = self._call_gemini(prompt, system_instruction) self.route_description = description return description def answer_question(self, question: str) -> str: """ Отвечает на вопрос пользователя о точках маршрута. Видит всю историю вопросов и ответов, а также описание маршрута. Args: question: Вопрос пользователя Returns: Ответ на основе информации из описаний точек """ # Формируем контекст с информацией о точках points_context = [] for i, point in enumerate(self.route_points, 1): name = point.get('name', f'Точка {i}') description = point.get('description', 'Нет описания') points_context.append(f"{i}. {name}: {description}") context = "\n\n".join(points_context) # Добавляем информацию о парсере если есть parser_info = "" if self.parser_chat_context: parser_info = f""" ИСТОРИЯ ЗАПРОСА ПОЛЬЗОВАТЕЛЯ (для контекста): {self.parser_chat_context} """ # Добавляем описание маршрута если оно было сгенерировано route_desc_info = "" if self.route_description: route_desc_info = f""" ОПИСАНИЕ МАРШРУТА, КОТОРОЕ БЫЛО ПОКАЗАНО ПОЛЬЗОВАТЕЛЮ: {self.route_description} """ system_instruction = f"""Ты — помощник по туристическому маршруту. {parser_info}{route_desc_info} ДОСТУПНАЯ ИНФОРМАЦИЯ О ТОЧКАХ МАРШРУТА: {context} СТРОГИЕ ПРАВИЛА: 1. Отвечай ТОЛЬКО на вопросы о точках маршрута 2. Используй ТОЛЬКО информацию из описаний точек выше 3. Если в описаниях нет информации для ответа на вопрос, вежливо уходи от ответа фразами вида "Вы узнаете ответ, когда посетите это место", адаптируй под контекст. 4. НЕ давай рекомендаций и советов 5. НЕ отвечай на вопросы, не связанные с маршрутом. В таких случаях отвечай: "Я могу ответить только на вопросы о точках вашего маршрута" 6. НЕ придумывай информацию, которой нет в описаниях 7. Отвечай кратко и по существу на русском языке 8. Ты видишь всю историю разговора, поэтому можешь отвечать на уточняющие вопросы""" answer = self._call_gemini(question, system_instruction) return answer def reset_conversation(self): """Очищает историю разговора.""" self.conversation_history = [] self.route_points = [] self.route_description = "" self.parser_chat_context = "" # Пример использования if __name__ == "__main__": # Инициализация (замените на ваш API ключ) API_KEY = "YOUR_GEMINI_API_KEY" describer = RouteDescriber(API_KEY) # Пример истории чата с парсером parser_chat = [ {"role": "user", "text": "Хочу погулять по центру Москвы, интересует история и искусство"}, {"role": "model", "text": "Понял, вы хотите прогулку по историческому центру с посещением музеев. Сколько времени у вас есть?"}, {"role": "user", "text": "Часа 3-4"}, {"role": "model", "text": "Отлично, я составил маршрут из 3 точек по центру Москвы с историческими местами и музеем искусства."} ] # Пример данных маршрута route_points = [ { "name": "Красная площадь", "description": "Главная площадь Москвы, окружённая историческими зданиями. Здесь находятся Кремль, собор Василия Блаженного и Мавзолей Ленина. Площадь была основана в конце XV века." }, { "name": "Парк Горького", "description": "Один из самых известных парков Москвы, расположенный на берегу Москвы-реки. Здесь есть зоны для отдыха, спортивные площадки, прокат велосипедов. Парк был открыт в 1928 году." }, { "name": "Третьяковская галерея", "description": "Художественный музей с крупнейшей коллекцией русского искусства. В экспозиции представлены работы от древнерусских икон до картин XX века. Основана купцом Павлом Третьяковым." } ] # Генерация описания маршрута с историей парсера print("=== ОПИСАНИЕ МАРШРУТА ===") description = describer.generate_route_description(route_points, parser_chat) print(description) print() # Примеры вопросов (теперь с контекстом истории) print("=== ДИАЛОГ С ПОЛЬЗОВАТЕЛЕМ ===") questions = [ "Когда был основан Парк Горького?", "А что там можно делать?", # Уточняющий вопрос о предыдущей теме "Какие картины есть в Третьяковской галерее?", "Сколько стоит билет?", # Без контекста, но система поймет что это про музей "Расскажи про Кремль", "Что посоветуешь взять с собой?", # Попытка получить рекомендацию "Как погода сегодня?" # Нерелевантный вопрос ] for q in questions: print(f"\n👤 Пользователь: {q}") answer = describer.answer_question(q) print(f"🤖 Бот: {answer}")