Skip to content

Add AWS/Bedrock user simulator (Nova Sonic) + optional Bedrock agent STT/TTS & judges#183

Open
nevo-biton wants to merge 3 commits into
ServiceNow:mainfrom
nevo-biton:feature/bedrock-user-simulator
Open

Add AWS/Bedrock user simulator (Nova Sonic) + optional Bedrock agent STT/TTS & judges#183
nevo-biton wants to merge 3 commits into
ServiceNow:mainfrom
nevo-biton:feature/bedrock-user-simulator

Conversation

@nevo-biton

Copy link
Copy Markdown

Proposed in #182 — addresses the User Simulator Options roadmap item and extends AWS/Bedrock across the pipeline. Happy to split into separate PRs if you prefer (caller / agent STT+TTS / judges).

What

A full AWS/Bedrock EVA pipeline — caller, agent-under-test, and validation judges can all run on AWS. Three commits:

  1. bedrock_s2s user simulator — an audio-native Amazon Nova Sonic caller (Bedrock bidirectional streaming), the AWS analog of OpenAIRealtimeUserSimulator (reuses BotToBotAudioBridge, _build_prompt(), the end_call tool). This is the roadmap "User Simulator Options" item.
  2. Amazon Transcribe (STT) + Amazon Polly (TTS) assistant providers — the agent-under-test's cascade runs entirely on AWS: Transcribe → Bedrock LLM → Polly (the LLM already ran on Bedrock via the LiteLLM model_list).
  3. Bedrock LLM validation judges — so an all-AWS run also validates on AWS.

Configure with EVA_USER_SIMULATOR__PROVIDER=bedrock_s2s, EVA_MODEL__STT=aws_transcribe, EVA_MODEL__TTS=aws_polly, an EVA_MODEL__LLM pointing at a bedrock/... deployment, and JUDGE_MODEL=<a bedrock model_name>. AWS creds + region come from the standard env chain.

Live-validated end-to-end ✅

A real itsm conversation ran entirely on AWS — the bedrock_s2s Nova Sonic caller vs a pipecat agent (Transcribe → Bedrock Claude → Polly) — and passed validation on Bedrock (user_behavioral_fidelity score=1.0, audio judge auto-skipped, 100% success). Per-stage latencies confirmed each AWS service ran.

Commit 1 — bedrock_s2s caller

Audio-native S2S over Nova Sonic bidirectional streaming; mirrors OpenAIRealtimeUserSimulator. Turn-taking uses Nova's native endpointing + half-duplex suppression, with a ~55s keepalive, notify_user_utterance_complete() at turn end, buffered caller-text flush (SPECULATIVE→FINAL), and prompt end_call termination.

Commit 2 — Amazon Transcribe / Polly assistant STT+TTS

create_stt_service/create_tts_service gain aws_transcribe/aws_polly branches wiring pipecat's AWSTranscribeSTTService/AWSPollyTTSService; creds/region via a shared helper. _validate_service_params skips api_key/model for AWS (env-credentialed). No new dependencies — uses websockets (core) + aiobotocore (via aioboto3); avoids pipecat's [aws] extra, which pins aiobotocore>=3 and conflicts with aioboto3 2.x.

Commit 3 — Bedrock validation judges

Audio judge (user_speech_fidelity) auto-skips when its model isn't a deployment in EVA_MODEL_LIST (no Bedrock model accepts audio input). LLMClient drops the OpenAI-only service_tier param for non-OpenAI/Azure judge models (Bedrock rejects it and litellm's drop_params doesn't strip it). JUDGE_MODEL points the text judges at a Bedrock deployment.

Caveats (please review)

  • The bedrock_s2s caller needs the experimental aws-sdk-bedrock-runtime (pre-1.0, Python ≥3.12), isolated as an optional eva[bedrock-s2s] extra behind a lazy factory guard + scoped mypy override — EVA's 3.11 floor and base install are unaffected.
  • Speech-fidelity validation has no Bedrock equivalent (no audio-input Bedrock model), so it's skipped on AWS-only runs, not silently broken.

Tests

Fully mocked (no AWS creds / no experimental SDK needed): the caller, the AWS STT/TTS branches + config validation, the audio-judge-skip guard, and service_tier provider resolution. ruff clean; full unit suite green (1866 passed). simulation_version bumped; no metric-output change for existing OpenAI/Gemini setups.

Adds a native, audio-native S2S caller (`provider="bedrock_s2s"`) — the AWS
analog of the OpenAI Realtime caller. EVA streams the agent's audio straight to
Amazon Nova Sonic over Bedrock's bidirectional streaming API and streams the
caller's spoken reply back; Nova Sonic also emits both transcripts itself
(agent ASR + caller text), so there is no separate STT/LLM/TTS cascade.

- `BedrockS2SUserSimulator` mirrors `OpenAIRealtimeUserSimulator`: same
  `BotToBotAudioBridge`, same `_build_prompt()` (sent as the Nova SYSTEM text),
  same `end_call` tool. Turn-taking uses Nova's native endpointing plus
  half-duplex suppression (gated on the bridge's is_caller_playing()).
- Audio: μ-law 8 kHz -> LPCM 16 kHz in; Nova output pinned to 16 kHz to match
  the bridge (no resample) out.
- `BedrockS2SSimulatorConfig` added to the `UserSimulatorConfig` union;
  `factory.create_user_simulator` gains a guarded `bedrock_s2s` branch.

Robustness (found and fixed via a live smoke against a stub agent):
- Keepalive silence frames so Nova doesn't drop the session on its ~55s
  no-input timeout when the agent goes quiet / at end-of-call.
- Signal the bridge with notify_user_utterance_complete() at caller turn end,
  or is_caller_playing() wedges True and starves the input stream (a freeze).
- Buffer the caller's text and flush once per turn (Nova may only send the
  SPECULATIVE preview, not a FINAL, for short turns).
- end_call terminates promptly (Nova may invoke it as a pure tool with no
  trailing audio END_TURN); _finish_caller_response is idempotent.

The experimental `aws-sdk-bedrock-runtime` client (pre-1.0, Python >=3.12) is
isolated as an optional extra (`eva[bedrock-s2s]`), imported lazily behind a
factory guard, with a scoped mypy override for its untyped smithy deps — so
EVA's 3.11 floor and base install are unaffected.

Tests are fully mocked (no AWS creds / no SDK required). Live-validated
end-to-end against a real Nova Sonic session (agent ASR, caller speech + text,
end_call -> clean hang-up). `simulation_version` bumped; no metrics touched.
Completes the all-AWS agent-under-test pipeline. The agent LLM already runs on
Bedrock via EVA's LiteLLM router (a bedrock/... deployment in EVA_MODEL_LIST); this
adds the missing STT and TTS so a cascade agent runs entirely on AWS
(Amazon Transcribe -> Bedrock -> Amazon Polly).

- services.py: `aws_transcribe` (aliases: aws, transcribe, amazon_transcribe) and
  `aws_polly` (aliases: aws, polly, amazon_polly) branches wiring pipecat's
  `AWSTranscribeSTTService` / `AWSPollyTTSService`. Credentials + region resolve
  from params then the standard AWS env chain via a shared `_aws_credentials`
  helper; voice/engine/language forwarded through the pipecat Settings.
- config.py: AWS Transcribe/Polly authenticate via the env chain and have no
  api_key/model, so `_validate_service_params` skips the required-keys check for
  them (`_AWS_ENV_CREDENTIALED_STT_TTS`), and `_param_alias` / `pipeline_parts`
  fall back to the provider name when params are absent.
- runner.py: `resolved_models` no longer assumes stt_params/tts_params carry a
  "model" (None for AWS) — falls back to the provider name.
- No new dependencies: pipecat's Transcribe uses `websockets` (core) and Polly
  uses `aiobotocore` (already pulled by `aioboto3`). Not using pipecat's [aws]
  extra because it pins aiobotocore>=3, conflicting with aioboto3's 2.x.
- `.env.example`: all-AWS cascade example; `simulation_version` bumped.

Tests: STT/TTS creation (aliases, voice/engine, no api_key/model) and a config
test that a full all-AWS cascade validates. Live-validated end-to-end: an itsm
conversation ran with Transcribe (STT) -> Bedrock Claude (LLM) -> Polly (TTS)
against the bedrock_s2s Nova Sonic caller (completed=True; STT/LLM/TTS latencies
all recorded).
Lets the validation judges run on Amazon Bedrock so an all-AWS pipeline validates
cleanly. The text judge (user_behavioral_fidelity) already honors the JUDGE_MODEL
env var, but two things blocked Bedrock:

- The audio judge (user_speech_fidelity) needs an audio-capable model; no Bedrock
  model accepts audio input (Nova understanding = image/video/doc only; audio is
  Nova Sonic streaming). ValidationRunner now skips the audio judge when its model
  isn't a deployment in EVA_MODEL_LIST — so AWS-only validation runs the
  Bedrock-capable judges instead of erroring on a model that can never run.
- The LLM judges send service_tier="flex" (OpenAI/Azure-only); Bedrock rejects it
  and litellm's drop_params doesn't strip it. LLMClient now drops service_tier when
  the judge model doesn't resolve to an OpenAI/Azure deployment.

Config: set JUDGE_MODEL to a Bedrock model_name in EVA_MODEL_LIST (documented in
.env.example). Live-validated: an all-AWS itsm run now passes validation
(user_behavioral_fidelity on Bedrock score=1.0; audio judge skipped; 100% success).

simulation_version bumped (orchestrator change). No metric outputs change for
existing OpenAI/Gemini setups.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant