Merge pull request 'Обновлены настройки для интеграции с AI-agent сервисом. Изменены переменные окружения в .env, добавлен путь к .env в конфигурации, обновлен GigaChatSe…' (#3) from backendFixes into master

Reviewed-on: #3
This commit was merged in pull request #3.
This commit is contained in:
61 changed files with 266 additions and 49 deletions

36
logs/app.log Normal file
View File

@@ -0,0 +1,36 @@
2025-12-23 21:37:30 - root - INFO - Starting up...
2025-12-23 21:37:30 - root - INFO - Starting up...
2025-12-23 21:39:37 - root - INFO - Shutting down...
2025-12-23 21:39:37 - root - INFO - Shutting down...
2025-12-23 21:39:42 - root - INFO - Starting up...
2025-12-23 21:39:42 - root - INFO - Starting up...
2025-12-23 22:07:40 - root - INFO - Shutting down...
2025-12-23 22:07:40 - root - INFO - Shutting down...
2025-12-23 22:07:45 - root - INFO - Starting up...
2025-12-23 22:07:45 - root - INFO - Starting up...
2025-12-23 22:12:42 - root - INFO - Shutting down...
2025-12-23 22:12:42 - root - INFO - Shutting down...
2025-12-23 22:12:47 - root - INFO - Starting up...
2025-12-23 22:12:47 - root - INFO - Starting up...
2025-12-23 22:23:22 - root - INFO - Shutting down...
2025-12-23 22:23:22 - root - INFO - Shutting down...
2025-12-23 22:23:27 - root - INFO - Starting up...
2025-12-23 22:23:27 - root - INFO - Starting up...
2025-12-23 22:52:47 - root - INFO - Shutting down...
2025-12-23 22:52:47 - root - INFO - Shutting down...
2025-12-23 22:52:54 - root - INFO - Starting up...
2025-12-23 22:52:54 - root - INFO - Starting up...
2025-12-23 23:12:01 - root - INFO - Shutting down...
2025-12-23 23:12:01 - root - INFO - Shutting down...
2025-12-23 23:12:07 - root - INFO - Starting up...
2025-12-23 23:12:07 - root - INFO - Starting up...
2025-12-23 23:15:05 - root - INFO - Shutting down...
2025-12-23 23:15:05 - root - INFO - Shutting down...
2025-12-23 23:15:10 - root - INFO - Starting up...
2025-12-23 23:15:10 - root - INFO - Starting up...
2025-12-24 00:01:39 - root - INFO - Shutting down...
2025-12-24 00:01:39 - root - INFO - Shutting down...
2025-12-24 00:01:45 - root - INFO - Starting up...
2025-12-24 00:01:45 - root - INFO - Starting up...
2025-12-24 01:38:58 - root - INFO - Shutting down...
2025-12-24 01:38:58 - root - INFO - Shutting down...

View File

@@ -1,6 +1,6 @@
 
GIGACHAT_CLIENT_ID=019966f4-1c5c-7382-9006-b84419fbe5d1 GIGACHAT_CLIENT_ID=019966f0-5781-76e6-a84f-ec7de158188a
GIGACHAT_CLIENT_SECRET=MDE5OTY2ZjQtMWM1Yy03MzgyLTkwMDYtYjg0NDE5ZmJlNWQxOjJjODBmOWE2LWU4YWMtNDE4YS1iOGVkLWE4NTE0YzVkNDAwNw== GIGACHAT_CLIENT_SECRET=MDE5OTY2ZjAtNTc4MS03NmU2LWE4NGYtZWM3ZGUxNTgxODhhOjI3MDMxZjIxLWY3NWYtNGI4NS05MzM1LTI4ZDYyOWM3MmM0MA==
GIGACHAT_AUTH_URL=https://ngw.devices.sberbank.ru:9443/api/v2/oauth GIGACHAT_AUTH_URL=https://ngw.devices.sberbank.ru:9443/api/v2/oauth
GIGACHAT_BASE_URL=https://gigachat.devices.sberbank.ru/api/v1 GIGACHAT_BASE_URL=https://gigachat.devices.sberbank.ru/api/v1
GIGACHAT_MODEL_CHAT=GigaChat-2-Lite GIGACHAT_MODEL_CHAT=GigaChat-2-Lite
@@ -29,3 +29,5 @@ STORAGE_BUCKET=new-planet-images
STORAGE_USE_SSL=false STORAGE_USE_SSL=false
STORAGE_REGION=us-east-1 STORAGE_REGION=us-east-1
# Agents
AI_AGENT_BASE_URL=http://localhost:8001

View File

@@ -1,5 +1,13 @@
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings
from typing import Optional from typing import Optional
from dotenv import load_dotenv
from pathlib import Path
# Загружаем .env файл перед созданием Settings
# Ищем .env в корне проекта (на уровень выше от app/)
env_path = Path(__file__).parent.parent.parent / ".env"
# override=True гарантирует, что переменные из .env перезапишут существующие
load_dotenv(dotenv_path=env_path, override=True)
class Settings(BaseSettings): class Settings(BaseSettings):
@@ -49,7 +57,14 @@ class Settings(BaseSettings):
STORAGE_USE_SSL: bool = False STORAGE_USE_SSL: bool = False
STORAGE_REGION: str = "us-east-1" STORAGE_REGION: str = "us-east-1"
# GigaChat # AI Agent Service (внешний сервис для работы с GigaChat)
# URL можно переопределить через переменную окружения AI_AGENT_BASE_URL
# Для Docker сети используйте: http://ai-agent:8000 (или имя сервиса из docker-compose)
# Для локальной разработки используйте: http://localhost:8000
AI_AGENT_BASE_URL: str = "http://ai-agent:8000"
AI_AGENT_TIMEOUT: int = 120 # Таймаут в секундах
# GigaChat (оставлено для обратной совместимости, но используется через AI-agent сервис)
GIGACHAT_CLIENT_ID: str = "019966f4-1c5c-7382-9006-b84419fbe5d1" GIGACHAT_CLIENT_ID: str = "019966f4-1c5c-7382-9006-b84419fbe5d1"
GIGACHAT_CLIENT_SECRET: str = "MDE5OTY2ZjQtMWM1Yy03MzgyLTkwMDYtYjg0NDE5ZmJlNWQxOjJjODBmOWE2LWU4YWMtNDE4YS1iOGVkLWE4NTE0YzVkNDAwNw==" GIGACHAT_CLIENT_SECRET: str = "MDE5OTY2ZjQtMWM1Yy03MzgyLTkwMDYtYjg0NDE5ZmJlNWQxOjJjODBmOWE2LWU4YWMtNDE4YS1iOGVkLWE4NTE0YzVkNDAwNw=="
GIGACHAT_AUTH_URL: str = "https://ngw.devices.sberbank.ru:9443/api/v2/oauth" GIGACHAT_AUTH_URL: str = "https://ngw.devices.sberbank.ru:9443/api/v2/oauth"
@@ -65,7 +80,9 @@ class Settings(BaseSettings):
RATE_LIMIT_PER_MINUTE: int = 60 RATE_LIMIT_PER_MINUTE: int = 60
class Config: class Config:
env_file = ".env" # Путь к .env файлу относительно корня проекта
env_file = str(env_path) if env_path.exists() else ".env"
env_file_encoding = "utf-8"
case_sensitive = True case_sensitive = True

View File

@@ -4,6 +4,7 @@ from app.services.storage_service import storage_service, StorageService
from app.services.gigachat_service import gigachat_service, GigaChatService from app.services.gigachat_service import gigachat_service, GigaChatService
from app.services.chat_service import chat_service, ChatService from app.services.chat_service import chat_service, ChatService
from app.services.schedule_generator import schedule_generator, ScheduleGenerator from app.services.schedule_generator import schedule_generator, ScheduleGenerator
from app.services.ai_agent_client import ai_agent_client, AIAgentClient
__all__ = [ __all__ = [
"auth_service", "auth_service",
@@ -18,5 +19,7 @@ __all__ = [
"ChatService", "ChatService",
"schedule_generator", "schedule_generator",
"ScheduleGenerator", "ScheduleGenerator",
"ai_agent_client",
"AIAgentClient",
] ]

View File

@@ -0,0 +1,117 @@
import aiohttp
from typing import Optional, List, Dict, Any
from app.core.config import settings
class AIAgentClient:
"""
Клиент для взаимодействия с внешним AI-agent сервисом.
Сервис должен быть доступен в Docker сети и предоставлять следующие endpoints:
- POST /api/v1/chat - для чата с ИИ
- POST /api/v1/schedule/generate - для генерации расписаний
Примечание: Структура API endpoints может отличаться в зависимости от реализации
внешнего сервиса. При необходимости измените пути в методах этого класса.
"""
def __init__(self, base_url: Optional[str] = None):
self.base_url = base_url or settings.AI_AGENT_BASE_URL
if not self.base_url.endswith('/'):
self.base_url = self.base_url.rstrip('/')
async def chat(
self,
message: str,
conversation_id: Optional[str] = None,
context: Optional[List[Dict[str, Any]]] = None
) -> Dict[str, Any]:
"""
Отправить сообщение в чат через AI-agent сервис.
Ожидаемый формат ответа от сервиса:
{
"response": "текст ответа",
"conversation_id": "id беседы",
"tokens_used": 100,
"model": "модель"
}
или формат GigaChat API (с полем choices).
"""
url = f"{self.base_url}/api/v1/chat"
payload = {
"message": message,
}
if conversation_id:
payload["conversation_id"] = conversation_id
if context:
payload["context"] = context
async with aiohttp.ClientSession() as session:
async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=120)) as response:
if response.status != 200:
error_text = await response.text()
raise Exception(
f"AI-agent service error: HTTP {response.status} - {error_text}"
)
result = await response.json()
return result
async def generate_schedule(
self,
child_age: int,
preferences: List[str],
date: str,
description: Optional[str] = None
) -> Dict[str, Any]:
"""Сгенерировать расписание через AI-agent сервис"""
url = f"{self.base_url}/api/v1/schedule/generate"
payload = {
"child_age": child_age,
"preferences": preferences,
"date": date
}
if description:
payload["description"] = description
async with aiohttp.ClientSession() as session:
async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=120)) as response:
if response.status != 200:
error_text = await response.text()
raise Exception(
f"AI-agent service error: HTTP {response.status} - {error_text}"
)
result = await response.json()
return result
async def generate_text(
self,
prompt: str,
model: Optional[str] = None
) -> str:
"""Генерация текста по промпту через AI-agent сервис"""
# Для совместимости с текущим интерфейсом используем chat endpoint
result = await self.chat(message=prompt)
# Извлекаем текст ответа
# Предполагаем, что ответ имеет структуру ChatResponse
response_text = result.get("response", "")
if not response_text:
# Если структура другая, пытаемся извлечь из choices (как в GigaChat формате)
choices = result.get("choices", [])
if choices:
response_text = choices[0].get("message", {}).get("content", "")
return response_text
# Создаем экземпляр клиента
ai_agent_client = AIAgentClient()

View File

@@ -7,19 +7,26 @@ import time
from urllib.parse import urlencode from urllib.parse import urlencode
from typing import Optional, List, Dict, Any from typing import Optional, List, Dict, Any
from dotenv import load_dotenv
from app.core.config import settings from app.core.config import settings
from app.services.ai_agent_client import ai_agent_client
load_dotenv()
class GigaChatService: class GigaChatService:
"""
Сервис для работы с GigaChat через внешний AI-agent сервис.
Все запросы к GigaChat теперь проходят через внешний сервис.
"""
def __init__(self): def __init__(self):
self.access_token: Optional[str] = None self.access_token: Optional[str] = None
self.token_expires_at: Optional[float] = None self.token_expires_at: Optional[float] = None
async def _get_token(self) -> str: async def _get_token(self) -> str:
"""Получить OAuth токен""" """
Получить OAuth токен.
ВНИМАНИЕ: Этот метод больше не используется, так как все запросы
к GigaChat теперь проходят через внешний AI-agent сервис.
Метод оставлен для возможной обратной совместимости.
"""
# Проверяем, не истек ли токен (оставляем запас 60 секунд) # Проверяем, не истек ли токен (оставляем запас 60 секунд)
if self.access_token and self.token_expires_at: if self.access_token and self.token_expires_at:
if time.time() < (self.token_expires_at - 60): if time.time() < (self.token_expires_at - 60):
@@ -110,49 +117,59 @@ class GigaChatService:
context: Optional[List[Dict[str, Any]]] = None, context: Optional[List[Dict[str, Any]]] = None,
model: str = None model: str = None
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Отправить сообщение в GigaChat""" """
token = await self._get_token() Отправить сообщение в GigaChat через внешний AI-agent сервис.
model = model or settings.GIGACHAT_MODEL_CHAT or "GigaChat" Сохраняет обратную совместимость с форматом ответа GigaChat API.
"""
try:
# Используем внешний AI-agent сервис
result = await ai_agent_client.chat(
message=message,
conversation_id=None, # Если нужен conversation_id, его нужно передавать отдельно
context=context
)
messages = context or [] # Преобразуем ответ AI-agent сервиса в формат, совместимый с GigaChat API
messages.append({"role": "user", "content": message}) # Предполагаем, что ai_agent_client возвращает структуру ChatResponse или аналогичную
if "response" in result:
headers = { # Если ответ в формате ChatResponse, преобразуем в формат GigaChat
"Authorization": f"Bearer {token}", return {
"Content-Type": "application/json" "model": result.get("model", model or settings.GIGACHAT_MODEL_CHAT or "GigaChat"),
"choices": [{
"message": {
"role": "assistant",
"content": result["response"]
},
"finish_reason": "stop"
}],
"usage": {
"total_tokens": result.get("tokens_used", 0),
"prompt_tokens": 0,
"completion_tokens": result.get("tokens_used", 0)
} }
payload = {
"model": model,
"messages": messages,
"temperature": 0.7,
"max_tokens": 2000
} }
else:
# Отключаем проверку SSL (только для разработки!) # Если ответ уже в формате GigaChat, возвращаем как есть
# Используем ssl=False для полного отключения проверки сертификата
connector = aiohttp.TCPConnector(ssl=False)
async with aiohttp.ClientSession(connector=connector) as session:
async with session.post(
f"{settings.GIGACHAT_BASE_URL}/chat/completions",
headers=headers,
json=payload
) as response:
if response.status != 200:
error_text = await response.text()
raise Exception(f"GigaChat API error: {response.status} - {error_text}")
result = await response.json()
return result return result
except Exception as e:
# Если внешний сервис недоступен, пробрасываем ошибку
raise Exception(f"AI-agent service error: {str(e)}")
async def generate_text( async def generate_text(
self, self,
prompt: str, prompt: str,
model: str = None model: str = None
) -> str: ) -> str:
"""Генерация текста по промпту""" """
result = await self.chat(prompt, model=model) Генерация текста по промпту через внешний AI-agent сервис.
return result.get("choices", [{}])[0].get("message", {}).get("content", "") """
try:
# Используем метод generate_text из ai_agent_client
response_text = await ai_agent_client.generate_text(prompt=prompt, model=model)
return response_text
except Exception as e:
# Если произошла ошибка, пробрасываем её
raise Exception(f"AI-agent service error: {str(e)}")
gigachat_service = GigaChatService() gigachat_service = GigaChatService()

View File

@@ -1,11 +1,14 @@
import json import json
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from app.services.gigachat_service import gigachat_service from app.services.gigachat_service import gigachat_service
from app.core.config import settings from app.core.config import settings
from app.crud import schedule as crud_schedule, task as crud_task from app.crud import schedule as crud_schedule, task as crud_task
from app.schemas.schedule import ScheduleCreate from app.schemas.schedule import ScheduleCreate
from app.schemas.task import TaskCreate from app.schemas.task import TaskCreate
from app.models.schedule import Schedule
from datetime import date from datetime import date
@@ -104,11 +107,17 @@ class ScheduleGenerator:
await crud_task.create(db, task_create.model_dump()) await crud_task.create(db, task_create.model_dump())
await db.refresh(db_schedule) # Загружаем расписание с задачами через selectinload для async корректной работы
result = await db.execute(
select(Schedule)
.where(Schedule.id == db_schedule.id)
.options(selectinload(Schedule.tasks))
)
db_schedule_with_tasks = result.scalar_one()
return { return {
"schedule_id": db_schedule.id, "schedule_id": db_schedule_with_tasks.id,
"title": db_schedule.title, "title": db_schedule_with_tasks.title,
"tasks": [ "tasks": [
{ {
"title": task.title, "title": task.title,
@@ -117,7 +126,7 @@ class ScheduleGenerator:
"category": task.category, "category": task.category,
"order": task.order "order": task.order
} }
for task in db_schedule.tasks for task in db_schedule_with_tasks.tasks
] ]
} }
except json.JSONDecodeError as e: except json.JSONDecodeError as e:

View File

@@ -63,4 +63,8 @@ volumes:
networks: networks:
new-planet-network: new-planet-network:
driver: bridge driver: bridge
# ВАЖНО: Внешний AI-agent сервис (https://git.bro-js.ru/Glevel/New-planet-ai-agent.git)
# должен быть запущен в этой же сети для доступа к GigaChat.
# Убедитесь, что сервис ai-agent доступен по имени 'ai-agent' в сети new-planet-network.
external: false

View File

@@ -1580,3 +1580,15 @@ redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379. Connec
2025-12-18 23:00:15 - root - INFO - Starting up... 2025-12-18 23:00:15 - root - INFO - Starting up...
2025-12-18 23:00:15 - root - INFO - Shutting down... 2025-12-18 23:00:15 - root - INFO - Shutting down...
2025-12-18 23:00:15 - root - INFO - Shutting down... 2025-12-18 23:00:15 - root - INFO - Shutting down...
2025-12-23 19:00:39 - root - INFO - Starting up...
2025-12-23 19:00:39 - root - INFO - Starting up...
2025-12-23 19:52:34 - root - INFO - Shutting down...
2025-12-23 19:52:34 - root - INFO - Shutting down...
2025-12-23 19:52:58 - root - INFO - Starting up...
2025-12-23 19:52:58 - root - INFO - Starting up...
2025-12-23 19:54:27 - root - INFO - Shutting down...
2025-12-23 19:54:27 - root - INFO - Shutting down...
2025-12-23 19:54:34 - root - INFO - Starting up...
2025-12-23 19:54:34 - root - INFO - Starting up...
2025-12-23 20:00:23 - root - INFO - Shutting down...
2025-12-23 20:00:23 - root - INFO - Shutting down...