diff --git a/api/services/pipecat/run_pipeline.py b/api/services/pipecat/run_pipeline.py index de9cb18..b29f225 100644 --- a/api/services/pipecat/run_pipeline.py +++ b/api/services/pipecat/run_pipeline.py @@ -241,12 +241,10 @@ async def _run_pipeline( raise HTTPException(status_code=400, detail="Workflow run already completed") merged_call_context_vars = workflow_run.initial_context - # If there is some extra call_context_vars, update them + # If there is some extra call_context_vars, fold them in. Persistence + # happens once below, after runtime_configuration is also resolved. if call_context_vars: merged_call_context_vars = {**merged_call_context_vars, **call_context_vars} - await db_client.update_workflow_run( - workflow_run_id, initial_context=merged_call_context_vars - ) # Get user configuration user_config = await db_client.get_user_configurations(user_id) @@ -312,6 +310,36 @@ async def _run_pipeline( llm = create_llm_service(user_config) inference_llm = None + # Stamp the providers/models actually resolved for this run onto + # initial_context so they're available for post-call analytics + # (model_overrides may have shifted them away from the org-level + # user_config). + if is_realtime: + # llm_* refers to the side-channel text LLM (variable extraction, + # voicemail detection); realtime_* is the speech-to-speech service. + runtime_configuration = { + "realtime_provider": user_config.realtime.provider, + "realtime_model": user_config.realtime.model, + "llm_provider": user_config.llm.provider, + "llm_model": user_config.llm.model, + } + else: + runtime_configuration = { + "stt_provider": user_config.stt.provider, + "stt_model": user_config.stt.model, + "tts_provider": user_config.tts.provider, + "tts_model": user_config.tts.model, + "llm_provider": user_config.llm.provider, + "llm_model": user_config.llm.model, + } + merged_call_context_vars = { + **merged_call_context_vars, + "runtime_configuration": runtime_configuration, + } + await db_client.update_workflow_run( + workflow_run_id, initial_context=merged_call_context_vars + ) + workflow_graph = WorkflowGraph(ReactFlowDTO.model_validate(run_workflow_json)) # Pre-call fetch: fire early so it runs concurrently with remaining setup diff --git a/api/services/quota_service.py b/api/services/quota_service.py index 929acb1..23c7120 100644 --- a/api/services/quota_service.py +++ b/api/services/quota_service.py @@ -106,7 +106,7 @@ async def check_dograh_quota( logger.info( f"Dograh quota check passed for key ...{api_key[-8:]}: " - f"${remaining:.2f} remaining" + f"{remaining:.2f} credits remaining" ) except Exception as e: logger.error(f"Failed to check quota for Dograh key: {str(e)}") diff --git a/docker-compose.yaml b/docker-compose.yaml index c425cf8..653427b 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -121,6 +121,7 @@ services: # Uses time-limited credentials via TURN REST API (HMAC-SHA1) TURN_HOST: "${TURN_HOST:-}" TURN_SECRET: "${TURN_SECRET:-}" + FORCE_TURN_RELAY: "${FORCE_TURN_RELAY:-false}" OSS_JWT_SECRET: "${OSS_JWT_SECRET:-ChangeMeInProduction}" diff --git a/docs/deployment/docker.mdx b/docs/deployment/docker.mdx index c518ffd..7e07516 100644 --- a/docs/deployment/docker.mdx +++ b/docs/deployment/docker.mdx @@ -46,6 +46,38 @@ http://localhost:3010 You can disable telemetry by setting `ENABLE_TELEMETRY=false` in the command above. +### Troubleshooting WebRTC Connectivity + +The Quick Start above relies on direct peer-to-peer WebRTC between your browser and the API container. On most single-machine setups this works without any extra configuration. If you hit any of the following, you likely need a TURN server in the path: + +- The call connects but no audio flows in either direction +- The browser console reports `iceConnectionState: failed` +- You are testing from a phone or another device on your LAN against the laptop running Docker +- A VPN, corporate firewall, or strict NAT sits between the browser and Docker + +For these cases, use the alternate local setup script which configures a coturn TURN server alongside the rest of the stack: + +```bash +curl -o setup_local.sh https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/setup_local.sh && chmod +x setup_local.sh && ./setup_local.sh +``` + +The script will prompt you for: +- Whether to enable coturn (answer `y`) +- The host browsers should use to reach TURN (press Enter for `127.0.0.1`; use your LAN IP if testing from another device on the same network) +- A shared secret for the TURN server (press Enter to generate a random one) + +It creates `docker-compose.yaml`, `turnserver.conf`, and a `.env` file with TURN credentials. Start the stack with the `local-turn` profile so coturn comes up alongside the other services: + +```bash +docker compose --profile local-turn up --pull always +``` + +The application is still available at `http://localhost:3010`. + + +To verify that media is actually traversing TURN (and not silently falling back to a direct path), set `FORCE_TURN_RELAY=true` in `.env` and restart the API. The browser will then only use relay ICE candidates — if TURN is misconfigured or unreachable, the call fails cleanly with `ICE failed` in the console instead of appearing to work. Set it back to `false` once you have verified connectivity. + + ## Option 2: Remote Server Deployment Watch the video tutorial below for a step-by-step walkthrough of deploying Dograh AI to a remote server. diff --git a/docs/developer/environment-variables.mdx b/docs/developer/environment-variables.mdx index 3b3c6d0..05e95c6 100644 --- a/docs/developer/environment-variables.mdx +++ b/docs/developer/environment-variables.mdx @@ -93,6 +93,7 @@ Dograh uses **MinIO by default**, which is bundled with the self-hosted deployme | `TURN_TLS_PORT` | `5349` | TURN server TLS port | | `TURN_SECRET` | `null` | **Required for WebRTC.** Shared secret for TURN credential generation | | `TURN_CREDENTIAL_TTL` | `86400` | TURN credential validity in seconds (default: 24h) | +| `FORCE_TURN_RELAY` | `false` | Diagnostic flag. When `true`, restricts ICE to relay-only candidates on both server (SDP filter) and browser (`iceTransportPolicy: 'relay'`). Use to verify TURN connectivity end-to-end — calls fail cleanly if TURN is misconfigured instead of silently falling back to a direct path. | --- diff --git a/scripts/setup_local.sh b/scripts/setup_local.sh index 72e79da..86a0b71 100755 --- a/scripts/setup_local.sh +++ b/scripts/setup_local.sh @@ -27,12 +27,39 @@ if [[ -z "$ENABLE_COTURN" ]]; then fi if [[ "$ENABLE_COTURN" == "true" ]]; then + # Pick a TURN_HOST that's reachable from BOTH the browser (running on the + # host) and the API container (running in docker). 127.0.0.1 is tempting + # but doesn't work for the api container — its own loopback isn't where + # coturn lives, so aiortc can't allocate a relay and FORCE_TURN_RELAY + # ends up with an empty answer SDP. The host's LAN IP works for both. + detect_lan_ip() { + local ip="" + if command -v ipconfig >/dev/null 2>&1; then + for iface in en0 en1 en2 en3 en4; do + ip=$(ipconfig getifaddr "$iface" 2>/dev/null) + [[ -n "$ip" ]] && { echo "$ip"; return; } + done + fi + if command -v ip >/dev/null 2>&1; then + ip=$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}') + [[ -n "$ip" ]] && { echo "$ip"; return; } + fi + if command -v hostname >/dev/null 2>&1; then + ip=$(hostname -I 2>/dev/null | awk '{print $1}') + [[ -n "$ip" ]] && { echo "$ip"; return; } + fi + } + + DEFAULT_TURN_HOST="$(detect_lan_ip)" + DEFAULT_TURN_HOST="${DEFAULT_TURN_HOST:-127.0.0.1}" + # Get the host browsers/peers will use to reach the TURN server if [[ -z "$TURN_HOST" ]]; then - echo -e "${YELLOW}Enter the host browsers should use to reach TURN (press Enter for 127.0.0.1):${NC}" + echo -e "${YELLOW}Enter the host browsers AND the API container will use to reach TURN${NC}" + echo -e "${YELLOW}(press Enter for ${DEFAULT_TURN_HOST}):${NC}" read -p "> " TURN_HOST fi - TURN_HOST="${TURN_HOST:-127.0.0.1}" + TURN_HOST="${TURN_HOST:-$DEFAULT_TURN_HOST}" # Validate that TURN_HOST is either an IP or a hostname (basic check) if ! [[ "$TURN_HOST" =~ ^[A-Za-z0-9.-]+$ ]]; then