diff --git a/api/Dockerfile b/api/Dockerfile index 1e1d27e..c249e6d 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -58,8 +58,11 @@ COPY ./scripts/start_services.sh ./scripts/start_services.sh ENV PYTHONPATH=/app +# Disable file logging in Docker - logs go to stdout for docker logs +ENV LOG_TO_FILE=false + # Expose the port FastAPI will run on EXPOSE 8000 # Run the FastAPI app with uvicorn -CMD ["bash", "-c", "./scripts/start_services.sh && tail -f ./logs/latest/*.log"] \ No newline at end of file +CMD ["./scripts/start_services.sh"] \ No newline at end of file diff --git a/api/services/pipecat/run_pipeline.py b/api/services/pipecat/run_pipeline.py index fce8cea..1505179 100644 --- a/api/services/pipecat/run_pipeline.py +++ b/api/services/pipecat/run_pipeline.py @@ -44,6 +44,7 @@ from api.services.telephony.stasis_rtp_connection import StasisRTPConnection from api.services.workflow.dto import ReactFlowDTO from api.services.workflow.pipecat_engine import PipecatEngine from api.services.workflow.workflow import WorkflowGraph +from pipecat.audio.turn.smart_turn.local_smart_turn_v3 import LocalSmartTurnAnalyzerV3 from pipecat.extensions.voicemail.voicemail_detector import VoicemailDetector from pipecat.pipeline.base_task import PipelineTaskParams from pipecat.processors.aggregators.llm_response_universal import ( @@ -65,7 +66,7 @@ from pipecat.turns.user_start.vad_user_turn_start_strategy import ( ) from pipecat.turns.user_stop import ( ExternalUserTurnStopStrategy, - TranscriptionUserTurnStopStrategy, + TurnAnalyzerUserTurnStopStrategy, ) from pipecat.turns.user_turn_strategies import UserTurnStrategies from pipecat.utils.context import set_current_run_id @@ -566,7 +567,11 @@ async def _run_pipeline( else: user_turn_strategies = UserTurnStrategies( start=[VADUserTurnStartStrategy(), TranscriptionUserTurnStartStrategy()], - stop=[TranscriptionUserTurnStopStrategy()], + stop=[ + TurnAnalyzerUserTurnStopStrategy( + turn_analyzer=LocalSmartTurnAnalyzerV3() + ) + ], ) # Create user mute strategies diff --git a/api/tests/test_is_private_ip_candidate.py b/api/tests/test_is_private_ip_candidate.py index 146f4ae..25feb5a 100644 --- a/api/tests/test_is_private_ip_candidate.py +++ b/api/tests/test_is_private_ip_candidate.py @@ -6,37 +6,51 @@ class TestIsPrivateIpCandidate: def test_private_ip_192_168(self): """192.168.x.x addresses are detected as private.""" - candidate = "candidate:123 1 udp 2122260223 192.168.50.24 63603 typ host generation 0" + candidate = ( + "candidate:123 1 udp 2122260223 192.168.50.24 63603 typ host generation 0" + ) assert is_private_ip_candidate(candidate) is True def test_private_ip_10_x(self): """10.x.x.x addresses are detected as private.""" - candidate = "candidate:456 1 udp 2122260223 10.0.0.1 12345 typ host generation 0" + candidate = ( + "candidate:456 1 udp 2122260223 10.0.0.1 12345 typ host generation 0" + ) assert is_private_ip_candidate(candidate) is True def test_private_ip_172_16(self): """172.16.x.x addresses are detected as private.""" - candidate = "candidate:789 1 udp 2122260223 172.16.0.1 54321 typ host generation 0" + candidate = ( + "candidate:789 1 udp 2122260223 172.16.0.1 54321 typ host generation 0" + ) assert is_private_ip_candidate(candidate) is True def test_private_ip_172_31(self): """172.31.x.x addresses are detected as private.""" - candidate = "candidate:101 1 udp 2122260223 172.31.255.255 12345 typ host generation 0" + candidate = ( + "candidate:101 1 udp 2122260223 172.31.255.255 12345 typ host generation 0" + ) assert is_private_ip_candidate(candidate) is True def test_cgnat_ip(self): """CGNAT addresses (100.64.0.0/10) are detected as private.""" - candidate = "candidate:202 1 udp 2122260223 100.64.0.1 12345 typ host generation 0" + candidate = ( + "candidate:202 1 udp 2122260223 100.64.0.1 12345 typ host generation 0" + ) assert is_private_ip_candidate(candidate) is True def test_cgnat_ip_upper_bound(self): """Upper bound of CGNAT range is detected.""" - candidate = "candidate:303 1 udp 2122260223 100.127.255.255 12345 typ host generation 0" + candidate = ( + "candidate:303 1 udp 2122260223 100.127.255.255 12345 typ host generation 0" + ) assert is_private_ip_candidate(candidate) is True def test_public_ip(self): """Public IP addresses return False.""" - candidate = "candidate:404 1 udp 2122260223 142.250.190.46 12345 typ host generation 0" + candidate = ( + "candidate:404 1 udp 2122260223 142.250.190.46 12345 typ host generation 0" + ) assert is_private_ip_candidate(candidate) is False def test_public_ip_8_8_8_8(self): @@ -46,17 +60,23 @@ class TestIsPrivateIpCandidate: def test_non_cgnat_100_range(self): """100.x.x.x outside CGNAT range is public.""" - candidate = "candidate:606 1 udp 2122260223 100.128.0.1 12345 typ host generation 0" + candidate = ( + "candidate:606 1 udp 2122260223 100.128.0.1 12345 typ host generation 0" + ) assert is_private_ip_candidate(candidate) is False def test_172_15_is_public(self): """172.15.x.x is outside private range and should be public.""" - candidate = "candidate:707 1 udp 2122260223 172.15.255.255 12345 typ srflx generation 0" + candidate = ( + "candidate:707 1 udp 2122260223 172.15.255.255 12345 typ srflx generation 0" + ) assert is_private_ip_candidate(candidate) is False def test_172_32_is_public(self): """172.32.x.x is outside private range and should be public.""" - candidate = "candidate:808 1 udp 2122260223 172.32.0.1 12345 typ srflx generation 0" + candidate = ( + "candidate:808 1 udp 2122260223 172.32.0.1 12345 typ srflx generation 0" + ) assert is_private_ip_candidate(candidate) is False def test_srflx_candidate_type(self): @@ -66,17 +86,23 @@ class TestIsPrivateIpCandidate: def test_relay_candidate_type(self): """Relay candidates are parsed correctly.""" - candidate = "candidate:111 1 udp 41885439 1.1.1.1 50000 typ relay raddr 0.0.0.0 rport 0" + candidate = ( + "candidate:111 1 udp 41885439 1.1.1.1 50000 typ relay raddr 0.0.0.0 rport 0" + ) assert is_private_ip_candidate(candidate) is False def test_loopback_ip(self): """Loopback addresses are detected as private.""" - candidate = "candidate:222 1 udp 2122260223 127.0.0.1 12345 typ host generation 0" + candidate = ( + "candidate:222 1 udp 2122260223 127.0.0.1 12345 typ host generation 0" + ) assert is_private_ip_candidate(candidate) is True def test_link_local_ip(self): """Link-local addresses (169.254.x.x) are detected as private.""" - candidate = "candidate:333 1 udp 2122260223 169.254.1.1 12345 typ host generation 0" + candidate = ( + "candidate:333 1 udp 2122260223 169.254.1.1 12345 typ host generation 0" + ) assert is_private_ip_candidate(candidate) is True def test_ipv6_link_local(self): @@ -100,7 +126,9 @@ class TestIsPrivateIpCandidate: def test_malformed_candidate_invalid_ip(self): """Candidate with invalid IP returns False.""" - candidate = "candidate:777 1 udp 2122260223 not.an.ip 12345 typ host generation 0" + candidate = ( + "candidate:777 1 udp 2122260223 not.an.ip 12345 typ host generation 0" + ) assert is_private_ip_candidate(candidate) is False def test_malformed_candidate_short(self): @@ -110,5 +138,7 @@ class TestIsPrivateIpCandidate: def test_tcp_candidate(self): """TCP candidates are parsed correctly.""" - candidate = "candidate:999 1 tcp 1518280447 192.168.1.100 9 typ host tcptype active" + candidate = ( + "candidate:999 1 tcp 1518280447 192.168.1.100 9 typ host tcptype active" + ) assert is_private_ip_candidate(candidate) is True diff --git a/docker-compose.yaml b/docker-compose.yaml index 14ce51c..1d33f0d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -85,7 +85,7 @@ services: # Replace this environment variable if you are using a custom # domain to host the stack - BACKEND_API_ENDPOINT: "http://localhost:8000" + BACKEND_API_ENDPOINT: "${BACKEND_API_ENDPOINT:-http://localhost:8000}" # Database configuration (using containerized postgres) DATABASE_URL: "postgresql+asyncpg://postgres:postgres@postgres:5432/postgres" diff --git a/scripts/setup_remote.sh b/scripts/setup_remote.sh index 355ce0b..17eab2a 100755 --- a/scripts/setup_remote.sh +++ b/scripts/setup_remote.sh @@ -135,6 +135,9 @@ echo -e "${GREEN}✓ SSL certificates generated${NC}" echo -e "${BLUE}[5/5] Creating environment file...${NC}" cat > .env << ENV_EOF +# Backend API endpoint (for remote deployment) +BACKEND_API_ENDPOINT=https://$SERVER_IP + # TURN Server Configuration (time-limited credentials via TURN REST API) TURN_HOST=$SERVER_IP TURN_SECRET=$TURN_SECRET diff --git a/scripts/start_services.sh b/scripts/start_services.sh index a1d9951..cad552d 100755 --- a/scripts/start_services.sh +++ b/scripts/start_services.sh @@ -54,6 +54,7 @@ LATEST_LINK="$BASE_LOG_DIR/latest" # Symlink to latest logs VENV_PATH="$BASE_DIR/venv" ARQ_WORKERS=${ARQ_WORKERS:-1} +LOG_TO_FILE=${LOG_TO_FILE:-true} # Set to false in Docker to use stdout # Log startup cd "$BASE_DIR" @@ -239,8 +240,13 @@ for i in "${!SERVICE_NAMES[@]}"; do ( cd "$BASE_DIR" - export LOG_FILE_PATH="$LOG_DIR/$name.log" - exec $cmd >>"$LOG_DIR/$name.log" 2>&1 + if [[ "$LOG_TO_FILE" == "true" ]]; then + export LOG_FILE_PATH="$LOG_DIR/$name.log" + exec $cmd >>"$LOG_DIR/$name.log" 2>&1 + else + # Log to stdout/stderr for Docker + exec $cmd + fi ) & pid=$!