Compare commits

...

5 Commits

Author SHA1 Message Date
Petr 7378820649 (feat) добавлена интеграция с sentry 2026-09-18 16:27:40 +05:00
Petr d871f1fe32 (fix) 2026-09-02 17:38:24 +05:00
Petr 42df024940 (fix) не брать в обработку файлы c префиксами _r, _t 2026-09-01 15:53:55 +05:00
Petr 22a3991c76 Merge remote-tracking branch 'origin/dev' into dev 2026-08-25 12:01:00 +05:00
Petr e689e05fbe fix 2026-08-25 12:00:41 +05:00
7 changed files with 100 additions and 34 deletions
+8
View File
@@ -15,3 +15,11 @@
# HOST=0.0.0.0 # HOST=0.0.0.0
# PORT=8000 # PORT=8000
# RELOAD=True # RELOAD=True
# Sentry Configuration
# SENTRY_DSN=https://a5da7e2cd15fd85150e012a6d62fc13c@sentry.pirogov.ai/26
# SENTRY_ENVIRONMENT=production
# SENTRY_TRACES_SAMPLE_RATE=0.2
# SENTRY_PROFILES_SAMPLE_RATE=0.1
# SENTRY_SEND_DEFAULT_PII=true
+8
View File
@@ -26,3 +26,11 @@ FILESAPTH = audiofiles
GIGAAM_API_URL = http://host.docker.internal:5002 GIGAAM_API_URL = http://host.docker.internal:5002
CALLS_WEB_CLIENT_URL=http://web_client_test:8642 CALLS_WEB_CLIENT_URL=http://web_client_test:8642
AUDIO_FILES_PATH_TEST=D:\SpeechAnalitics\audiofiles AUDIO_FILES_PATH_TEST=D:\SpeechAnalitics\audiofiles
# Sentry Configuration
SENTRY_DSN=https://a5da7e2cd15fd85150e012a6d62fc13c@sentry.pirogov.ai/26
SENTRY_ENVIRONMENT=test
SENTRY_TRACES_SAMPLE_RATE=0.2
SENTRY_PROFILES_SAMPLE_RATE=0.1
SENTRY_SEND_DEFAULT_PII=true
+7
View File
@@ -44,3 +44,10 @@ AUTO_RESTORE_DELAY = int(os.getenv("AUTO_RESTORE_DELAY", "5")) # Задержк
# Recognition retry policy (FileAudioAPI side) # Recognition retry policy (FileAudioAPI side)
MAX_RECOGNITION_ATTEMPTS = int(os.getenv("MAX_RECOGNITION_ATTEMPTS", "3")) MAX_RECOGNITION_ATTEMPTS = int(os.getenv("MAX_RECOGNITION_ATTEMPTS", "3"))
# Sentry Configuration
SENTRY_DSN = os.getenv("SENTRY_DSN", "")
SENTRY_ENVIRONMENT = os.getenv("SENTRY_ENVIRONMENT", "production")
SENTRY_TRACES_SAMPLE_RATE = float(os.getenv("SENTRY_TRACES_SAMPLE_RATE", "0.2"))
SENTRY_PROFILES_SAMPLE_RATE = float(os.getenv("SENTRY_PROFILES_SAMPLE_RATE", "0.1"))
SENTRY_SEND_DEFAULT_PII = os.getenv("SENTRY_SEND_DEFAULT_PII", "true").lower() == "true"
+40 -30
View File
@@ -9,9 +9,10 @@ from apiApp.database import get_db
from apiApp.database.Audio import Audio from apiApp.database.Audio import Audio
from apiApp.database.AiConclusion import AiConclusion from apiApp.database.AiConclusion import AiConclusion
from datetime import datetime from datetime import datetime
import time
import logging import logging
import requests import requests
from apiApp.config import WEBHOOK_ENDPOINT, WEBHOOK_API_KEY from apiApp.config import WEBHOOK_ENDPOINT, WEBHOOK_API_KEY, CALLS_WEB_CLIENT_URL
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
ai_conclusion_router = APIRouter() ai_conclusion_router = APIRouter()
@@ -168,39 +169,48 @@ async def save_ai_conclusion(request: AiConclusionRequest, db: Session = Depends
if (audio.sourse or "").lower() == "external" and request.callback_url: if (audio.sourse or "").lower() == "external" and request.callback_url:
_send_callback(request.callback_url, audio, conclusion_data) _send_callback(request.callback_url, audio, conclusion_data)
# Отправляем webhook в Calls_WEB_Client_main для анализа # Отправляем webhook в Calls_WEB_Client_main для анализа (с retry при перезагрузке контейнера)
try: if (audio.sourse or "").lower() != "external":
if (audio.sourse or "").lower() != "external": logger.info(f"📤 Отправка webhook в Calls_WEB_Client_main для {request.filename}")
logger.info(f"📤 Отправка webhook в Calls_WEB_Client_main для {request.filename}")
webhook_payload = { webhook_payload = {
"audio_id": str(audio.id), "audio_id": str(audio.id),
"filename": request.filename, "filename": request.filename,
"transcription": request.transcription, "transcription": request.transcription,
"corrected_transcription": request.corrected_transcription, "corrected_transcription": request.corrected_transcription,
"segments": request.segments, "segments": request.segments,
"processing_time_seconds": request.processing_time_seconds "processing_time_seconds": request.processing_time_seconds
} }
webhook_response = requests.post( max_retries = 5
WEBHOOK_ENDPOINT, delays = [2, 4, 8, 15, 30]
json=webhook_payload, delivered = False
headers={"X-Webhook-Key": WEBHOOK_API_KEY},
timeout=30
)
if webhook_response.status_code == 200: for attempt in range(1, max_retries + 1):
logger.info(f"✅ Webhook успешно отправлен для {request.filename}") try:
else: webhook_response = requests.post(
logger.warning(f"⚠️ Webhook вернул статус {webhook_response.status_code}") WEBHOOK_ENDPOINT,
logger.warning(f"Response: {webhook_response.text}") json=webhook_payload,
headers={"X-Webhook-Key": WEBHOOK_API_KEY},
timeout=30
)
if webhook_response.status_code in (200, 201):
logger.info(f"✅ Webhook успешно отправлен для {request.filename} (попытка {attempt}/{max_retries})")
delivered = True
break
else:
logger.warning(f"⚠️ Webhook вернул статус {webhook_response.status_code} (попытка {attempt}/{max_retries})")
logger.warning(f"Response: {webhook_response.text[:200]}")
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
logger.warning(f"⏳ Calls_WEB_Client_main временно недоступен ({type(e).__name__}), попытка {attempt}/{max_retries}...")
except Exception as e:
logger.error(f"❌ Непредвиденная ошибка при отправке webhook (попытка {attempt}): {e}")
except requests.exceptions.ConnectionError: if attempt < max_retries:
logger.error(f"❌ Не удалось подключиться к Calls_WEB_Client_main webhook: {WEBHOOK_ENDPOINT}") time.sleep(delays[attempt - 1])
except requests.exceptions.Timeout:
logger.warning(f"⚠️ Таймаут при отправке webhook для {request.filename}") if not delivered:
except Exception as e: logger.error(f"❌ Не удалось доставить webhook для {request.filename} после {max_retries} попыток")
logger.error(f"❌ Ошибка при отправке webhook: {e}")
return AiConclusionResponse( return AiConclusionResponse(
success=True, success=True,
+9 -1
View File
@@ -11,7 +11,15 @@ class Loader():
self.call_types = ['in'] self.call_types = ['in']
self.loaded_files = [] # Список загруженных файлов self.loaded_files = [] # Список загруженных файлов
def filter_call(self, filename: str): def filter_call(self, filename: str) -> bool:
if not filename or not filename.endswith('.wav'):
return False
# Пропускаем раздельные каналы звонков (_r = receive/клиент, _t = transmit/оператор)
name_without_ext = filename[:-4]
if name_without_ext.endswith(('_r', '_t', '_in', '_out', '-r', '-t')):
return False
if filename.split("-")[0] in self.call_types: if filename.split("-")[0] in self.call_types:
return True return True
return False return False
+25 -1
View File
@@ -5,7 +5,10 @@ from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
import logging import logging
from apiApp.config import APP_TITLE, APP_VERSION, API_V1_PREFIX, UPLOAD_FOLDER, DATABASE_URL, PORT, HOST from apiApp.config import (
APP_TITLE, APP_VERSION, API_V1_PREFIX, UPLOAD_FOLDER, DATABASE_URL, PORT, HOST,
SENTRY_DSN, SENTRY_ENVIRONMENT, SENTRY_TRACES_SAMPLE_RATE, SENTRY_PROFILES_SAMPLE_RATE, SENTRY_SEND_DEFAULT_PII
)
from apiApp.database import engine, Base from apiApp.database import engine, Base
from apiApp.routers import audio_router, recognition_router from apiApp.routers import audio_router, recognition_router
from apiApp.routers.ai_conclusion_router import ai_conclusion_router from apiApp.routers.ai_conclusion_router import ai_conclusion_router
@@ -19,6 +22,20 @@ print("✅ audio_management_router imported successfully")
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Инициализация Sentry SDK до создания FastAPI приложения
if SENTRY_DSN:
import sentry_sdk
sentry_sdk.init(
dsn=SENTRY_DSN,
traces_sample_rate=SENTRY_TRACES_SAMPLE_RATE,
profiles_sample_rate=SENTRY_PROFILES_SAMPLE_RATE,
send_default_pii=SENTRY_SEND_DEFAULT_PII,
environment=SENTRY_ENVIRONMENT,
release=f"speech-fileaudio-api@{APP_VERSION}",
)
logger.info("✅ Sentry initialized for speech-fileaudio-api")
# Создание FastAPI приложения # Создание FastAPI приложения
app = FastAPI( app = FastAPI(
title=APP_TITLE, title=APP_TITLE,
@@ -28,6 +45,13 @@ app = FastAPI(
openapi_url=f"{API_V1_PREFIX}/openapi.json" openapi_url=f"{API_V1_PREFIX}/openapi.json"
) )
@app.get("/sentry-debug")
async def trigger_sentry_debug():
"""Эндпоинт для проверки интеграции Sentry"""
division_by_zero = 1 / 0
return {"status": "ok"}
# CORS middleware # CORS middleware
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
+1
View File
@@ -7,3 +7,4 @@ aiofiles==24.1.0
psycopg2-binary psycopg2-binary
paramiko paramiko
requests requests
sentry-sdk[fastapi]>=2.0.0