Compare commits

..

4 Commits

Author SHA1 Message Date
95f87c253c Merge pull request 'Обновлены настройки для интеграции с AI-agent сервисом. Изменены переменные окружения в .env, добавлен путь к .env в конфигурации, обновлен GigaChatSe…' (#3) from backendFixes into master
Reviewed-on: #3
2025-12-24 02:00:29 +03:00
32bfec2074 Обновлены настройки для интеграции с AI-agent сервисом. Изменены переменные окружения в .env, добавлен путь к .env в конфигурации, обновлен GigaChatService для работы через AI-agent. Также исправлены запросы в ScheduleGenerator для корректной загрузки задач. Обновлен docker-compose для подключения к AI-agent сервису. 2025-12-24 01:59:39 +03:00
a649fb1192 Изменения:
-добавлены нетворки в докер композ
-исправлен рутинг (баг пайчарма)
-запросы к ии агентам не проходят из-за ссл сертификата (пробовали отключить, но пока не выходит, нужно доделать)
2025-12-19 00:57:13 +03:00
24f4ce118f Обновить new-planet-backend/requirements.txt 2025-12-18 17:18:04 +03:00
73 changed files with 691 additions and 102 deletions

5
.idea/misc.xml generated
View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_22" project-jdk-name="openjdk-23" project-jdk-type="JavaSDK"> <component name="Black">
<output url="file://$PROJECT_DIR$/out" /> <option name="sdkName" value="Python 3.14 (New-planet-api)" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.14 (New-planet-api)" project-jdk-type="Python SDK" />
</project> </project>

View File

@@ -2,8 +2,11 @@
<module type="JAVA_MODULE" version="4"> <module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true"> <component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$">
<orderEntry type="inheritedJdk" /> <sourceFolder url="file://$MODULE_DIR$/new-planet-backend" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.14 (New-planet-api)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

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,21 +1,27 @@
# Database 
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
GIGACHAT_MODEL_SCHEDULE=GigaChat-2-Pro
# Security
SECRET_KEY=3db8542397edddbd6162ad823157e36f8d47232aa646725d4799266229ba7aa4
# Database
POSTGRES_USER=postgres POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres POSTGRES_PASSWORD=postgres
POSTGRES_DB=newplanet POSTGRES_DB=newplanet
POSTGRES_HOST=localhost POSTGRES_HOST=localhost
POSTGRES_PORT=5432 POSTGRES_PORT=5432
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/newplanet
# Redis # Redis
REDIS_HOST=localhost REDIS_HOST=localhost
REDIS_PORT=6379 REDIS_PORT=6379
REDIS_DB=0 REDIS_DB=0
REDIS_URL=redis://localhost:6379
# Security # Storage
SECRET_KEY=jwt-secret-key
# Storage (MinIO/S3)
STORAGE_ENDPOINT=localhost:9000 STORAGE_ENDPOINT=localhost:9000
STORAGE_ACCESS_KEY=minioadmin STORAGE_ACCESS_KEY=minioadmin
STORAGE_SECRET_KEY=minioadmin STORAGE_SECRET_KEY=minioadmin
@@ -23,6 +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
# GigaChat API # Agents
GIGACHAT_CLIENT_ID=gigachat-client-id AI_AGENT_BASE_URL=http://localhost:8001
GIGACHAT_CLIENT_SECRET="gigachat-token-here"

View File

@@ -23,12 +23,13 @@ Backend API для мобильного приложения **Новая Пла
### Установка ### Установка
1. Клонируйте репозиторий 1. Клонируйте репозиторий
2. Установите зависимости: 2. Установить окружение ("python -m venv venv")
3. Установите зависимости:
```bash ```bash
pip install -r requirements.txt pip install -r requirements.txt
``` ```
3. Настройте `.env`: 4. Настройте `.env`:
```bash ```bash
cp .env.example .env cp .env.example .env
#В целом вам нужно поменять GIGACHAT API секцию, JWT Secret key сгенерить, просто в поисковике генератор на 256 байт сделаете JWT #В целом вам нужно поменять GIGACHAT API секцию, JWT Secret key сгенерить, просто в поисковике генератор на 256 байт сделаете JWT
@@ -36,7 +37,7 @@ cp .env.example .env
# Отредактируйте .env с вашими настройками # Отредактируйте .env с вашими настройками
``` ```
4. Запустите инфраструктуру (Docker): 5. Запустите инфраструктуру (Docker):
```bash ```bash
docker-compose -f docker/docker-compose.yml up -d docker-compose -f docker/docker-compose.yml up -d
# или используйте вариант ниже,но лучше вариант выше для избежания непредвиденного # или используйте вариант ниже,но лучше вариант выше для избежания непредвиденного
@@ -44,12 +45,12 @@ docker-compose -f docker/docker-compose.yml up -d
docker-compose up docker-compose up
``` ```
5. Примените миграции: 6. Примените миграции:
```bash ```bash
alembic upgrade head alembic upgrade head
``` ```
6. Запустите сервер: 7. Запустите сервер:
```bash ```bash
uvicorn app.main:app --reload uvicorn app.main:app --reload
# если не запустилось, проверяйте есть ли .venv(установлено ли окружение для питона), также попробуйте в венве uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 # если не запустилось, проверяйте есть ли .venv(установлено ли окружение для питона), также попробуйте в венве uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

View File

@@ -7,7 +7,7 @@ from app.crud import user as crud_user
from app.services.auth_service import auth_service from app.services.auth_service import auth_service
from app.models.user import User from app.models.user import User
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/auth/login") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
async def get_current_user( async def get_current_user(

View File

@@ -3,9 +3,27 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import get_db from app.db.session import get_db
from app.api.deps import get_current_active_user from app.api.deps import get_current_active_user
from app.models.user import User from app.models.user import User
from app.schemas.ai import ChatRequest, ChatResponse, ScheduleGenerateRequest, ScheduleGenerateResponse from app.models.ai_conversation import AIConversation
from app.schemas.ai import (
ChatRequest,
ChatResponse,
ScheduleGenerateRequest,
ScheduleGenerateResponse,
ConversationHistory,
ConversationListItem,
ScheduleUpdateRequest,
ScheduleUpdateResponse,
RecommendationRequest,
RecommendationResponse,
)
from app.services.chat_service import chat_service from app.services.chat_service import chat_service
from app.services.schedule_generator import schedule_generator from app.services.schedule_generator import schedule_generator
from app.services.cache_service import cache_service
from app.services.gigachat_service import gigachat_service
from app.crud import schedule as crud_schedule, task as crud_task
from app.schemas.task import TaskCreate
from app.schemas.schedule import ScheduleUpdate
from app.core.config import settings
router = APIRouter() router = APIRouter()
@@ -30,7 +48,6 @@ async def chat_with_ai(
detail=f"Chat error: {str(e)}" detail=f"Chat error: {str(e)}"
) )
@router.post("/schedule/generate", response_model=ScheduleGenerateResponse) @router.post("/schedule/generate", response_model=ScheduleGenerateResponse)
async def generate_schedule_ai( async def generate_schedule_ai(
request: ScheduleGenerateRequest, request: ScheduleGenerateRequest,
@@ -58,3 +75,4 @@ async def generate_schedule_ai(
detail=f"Failed to generate schedule: {str(e)}" detail=f"Failed to generate schedule: {str(e)}"
) )

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):
@@ -10,15 +18,15 @@ class Settings(BaseSettings):
DEBUG: bool = False DEBUG: bool = False
# Security # Security
SECRET_KEY: str SECRET_KEY: str = "3db8542397edddbd6162ad823157e36f8d47232aa646725d4799266229ba7aa4"
ALGORITHM: str = "HS256" ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
REFRESH_TOKEN_EXPIRE_DAYS: int = 7 REFRESH_TOKEN_EXPIRE_DAYS: int = 7
# Database # Database
POSTGRES_USER: str POSTGRES_USER: str = "postgres"
POSTGRES_PASSWORD: str POSTGRES_PASSWORD: str = "postgres"
POSTGRES_DB: str POSTGRES_DB: str = "newplanet"
POSTGRES_HOST: str = "localhost" POSTGRES_HOST: str = "localhost"
POSTGRES_PORT: int = 5432 POSTGRES_PORT: int = 5432
DATABASE_URL: Optional[str] = None DATABASE_URL: Optional[str] = None
@@ -43,15 +51,22 @@ class Settings(BaseSettings):
# Storage (MinIO/S3) # Storage (MinIO/S3)
STORAGE_ENDPOINT: str = "localhost:9000" STORAGE_ENDPOINT: str = "localhost:9000"
STORAGE_ACCESS_KEY: str STORAGE_ACCESS_KEY: str = "minioadmin"
STORAGE_SECRET_KEY: str STORAGE_SECRET_KEY: str = "minioadmin"
STORAGE_BUCKET: str = "new-planet-images" STORAGE_BUCKET: str = "new-planet-images"
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)
GIGACHAT_CLIENT_ID: str # URL можно переопределить через переменную окружения AI_AGENT_BASE_URL
GIGACHAT_CLIENT_SECRET: str # Для 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" GIGACHAT_AUTH_URL: str = "https://ngw.devices.sberbank.ru:9443/api/v2/oauth"
GIGACHAT_BASE_URL: str = "https://gigachat.devices.sberbank.ru/api/v1" GIGACHAT_BASE_URL: str = "https://gigachat.devices.sberbank.ru/api/v1"
GIGACHAT_MODEL_CHAT: str = "GigaChat-2-Lite" GIGACHAT_MODEL_CHAT: str = "GigaChat-2-Lite"
@@ -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

@@ -2,7 +2,7 @@ from datetime import datetime, timedelta
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
from jose import JWTError, jwt from jose import JWTError, jwt
import bcrypt import bcrypt
from app.core.config import settings from config import settings
def verify_password(plain_password: str, hashed_password: str) -> bool: def verify_password(plain_password: str, hashed_password: str) -> bool:

View File

@@ -1,4 +1,4 @@
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import declarative_base
from sqlalchemy import Column, DateTime, func, String from sqlalchemy import Column, DateTime, func, String
import uuid import uuid

View File

@@ -1,5 +1,5 @@
from app.db.base import Base from base import Base
from app.db.session import engine from session import engine
from app.models import user, schedule, task, reward, ai_conversation from app.models import user, schedule, task, reward, ai_conversation

View File

@@ -3,8 +3,7 @@ from app.core.config import settings
engine = create_async_engine( engine = create_async_engine(
settings.database_url, settings.database_url,
echo=settings.DEBUG, echo=settings.DEBUG
future=True
) )
AsyncSessionLocal = async_sessionmaker( AsyncSessionLocal = async_sessionmaker(

View File

@@ -1,3 +1,18 @@
import sys
from pathlib import Path
# Добавляем корневую директорию проекта в PYTHONPATH при прямом запуске
# Это нужно, чтобы Python мог найти модуль 'app'
# Проверяем, запускается ли файл напрямую, проверяя имя скрипта
if sys.argv and len(sys.argv) > 0:
script_path = Path(sys.argv[0]).resolve()
current_file = Path(__file__).resolve()
# Если скрипт запускается напрямую (не через модуль)
if script_path == current_file or script_path.name == current_file.name:
project_root = current_file.parent.parent # new-planet-backend
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.exceptions import HTTPException as StarletteHTTPException
@@ -73,7 +88,7 @@ if __name__ == "__main__":
import uvicorn import uvicorn
uvicorn.run( uvicorn.run(
"app.main:app", "app.main:app",
host="0.0.0.0", host="127.0.0.1",
port=8000, port=8000,
reload=settings.DEBUG reload=settings.DEBUG
) )

View File

@@ -38,3 +38,41 @@ class ConversationHistory(BaseModel):
class Config: class Config:
from_attributes = True from_attributes = True
class ConversationListItem(BaseModel):
"""Элемент списка разговоров"""
conversation_id: str
last_message: Optional[str] = None
created_at: datetime
updated_at: datetime
message_count: int = 0
class Config:
from_attributes = True
class ScheduleUpdateRequest(BaseModel):
"""Запрос на обновление расписания через ИИ"""
user_request: str = Field(..., min_length=1, max_length=1000, description="Описание желаемых изменений")
class ScheduleUpdateResponse(BaseModel):
"""Ответ после обновления расписания"""
schedule_id: str
title: str
tasks: List[Dict[str, Any]]
tokens_used: Optional[int] = None
class RecommendationRequest(BaseModel):
"""Запрос на получение рекомендаций"""
preferences: List[str] = Field(default_factory=list, description="Предпочтения пользователя")
category: Optional[str] = Field(None, description="Категория заданий")
completed_tasks: Optional[List[str]] = Field(default_factory=list, description="Уже выполненные задания")
top_k: int = Field(5, ge=1, le=20, description="Количество рекомендаций")
class RecommendationResponse(BaseModel):
"""Ответ с рекомендациями"""
recommendations: List[Dict[str, Any]]
total: int

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

@@ -1,26 +1,50 @@
import os
import aiohttp import aiohttp
import ssl
import base64 import base64
import uuid import uuid
import time import time
from urllib.parse import urlencode
from typing import Optional, List, Dict, Any from typing import Optional, List, Dict, Any
from app.core.config import settings
from app.core.config import settings
from app.services.ai_agent_client import ai_agent_client
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):
return self.access_token return self.access_token
credentials = f"{settings.GIGACHAT_CLIENT_ID}:{settings.GIGACHAT_CLIENT_SECRET}" # Проверяем наличие credentials
encoded_credentials = base64.b64encode(credentials.encode()).decode() client_id = os.getenv("GIGACHAT_CLIENT_ID")
client_secret = os.getenv("GIGACHAT_CLIENT_SECRET")
if not client_id or not client_secret:
raise Exception(
"GigaChat credentials not configured. "
"Please set GIGACHAT_CLIENT_ID and GIGACHAT_CLIENT_SECRET in .env file"
)
# Формируем credentials и кодируем в Base64 с явным указанием UTF-8
credentials = f"{client_id}:{client_secret}".strip().encode('utf-8') # Обрезаем лишние символы
encoded_credentials = base64.b64encode(credentials).decode('utf-8')
headers = { headers = {
"Authorization": f"Basic {encoded_credentials}", "Authorization": f"Basic {encoded_credentials}",
@@ -29,31 +53,61 @@ class GigaChatService:
"RqUID": str(uuid.uuid4()) "RqUID": str(uuid.uuid4())
} }
data = {"scope": "GIGACHAT_API_PERS"} # Правильно кодируем данные формы (как в рабочем примере)
form_data = {
"grant_type": "client_credentials",
"scope": "GIGACHAT_API_PERS"
}
# Создаем SSL контекст без проверки сертификата (только для разработки!) # Отключаем проверку SSL (только для разработки!)
ssl_context = ssl.create_default_context() # Используем ssl=False для полного отключения проверки сертификата
ssl_context.check_hostname = False connector = aiohttp.TCPConnector(ssl=False)
ssl_context.verify_mode = ssl.CERT_NONE
connector = aiohttp.TCPConnector(ssl=ssl_context)
async with aiohttp.ClientSession(connector=connector) as session: async with aiohttp.ClientSession(connector=connector) as session:
async with session.post( async with session.post(
settings.GIGACHAT_AUTH_URL, os.getenv("GIGACHAT_BASE_URL"),
headers=headers, headers=headers,
data=data data=form_data
) as response: ) as response:
if response.status != 200: if response.status != 200:
raise Exception(f"Failed to get token: {response.status}") # Получаем детали ошибки из ответа
try:
error_body = await response.text()
# Пытаемся распарсить как JSON, если не получается - возвращаем текст
try:
error_json = await response.json()
error_detail = error_json.get("error_description") or error_json.get("error") or str(error_json)
except:
error_detail = error_body
except:
error_detail = "No error details available"
raise Exception(
f"Failed to get token: HTTP {response.status}. "
f"Error details: {error_detail}. "
f"Check your GIGACHAT_CLIENT_ID and GIGACHAT_CLIENT_SECRET_2 in .env file"
)
result = await response.json() result = await response.json()
self.access_token = result.get("access_token") self.access_token = result.get("access_token")
expires_in = result.get("expires_at", 1800) if not self.access_token:
# expires_at может быть timestamp или количество секунд raise Exception(f"Token not found in response: {result}")
if expires_in > 1000000000: # Это timestamp
self.token_expires_at = expires_in # Обрабатываем время истечения токена (может быть expires_at или expires_in)
else: # Это количество секунд expires_at = result.get("expires_at")
expires_in = result.get("expires_in")
if expires_at:
# expires_at может быть timestamp или количество секунд
if expires_at > 1000000000: # Это timestamp
self.token_expires_at = expires_at
else: # Это количество секунд
self.token_expires_at = time.time() + expires_at
elif expires_in:
# expires_in - это всегда количество секунд до истечения
self.token_expires_at = time.time() + expires_in self.token_expires_at = time.time() + expires_in
else:
# По умолчанию 30 минут (1800 секунд)
self.token_expires_at = time.time() + 1800
return self.access_token return self.access_token
@@ -63,52 +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 Сохраняет обратную совместимость с форматом ответа GigaChat API.
"""
messages = context or [] try:
messages.append({"role": "user", "content": message}) # Используем внешний AI-agent сервис
result = await ai_agent_client.chat(
headers = { message=message,
"Authorization": f"Bearer {token}", conversation_id=None, # Если нужен conversation_id, его нужно передавать отдельно
"Content-Type": "application/json" context=context
} )
payload = { # Преобразуем ответ AI-agent сервиса в формат, совместимый с GigaChat API
"model": model, # Предполагаем, что ai_agent_client возвращает структуру ChatResponse или аналогичную
"messages": messages, if "response" in result:
"temperature": 0.7, # Если ответ в формате ChatResponse, преобразуем в формат GigaChat
"max_tokens": 2000 return {
} "model": result.get("model", model or settings.GIGACHAT_MODEL_CHAT or "GigaChat"),
"choices": [{
# Создаем SSL контекст без проверки сертификата (только для разработки!) "message": {
ssl_context = ssl.create_default_context() "role": "assistant",
ssl_context.check_hostname = False "content": result["response"]
ssl_context.verify_mode = ssl.CERT_NONE },
"finish_reason": "stop"
connector = aiohttp.TCPConnector(ssl=ssl_context) }],
async with aiohttp.ClientSession(connector=connector) as session: "usage": {
async with session.post( "total_tokens": result.get("tokens_used", 0),
f"{settings.GIGACHAT_BASE_URL}/chat/completions", "prompt_tokens": 0,
headers=headers, "completion_tokens": result.get("tokens_used", 0)
json=payload }
) as response: }
if response.status != 200: else:
error_text = await response.text() # Если ответ уже в формате GigaChat, возвращаем как есть
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

@@ -17,6 +17,8 @@ services:
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
networks:
- new-planet-network
redis: redis:
image: redis:7-alpine image: redis:7-alpine
@@ -30,6 +32,8 @@ services:
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
networks:
- new-planet-network
minio: minio:
image: minio/minio:latest image: minio/minio:latest
@@ -48,9 +52,19 @@ services:
interval: 30s interval: 30s
timeout: 20s timeout: 20s
retries: 3 retries: 3
networks:
- new-planet-network
volumes: volumes:
postgres_data: postgres_data:
redis_data: redis_data:
minio_data: minio_data:
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

@@ -1341,3 +1341,254 @@ WHERE users.id = $1::UUID]
2025-12-18 13:49:33 - root - INFO - Shutting down... 2025-12-18 13:49:33 - root - INFO - Shutting down...
2025-12-18 13:49:38 - root - INFO - Starting up... 2025-12-18 13:49:38 - root - INFO - Starting up...
2025-12-18 13:51:42 - root - INFO - Shutting down... 2025-12-18 13:51:42 - root - INFO - Shutting down...
2025-12-18 18:44:49 - root - INFO - Starting up...
2025-12-18 19:39:26 - root - INFO - Starting up...
2025-12-18 19:40:16 - app.middleware.error_handler - ERROR - Unhandled exception: Error 111 connecting to localhost:6379. Connection refused.
+ Exception Group Traceback (most recent call last):
| File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 79, in collapse_excgroups
| yield
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 192, in __call__
| async with anyio.create_task_group() as task_group:
| File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 783, in __aexit__
| raise BaseExceptionGroup(
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 191, in __call__
| with recv_stream, send_stream, collapse_excgroups():
| File "/usr/local/lib/python3.11/contextlib.py", line 158, in __exit__
| self.gen.throw(typ, value, traceback)
| File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
| raise exc
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 193, in __call__
| response = await self.dispatch_func(request, call_next)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/app/app/middleware/rate_limiter.py", line 20, in dispatch
| current_requests = await cache_service.get(cache_key)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/app/app/services/cache_service.py", line 28, in get
| return await self.redis_client.get(key)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/client.py", line 720, in execute_command
| conn = self.connection or await pool.get_connection()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1198, in get_connection
| await self.ensure_connection(connection)
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1231, in ensure_connection
| await connection.connect()
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 298, in connect
| await self.connect_check_health(check_health=True)
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 317, in connect_check_health
| raise ConnectionError(self._error_message(e))
| redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379. Connection refused.
+------------------------------------
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
File "/usr/local/lib/python3.11/contextlib.py", line 158, in __exit__
self.gen.throw(typ, value, traceback)
File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
raise exc
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/app/middleware/rate_limiter.py", line 20, in dispatch
current_requests = await cache_service.get(cache_key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/app/services/cache_service.py", line 28, in get
return await self.redis_client.get(key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/client.py", line 720, in execute_command
conn = self.connection or await pool.get_connection()
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1198, in get_connection
await self.ensure_connection(connection)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1231, in ensure_connection
await connection.connect()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 298, in connect
await self.connect_check_health(check_health=True)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 317, in connect_check_health
raise ConnectionError(self._error_message(e))
redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379. Connection refused.
2025-12-18 19:40:16 - app.middleware.error_handler - ERROR - Unhandled exception: Error 111 connecting to localhost:6379. Connection refused.
+ Exception Group Traceback (most recent call last):
| File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 79, in collapse_excgroups
| yield
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 192, in __call__
| async with anyio.create_task_group() as task_group:
| File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 783, in __aexit__
| raise BaseExceptionGroup(
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 191, in __call__
| with recv_stream, send_stream, collapse_excgroups():
| File "/usr/local/lib/python3.11/contextlib.py", line 158, in __exit__
| self.gen.throw(typ, value, traceback)
| File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
| raise exc
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 193, in __call__
| response = await self.dispatch_func(request, call_next)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/app/app/middleware/rate_limiter.py", line 20, in dispatch
| current_requests = await cache_service.get(cache_key)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/app/app/services/cache_service.py", line 28, in get
| return await self.redis_client.get(key)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/client.py", line 720, in execute_command
| conn = self.connection or await pool.get_connection()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1198, in get_connection
| await self.ensure_connection(connection)
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1231, in ensure_connection
| await connection.connect()
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 298, in connect
| await self.connect_check_health(check_health=True)
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 317, in connect_check_health
| raise ConnectionError(self._error_message(e))
| redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379. Connection refused.
+------------------------------------
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
File "/usr/local/lib/python3.11/contextlib.py", line 158, in __exit__
self.gen.throw(typ, value, traceback)
File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
raise exc
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/app/middleware/rate_limiter.py", line 20, in dispatch
current_requests = await cache_service.get(cache_key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/app/services/cache_service.py", line 28, in get
return await self.redis_client.get(key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/client.py", line 720, in execute_command
conn = self.connection or await pool.get_connection()
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1198, in get_connection
await self.ensure_connection(connection)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1231, in ensure_connection
await connection.connect()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 298, in connect
await self.connect_check_health(check_health=True)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 317, in connect_check_health
raise ConnectionError(self._error_message(e))
redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379. Connection refused.
2025-12-18 19:40:16 - app.middleware.error_handler - ERROR - Unhandled exception: Error 111 connecting to localhost:6379. Connection refused.
+ Exception Group Traceback (most recent call last):
| File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 79, in collapse_excgroups
| yield
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 192, in __call__
| async with anyio.create_task_group() as task_group:
| File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 783, in __aexit__
| raise BaseExceptionGroup(
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 191, in __call__
| with recv_stream, send_stream, collapse_excgroups():
| File "/usr/local/lib/python3.11/contextlib.py", line 158, in __exit__
| self.gen.throw(typ, value, traceback)
| File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
| raise exc
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 193, in __call__
| response = await self.dispatch_func(request, call_next)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/app/app/middleware/rate_limiter.py", line 20, in dispatch
| current_requests = await cache_service.get(cache_key)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/app/app/services/cache_service.py", line 28, in get
| return await self.redis_client.get(key)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/client.py", line 720, in execute_command
| conn = self.connection or await pool.get_connection()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1198, in get_connection
| await self.ensure_connection(connection)
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1231, in ensure_connection
| await connection.connect()
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 298, in connect
| await self.connect_check_health(check_health=True)
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 317, in connect_check_health
| raise ConnectionError(self._error_message(e))
| redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379. Connection refused.
+------------------------------------
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
File "/usr/local/lib/python3.11/contextlib.py", line 158, in __exit__
self.gen.throw(typ, value, traceback)
File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
raise exc
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/app/middleware/rate_limiter.py", line 20, in dispatch
current_requests = await cache_service.get(cache_key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/app/services/cache_service.py", line 28, in get
return await self.redis_client.get(key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/client.py", line 720, in execute_command
conn = self.connection or await pool.get_connection()
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1198, in get_connection
await self.ensure_connection(connection)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1231, in ensure_connection
await connection.connect()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 298, in connect
await self.connect_check_health(check_health=True)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 317, in connect_check_health
raise ConnectionError(self._error_message(e))
redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379. Connection refused.
2025-12-18 22:55:17 - root - INFO - Starting up...
2025-12-18 22:55:17 - root - INFO - Starting up...
2025-12-18 22:56:56 - root - INFO - Starting up...
2025-12-18 22:56:56 - root - INFO - Shutting down...
2025-12-18 22:59:02 - root - INFO - Starting up...
2025-12-18 22:59:02 - root - INFO - Shutting down...
2025-12-18 22:59:53 - root - INFO - Starting up...
2025-12-18 22:59:53 - root - INFO - Starting up...
2025-12-18 22:59:53 - root - INFO - Shutting down...
2025-12-18 22:59:53 - root - INFO - Shutting down...
2025-12-18 20:00:09 - root - INFO - Shutting down...
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-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...

View File

@@ -13,3 +13,4 @@ boto3
aiohttp aiohttp
websockets websockets
python-multipart python-multipart
email-validator