import logging import httpx from openai import AsyncOpenAI from config.settings import settings from typing import Dict, List logger = logging.getLogger(__name__) class AIService: """Сервис для работы с OpenAI API через Cloudflare Gateway""" def __init__(self): self.api_key = settings.openai_api_key # Проверяем, настроен ли Cloudflare AI Gateway if settings.cloudflare_account_id: # Используем Cloudflare AI Gateway с прямыми HTTP запросами self.base_url = ( f"https://gateway.ai.cloudflare.com/v1/" f"{settings.cloudflare_account_id}/" f"{settings.cloudflare_gateway_id}/" f"openai" ) self.use_cloudflare = True logger.info(f"AI Service initialized with Cloudflare Gateway: {self.base_url}") else: # Прямое подключение к OpenAI self.base_url = "https://api.openai.com/v1" self.use_cloudflare = False logger.info("AI Service initialized with direct OpenAI connection") # HTTP клиент для всех запросов self.http_client = httpx.AsyncClient( timeout=httpx.Timeout(60.0, connect=10.0), limits=httpx.Limits(max_keepalive_connections=5, max_connections=10) ) async def _make_openai_request(self, messages: list, temperature: float = 0.3, model: str = "gpt-4o-mini") -> dict: """Выполнить запрос к OpenAI API (через Cloudflare или напрямую)""" url = f"{self.base_url}/chat/completions" headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" } payload = { "model": model, "messages": messages, "temperature": temperature, "response_format": {"type": "json_object"} } response = await self.http_client.post(url, headers=headers, json=payload) response.raise_for_status() return response.json() async def translate_word(self, word: str, source_lang: str = "en", translation_lang: str = "ru") -> Dict: """ Перевести слово и получить дополнительную информацию Args: word: Слово для перевода source_lang: Язык исходного слова (ISO2) translation_lang: Язык перевода (ISO2) Returns: Dict с переводом, транскрипцией и примерами """ prompt = f"""Переведи слово/фразу "{word}" с языка {source_lang} на {translation_lang}. Верни ответ строго в формате JSON: {{ "word": "исходное слово на {source_lang}", "translation": "перевод на {translation_lang}", "transcription": "транскрипция в IPA (если применимо)", "examples": [ {{"{source_lang}": "пример на языке обучения", "{translation_lang}": "перевод примера"}} ], "category": "категория слова (работа, еда, путешествия и т.д.)", "difficulty": "уровень сложности (A1/A2/B1/B2/C1/C2)" }} Важно: верни только JSON, без дополнительного текста.""" try: logger.info(f"[GPT Request] translate_word: word='{word}', source='{source_lang}', to='{translation_lang}'") messages = [ {"role": "system", "content": "Ты - помощник для изучения языков. Отвечай только в формате JSON."}, {"role": "user", "content": prompt} ] response_data = await self._make_openai_request(messages, temperature=0.3) import json result = json.loads(response_data['choices'][0]['message']['content']) logger.info(f"[GPT Response] translate_word: success, translation='{result.get('translation', 'N/A')}'") return result except Exception as e: logger.error(f"[GPT Error] translate_word: {type(e).__name__}: {str(e)}") # Fallback в случае ошибки return { "word": word, "translation": "Ошибка перевода", "transcription": "", "examples": [], "category": "unknown", "difficulty": "A1" } async def check_answer(self, question: str, correct_answer: str, user_answer: str) -> Dict: """ Проверить ответ пользователя с помощью ИИ Args: question: Вопрос задания correct_answer: Правильный ответ user_answer: Ответ пользователя Returns: Dict с результатом проверки и обратной связью """ prompt = f"""Проверь ответ пользователя на задание по английскому языку. Задание: {question} Правильный ответ: {correct_answer} Ответ пользователя: {user_answer} Верни ответ в формате JSON: {{ "is_correct": true/false, "feedback": "краткое объяснение (если ответ неверный, объясни ошибку и дай правильный вариант)", "score": 0-100 }} Учитывай возможные вариации ответа. Если смысл передан правильно, даже с небольшими грамматическими неточностями, засчитывай ответ.""" try: logger.info(f"[GPT Request] check_answer: user_answer='{user_answer[:30]}...'") messages = [ {"role": "system", "content": "Ты - преподаватель английского языка. Проверяй ответы справедливо, учитывая контекст."}, {"role": "user", "content": prompt} ] response_data = await self._make_openai_request(messages, temperature=0.3) import json result = json.loads(response_data['choices'][0]['message']['content']) logger.info(f"[GPT Response] check_answer: is_correct={result.get('is_correct', False)}, score={result.get('score', 0)}") return result except Exception as e: logger.error(f"[GPT Error] check_answer: {type(e).__name__}: {str(e)}") return { "is_correct": False, "feedback": "Ошибка проверки ответа", "score": 0 } async def generate_fill_in_sentence(self, word: str, learning_lang: str = "en", translation_lang: str = "ru") -> Dict: """ Сгенерировать предложение с пропуском для заданного слова Args: word: Слово (на языке обучения), для которого нужно создать предложение learning_lang: Язык обучения (ISO2) translation_lang: Язык перевода предложения (ISO2) Returns: Dict с предложением и правильным ответом """ prompt = f"""Создай предложение на языке {learning_lang}, используя слово "{word}". Замени это слово на пропуск "___". Верни ответ в формате JSON: {{ "sentence": "предложение с пропуском ___", "answer": "{word}", "translation": "перевод предложения на {translation_lang}" }} Предложение должно быть простым и естественным. Контекст должен четко подсказывать правильное слово.""" try: logger.info(f"[GPT Request] generate_fill_in_sentence: word='{word}', lang='{learning_lang}', to='{translation_lang}'") messages = [ {"role": "system", "content": "Ты - преподаватель иностранных языков. Создавай простые и понятные упражнения."}, {"role": "user", "content": prompt} ] response_data = await self._make_openai_request(messages, temperature=0.7) import json result = json.loads(response_data['choices'][0]['message']['content']) logger.info(f"[GPT Response] generate_fill_in_sentence: success") return result except Exception as e: logger.error(f"[GPT Error] generate_fill_in_sentence: {type(e).__name__}: {str(e)}") return { "sentence": f"I like to ___ every day.", "answer": word, "translation": f"Мне нравится {word} каждый день." } async def generate_thematic_words(self, theme: str, level: str = "B1", count: int = 10, learning_lang: str = "en", translation_lang: str = "ru") -> List[Dict]: """ Сгенерировать подборку слов по теме Args: theme: Тема для подборки слов level: Уровень сложности (A1-C2) count: Количество слов Returns: Список словарей с информацией о словах """ prompt = f"""Создай подборку из {count} слов на языке {learning_lang} по теме "{theme}" для уровня {level}. Переводы дай на {translation_lang}. Верни ответ в формате JSON: {{ "theme": "{theme}", "words": [ {{ "word": "слово на {learning_lang}", "translation": "перевод на {translation_lang}", "transcription": "транскрипция в IPA (если применимо)", "example": "пример использования на {learning_lang}" }} ] }} Слова должны быть: - Полезными и часто используемыми - Соответствовать уровню {level} - Связаны с темой "{theme}" - Разнообразными (существительные, глаголы, прилагательные)""" try: logger.info(f"[GPT Request] generate_thematic_words: theme='{theme}', level='{level}', count={count}, learn='{learning_lang}', to='{translation_lang}'") messages = [ {"role": "system", "content": "Ты - преподаватель иностранных языков. Подбирай полезные и актуальные слова."}, {"role": "user", "content": prompt} ] response_data = await self._make_openai_request(messages, temperature=0.7) import json result = json.loads(response_data['choices'][0]['message']['content']) words_count = len(result.get('words', [])) logger.info(f"[GPT Response] generate_thematic_words: success, generated {words_count} words") return result.get('words', []) except Exception as e: logger.error(f"[GPT Error] generate_thematic_words: {type(e).__name__}: {str(e)}") return [] async def extract_words_from_text(self, text: str, level: str = "B1", max_words: int = 15, learning_lang: str = "en", translation_lang: str = "ru") -> List[Dict]: """ Извлечь ключевые слова из текста для изучения Args: text: Текст на английском языке level: Уровень пользователя (A1-C2) max_words: Максимальное количество слов для извлечения Returns: Список словарей с информацией о словах """ prompt = f"""Проанализируй следующий текст на языке {learning_lang} и извлеки из него до {max_words} самых полезных слов для изучения на уровне {level}. Переводы дай на {translation_lang}. Текст: {text} Верни ответ в формате JSON: {{ "words": [ {{ "word": "слово на {learning_lang} (в базовой форме)", "translation": "перевод на {translation_lang}", "transcription": "транскрипция в IPA (если применимо)", "context": "предложение из текста на {learning_lang}, где используется это слово" }} ] }} Критерии отбора слов: - Выбирай самые важные и полезные слова из текста - Слова должны быть интересны для уровня {level} - Не включай простейшие слова (a, the, is, и т.д.) - Слова должны быть в базовой форме (инфинитив для глаголов, ед.число для существительных) - Разнообразие: существительные, глаголы, прилагательные, устойчивые выражения""" try: text_preview = text[:100] + "..." if len(text) > 100 else text logger.info(f"[GPT Request] extract_words_from_text: text_length={len(text)}, level='{level}', max_words={max_words}, learn='{learning_lang}', to='{translation_lang}'") messages = [ {"role": "system", "content": "Ты - преподаватель иностранных языков. Помогаешь извлекать полезные слова для изучения из текстов."}, {"role": "user", "content": prompt} ] response_data = await self._make_openai_request(messages, temperature=0.5) import json result = json.loads(response_data['choices'][0]['message']['content']) words_count = len(result.get('words', [])) logger.info(f"[GPT Response] extract_words_from_text: success, extracted {words_count} words") return result.get('words', []) except Exception as e: logger.error(f"[GPT Error] extract_words_from_text: {type(e).__name__}: {str(e)}") return [] async def start_conversation(self, scenario: str, level: str = "B1", learning_lang: str = "en", translation_lang: str = "ru") -> Dict: """ Начать диалоговую практику с AI Args: scenario: Сценарий диалога (restaurant, shopping, travel, etc.) level: Уровень пользователя (A1-C2) Returns: Dict с начальной репликой и контекстом """ scenarios = { "restaurant": "ресторан - заказ еды", "shopping": "магазин - покупка одежды", "travel": "аэропорт/отель - путешествие", "work": "офис - рабочая встреча", "doctor": "клиника - визит к врачу", "casual": "повседневный разговор" } scenario_desc = scenarios.get(scenario, "повседневный разговор") extra_fields = '' if learning_lang.lower() == 'ja': extra_fields = ",\n \"furigana\": \"фуригана (кана над/после иероглифов для поля message)\"" prompt = f"""Ты - собеседник для практики языка {learning_lang} уровня {level}. Начни диалог в сценарии: {scenario_desc} на {learning_lang}. Верни ответ в формате JSON: {{ "message": "твоя первая реплика на {learning_lang}", "translation": "перевод на {translation_lang}", "context": "краткое описание ситуации на {translation_lang}", "suggestions": ["подсказка 1", "подсказка 2", "подсказка 3"]{extra_fields} }} Требования: - Говори естественно, используй уровень {level} - Создай интересную ситуацию - Задай вопрос или начни разговор - Подсказки должны помочь пользователю ответить""" try: logger.info(f"[GPT Request] start_conversation: scenario='{scenario}', level='{level}', learn='{learning_lang}', to='{translation_lang}'") messages = [ {"role": "system", "content": "Ты - дружелюбный собеседник для практики иностранных языков. Веди естественный диалог."}, {"role": "user", "content": prompt} ] response_data = await self._make_openai_request(messages, temperature=0.8) import json result = json.loads(response_data['choices'][0]['message']['content']) logger.info(f"[GPT Response] start_conversation: success, scenario='{scenario}'") return result except Exception as e: logger.error(f"[GPT Error] start_conversation: {type(e).__name__}: {str(e)}") return { "message": "Hello! How are you today?", "translation": "Привет! Как дела сегодня?", "context": "Повседневный разговор", "suggestions": ["I'm fine, thank you!", "Good, and you?", "Not bad!"] } async def continue_conversation( self, conversation_history: List[Dict], user_message: str, scenario: str, level: str = "B1", learning_lang: str = "en", translation_lang: str = "ru" ) -> Dict: """ Продолжить диалог и проверить ответ пользователя Args: conversation_history: История диалога user_message: Сообщение пользователя scenario: Сценарий диалога level: Уровень пользователя Returns: Dict с ответом AI, проверкой и подсказками """ # Формируем историю для контекста history_text = "\n".join([ f"{'AI' if msg['role'] == 'assistant' else 'User'}: {msg['content']}" for msg in conversation_history[-6:] # Последние 6 сообщений ]) extra_fields_resp = '' if learning_lang.lower() == 'ja': extra_fields_resp = ",\n \"furigana\": \"фуригана (кана над/после иероглифов для поля response)\"" prompt = f"""Ты ведешь диалог на языке {learning_lang} уровня {level} в сценарии "{scenario}". История диалога: {history_text} User: {user_message} Верни ответ в формате JSON: {{ "response": "твой ответ на {learning_lang}", "translation": "перевод твоего ответа на {translation_lang}", "feedback": {{ "has_errors": true/false, "corrections": "исправления ошибок пользователя (если есть)", "comment": "краткий комментарий об ответе пользователя" }}, "suggestions": ["подсказка 1 для следующего ответа", "подсказка 2"]{extra_fields_resp} }} Требования: - Продолжай естественный диалог - Если у пользователя есть грамматические или лексические ошибки, укажи их в corrections - Будь дружелюбным и поддерживающим - Используй лексику уровня {level}""" try: logger.info(f"[GPT Request] continue_conversation: scenario='{scenario}', level='{level}', history_length={len(conversation_history)}, learn='{learning_lang}', to='{translation_lang}'") # Формируем сообщения для API messages = [ {"role": "system", "content": f"Ты - дружелюбный собеседник для практики языка {learning_lang} уровня {level}. Веди естественный диалог и помогай исправлять ошибки."} ] # Добавляем историю for msg in conversation_history[-6:]: messages.append(msg) # Добавляем текущее сообщение пользователя messages.append({"role": "user", "content": user_message}) # Добавляем инструкцию для форматирования ответа messages.append({"role": "user", "content": prompt}) response_data = await self._make_openai_request(messages, temperature=0.8) import json result = json.loads(response_data['choices'][0]['message']['content']) has_errors = result.get('feedback', {}).get('has_errors', False) logger.info(f"[GPT Response] continue_conversation: success, has_errors={has_errors}") return result except Exception as e: logger.error(f"[GPT Error] continue_conversation: {type(e).__name__}: {str(e)}") return { "response": "I see. Tell me more about that.", "translation": "Понятно. Расскажи мне больше об этом.", "feedback": { "has_errors": False, "corrections": "", "comment": "Good!" }, "suggestions": ["Sure!", "Well...", "Actually..."] } async def generate_level_test(self) -> List[Dict]: """ Сгенерировать тест для определения уровня английского Returns: Список из 7 вопросов разной сложности """ prompt = """Создай тест из 7 вопросов для определения уровня английского языка (A1-C2). Верни ответ в формате JSON: { "questions": [ { "question": "текст вопроса на английском", "question_ru": "перевод вопроса на русский", "options": ["вариант A", "вариант B", "вариант C", "вариант D"], "correct": 0, "level": "A1" } ] } Требования: - Вопросы 1-2: уровень A1 (базовый) - Вопросы 3-4: уровень A2-B1 (элементарный-средний) - Вопросы 5-6: уровень B2-C1 (продвинутый) - Вопрос 7: уровень C2 (профессиональный) - Каждый вопрос с 4 вариантами ответа - correct - индекс правильного ответа (0-3) - Вопросы на грамматику, лексику и понимание""" try: logger.info(f"[GPT Request] generate_level_test: generating 7 questions") messages = [ {"role": "system", "content": "Ты - эксперт по тестированию уровня английского языка. Создавай объективные тесты."}, {"role": "user", "content": prompt} ] response_data = await self._make_openai_request(messages, temperature=0.7) import json result = json.loads(response_data['choices'][0]['message']['content']) questions_count = len(result.get('questions', [])) logger.info(f"[GPT Response] generate_level_test: success, generated {questions_count} questions") return result.get('questions', []) except Exception as e: logger.error(f"[GPT Error] generate_level_test: {type(e).__name__}: {str(e)}, using fallback questions") # Fallback с базовыми вопросами return [ { "question": "What is your name?", "question_ru": "Как тебя зовут?", "options": ["My name is", "I am name", "Name my is", "Is name my"], "correct": 0, "level": "A1" }, { "question": "I ___ to school every day.", "question_ru": "Я ___ в школу каждый день.", "options": ["go", "goes", "going", "went"], "correct": 0, "level": "A1" }, { "question": "She ___ been to Paris twice.", "question_ru": "Она ___ в Париже дважды.", "options": ["have", "has", "had", "having"], "correct": 1, "level": "A2" }, { "question": "If I ___ rich, I would travel the world.", "question_ru": "Если бы я был богат, я бы путешествовал по миру.", "options": ["am", "was", "were", "be"], "correct": 2, "level": "B1" }, { "question": "The project ___ by next Monday.", "question_ru": "Проект ___ к следующему понедельнику.", "options": ["will complete", "will be completed", "completes", "is completing"], "correct": 1, "level": "B2" }, { "question": "Had I known about the meeting, I ___ attended.", "question_ru": "Если бы я знал о встрече, я бы посетил.", "options": ["would have", "will have", "would", "will"], "correct": 0, "level": "C1" }, { "question": "The nuances of his argument were so ___ that few could grasp them.", "question_ru": "Нюансы его аргумента были настолько ___, что немногие могли их понять.", "options": ["subtle", "obvious", "simple", "clear"], "correct": 0, "level": "C2" } ] # Глобальный экземпляр сервиса ai_service = AIService()