mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +02:00
feat: add logs in campaigns for failure or pausing (#265)
* feat: add logs in campaigns on failure * chore: bump pipecat * chore: update format.sh * chore: fix github workflow * fix: fix formatting errors
This commit is contained in:
parent
abfb678b4d
commit
d4b6afb020
77 changed files with 1001 additions and 245 deletions
|
|
@ -345,7 +345,12 @@ class CampaignCallDispatcher:
|
|||
)
|
||||
|
||||
# Record call initiation failure in circuit breaker
|
||||
await circuit_breaker.record_and_evaluate(campaign.id, is_failure=True)
|
||||
await circuit_breaker.record_and_evaluate(
|
||||
campaign.id,
|
||||
is_failure=True,
|
||||
workflow_run_id=workflow_run.id,
|
||||
reason="call_initiation_failed",
|
||||
)
|
||||
|
||||
# Release concurrent slot on failure
|
||||
mapping = await rate_limiter.get_workflow_slot_mapping(workflow_run.id)
|
||||
|
|
@ -459,13 +464,18 @@ class CampaignCallDispatcher:
|
|||
await asyncio.sleep(1)
|
||||
|
||||
async def acquire_from_number(
|
||||
self, organization_id: int, timeout: float = 60
|
||||
self, organization_id: int, timeout: float = 600
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Acquire a from_number from the pool with retry.
|
||||
Waits up to timeout seconds, polling every 1s.
|
||||
|
||||
Returns the phone number or None if timeout is exceeded.
|
||||
Args:
|
||||
organization_id: ID of the organization for which to acquire the from_number.
|
||||
timeout: Maximum time in seconds to wait for a from_number before giving up.
|
||||
|
||||
Returns:
|
||||
The acquired phone number as a string, or None if timeout is exceeded.
|
||||
"""
|
||||
wait_start = time.time()
|
||||
|
||||
|
|
|
|||
|
|
@ -383,6 +383,20 @@ class CampaignOrchestrator:
|
|||
f"pausing campaign. Stats: {stats}"
|
||||
)
|
||||
await db_client.update_campaign(campaign_id=campaign_id, state="paused")
|
||||
await db_client.append_campaign_log(
|
||||
campaign_id=campaign_id,
|
||||
level="warning",
|
||||
event="circuit_breaker_tripped",
|
||||
message=(
|
||||
f"Paused at scheduling: failure rate "
|
||||
f"{stats['failure_rate']:.2%} "
|
||||
f"({stats['failure_count']}/"
|
||||
f"{stats['failure_count'] + stats['success_count']}) "
|
||||
f"exceeded threshold {stats['threshold']:.2%} "
|
||||
f"in {stats['window_seconds']}s window"
|
||||
),
|
||||
details=stats,
|
||||
)
|
||||
await self.publisher.publish_circuit_breaker_tripped(
|
||||
campaign_id=campaign_id,
|
||||
failure_rate=stats["failure_rate"],
|
||||
|
|
|
|||
|
|
@ -3,10 +3,15 @@
|
|||
Uses two Redis sorted sets (ZSETs) per campaign — one for failures, one for
|
||||
successes — as sliding windows. ZCARD gives O(1) counts without iterating
|
||||
members, keeping the Lua scripts simple.
|
||||
|
||||
A separate capped Redis list (``cb_recent_failures:{campaign_id}``) stores the
|
||||
last N failing ``{workflow_run_id, reason, ts}`` entries so the campaign log
|
||||
written when the breaker trips can show *which* calls pushed it over.
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
from typing import Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import redis.asyncio as aioredis
|
||||
from loguru import logger
|
||||
|
|
@ -15,6 +20,11 @@ from api.constants import DEFAULT_CIRCUIT_BREAKER_CONFIG, REDIS_URL
|
|||
from api.db import db_client
|
||||
from api.services.campaign.campaign_event_publisher import get_campaign_event_publisher
|
||||
|
||||
# Cap on the number of recent failure entries kept per campaign — large enough
|
||||
# to be useful for debugging a trip, small enough that the JSON details stay
|
||||
# bounded.
|
||||
MAX_RECENT_FAILURES = 20
|
||||
|
||||
|
||||
class CircuitBreaker:
|
||||
"""Sliding window circuit breaker for campaign call failures."""
|
||||
|
|
@ -35,6 +45,60 @@ class CircuitBreaker:
|
|||
"""Return (failures_key, successes_key) for a campaign."""
|
||||
return f"cb_failures:{campaign_id}", f"cb_successes:{campaign_id}"
|
||||
|
||||
@staticmethod
|
||||
def _recent_failures_key(campaign_id: int) -> str:
|
||||
"""Return the Redis key used for the capped recent-failures list."""
|
||||
return f"cb_recent_failures:{campaign_id}"
|
||||
|
||||
async def _push_recent_failure(
|
||||
self,
|
||||
campaign_id: int,
|
||||
workflow_run_id: int,
|
||||
reason: Optional[str],
|
||||
) -> None:
|
||||
"""Push a failure entry onto the capped recent-failures list."""
|
||||
redis_client = await self._get_redis()
|
||||
key = self._recent_failures_key(campaign_id)
|
||||
entry = json.dumps(
|
||||
{
|
||||
"workflow_run_id": workflow_run_id,
|
||||
"reason": reason,
|
||||
"ts": time.time(),
|
||||
}
|
||||
)
|
||||
try:
|
||||
await redis_client.lpush(key, entry)
|
||||
await redis_client.ltrim(key, 0, MAX_RECENT_FAILURES - 1)
|
||||
# Keep this list around as long as the sliding window plus a buffer.
|
||||
await redis_client.expire(
|
||||
key,
|
||||
DEFAULT_CIRCUIT_BREAKER_CONFIG["window_seconds"] + 60,
|
||||
)
|
||||
except Exception as e:
|
||||
# Never let recent-failure bookkeeping disrupt the call path.
|
||||
logger.error(
|
||||
f"Failed to record recent failure for campaign {campaign_id}: {e}"
|
||||
)
|
||||
|
||||
async def _get_recent_failures(self, campaign_id: int) -> List[Dict[str, Any]]:
|
||||
"""Return the recent-failures list (most-recent first)."""
|
||||
redis_client = await self._get_redis()
|
||||
key = self._recent_failures_key(campaign_id)
|
||||
try:
|
||||
entries = await redis_client.lrange(key, 0, -1)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Failed to read recent failures for campaign {campaign_id}: {e}"
|
||||
)
|
||||
return []
|
||||
decoded: List[Dict[str, Any]] = []
|
||||
for raw in entries:
|
||||
try:
|
||||
decoded.append(json.loads(raw))
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
return decoded
|
||||
|
||||
async def record_call_outcome(
|
||||
self,
|
||||
campaign_id: int,
|
||||
|
|
@ -227,13 +291,25 @@ class CircuitBreaker:
|
|||
logger.error(f"Circuit breaker check error for campaign {campaign_id}: {e}")
|
||||
return False, None
|
||||
|
||||
async def record_and_evaluate(self, campaign_id: int, is_failure: bool) -> None:
|
||||
async def record_and_evaluate(
|
||||
self,
|
||||
campaign_id: int,
|
||||
is_failure: bool,
|
||||
*,
|
||||
workflow_run_id: Optional[int] = None,
|
||||
reason: Optional[str] = None,
|
||||
) -> None:
|
||||
"""Record a call outcome, and if the breaker trips, pause the campaign.
|
||||
|
||||
This is the main entry point called from telephony status callbacks.
|
||||
It handles fetching campaign config, recording the outcome, and
|
||||
pausing + publishing an event if the breaker trips.
|
||||
|
||||
``workflow_run_id`` and ``reason`` are optional but should be supplied
|
||||
on failures: they are appended to a capped Redis list so the campaign
|
||||
log entry written on trip can name the calls that pushed the breaker
|
||||
over the threshold.
|
||||
|
||||
Exceptions are caught internally so this never disrupts the caller.
|
||||
"""
|
||||
try:
|
||||
|
|
@ -245,6 +321,13 @@ class CircuitBreaker:
|
|||
if campaign.orchestrator_metadata:
|
||||
cb_config = campaign.orchestrator_metadata.get("circuit_breaker", {})
|
||||
|
||||
if is_failure and workflow_run_id is not None:
|
||||
await self._push_recent_failure(
|
||||
campaign_id=campaign_id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
reason=reason,
|
||||
)
|
||||
|
||||
tripped, stats = await self.record_call_outcome(
|
||||
campaign_id=campaign_id,
|
||||
is_failure=is_failure,
|
||||
|
|
@ -257,7 +340,22 @@ class CircuitBreaker:
|
|||
f"pausing campaign. Stats: {stats}"
|
||||
)
|
||||
|
||||
recent_failures = await self._get_recent_failures(campaign_id)
|
||||
|
||||
await db_client.update_campaign(campaign_id=campaign_id, state="paused")
|
||||
await db_client.append_campaign_log(
|
||||
campaign_id=campaign_id,
|
||||
level="warning",
|
||||
event="circuit_breaker_tripped",
|
||||
message=(
|
||||
f"Paused: failure rate {stats['failure_rate']:.2%} "
|
||||
f"({stats['failure_count']}/"
|
||||
f"{stats['failure_count'] + stats['success_count']}) "
|
||||
f"exceeded threshold {stats['threshold']:.2%} "
|
||||
f"in {stats['window_seconds']}s window"
|
||||
),
|
||||
details={**stats, "recent_failures": recent_failures},
|
||||
)
|
||||
|
||||
publisher = await get_campaign_event_publisher()
|
||||
await publisher.publish_circuit_breaker_tripped(
|
||||
|
|
@ -275,13 +373,16 @@ class CircuitBreaker:
|
|||
async def reset(self, campaign_id: int) -> bool:
|
||||
"""Reset the circuit breaker state for a campaign.
|
||||
|
||||
Called when a campaign is resumed to give it a clean slate.
|
||||
Called when a campaign is resumed to give it a clean slate. Also clears
|
||||
the recent-failures list so log entries from the next trip reference
|
||||
only post-resume failures.
|
||||
"""
|
||||
redis_client = await self._get_redis()
|
||||
fail_key, succ_key = self._keys(campaign_id)
|
||||
recent_key = self._recent_failures_key(campaign_id)
|
||||
|
||||
try:
|
||||
await redis_client.delete(fail_key, succ_key)
|
||||
await redis_client.delete(fail_key, succ_key, recent_key)
|
||||
logger.info(f"Circuit breaker reset for campaign {campaign_id}")
|
||||
return True
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import asyncio
|
|||
from typing import Dict, Set
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from pipecat.audio.utils import mix_audio
|
||||
from pipecat.frames.frames import (
|
||||
Frame,
|
||||
|
|
|
|||
|
|
@ -3,10 +3,6 @@
|
|||
from typing import Any, Dict
|
||||
|
||||
from loguru import logger
|
||||
from pipecat.pipeline.pipeline import Pipeline
|
||||
from pipecat.processors.aggregators.llm_response_universal import (
|
||||
LLMContextAggregatorPair,
|
||||
)
|
||||
|
||||
from api.db.db_client import DBClient
|
||||
from api.services.looptalk.audio_streamer import get_or_create_audio_streamer
|
||||
|
|
@ -27,6 +23,10 @@ from api.services.pipecat.service_factory import (
|
|||
from api.services.workflow.dto import ReactFlowDTO
|
||||
from api.services.workflow.pipecat_engine import PipecatEngine
|
||||
from api.services.workflow.workflow import WorkflowGraph
|
||||
from pipecat.pipeline.pipeline import Pipeline
|
||||
from pipecat.processors.aggregators.llm_response_universal import (
|
||||
LLMContextAggregatorPair,
|
||||
)
|
||||
|
||||
|
||||
class LoopTalkPipelineBuilder:
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"""Internal frame serializer for agent-to-agent communication."""
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from pipecat.frames.frames import (
|
||||
Frame,
|
||||
InputAudioRawFrame,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import time
|
|||
from typing import Dict, Optional, Tuple
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from api.services.looptalk.internal_serializer import InternalFrameSerializer
|
||||
from pipecat.frames.frames import (
|
||||
CancelFrame,
|
||||
EndFrame,
|
||||
|
|
@ -27,8 +29,6 @@ from pipecat.transports.base_input import BaseInputTransport
|
|||
from pipecat.transports.base_output import BaseOutputTransport
|
||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
||||
|
||||
from api.services.looptalk.internal_serializer import InternalFrameSerializer
|
||||
|
||||
|
||||
class InternalInputTransport(BaseInputTransport):
|
||||
"""Input side of internal transport for agent-to-agent communication."""
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ from pathlib import Path
|
|||
from typing import Any, Dict, Optional
|
||||
|
||||
from loguru import logger
|
||||
from pipecat.pipeline.task import PipelineTask
|
||||
from pipecat.utils.run_context import set_current_run_id
|
||||
|
||||
from api.db.db_client import DBClient
|
||||
from api.services.looptalk.internal_transport import (
|
||||
|
|
@ -15,6 +13,8 @@ from api.services.looptalk.internal_transport import (
|
|||
InternalTransportManager,
|
||||
)
|
||||
from api.services.pipecat.transport_setup import create_internal_transport
|
||||
from pipecat.pipeline.task import PipelineTask
|
||||
from pipecat.utils.run_context import set_current_run_id
|
||||
|
||||
from .core.pipeline_builder import LoopTalkPipelineBuilder
|
||||
from .core.recording_manager import RecordingManager
|
||||
|
|
|
|||
|
|
@ -235,7 +235,10 @@ def register_event_handlers(
|
|||
workflow_run = await db_client.get_workflow_run_by_id(workflow_run_id)
|
||||
if workflow_run and workflow_run.campaign_id:
|
||||
await circuit_breaker.record_and_evaluate(
|
||||
campaign_id=workflow_run.campaign_id, is_failure=True
|
||||
campaign_id=workflow_run.campaign_id,
|
||||
is_failure=True,
|
||||
workflow_run_id=workflow_run_id,
|
||||
reason="pipeline_error",
|
||||
)
|
||||
asyncio.create_task(
|
||||
_capture_call_event(
|
||||
|
|
|
|||
|
|
@ -21,9 +21,10 @@ from fastapi import (
|
|||
status,
|
||||
)
|
||||
from fastapi.websockets import WebSocketState
|
||||
from pipecat.audio.turn.smart_turn.local_smart_turn_v2 import LocalSmartTurnAnalyzerV2
|
||||
from scipy.io import wavfile
|
||||
|
||||
from pipecat.audio.turn.smart_turn.local_smart_turn_v2 import LocalSmartTurnAnalyzerV2
|
||||
|
||||
LOG_LEVEL = (
|
||||
logging.DEBUG
|
||||
if os.environ.get("LOG_LEVEL", "DEBUG").lower() == "debug"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from typing import Any, Dict, Optional
|
|||
import numpy as np
|
||||
import websockets
|
||||
from loguru import logger
|
||||
|
||||
from pipecat.audio.turn.smart_turn.base_smart_turn import (
|
||||
BaseSmartTurn,
|
||||
SmartTurnTimeoutException,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ This module contains the business logic for Asterisk ARI call operations.
|
|||
from typing import Any, Dict
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from pipecat.serializers.call_strategies import HangupStrategy, TransferStrategy
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
"""ARI (Asterisk) transport factory."""
|
||||
|
||||
from fastapi import WebSocket
|
||||
from pipecat.transports.websocket.fastapi import (
|
||||
FastAPIWebsocketParams,
|
||||
FastAPIWebsocketTransport,
|
||||
)
|
||||
|
||||
from api.services.pipecat.audio_config import AudioConfig
|
||||
from api.services.pipecat.audio_mixer import build_audio_out_mixer
|
||||
from api.services.telephony.factory import load_credentials_for_transport
|
||||
from pipecat.transports.websocket.fastapi import (
|
||||
FastAPIWebsocketParams,
|
||||
FastAPIWebsocketTransport,
|
||||
)
|
||||
|
||||
from .serializers import AsteriskFrameSerializer
|
||||
from .strategies import ARIBridgeSwapStrategy, ARIHangupStrategy
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import json
|
|||
|
||||
from fastapi import APIRouter, Request
|
||||
from loguru import logger
|
||||
from pipecat.utils.run_context import set_current_run_id
|
||||
|
||||
from api.db import db_client
|
||||
from api.services.telephony.factory import get_telephony_provider_for_run
|
||||
|
|
@ -16,6 +15,7 @@ from api.services.telephony.status_processor import (
|
|||
StatusCallbackRequest,
|
||||
_process_status_update,
|
||||
)
|
||||
from pipecat.utils.run_context import set_current_run_id
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
from typing import Any, Dict
|
||||
|
||||
from loguru import logger
|
||||
from pipecat.serializers.call_strategies import HangupStrategy
|
||||
|
||||
from api.services.telephony.providers.cloudonix.provider import CLOUDONIX_API_BASE_URL
|
||||
from pipecat.serializers.call_strategies import HangupStrategy
|
||||
|
||||
|
||||
class CloudonixHangupStrategy(HangupStrategy):
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
"""Cloudonix transport factory."""
|
||||
|
||||
from fastapi import WebSocket
|
||||
from pipecat.transports.websocket.fastapi import (
|
||||
FastAPIWebsocketParams,
|
||||
FastAPIWebsocketTransport,
|
||||
)
|
||||
|
||||
from api.services.pipecat.audio_config import AudioConfig
|
||||
from api.services.pipecat.audio_mixer import build_audio_out_mixer
|
||||
from api.services.telephony.factory import load_credentials_for_transport
|
||||
from pipecat.transports.websocket.fastapi import (
|
||||
FastAPIWebsocketParams,
|
||||
FastAPIWebsocketTransport,
|
||||
)
|
||||
|
||||
from .serializers import CloudonixFrameSerializer
|
||||
from .strategies import CloudonixHangupStrategy
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ from typing import Optional
|
|||
|
||||
from fastapi import APIRouter, Header, Request
|
||||
from loguru import logger
|
||||
from pipecat.utils.run_context import set_current_run_id
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
from api.db import db_client
|
||||
|
|
@ -19,6 +18,7 @@ from api.services.telephony.status_processor import (
|
|||
_process_status_update,
|
||||
)
|
||||
from api.utils.common import get_backend_endpoints
|
||||
from pipecat.utils.run_context import set_current_run_id
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
"""Plivo transport factory."""
|
||||
|
||||
from fastapi import WebSocket
|
||||
from pipecat.transports.websocket.fastapi import (
|
||||
FastAPIWebsocketParams,
|
||||
FastAPIWebsocketTransport,
|
||||
)
|
||||
|
||||
from api.services.pipecat.audio_config import AudioConfig
|
||||
from api.services.pipecat.audio_mixer import build_audio_out_mixer
|
||||
from api.services.telephony.factory import load_credentials_for_transport
|
||||
from pipecat.transports.websocket.fastapi import (
|
||||
FastAPIWebsocketParams,
|
||||
FastAPIWebsocketTransport,
|
||||
)
|
||||
|
||||
from .serializers import PlivoFrameSerializer
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import json
|
|||
|
||||
from fastapi import APIRouter, Request
|
||||
from loguru import logger
|
||||
from pipecat.utils.run_context import set_current_run_id
|
||||
|
||||
from api.db import db_client
|
||||
from api.services.telephony.factory import get_telephony_provider_for_run
|
||||
|
|
@ -17,6 +16,7 @@ from api.services.telephony.status_processor import (
|
|||
StatusCallbackRequest,
|
||||
_process_status_update,
|
||||
)
|
||||
from pipecat.utils.run_context import set_current_run_id
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
"""Telnyx transport factory."""
|
||||
|
||||
from fastapi import WebSocket
|
||||
from pipecat.transports.websocket.fastapi import (
|
||||
FastAPIWebsocketParams,
|
||||
FastAPIWebsocketTransport,
|
||||
)
|
||||
|
||||
from api.services.pipecat.audio_config import AudioConfig
|
||||
from api.services.pipecat.audio_mixer import build_audio_out_mixer
|
||||
from api.services.telephony.factory import load_credentials_for_transport
|
||||
from pipecat.transports.websocket.fastapi import (
|
||||
FastAPIWebsocketParams,
|
||||
FastAPIWebsocketTransport,
|
||||
)
|
||||
|
||||
from .serializers import TelnyxFrameSerializer
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ from typing import Optional
|
|||
|
||||
from fastapi import APIRouter, Header, Request
|
||||
from loguru import logger
|
||||
from pipecat.utils.run_context import set_current_run_id
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
from api.db import db_client
|
||||
|
|
@ -19,6 +18,7 @@ from api.services.telephony.status_processor import (
|
|||
_process_status_update,
|
||||
)
|
||||
from api.utils.common import get_backend_endpoints
|
||||
from pipecat.utils.run_context import set_current_run_id
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from typing import Any, Dict
|
|||
|
||||
import aiohttp
|
||||
from loguru import logger
|
||||
|
||||
from pipecat.serializers.call_strategies import HangupStrategy, TransferStrategy
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
"""Twilio transport factory."""
|
||||
|
||||
from fastapi import WebSocket
|
||||
from pipecat.transports.websocket.fastapi import (
|
||||
FastAPIWebsocketParams,
|
||||
FastAPIWebsocketTransport,
|
||||
)
|
||||
|
||||
from api.services.pipecat.audio_config import AudioConfig
|
||||
from api.services.pipecat.audio_mixer import build_audio_out_mixer
|
||||
from api.services.telephony.factory import load_credentials_for_transport
|
||||
from pipecat.transports.websocket.fastapi import (
|
||||
FastAPIWebsocketParams,
|
||||
FastAPIWebsocketTransport,
|
||||
)
|
||||
|
||||
from .serializers import TwilioFrameSerializer
|
||||
from .strategies import TwilioConferenceStrategy, TwilioHangupStrategy
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ from typing import Optional
|
|||
|
||||
from fastapi import APIRouter, Header, Request
|
||||
from loguru import logger
|
||||
from pipecat.utils.run_context import set_current_run_id
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
from api.db import db_client
|
||||
|
|
@ -25,6 +24,7 @@ from api.utils.common import get_backend_endpoints
|
|||
from api.utils.telephony_helper import (
|
||||
parse_webhook_request,
|
||||
)
|
||||
from pipecat.utils.run_context import set_current_run_id
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ Vobiz uses Plivo-compatible WebSocket protocol:
|
|||
|
||||
from fastapi import WebSocket
|
||||
from loguru import logger
|
||||
from pipecat.transports.websocket.fastapi import (
|
||||
FastAPIWebsocketParams,
|
||||
FastAPIWebsocketTransport,
|
||||
)
|
||||
|
||||
from api.services.pipecat.audio_config import AudioConfig
|
||||
from api.services.pipecat.audio_mixer import build_audio_out_mixer
|
||||
from api.services.telephony.factory import load_credentials_for_transport
|
||||
from pipecat.transports.websocket.fastapi import (
|
||||
FastAPIWebsocketParams,
|
||||
FastAPIWebsocketTransport,
|
||||
)
|
||||
|
||||
from .serializers import VobizFrameSerializer
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ from typing import Optional
|
|||
|
||||
from fastapi import APIRouter, Request
|
||||
from loguru import logger
|
||||
from pipecat.utils.run_context import set_current_run_id
|
||||
|
||||
from api.db import db_client
|
||||
from api.services.telephony.factory import get_telephony_provider_for_run
|
||||
|
|
@ -17,6 +16,7 @@ from api.services.telephony.status_processor import (
|
|||
StatusCallbackRequest,
|
||||
_process_status_update,
|
||||
)
|
||||
from pipecat.utils.run_context import set_current_run_id
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
"""Vonage transport factory."""
|
||||
|
||||
from pipecat.transports.websocket.fastapi import (
|
||||
FastAPIWebsocketParams,
|
||||
FastAPIWebsocketTransport,
|
||||
)
|
||||
|
||||
from api.services.pipecat.audio_config import AudioConfig
|
||||
from api.services.pipecat.audio_mixer import build_audio_out_mixer
|
||||
from api.services.telephony.factory import load_credentials_for_transport
|
||||
from pipecat.transports.websocket.fastapi import (
|
||||
FastAPIWebsocketParams,
|
||||
FastAPIWebsocketTransport,
|
||||
)
|
||||
|
||||
from .serializers import VonageFrameSerializer
|
||||
|
||||
|
|
|
|||
|
|
@ -179,9 +179,12 @@ async def _process_status_update(workflow_run_id: int, status: StatusCallbackReq
|
|||
|
||||
if workflow_run.campaign_id:
|
||||
await campaign_call_dispatcher.release_call_slot(workflow_run_id)
|
||||
is_failure = status.status in ("error", "failed")
|
||||
await circuit_breaker.record_and_evaluate(
|
||||
workflow_run.campaign_id,
|
||||
is_failure=status.status in ("error", "failed"),
|
||||
is_failure=is_failure,
|
||||
workflow_run_id=workflow_run_id if is_failure else None,
|
||||
reason=status.status if is_failure else None,
|
||||
)
|
||||
|
||||
if status.status in ["busy", "no-answer"] and workflow_run.campaign_id:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
from typing import TYPE_CHECKING, Awaitable, Callable, Optional, Union
|
||||
|
||||
from api.db import db_client
|
||||
from api.services.pipecat.audio_playback import play_audio
|
||||
from api.services.workflow.disposition_mapper import apply_disposition_mapping
|
||||
from api.services.workflow.workflow import Node, WorkflowGraph
|
||||
from pipecat.adapters.schemas.tools_schema import ToolsSchema
|
||||
from pipecat.frames.frames import (
|
||||
BotStartedSpeakingFrame,
|
||||
|
|
@ -15,11 +19,6 @@ from pipecat.services.llm_service import FunctionCallParams
|
|||
from pipecat.services.settings import LLMSettings
|
||||
from pipecat.utils.enums import EndTaskReason
|
||||
|
||||
from api.db import db_client
|
||||
from api.services.pipecat.audio_playback import play_audio
|
||||
from api.services.workflow.disposition_mapper import apply_disposition_mapping
|
||||
from api.services.workflow.workflow import Node, WorkflowGraph
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pipecat.frames.frames import Frame
|
||||
from pipecat.services.anthropic.llm import AnthropicLLMService
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import re
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from pipecat.frames.frames import (
|
||||
LLMMessagesAppendFrame,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ from typing import TYPE_CHECKING, Optional
|
|||
|
||||
from loguru import logger
|
||||
from opentelemetry import trace
|
||||
|
||||
from api.services.pipecat.tracing_config import ensure_tracing
|
||||
from pipecat.frames.frames import LLMContextSummaryRequestFrame
|
||||
from pipecat.utils.context.llm_context_summarization import (
|
||||
LLMContextSummarizationUtil,
|
||||
|
|
@ -13,8 +15,6 @@ from pipecat.utils.context.llm_context_summarization import (
|
|||
)
|
||||
from pipecat.utils.tracing.service_attributes import add_llm_span_attributes
|
||||
|
||||
from api.services.pipecat.tracing_config import ensure_tracing
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from api.services.workflow.pipecat_engine import PipecatEngine
|
||||
|
||||
|
|
|
|||
|
|
@ -13,13 +13,6 @@ import uuid
|
|||
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||
|
||||
from loguru import logger
|
||||
from pipecat.adapters.schemas.function_schema import FunctionSchema
|
||||
from pipecat.frames.frames import (
|
||||
FunctionCallResultProperties,
|
||||
TTSSpeakFrame,
|
||||
)
|
||||
from pipecat.services.llm_service import FunctionCallParams
|
||||
from pipecat.utils.enums import EndTaskReason
|
||||
|
||||
from api.db import db_client
|
||||
from api.enums import ToolCategory, WorkflowRunMode
|
||||
|
|
@ -32,6 +25,13 @@ from api.services.workflow.tools.custom_tool import (
|
|||
execute_http_tool,
|
||||
tool_to_function_schema,
|
||||
)
|
||||
from pipecat.adapters.schemas.function_schema import FunctionSchema
|
||||
from pipecat.frames.frames import (
|
||||
FunctionCallResultProperties,
|
||||
TTSSpeakFrame,
|
||||
)
|
||||
from pipecat.services.llm_service import FunctionCallParams
|
||||
from pipecat.utils.enums import EndTaskReason
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from api.services.workflow.pipecat_engine import PipecatEngine
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ from typing import TYPE_CHECKING, Any, List
|
|||
|
||||
from loguru import logger
|
||||
from opentelemetry import trace
|
||||
from pipecat.processors.aggregators.llm_context import LLMContext
|
||||
from pipecat.utils.tracing.service_attributes import add_llm_span_attributes
|
||||
|
||||
from api.services.gen_ai.json_parser import parse_llm_json
|
||||
from api.services.pipecat.tracing_config import ensure_tracing
|
||||
from api.services.workflow.dto import ExtractionVariableDTO
|
||||
from pipecat.processors.aggregators.llm_context import LLMContext
|
||||
from pipecat.utils.tracing.service_attributes import add_llm_span_attributes
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from api.services.workflow.pipecat_engine import PipecatEngine
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import json
|
|||
from typing import Any
|
||||
|
||||
from loguru import logger
|
||||
from pipecat.processors.aggregators.llm_context import LLMContext
|
||||
|
||||
from api.db.models import WorkflowRunModel
|
||||
from api.services.gen_ai.json_parser import parse_llm_json
|
||||
|
|
@ -27,6 +26,7 @@ from api.services.workflow.qa.tracing import (
|
|||
setup_langfuse_parent_context,
|
||||
)
|
||||
from api.utils.template_renderer import render_template
|
||||
from pipecat.processors.aggregators.llm_context import LLMContext
|
||||
|
||||
|
||||
async def _run_llm_inference(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
from typing import Any
|
||||
|
||||
from loguru import logger
|
||||
from pipecat.processors.aggregators.llm_context import LLMContext
|
||||
|
||||
from api.db import db_client
|
||||
from api.db.models import WorkflowRunModel
|
||||
|
|
@ -11,6 +10,7 @@ from api.services.pipecat.service_factory import create_llm_service_from_provide
|
|||
from api.services.workflow.dto import NodeType, QANodeData
|
||||
from api.services.workflow.qa.llm_config import resolve_llm_config
|
||||
from api.services.workflow.qa.tracing import create_node_summary_trace
|
||||
from pipecat.processors.aggregators.llm_context import LLMContext
|
||||
|
||||
NODE_SUMMARY_SYSTEM_PROMPT = (
|
||||
"You are analyzing a voice AI agent script. This is only a part of a larger script. "
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ def add_qa_span_to_trace(
|
|||
return
|
||||
try:
|
||||
from opentelemetry import trace as otel_trace
|
||||
|
||||
from pipecat.utils.tracing.service_attributes import add_llm_span_attributes
|
||||
|
||||
tracer = otel_trace.get_tracer("pipecat")
|
||||
|
|
@ -121,9 +122,9 @@ def create_node_summary_trace(
|
|||
try:
|
||||
from opentelemetry import trace as otel_trace
|
||||
from opentelemetry.context import Context
|
||||
from pipecat.utils.tracing.service_attributes import add_llm_span_attributes
|
||||
|
||||
from api.services.pipecat.tracing_config import ensure_tracing
|
||||
from pipecat.utils.tracing.service_attributes import add_llm_span_attributes
|
||||
|
||||
if not ensure_tracing():
|
||||
return None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue