Compare commits

...

4 Commits

64 changed files with 559 additions and 50 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

@@ -61,6 +61,10 @@ async def create_schedule(
schedule_data = schedule_in.model_dump() schedule_data = schedule_in.model_dump()
schedule_data["user_id"] = current_user.id schedule_data["user_id"] = current_user.id
schedule = await crud_schedule.create(db, schedule_data) schedule = await crud_schedule.create(db, schedule_data)
# Перезагрузить расписание с tasks для корректной сериализации
schedule = await crud_schedule.get_with_tasks(db, schedule.id)
return schedule return schedule
@@ -86,6 +90,10 @@ async def update_schedule(
update_data = schedule_in.model_dump(exclude_unset=True) update_data = schedule_in.model_dump(exclude_unset=True)
schedule = await crud_schedule.update(db, schedule, update_data) schedule = await crud_schedule.update(db, schedule, update_data)
# Перезагрузить расписание с tasks для корректной сериализации
schedule = await crud_schedule.get_with_tasks(db, schedule.id)
return schedule return schedule

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

@@ -0,0 +1,284 @@
2025-12-26 19:02:56 - root - INFO - Starting up...
2025-12-26 19:02:56 - root - INFO - Starting up...
2025-12-26 19:02:59 - root - INFO - Shutting down...
2025-12-26 19:02:59 - root - INFO - Shutting down...
2025-12-26 19:03:28 - root - INFO - Starting up...
2025-12-26 19:03:28 - root - INFO - Starting up...
2025-12-26 19:36:24 - app.middleware.error_handler - ERROR - Unhandled exception: 1 validation error:
{'type': 'get_attribute_error', 'loc': ('response', 'tasks'), 'msg': "Error extracting attribute: MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)", 'input': <app.models.schedule.Schedule object at 0x000001BB3C11BE00>, 'ctx': {'error': "MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)"}}
File "C:\Users\yfili\Desktop\Planet\New-planet-api\new-planet-backend\app\api\v1\schedules.py", line 54, in create_schedule
POST /api/v1/schedules
Traceback (most recent call last):
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
~~~~~~~~~~~~~~~~~~^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\contextlib.py", line 162, in __exit__
self.gen.throw(value)
~~~~~~~~~~~~~~^^^^^^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_utils.py", line 85, in collapse_excgroups
raise exc
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\yfili\Desktop\Planet\New-planet-api\new-planet-backend\app\middleware\rate_limiter.py", line 33, in dispatch
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 168, in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 144, in coro
await self.app(scope, receive_or_disconnect, send_no_error)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\cors.py", line 85, in __call__
await self.app(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\exceptions.py", line 63, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
raise exc
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__
await self.app(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\routing.py", line 716, in __call__
await self.middleware_stack(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\routing.py", line 736, in app
await route.handle(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\routing.py", line 290, in handle
await self.app(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 119, in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
raise exc
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 105, in app
response = await f(request)
^^^^^^^^^^^^^^^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 448, in app
content = await serialize_response(
^^^^^^^^^^^^^^^^^^^^^^^^^
...<10 lines>...
)
^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 274, in serialize_response
raise ResponseValidationError(
...<3 lines>...
)
fastapi.exceptions.ResponseValidationError: 1 validation error:
{'type': 'get_attribute_error', 'loc': ('response', 'tasks'), 'msg': "Error extracting attribute: MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)", 'input': <app.models.schedule.Schedule object at 0x000001BB3C11BE00>, 'ctx': {'error': "MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)"}}
File "C:\Users\yfili\Desktop\Planet\New-planet-api\new-planet-backend\app\api\v1\schedules.py", line 54, in create_schedule
POST /api/v1/schedules
2025-12-26 19:36:24 - app.middleware.error_handler - ERROR - Unhandled exception: 1 validation error:
{'type': 'get_attribute_error', 'loc': ('response', 'tasks'), 'msg': "Error extracting attribute: MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)", 'input': <app.models.schedule.Schedule object at 0x000001BB3C11BE00>, 'ctx': {'error': "MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)"}}
File "C:\Users\yfili\Desktop\Planet\New-planet-api\new-planet-backend\app\api\v1\schedules.py", line 54, in create_schedule
POST /api/v1/schedules
Traceback (most recent call last):
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
~~~~~~~~~~~~~~~~~~^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\contextlib.py", line 162, in __exit__
self.gen.throw(value)
~~~~~~~~~~~~~~^^^^^^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_utils.py", line 85, in collapse_excgroups
raise exc
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\yfili\Desktop\Planet\New-planet-api\new-planet-backend\app\middleware\rate_limiter.py", line 33, in dispatch
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 168, in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 144, in coro
await self.app(scope, receive_or_disconnect, send_no_error)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\cors.py", line 85, in __call__
await self.app(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\exceptions.py", line 63, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
raise exc
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__
await self.app(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\routing.py", line 716, in __call__
await self.middleware_stack(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\routing.py", line 736, in app
await route.handle(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\routing.py", line 290, in handle
await self.app(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 119, in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
raise exc
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 105, in app
response = await f(request)
^^^^^^^^^^^^^^^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 448, in app
content = await serialize_response(
^^^^^^^^^^^^^^^^^^^^^^^^^
...<10 lines>...
)
^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 274, in serialize_response
raise ResponseValidationError(
...<3 lines>...
)
fastapi.exceptions.ResponseValidationError: 1 validation error:
{'type': 'get_attribute_error', 'loc': ('response', 'tasks'), 'msg': "Error extracting attribute: MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)", 'input': <app.models.schedule.Schedule object at 0x000001BB3C11BE00>, 'ctx': {'error': "MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)"}}
File "C:\Users\yfili\Desktop\Planet\New-planet-api\new-planet-backend\app\api\v1\schedules.py", line 54, in create_schedule
POST /api/v1/schedules
2025-12-26 19:37:04 - root - INFO - Shutting down...
2025-12-26 19:37:04 - root - INFO - Shutting down...
2025-12-26 19:37:13 - root - INFO - Starting up...
2025-12-26 19:37:13 - root - INFO - Starting up...
2025-12-26 19:38:27 - root - INFO - Shutting down...
2025-12-26 19:38:27 - root - INFO - Shutting down...
2025-12-26 19:39:43 - root - INFO - Starting up...
2025-12-26 19:39:43 - root - INFO - Starting up...
2025-12-26 19:40:04 - app.middleware.error_handler - ERROR - Unhandled exception: 1 validation error:
{'type': 'get_attribute_error', 'loc': ('response', 'tasks'), 'msg': "Error extracting attribute: MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)", 'input': <app.models.schedule.Schedule object at 0x000002682DC8AFD0>, 'ctx': {'error': "MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)"}}
File "C:\Users\yfili\Desktop\Planet\New-planet-api\new-planet-backend\app\api\v1\schedules.py", line 54, in create_schedule
POST /api/v1/schedules
Traceback (most recent call last):
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
~~~~~~~~~~~~~~~~~~^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\contextlib.py", line 162, in __exit__
self.gen.throw(value)
~~~~~~~~~~~~~~^^^^^^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_utils.py", line 85, in collapse_excgroups
raise exc
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\yfili\Desktop\Planet\New-planet-api\new-planet-backend\app\middleware\rate_limiter.py", line 33, in dispatch
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 168, in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 144, in coro
await self.app(scope, receive_or_disconnect, send_no_error)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\cors.py", line 85, in __call__
await self.app(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\exceptions.py", line 63, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
raise exc
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__
await self.app(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\routing.py", line 716, in __call__
await self.middleware_stack(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\routing.py", line 736, in app
await route.handle(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\routing.py", line 290, in handle
await self.app(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 119, in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
raise exc
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 105, in app
response = await f(request)
^^^^^^^^^^^^^^^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 448, in app
content = await serialize_response(
^^^^^^^^^^^^^^^^^^^^^^^^^
...<10 lines>...
)
^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 274, in serialize_response
raise ResponseValidationError(
...<3 lines>...
)
fastapi.exceptions.ResponseValidationError: 1 validation error:
{'type': 'get_attribute_error', 'loc': ('response', 'tasks'), 'msg': "Error extracting attribute: MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)", 'input': <app.models.schedule.Schedule object at 0x000002682DC8AFD0>, 'ctx': {'error': "MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)"}}
File "C:\Users\yfili\Desktop\Planet\New-planet-api\new-planet-backend\app\api\v1\schedules.py", line 54, in create_schedule
POST /api/v1/schedules
2025-12-26 19:40:04 - app.middleware.error_handler - ERROR - Unhandled exception: 1 validation error:
{'type': 'get_attribute_error', 'loc': ('response', 'tasks'), 'msg': "Error extracting attribute: MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)", 'input': <app.models.schedule.Schedule object at 0x000002682DC8AFD0>, 'ctx': {'error': "MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)"}}
File "C:\Users\yfili\Desktop\Planet\New-planet-api\new-planet-backend\app\api\v1\schedules.py", line 54, in create_schedule
POST /api/v1/schedules
Traceback (most recent call last):
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
~~~~~~~~~~~~~~~~~~^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\contextlib.py", line 162, in __exit__
self.gen.throw(value)
~~~~~~~~~~~~~~^^^^^^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_utils.py", line 85, in collapse_excgroups
raise exc
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\yfili\Desktop\Planet\New-planet-api\new-planet-backend\app\middleware\rate_limiter.py", line 33, in dispatch
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 168, in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\base.py", line 144, in coro
await self.app(scope, receive_or_disconnect, send_no_error)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\cors.py", line 85, in __call__
await self.app(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\middleware\exceptions.py", line 63, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
raise exc
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__
await self.app(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\routing.py", line 716, in __call__
await self.middleware_stack(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\routing.py", line 736, in app
await route.handle(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\routing.py", line 290, in handle
await self.app(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 119, in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
raise exc
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 105, in app
response = await f(request)
^^^^^^^^^^^^^^^^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 448, in app
content = await serialize_response(
^^^^^^^^^^^^^^^^^^^^^^^^^
...<10 lines>...
)
^
File "C:\Users\yfili\AppData\Local\Programs\Python\Python313\Lib\site-packages\fastapi\routing.py", line 274, in serialize_response
raise ResponseValidationError(
...<3 lines>...
)
fastapi.exceptions.ResponseValidationError: 1 validation error:
{'type': 'get_attribute_error', 'loc': ('response', 'tasks'), 'msg': "Error extracting attribute: MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)", 'input': <app.models.schedule.Schedule object at 0x000002682DC8AFD0>, 'ctx': {'error': "MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s)"}}
File "C:\Users\yfili\Desktop\Planet\New-planet-api\new-planet-backend\app\api\v1\schedules.py", line 54, in create_schedule
POST /api/v1/schedules
2025-12-26 19:43:44 - root - INFO - Shutting down...
2025-12-26 19:43:44 - root - INFO - Shutting down...
2025-12-26 19:43:48 - root - INFO - Starting up...
2025-12-26 19:43:48 - root - INFO - Starting up...
2025-12-26 22:22:32 - root - INFO - Shutting down...
2025-12-26 22:22:32 - root - INFO - Shutting down...

View File

@@ -88,7 +88,7 @@ if __name__ == "__main__":
import uvicorn import uvicorn
uvicorn.run( uvicorn.run(
"app.main:app", "app.main:app",
host="127.0.0.1", host="0.0.0.0",
port=8000, port=8000,
reload=settings.DEBUG reload=settings.DEBUG
) )

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.
"""
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=False для полного отключения проверки сертификата "role": "assistant",
connector = aiohttp.TCPConnector(ssl=False) "content": result["response"]
async with aiohttp.ClientSession(connector=connector) as session: },
async with session.post( "finish_reason": "stop"
f"{settings.GIGACHAT_BASE_URL}/chat/completions", }],
headers=headers, "usage": {
json=payload "total_tokens": result.get("tokens_used", 0),
) as response: "prompt_tokens": 0,
if response.status != 200: "completion_tokens": result.get("tokens_used", 0)
error_text = await response.text() }
raise Exception(f"GigaChat API error: {response.status} - {error_text}") }
else:
result = await response.json() # Если ответ уже в формате GigaChat, возвращаем как есть
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...