v2.6.0 (#524)
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai> Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai> Co-authored-by: Gauthier Guinet <43207538+Gguinet@users.noreply.github.com> Co-authored-by: Kim-Adeline Miguel <kimadeline.miguel@mistral.ai> Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com> Co-authored-by: Quentin <torroba.q@gmail.com> Co-authored-by: Simon <80467011+sorgfresser@users.noreply.github.com> Co-authored-by: Simon Van de Kerckhove <simon.vandekerckhove@mistral.ai> Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com> Co-authored-by: angelapopopo <angele.lenglemetz@mistral.ai> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
parent
5103019b01
commit
eb580209d4
180 changed files with 11136 additions and 1030 deletions
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -47,12 +47,16 @@ def _make_manager(
|
|||
*,
|
||||
voice_mode_enabled: bool = True,
|
||||
transcribe_client: FakeTranscribeClient | None = None,
|
||||
telemetry_client: MagicMock | None = None,
|
||||
) -> tuple[VoiceManager, FakeAudioRecorder, FakeTranscribeClient]:
|
||||
recorder = FakeAudioRecorder()
|
||||
client = transcribe_client or FakeTranscribeClient()
|
||||
config = build_test_vibe_config(voice_mode_enabled=voice_mode_enabled)
|
||||
manager = VoiceManager(
|
||||
config_getter=lambda: config, audio_recorder=recorder, transcribe_client=client
|
||||
config_getter=lambda: config,
|
||||
audio_recorder=recorder,
|
||||
transcribe_client=client,
|
||||
telemetry_client=telemetry_client,
|
||||
)
|
||||
return manager, recorder, client
|
||||
|
||||
|
|
@ -175,7 +179,7 @@ class TestStopRecording:
|
|||
async def test_stop_recovers_when_no_audio_was_sent(self) -> None:
|
||||
client = FakeTranscribeClient(
|
||||
events=[
|
||||
TranscribeSessionCreated(),
|
||||
TranscribeSessionCreated(request_id="test-req-id"),
|
||||
TranscribeError(
|
||||
message="Cannot flush audio before sending any audio bytes"
|
||||
),
|
||||
|
|
@ -304,7 +308,7 @@ class TestTranscription:
|
|||
async def test_text_deltas_notify_listeners(self) -> None:
|
||||
client = FakeTranscribeClient(
|
||||
events=[
|
||||
TranscribeSessionCreated(),
|
||||
TranscribeSessionCreated(request_id="test-req-id"),
|
||||
TranscribeTextDelta(text="hello "),
|
||||
TranscribeTextDelta(text="world"),
|
||||
TranscribeDone(),
|
||||
|
|
@ -323,7 +327,7 @@ class TestTranscription:
|
|||
async def test_transcription_error_does_not_crash(self) -> None:
|
||||
client = FakeTranscribeClient(
|
||||
events=[
|
||||
TranscribeSessionCreated(),
|
||||
TranscribeSessionCreated(request_id="test-req-id"),
|
||||
TranscribeTextDelta(text="partial"),
|
||||
TranscribeError(message="something broke"),
|
||||
TranscribeDone(),
|
||||
|
|
@ -341,7 +345,10 @@ class TestTranscription:
|
|||
@pytest.mark.asyncio
|
||||
async def test_cancel_during_transcription(self) -> None:
|
||||
client = FakeTranscribeClient(
|
||||
events=[TranscribeSessionCreated(), TranscribeTextDelta(text="hello")]
|
||||
events=[
|
||||
TranscribeSessionCreated(request_id="test-req-id"),
|
||||
TranscribeTextDelta(text="hello"),
|
||||
]
|
||||
)
|
||||
manager, _, _ = _make_manager(transcribe_client=client)
|
||||
listener = StateListener()
|
||||
|
|
@ -355,7 +362,10 @@ class TestTranscription:
|
|||
@pytest.mark.asyncio
|
||||
async def test_session_created_is_silent(self) -> None:
|
||||
client = FakeTranscribeClient(
|
||||
events=[TranscribeSessionCreated(), TranscribeDone()]
|
||||
events=[
|
||||
TranscribeSessionCreated(request_id="test-req-id"),
|
||||
TranscribeDone(),
|
||||
]
|
||||
)
|
||||
manager, _, _ = _make_manager(transcribe_client=client)
|
||||
listener = StateListener()
|
||||
|
|
@ -392,3 +402,178 @@ class TestTranscription:
|
|||
|
||||
assert manager.transcribe_state == TranscribeState.IDLE
|
||||
assert not recorder.is_recording
|
||||
|
||||
|
||||
def _find_telemetry_calls(
|
||||
mock: MagicMock, event_name: str
|
||||
) -> list[dict[str, str | int | float | None]]:
|
||||
"""Return the properties dicts for all calls matching a given event name."""
|
||||
results: list[dict[str, str | int | float | None]] = []
|
||||
for call in mock.send_telemetry_event.call_args_list:
|
||||
if call[0][0] == event_name:
|
||||
results.append(call[0][1])
|
||||
return results
|
||||
|
||||
|
||||
class TestTelemetryTracking:
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_sends_transcription_start_event(self) -> None:
|
||||
client = FakeTranscribeClient(
|
||||
events=[TranscribeSessionCreated(request_id="req-123"), TranscribeDone()]
|
||||
)
|
||||
mock_telemetry = MagicMock()
|
||||
manager, _, _ = _make_manager(
|
||||
transcribe_client=client, telemetry_client=mock_telemetry
|
||||
)
|
||||
manager.start_recording()
|
||||
await manager.stop_recording()
|
||||
|
||||
calls = _find_telemetry_calls(mock_telemetry, "vibe.audio.transcription.start")
|
||||
assert len(calls) == 1
|
||||
assert calls[0]["recording_id"] == "req-123"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cancel_sends_cancel_event(self) -> None:
|
||||
mock_telemetry = MagicMock()
|
||||
manager, _, _ = _make_manager(telemetry_client=mock_telemetry)
|
||||
manager.start_recording()
|
||||
manager.cancel_recording()
|
||||
|
||||
calls = _find_telemetry_calls(
|
||||
mock_telemetry, "vibe.audio.transcription.cancel_recording"
|
||||
)
|
||||
assert len(calls) == 1
|
||||
recording_duration_ms = calls[0]["recording_duration_ms"]
|
||||
assert isinstance(recording_duration_ms, (int, float))
|
||||
assert recording_duration_ms >= 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_done_sends_done_event(self) -> None:
|
||||
client = FakeTranscribeClient(
|
||||
events=[
|
||||
TranscribeSessionCreated(request_id="test-req-id"),
|
||||
TranscribeTextDelta(text="hello "),
|
||||
TranscribeTextDelta(text="world"),
|
||||
TranscribeDone(),
|
||||
]
|
||||
)
|
||||
mock_telemetry = MagicMock()
|
||||
manager, _, _ = _make_manager(
|
||||
transcribe_client=client, telemetry_client=mock_telemetry
|
||||
)
|
||||
|
||||
manager.start_recording()
|
||||
await manager.stop_recording()
|
||||
|
||||
calls = _find_telemetry_calls(mock_telemetry, "vibe.audio.transcription.done")
|
||||
assert len(calls) == 1
|
||||
assert calls[0]["recording_id"] == "test-req-id"
|
||||
assert calls[0]["transcript_length"] == len("hello ") + len("world")
|
||||
transcription_duration_ms = calls[0]["transcription_duration_ms"]
|
||||
assert isinstance(transcription_duration_ms, (int, float))
|
||||
assert transcription_duration_ms >= 0
|
||||
recording_duration_ms = calls[0]["recording_duration_ms"]
|
||||
assert isinstance(recording_duration_ms, (int, float))
|
||||
assert recording_duration_ms >= 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_error_sends_error_event(self) -> None:
|
||||
import asyncio
|
||||
|
||||
class CrashingTranscribeClient:
|
||||
def __init__(self, provider=None, model=None) -> None:
|
||||
pass
|
||||
|
||||
async def transcribe(self, audio_stream):
|
||||
raise RuntimeError("network error")
|
||||
yield
|
||||
|
||||
recorder = FakeAudioRecorder()
|
||||
config = build_test_vibe_config(voice_mode_enabled=True)
|
||||
mock_telemetry = MagicMock()
|
||||
manager = VoiceManager(
|
||||
config_getter=lambda: config,
|
||||
audio_recorder=recorder,
|
||||
transcribe_client=CrashingTranscribeClient(),
|
||||
telemetry_client=mock_telemetry,
|
||||
)
|
||||
|
||||
manager.start_recording()
|
||||
await asyncio.sleep(0)
|
||||
|
||||
calls = _find_telemetry_calls(mock_telemetry, "vibe.audio.transcription.error")
|
||||
assert len(calls) == 1
|
||||
error_message = calls[0]["error_message"]
|
||||
assert isinstance(error_message, str)
|
||||
assert "network error" in error_message
|
||||
transcription_duration_ms = calls[0]["transcription_duration_ms"]
|
||||
assert isinstance(transcription_duration_ms, (int, float))
|
||||
assert transcription_duration_ms >= 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_no_telemetry_when_client_is_none(self) -> None:
|
||||
manager, _, _ = _make_manager() # no telemetry_client
|
||||
manager.start_recording()
|
||||
manager.cancel_recording()
|
||||
# No error raised — tracking is silently skipped
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_each_recording_uses_session_request_id(self) -> None:
|
||||
client = FakeTranscribeClient(
|
||||
events=[TranscribeSessionCreated(request_id="req-first"), TranscribeDone()]
|
||||
)
|
||||
mock_telemetry = MagicMock()
|
||||
manager, _, _ = _make_manager(
|
||||
transcribe_client=client, telemetry_client=mock_telemetry
|
||||
)
|
||||
|
||||
manager.start_recording()
|
||||
await manager.stop_recording()
|
||||
|
||||
client.set_events([
|
||||
TranscribeSessionCreated(request_id="req-second"),
|
||||
TranscribeDone(),
|
||||
])
|
||||
|
||||
manager.start_recording()
|
||||
await manager.stop_recording()
|
||||
|
||||
calls = _find_telemetry_calls(mock_telemetry, "vibe.audio.transcription.start")
|
||||
assert len(calls) == 2
|
||||
assert calls[0]["recording_id"] == "req-first"
|
||||
assert calls[1]["recording_id"] == "req-second"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_timeout_sends_error_event(self) -> None:
|
||||
import asyncio
|
||||
|
||||
class HangingTranscribeClient:
|
||||
def __init__(self, provider=None, model=None) -> None:
|
||||
pass
|
||||
|
||||
async def transcribe(self, audio_stream):
|
||||
await asyncio.Event().wait()
|
||||
return
|
||||
yield
|
||||
|
||||
recorder = FakeAudioRecorder()
|
||||
config = build_test_vibe_config(voice_mode_enabled=True)
|
||||
mock_telemetry = MagicMock()
|
||||
manager = VoiceManager(
|
||||
config_getter=lambda: config,
|
||||
audio_recorder=recorder,
|
||||
transcribe_client=HangingTranscribeClient(),
|
||||
telemetry_client=mock_telemetry,
|
||||
)
|
||||
manager.start_recording()
|
||||
|
||||
with patch(
|
||||
"vibe.cli.voice_manager.voice_manager.TRANSCRIPTION_DRAIN_TIMEOUT", 0.01
|
||||
):
|
||||
await manager.stop_recording()
|
||||
|
||||
calls = _find_telemetry_calls(mock_telemetry, "vibe.audio.transcription.error")
|
||||
assert len(calls) == 1
|
||||
error_message = calls[0]["error_message"]
|
||||
assert isinstance(error_message, str)
|
||||
assert "timed out" in error_message.lower()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue