This commit is contained in:
2025-12-04 16:45:03 +03:00
parent cf8f71e24a
commit d48e3ff952

View File

@@ -1,28 +1,60 @@
import logging
import httpx
from openai import AsyncOpenAI from openai import AsyncOpenAI
from config.settings import settings from config.settings import settings
from typing import Dict, List from typing import Dict, List
logger = logging.getLogger(__name__)
class AIService: class AIService:
"""Сервис для работы с OpenAI API через Cloudflare Gateway""" """Сервис для работы с OpenAI API через Cloudflare Gateway"""
def __init__(self): def __init__(self):
self.api_key = settings.openai_api_key
# Проверяем, настроен ли Cloudflare AI Gateway # Проверяем, настроен ли Cloudflare AI Gateway
if settings.cloudflare_account_id: if settings.cloudflare_account_id:
# Используем Cloudflare AI Gateway # Используем Cloudflare AI Gateway с прямыми HTTP запросами
base_url = ( self.base_url = (
f"https://gateway.ai.cloudflare.com/v1/" f"https://gateway.ai.cloudflare.com/v1/"
f"{settings.cloudflare_account_id}/" f"{settings.cloudflare_account_id}/"
f"{settings.cloudflare_gateway_id}/" f"{settings.cloudflare_gateway_id}/"
f"compat" f"openai"
)
self.client = AsyncOpenAI(
api_key=settings.openai_api_key,
base_url=base_url
) )
self.use_cloudflare = True
logger.info(f"AI Service initialized with Cloudflare Gateway: {self.base_url}")
else: else:
# Прямое подключение к OpenAI # Прямое подключение к OpenAI
self.client = AsyncOpenAI(api_key=settings.openai_api_key) 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, target_lang: str = "ru") -> Dict: async def translate_word(self, word: str, target_lang: str = "ru") -> Dict:
""" """
@@ -53,21 +85,22 @@ class AIService:
Важно: верни только JSON, без дополнительного текста.""" Важно: верни только JSON, без дополнительного текста."""
try: try:
response = await self.client.chat.completions.create( logger.info(f"[GPT Request] translate_word: word='{word}', target_lang='{target_lang}'")
model="gpt-4o-mini",
messages=[ messages = [
{"role": "system", "content": "Ты - помощник для изучения английского языка. Отвечай только в формате JSON."}, {"role": "system", "content": "Ты - помощник для изучения английского языка. Отвечай только в формате JSON."},
{"role": "user", "content": prompt} {"role": "user", "content": prompt}
], ]
temperature=0.3,
response_format={"type": "json_object"} response_data = await self._make_openai_request(messages, temperature=0.3)
)
import json import json
result = json.loads(response.choices[0].message.content) 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 return result
except Exception as e: except Exception as e:
logger.error(f"[GPT Error] translate_word: {type(e).__name__}: {str(e)}")
# Fallback в случае ошибки # Fallback в случае ошибки
return { return {
"word": word, "word": word,
@@ -106,21 +139,22 @@ class AIService:
Учитывай возможные вариации ответа. Если смысл передан правильно, даже с небольшими грамматическими неточностями, засчитывай ответ.""" Учитывай возможные вариации ответа. Если смысл передан правильно, даже с небольшими грамматическими неточностями, засчитывай ответ."""
try: try:
response = await self.client.chat.completions.create( logger.info(f"[GPT Request] check_answer: user_answer='{user_answer[:30]}...'")
model="gpt-4o-mini",
messages=[ messages = [
{"role": "system", "content": "Ты - преподаватель английского языка. Проверяй ответы справедливо, учитывая контекст."}, {"role": "system", "content": "Ты - преподаватель английского языка. Проверяй ответы справедливо, учитывая контекст."},
{"role": "user", "content": prompt} {"role": "user", "content": prompt}
], ]
temperature=0.3,
response_format={"type": "json_object"} response_data = await self._make_openai_request(messages, temperature=0.3)
)
import json import json
result = json.loads(response.choices[0].message.content) 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 return result
except Exception as e: except Exception as e:
logger.error(f"[GPT Error] check_answer: {type(e).__name__}: {str(e)}")
return { return {
"is_correct": False, "is_correct": False,
"feedback": "Ошибка проверки ответа", "feedback": "Ошибка проверки ответа",
@@ -150,21 +184,22 @@ class AIService:
Предложение должно быть простым и естественным. Контекст должен четко подсказывать правильное слово.""" Предложение должно быть простым и естественным. Контекст должен четко подсказывать правильное слово."""
try: try:
response = await self.client.chat.completions.create( logger.info(f"[GPT Request] generate_fill_in_sentence: word='{word}'")
model="gpt-4o-mini",
messages=[ messages = [
{"role": "system", "content": "Ты - преподаватель английского языка. Создавай простые и понятные упражнения."}, {"role": "system", "content": "Ты - преподаватель английского языка. Создавай простые и понятные упражнения."},
{"role": "user", "content": prompt} {"role": "user", "content": prompt}
], ]
temperature=0.7,
response_format={"type": "json_object"} response_data = await self._make_openai_request(messages, temperature=0.7)
)
import json import json
result = json.loads(response.choices[0].message.content) result = json.loads(response_data['choices'][0]['message']['content'])
logger.info(f"[GPT Response] generate_fill_in_sentence: success")
return result return result
except Exception as e: except Exception as e:
logger.error(f"[GPT Error] generate_fill_in_sentence: {type(e).__name__}: {str(e)}")
return { return {
"sentence": f"I like to ___ every day.", "sentence": f"I like to ___ every day.",
"answer": word, "answer": word,
@@ -205,21 +240,23 @@ class AIService:
- Разнообразными (существительные, глаголы, прилагательные)""" - Разнообразными (существительные, глаголы, прилагательные)"""
try: try:
response = await self.client.chat.completions.create( logger.info(f"[GPT Request] generate_thematic_words: theme='{theme}', level='{level}', count={count}")
model="gpt-4o-mini",
messages=[ messages = [
{"role": "system", "content": "Ты - преподаватель английского языка. Подбирай полезные и актуальные слова."}, {"role": "system", "content": "Ты - преподаватель английского языка. Подбирай полезные и актуальные слова."},
{"role": "user", "content": prompt} {"role": "user", "content": prompt}
], ]
temperature=0.7,
response_format={"type": "json_object"} response_data = await self._make_openai_request(messages, temperature=0.7)
)
import json import json
result = json.loads(response.choices[0].message.content) 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', []) return result.get('words', [])
except Exception as e: except Exception as e:
logger.error(f"[GPT Error] generate_thematic_words: {type(e).__name__}: {str(e)}")
return [] return []
async def extract_words_from_text(self, text: str, level: str = "B1", max_words: int = 15) -> List[Dict]: async def extract_words_from_text(self, text: str, level: str = "B1", max_words: int = 15) -> List[Dict]:
@@ -259,21 +296,24 @@ class AIService:
- Разнообразие: существительные, глаголы, прилагательные, устойчивые выражения""" - Разнообразие: существительные, глаголы, прилагательные, устойчивые выражения"""
try: try:
response = await self.client.chat.completions.create( text_preview = text[:100] + "..." if len(text) > 100 else text
model="gpt-4o-mini", logger.info(f"[GPT Request] extract_words_from_text: text_length={len(text)}, level='{level}', max_words={max_words}")
messages=[
{"role": "system", "content": "Ты - преподаватель английского языка. Помогаешь извлекать полезные слова для изучения из текстов."}, messages = [
{"role": "user", "content": prompt} {"role": "system", "content": "Ты - преподаватель английского языка. Помогаешь извлекать полезные слова для изучения из текстов."},
], {"role": "user", "content": prompt}
temperature=0.5, ]
response_format={"type": "json_object"}
) response_data = await self._make_openai_request(messages, temperature=0.5)
import json import json
result = json.loads(response.choices[0].message.content) 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', []) return result.get('words', [])
except Exception as e: except Exception as e:
logger.error(f"[GPT Error] extract_words_from_text: {type(e).__name__}: {str(e)}")
return [] return []
async def start_conversation(self, scenario: str, level: str = "B1") -> Dict: async def start_conversation(self, scenario: str, level: str = "B1") -> Dict:
@@ -316,21 +356,22 @@ class AIService:
- Подсказки должны помочь пользователю ответить""" - Подсказки должны помочь пользователю ответить"""
try: try:
response = await self.client.chat.completions.create( logger.info(f"[GPT Request] start_conversation: scenario='{scenario}', level='{level}'")
model="gpt-4o-mini",
messages=[ messages = [
{"role": "system", "content": "Ты - дружелюбный собеседник для практики английского. Веди естественный диалог."}, {"role": "system", "content": "Ты - дружелюбный собеседник для практики английского. Веди естественный диалог."},
{"role": "user", "content": prompt} {"role": "user", "content": prompt}
], ]
temperature=0.8,
response_format={"type": "json_object"} response_data = await self._make_openai_request(messages, temperature=0.8)
)
import json import json
result = json.loads(response.choices[0].message.content) result = json.loads(response_data['choices'][0]['message']['content'])
logger.info(f"[GPT Response] start_conversation: success, scenario='{scenario}'")
return result return result
except Exception as e: except Exception as e:
logger.error(f"[GPT Error] start_conversation: {type(e).__name__}: {str(e)}")
return { return {
"message": "Hello! How are you today?", "message": "Hello! How are you today?",
"translation": "Привет! Как дела сегодня?", "translation": "Привет! Как дела сегодня?",
@@ -388,6 +429,8 @@ User: {user_message}
- Используй лексику уровня {level}""" - Используй лексику уровня {level}"""
try: try:
logger.info(f"[GPT Request] continue_conversation: scenario='{scenario}', level='{level}', history_length={len(conversation_history)}")
# Формируем сообщения для API # Формируем сообщения для API
messages = [ messages = [
{"role": "system", "content": f"Ты - дружелюбный собеседник для практики английского языка уровня {level}. Веди естественный диалог и помогай исправлять ошибки."} {"role": "system", "content": f"Ты - дружелюбный собеседник для практики английского языка уровня {level}. Веди естественный диалог и помогай исправлять ошибки."}
@@ -403,18 +446,16 @@ User: {user_message}
# Добавляем инструкцию для форматирования ответа # Добавляем инструкцию для форматирования ответа
messages.append({"role": "user", "content": prompt}) messages.append({"role": "user", "content": prompt})
response = await self.client.chat.completions.create( response_data = await self._make_openai_request(messages, temperature=0.8)
model="gpt-4o-mini",
messages=messages,
temperature=0.8,
response_format={"type": "json_object"}
)
import json import json
result = json.loads(response.choices[0].message.content) 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 return result
except Exception as e: except Exception as e:
logger.error(f"[GPT Error] continue_conversation: {type(e).__name__}: {str(e)}")
return { return {
"response": "I see. Tell me more about that.", "response": "I see. Tell me more about that.",
"translation": "Понятно. Расскажи мне больше об этом.", "translation": "Понятно. Расскажи мне больше об этом.",
@@ -458,21 +499,23 @@ User: {user_message}
- Вопросы на грамматику, лексику и понимание""" - Вопросы на грамматику, лексику и понимание"""
try: try:
response = await self.client.chat.completions.create( logger.info(f"[GPT Request] generate_level_test: generating 7 questions")
model="gpt-4o-mini",
messages=[ messages = [
{"role": "system", "content": "Ты - эксперт по тестированию уровня английского языка. Создавай объективные тесты."}, {"role": "system", "content": "Ты - эксперт по тестированию уровня английского языка. Создавай объективные тесты."},
{"role": "user", "content": prompt} {"role": "user", "content": prompt}
], ]
temperature=0.7,
response_format={"type": "json_object"} response_data = await self._make_openai_request(messages, temperature=0.7)
)
import json import json
result = json.loads(response.choices[0].message.content) 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', []) return result.get('questions', [])
except Exception as e: except Exception as e:
logger.error(f"[GPT Error] generate_level_test: {type(e).__name__}: {str(e)}, using fallback questions")
# Fallback с базовыми вопросами # Fallback с базовыми вопросами
return [ return [
{ {