Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/agents/voice/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,11 @@ async def _stream_audio(
input=text if self._voice_pipeline_config.trace_include_sensitive_data else "",
model_config={
"voice": self.tts_settings.voice,
"instructions": self.instructions,
"instructions": (
self.instructions
if self._voice_pipeline_config.trace_include_sensitive_data
else None
),
"speed": self.tts_settings.speed,
},
output_format="pcm",
Expand Down
42 changes: 41 additions & 1 deletion tests/voice/test_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import agents._debug as _debug
from agents import trace
from tests.testing_processor import fetch_events, fetch_span_errors
from tests.testing_processor import fetch_events, fetch_ordered_spans, fetch_span_errors

try:
from agents.voice import (
Expand Down Expand Up @@ -587,6 +587,46 @@ async def run(self, text: str, settings: TTSModelSettings):
]


@pytest.mark.asyncio
@pytest.mark.parametrize("trace_include_sensitive_data", [True, False])
async def test_speech_span_redacts_tts_instructions(
trace_include_sensitive_data: bool,
) -> None:
"""TTS instructions are author-written prompt text, so they follow the same gate as input."""
instructions = "sensitive-style-instructions"
received: list[str] = []

class RecordingTTS(ZeroPcmTTSModel):
async def run(self, text: str, settings: TTSModelSettings) -> AsyncIterator[bytes]:
received.append(settings.instructions)
yield np.zeros(2, dtype=np.int16).tobytes()

result = StreamedAudioResult(
RecordingTTS(),
TTSModelSettings(instructions=instructions),
VoicePipelineConfig(trace_include_sensitive_data=trace_include_sensitive_data),
)
local_queue: asyncio.Queue[VoiceStreamEvent | None] = asyncio.Queue()

with trace("tts-instructions"):
await result._stream_audio("spoken text", local_queue)

speech_spans = [span for span in fetch_ordered_spans() if span.span_data.type == "speech"]
assert len(speech_spans) == 1
model_config = cast(dict[str, Any], speech_spans[0].span_data.model_config)

if trace_include_sensitive_data:
assert model_config["instructions"] == instructions
assert speech_spans[0].span_data.input == "spoken text"
else:
assert model_config["instructions"] is None
assert speech_spans[0].span_data.input == ""

# Non-sensitive settings stay visible either way, and the model still gets the real value.
assert model_config["speed"] == TTSModelSettings().speed
assert received == [instructions]


@pytest.mark.asyncio
async def test_streamed_audio_dispatcher_handles_stream_failure() -> None:
"""A failed _stream_audio task must not leave _dispatch_audio blocked forever."""
Expand Down
Loading