Обновлены настройки для интеграции с AI-agent сервисом. Изменены переменные окружения в .env, добавлен путь к .env в конфигурации, обновлен GigaChatSe… #3

Merged
FDKost merged 1 commits from backendFixes into master 2025-12-24 02:00:30 +03:00
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_SECRET=MDE5OTY2ZjQtMWM1Yy03MzgyLTkwMDYtYjg0NDE5ZmJlNWQxOjJjODBmOWE2LWU4YWMtNDE4YS1iOGVkLWE4NTE0YzVkNDAwNw==
GIGACHAT_CLIENT_ID=019966f0-5781-76e6-a84f-ec7de158188a
GIGACHAT_CLIENT_SECRET=MDE5OTY2ZjAtNTc4MS03NmU2LWE4NGYtZWM3ZGUxNTgxODhhOjI3MDMxZjIxLWY3NWYtNGI4NS05MzM1LTI4ZDYyOWM3MmM0MA==
GIGACHAT_AUTH_URL=https://ngw.devices.sberbank.ru:9443/api/v2/oauth
GIGACHAT_BASE_URL=https://gigachat.devices.sberbank.ru/api/v1
GIGACHAT_MODEL_CHAT=GigaChat-2-Lite
@@ -29,3 +29,5 @@ STORAGE_BUCKET=new-planet-images
STORAGE_USE_SSL=false
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 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):
@@ -49,7 +57,14 @@ class Settings(BaseSettings):
STORAGE_USE_SSL: bool = False
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_SECRET: str = "MDE5OTY2ZjQtMWM1Yy03MzgyLTkwMDYtYjg0NDE5ZmJlNWQxOjJjODBmOWE2LWU4YWMtNDE4YS1iOGVkLWE4NTE0YzVkNDAwNw=="
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
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

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.chat_service import chat_service, ChatService
from app.services.schedule_generator import schedule_generator, ScheduleGenerator
from app.services.ai_agent_client import ai_agent_client, AIAgentClient
__all__ = [
"auth_service",
@@ -18,5 +19,7 @@ __all__ = [
"ChatService",
"schedule_generator",
"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 typing import Optional, List, Dict, Any
from dotenv import load_dotenv
from app.core.config import settings
load_dotenv()
from app.services.ai_agent_client import ai_agent_client
class GigaChatService:
"""
Сервис для работы с GigaChat через внешний AI-agent сервис.
Все запросы к GigaChat теперь проходят через внешний сервис.
"""
def __init__(self):
self.access_token: Optional[str] = None
self.token_expires_at: Optional[float] = None
async def _get_token(self) -> str:
"""Получить OAuth токен"""
"""
Получить OAuth токен.
ВНИМАНИЕ: Этот метод больше не используется, так как все запросы
к GigaChat теперь проходят через внешний AI-agent сервис.
Метод оставлен для возможной обратной совместимости.
"""
# Проверяем, не истек ли токен (оставляем запас 60 секунд)
if self.access_token and self.token_expires_at:
if time.time() < (self.token_expires_at - 60):
@@ -110,49 +117,59 @@ class GigaChatService:
context: Optional[List[Dict[str, Any]]] = None,
model: str = None
) -> Dict[str, Any]:
"""Отправить сообщение в GigaChat"""
token = await self._get_token()
model = model or settings.GIGACHAT_MODEL_CHAT or "GigaChat"
"""
Отправить сообщение в GigaChat через внешний AI-agent сервис.
Сохраняет обратную совместимость с форматом ответа GigaChat API.
"""
try:
# Используем внешний AI-agent сервис
result = await ai_agent_client.chat(
message=message,
conversation_id=None, # Если нужен conversation_id, его нужно передавать отдельно
context=context
)
messages = context or []
messages.append({"role": "user", "content": message})
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
payload = {
"model": model,
"messages": messages,
"temperature": 0.7,
"max_tokens": 2000
}
# Отключаем проверку SSL (только для разработки!)
# Используем 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()
# Преобразуем ответ AI-agent сервиса в формат, совместимый с GigaChat API
# Предполагаем, что ai_agent_client возвращает структуру ChatResponse или аналогичную
if "response" in result:
# Если ответ в формате ChatResponse, преобразуем в формат GigaChat
return {
"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)
}
}
else:
# Если ответ уже в формате GigaChat, возвращаем как есть
return result
except Exception as e:
# Если внешний сервис недоступен, пробрасываем ошибку
raise Exception(f"AI-agent service error: {str(e)}")
async def generate_text(
self,
prompt: str,
model: str = None
) -> str:
"""Генерация текста по промпту"""
result = await self.chat(prompt, model=model)
return result.get("choices", [{}])[0].get("message", {}).get("content", "")
"""
Генерация текста по промпту через внешний AI-agent сервис.
"""
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()

View File

@@ -1,11 +1,14 @@
import json
from typing import List, Dict, Any, Optional
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.core.config import settings
from app.crud import schedule as crud_schedule, task as crud_task
from app.schemas.schedule import ScheduleCreate
from app.schemas.task import TaskCreate
from app.models.schedule import Schedule
from datetime import date
@@ -104,11 +107,17 @@ class ScheduleGenerator:
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 {
"schedule_id": db_schedule.id,
"title": db_schedule.title,
"schedule_id": db_schedule_with_tasks.id,
"title": db_schedule_with_tasks.title,
"tasks": [
{
"title": task.title,
@@ -117,7 +126,7 @@ class ScheduleGenerator:
"category": task.category,
"order": task.order
}
for task in db_schedule.tasks
for task in db_schedule_with_tasks.tasks
]
}
except json.JSONDecodeError as e:

View File

@@ -63,4 +63,8 @@ volumes:
networks:
new-planet-network:
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 - 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...