Ops hardening: lead re-import, SMS health-watch, ambient office murmur
- scripts/reimport_leads.py drains appointment_requests.jsonl into Odoo (fallback leads were captured but invisible to staff forever); server.py now warns at startup when leads are pending. - scripts/healthwatch.sh (cron, every minute): after 3 consecutive /health failures sends a Twilio SMS to ALERT_SMS_TO (30-min cooldown) + a recovery SMS. systemd handles restarts; this makes a human aware when that isn't enough. Tested end-to-end (DOWN + RECOVERED SMS delivered). - Ambient office murmur (assets/office_ambience.wav, generated by scripts/make_ambience.py from low-passed unintelligible Kokoro babble + room tone) mixed continuously into outbound audio via pipecat SoundfileMixer, so callers hear a live office instead of dead silence while the agent thinks. AMBIENT_VOLUME/AMBIENT_ENABLED to tune/disable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
20
bot.py
20
bot.py
@@ -43,6 +43,7 @@ from pipecat.processors.aggregators.llm_response_universal import (
|
||||
LLMContextAggregatorPair,
|
||||
LLMUserAggregatorParams,
|
||||
)
|
||||
from pipecat.audio.mixers.soundfile_mixer import SoundfileMixer
|
||||
from pipecat.audio.turn.smart_turn.base_smart_turn import SmartTurnParams
|
||||
from pipecat.audio.turn.smart_turn.local_smart_turn_v3 import LocalSmartTurnAnalyzerV3
|
||||
from pipecat.turns.user_start import (
|
||||
@@ -149,6 +150,19 @@ CALL_STATE_TRACKING = (
|
||||
if _call_state_env is not None
|
||||
else (LLM_PROVIDER == "ollama")
|
||||
)
|
||||
# Ambient office murmur mixed continuously into the OUTBOUND call audio, so the caller
|
||||
# hears a live-office room tone instead of dead digital silence while the agent thinks
|
||||
# (masks per-turn latency). File generated by scripts/make_ambience.py — unintelligible
|
||||
# by construction (low-passed). Mixed by pipecat's SoundfileMixer at the output transport,
|
||||
# which keeps streaming it even when the bot isn't speaking. Volume is the mixer's scale
|
||||
# on top of an already-quiet bed; 0.0/missing file/AMBIENT_ENABLED=false disables.
|
||||
AMBIENT_SOUND = os.environ.get("AMBIENT_SOUND", os.path.join(HERE, "assets", "office_ambience.wav"))
|
||||
AMBIENT_VOLUME = float(os.environ.get("AMBIENT_VOLUME", "0.10"))
|
||||
AMBIENT_ENABLED = (
|
||||
os.environ.get("AMBIENT_ENABLED", "true").lower() not in ("false", "0", "no")
|
||||
and AMBIENT_VOLUME > 0
|
||||
and os.path.isfile(AMBIENT_SOUND)
|
||||
)
|
||||
# Record each call to a stereo WAV (caller = left, agent = right) for review/debugging.
|
||||
RECORD_CALLS = os.environ.get("RECORD_CALLS", "true").lower() not in ("false", "0", "no")
|
||||
RECORDINGS_DIR = os.environ.get("RECORDINGS_DIR", os.path.join(HERE, "recordings"))
|
||||
@@ -836,6 +850,11 @@ async def run_agent(transport, caller_number=None, call_sid=None, do_capture=Tru
|
||||
|
||||
async def run_call(websocket, serializer: TwilioFrameSerializer, caller_number=None, call_sid=None):
|
||||
"""Phone entrypoint: wrap the Twilio Media Stream in a transport, run the shared agent."""
|
||||
mixer = SoundfileMixer(
|
||||
sound_files={"office": AMBIENT_SOUND},
|
||||
default_sound="office",
|
||||
volume=AMBIENT_VOLUME,
|
||||
) if AMBIENT_ENABLED else None
|
||||
transport = FastAPIWebsocketTransport(
|
||||
websocket=websocket,
|
||||
params=FastAPIWebsocketParams(
|
||||
@@ -845,6 +864,7 @@ async def run_call(websocket, serializer: TwilioFrameSerializer, caller_number=N
|
||||
audio_out_sample_rate=PIPELINE_SAMPLE_RATE,
|
||||
add_wav_header=False,
|
||||
serializer=serializer,
|
||||
audio_out_mixer=mixer, # constant low office murmur masks reply latency
|
||||
),
|
||||
)
|
||||
await run_agent(transport, caller_number=caller_number, call_sid=call_sid, do_capture=True)
|
||||
|
||||
Reference in New Issue
Block a user