Compare commits
7 Commits
d7b85e4755
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 7378820649 | |||
| d871f1fe32 | |||
| 42df024940 | |||
| 22a3991c76 | |||
| e689e05fbe | |||
| 0cb045a2a4 | |||
| 1ef92eb768 |
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ APP_VERSION=1.0.0
|
|||||||
|
|
||||||
# Server
|
# Server
|
||||||
HOST=0.0.0.0
|
HOST=0.0.0.0
|
||||||
PORT=5056
|
PORT=5008
|
||||||
RELOAD=True
|
RELOAD=True
|
||||||
|
|
||||||
#SFTP
|
#SFTP
|
||||||
@@ -23,4 +23,14 @@ SFPT_PASSWORD = Audio4analy6!6
|
|||||||
|
|
||||||
FILESAPTH = audiofiles
|
FILESAPTH = audiofiles
|
||||||
|
|
||||||
GIGAAM_API_URL = http://localhost:5001
|
GIGAAM_API_URL = http://host.docker.internal:5002
|
||||||
|
CALLS_WEB_CLIENT_URL=http://web_client_test:8642
|
||||||
|
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
|
||||||
|
|
||||||
|
|||||||
+10
-3
@@ -25,14 +25,14 @@ PORT = int(os.getenv("PORT", "5008"))
|
|||||||
HOST = os.getenv("HOST", "localhost")
|
HOST = os.getenv("HOST", "localhost")
|
||||||
|
|
||||||
# GigaAM API Configuration
|
# GigaAM API Configuration
|
||||||
GIGAAM_API_URL = os.getenv("GIGAAM_API_URL", "http://192.168.1.73:5002")
|
GIGAAM_API_URL = os.getenv("GIGAAM_API_URL", "http://host.docker.internal:5002")
|
||||||
|
|
||||||
AUDIOFILES_PATH = os.path.join(os.getcwd(), os.getenv("FILESAPTH", "audiofiles"))
|
AUDIOFILES_PATH = os.path.join(os.getcwd(), os.getenv("FILESAPTH", "audiofiles"))
|
||||||
|
|
||||||
# Calls_WEB_Client_main Webhook Configuration
|
# Calls_WEB_Client_main Webhook Configuration
|
||||||
CALLS_WEB_CLIENT_URL = os.getenv(
|
CALLS_WEB_CLIENT_URL = os.getenv(
|
||||||
"CALLS_WEB_CLIENT_URL",
|
"CALLS_WEB_CLIENT_URL",
|
||||||
"http://192.168.1.73:8642"
|
"http://host.docker.internal:8642"
|
||||||
)
|
)
|
||||||
WEBHOOK_ENDPOINT = f"{CALLS_WEB_CLIENT_URL}/api/transcription/webhook"
|
WEBHOOK_ENDPOINT = f"{CALLS_WEB_CLIENT_URL}/api/transcription/webhook"
|
||||||
WEBHOOK_API_KEY = os.getenv("WEBHOOK_API_KEY", "webhook_secret_key")
|
WEBHOOK_API_KEY = os.getenv("WEBHOOK_API_KEY", "webhook_secret_key")
|
||||||
@@ -43,4 +43,11 @@ AUTO_RESTORE_LIMIT = int(os.getenv("AUTO_RESTORE_LIMIT", "100")) # Максим
|
|||||||
AUTO_RESTORE_DELAY = int(os.getenv("AUTO_RESTORE_DELAY", "5")) # Задержка перед запуском (секунды)
|
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"
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ async def process_all_pending_audio(
|
|||||||
logger.info(f"🚀 Поиск Audio без AiConclusion (limit={limit})")
|
logger.info(f"🚀 Поиск Audio без AiConclusion (limit={limit})")
|
||||||
|
|
||||||
# Находим все Audio без AiConclusion используя вспомогательную функцию
|
# Находим все Audio без AiConclusion используя вспомогательную функцию
|
||||||
pending_audio = query_audio_without_conclusion(db, limit).all()
|
pending_audio: list[Audio] = query_audio_without_conclusion(db, limit).all()
|
||||||
total_pending = query_audio_without_conclusion(db).count()
|
total_pending = query_audio_without_conclusion(db).count()
|
||||||
|
|
||||||
if not pending_audio:
|
if not pending_audio:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
+2
-1
@@ -6,4 +6,5 @@ python-multipart==0.0.12
|
|||||||
aiofiles==24.1.0
|
aiofiles==24.1.0
|
||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
paramiko
|
paramiko
|
||||||
requests
|
requests
|
||||||
|
sentry-sdk[fastapi]>=2.0.0
|
||||||
Reference in New Issue
Block a user