Skip to content

Commit 7d5876e

Browse files
committed
Add live end-to-end integration test for the VoiceAgent loop
Lands with the VoiceAgent core it covers. Speaks a question with Amazon Polly, appends a second of trailing silence so Amazon Transcribe endpoints the utterance, feeds it through VoiceAgent.Create (Transcribe -> Bedrock -> Polly), and asserts the loop transcribed the caller, took a turn, produced reply text, and produced spoken audio. Always runs against live AWS. The Bedrock model is overridable via SPEECH_MEAI_TEST_MODEL_ID (default us.anthropic.claude-haiku-4-5-20251001-v1:0) so the test tracks an invokable model as the catalog changes. Verified: passes against live AWS in us-west-2.
1 parent 4dd3b76 commit 7d5876e

1 file changed

Lines changed: 84 additions & 0 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#if NET8_0_OR_GREATER
5+
using Amazon.Polly;
6+
using Microsoft.Extensions.AI;
7+
using System;
8+
using System.Collections.Generic;
9+
using System.IO;
10+
using System.Linq;
11+
using System.Threading;
12+
using System.Threading.Tasks;
13+
using Xunit;
14+
15+
namespace AWS.Speech.MEAI.IntegrationTests;
16+
17+
/// <summary>
18+
/// Live end-to-end integration test for the <see cref="VoiceAgent"/> pipeline: Amazon Transcribe for
19+
/// speech-to-text, Amazon Bedrock for reasoning, and Amazon Polly for text-to-speech. Always runs
20+
/// against live AWS with the ambient credential and region chain.
21+
/// </summary>
22+
[Trait("Category", "Integration")]
23+
public class VoiceAgentLoopIntegrationTests
24+
{
25+
// Overridable so the test tracks a current, invokable model as Bedrock's catalog changes.
26+
private static string ModelId =>
27+
Environment.GetEnvironmentVariable("SPEECH_MEAI_TEST_MODEL_ID")
28+
?? "us.anthropic.claude-haiku-4-5-20251001-v1:0";
29+
30+
[Fact]
31+
[Trait("IntegrationTest", "Speech")]
32+
public async Task RunAsync_TranscribesReasonsAndSpeaks()
33+
{
34+
// Speak a question with Amazon Polly, then feed it through the full loop. A second of trailing
35+
// silence lets Transcribe endpoint the utterance so the agent takes its turn.
36+
using var polly = new AmazonPollyClient();
37+
var tts = polly.AsITextToSpeechClient(defaultSampleRateHertz: 16000);
38+
var question = await tts.GetAudioAsync(
39+
"In one word, what color is a clear daytime sky?", cancellationToken: TestContext.Current.CancellationToken);
40+
var speech = question.Contents.OfType<DataContent>().Single().Data.ToArray();
41+
42+
var pcm = new byte[speech.Length + 32000]; // ~1s trailing silence at 16 kHz mono 16-bit
43+
Array.Copy(speech, pcm, speech.Length);
44+
45+
await using var agent = VoiceAgent.Create(options =>
46+
{
47+
options.ModelId = ModelId;
48+
options.Instructions = "You are concise. Answer in one short sentence.";
49+
});
50+
51+
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken);
52+
timeout.CancelAfter(TimeSpan.FromSeconds(120));
53+
54+
var kinds = new HashSet<VoiceAgentUpdateKind>();
55+
var assistantText = new System.Text.StringBuilder();
56+
long assistantAudioBytes = 0;
57+
string? userFinal = null;
58+
59+
using var microphone = new MemoryStream(pcm);
60+
await foreach (var update in agent.RunAsync(microphone, timeout.Token))
61+
{
62+
kinds.Add(update.Kind);
63+
switch (update.Kind)
64+
{
65+
case VoiceAgentUpdateKind.UserTranscriptFinal:
66+
userFinal = update.Text;
67+
break;
68+
case VoiceAgentUpdateKind.AssistantText when update.Text is { Length: > 0 }:
69+
assistantText.Append(update.Text);
70+
break;
71+
case VoiceAgentUpdateKind.AssistantAudio when update.Audio is { } audio:
72+
assistantAudioBytes += audio.Length;
73+
break;
74+
}
75+
}
76+
77+
Assert.False(string.IsNullOrWhiteSpace(userFinal), "The caller's utterance was not transcribed.");
78+
Assert.Contains(VoiceAgentUpdateKind.TurnStarted, kinds);
79+
Assert.Contains(VoiceAgentUpdateKind.TurnComplete, kinds);
80+
Assert.False(string.IsNullOrWhiteSpace(assistantText.ToString()), "The agent produced no reply text.");
81+
Assert.True(assistantAudioBytes > 0, "The agent produced no spoken audio.");
82+
}
83+
}
84+
#endif

0 commit comments

Comments
 (0)