123
This commit is contained in:
@@ -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 [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user