feat: refine voice studio attention workflow
This commit is contained in:
@@ -681,6 +681,149 @@ async def test_voice_session_analytics_summarize_failures_and_confirmations(
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
async def test_voice_session_attention_filter_and_analytics_count(
|
||||
db_session,
|
||||
auth_token,
|
||||
):
|
||||
async def override_get_db():
|
||||
yield db_session
|
||||
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
|
||||
with (
|
||||
patch(
|
||||
"app.services.voice_session_service.generate_story_content",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_generate,
|
||||
patch(
|
||||
"app.services.voice_session_service.text_to_speech",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_tts,
|
||||
patch(
|
||||
"app.services.voice_session_service.transcribe_voice_audio",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_transcribe,
|
||||
):
|
||||
mock_generate.side_effect = [
|
||||
StoryOutput(
|
||||
mode="generated",
|
||||
title="正常故事",
|
||||
story_text="第一段温暖故事。",
|
||||
cover_prompt_suggestion="normal cover",
|
||||
),
|
||||
RuntimeError("provider down"),
|
||||
]
|
||||
mock_tts.side_effect = [
|
||||
b"normal-audio",
|
||||
b"confirmation-audio",
|
||||
b"safety-audio",
|
||||
]
|
||||
mock_transcribe.return_value = VoiceTranscriptionResult(
|
||||
transcript_text="我想听一个会发光的小恐龙故事",
|
||||
confidence=0.41,
|
||||
provider="openai",
|
||||
)
|
||||
|
||||
transport = ASGITransport(app=app)
|
||||
try:
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
client.cookies.set("access_token", auth_token)
|
||||
|
||||
response = await client.post("/api/voice-sessions", json={})
|
||||
normal_session_id = response.json()["id"]
|
||||
response = await client.post(
|
||||
f"/api/voice-sessions/{normal_session_id}/turns/fallback",
|
||||
json={"transcript_text": "先讲一个温暖的普通故事"},
|
||||
)
|
||||
assert response.status_code == 202
|
||||
|
||||
response = await client.post("/api/voice-sessions", json={})
|
||||
failed_session_id = response.json()["id"]
|
||||
response = await client.post(
|
||||
f"/api/voice-sessions/{failed_session_id}/turns/fallback",
|
||||
json={"transcript_text": "这轮会触发 provider 异常"},
|
||||
)
|
||||
assert response.status_code == 202
|
||||
|
||||
response = await client.post("/api/voice-sessions", json={})
|
||||
confirmation_session_id = response.json()["id"]
|
||||
response = await client.post(
|
||||
f"/api/voice-sessions/{confirmation_session_id}/turns",
|
||||
files={
|
||||
"audio_file": ("turn.webm", b"fake-webm-audio", "audio/webm"),
|
||||
},
|
||||
)
|
||||
assert response.status_code == 202
|
||||
|
||||
response = await client.post("/api/voice-sessions", json={})
|
||||
safety_session_id = response.json()["id"]
|
||||
response = await client.post(
|
||||
f"/api/voice-sessions/{safety_session_id}/turns/fallback",
|
||||
json={"transcript_text": "我想听一个拿着炸弹互相打的故事"},
|
||||
)
|
||||
assert response.status_code == 202
|
||||
|
||||
response = await client.get(
|
||||
"/api/voice-sessions?needs_attention=true&limit=8"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
attention_sessions = response.json()
|
||||
attention_session_ids = {item["id"] for item in attention_sessions}
|
||||
assert attention_session_ids == {
|
||||
failed_session_id,
|
||||
confirmation_session_id,
|
||||
safety_session_id,
|
||||
}
|
||||
assert normal_session_id not in attention_session_ids
|
||||
attention_reason_sets = {
|
||||
item["id"]: set(item["attention_reasons"]) for item in attention_sessions
|
||||
}
|
||||
assert attention_reason_sets[confirmation_session_id] == {
|
||||
"pending_confirmation"
|
||||
}
|
||||
assert attention_reason_sets[safety_session_id] == {
|
||||
"safety_intervention"
|
||||
}
|
||||
assert attention_reason_sets[failed_session_id] == {"failed_turn"}
|
||||
|
||||
response = await client.get(
|
||||
"/api/voice-sessions?needs_attention=true&attention_reason=pending_confirmation"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
confirmation_sessions = response.json()
|
||||
assert [item["id"] for item in confirmation_sessions] == [
|
||||
confirmation_session_id
|
||||
]
|
||||
|
||||
response = await client.get(
|
||||
"/api/voice-sessions?needs_attention=true&attention_reason=safety_intervention"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
safety_sessions = response.json()
|
||||
assert [item["id"] for item in safety_sessions] == [safety_session_id]
|
||||
|
||||
response = await client.get(
|
||||
"/api/voice-sessions?needs_attention=true&attention_reason=failed_turn"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
failed_sessions = response.json()
|
||||
assert [item["id"] for item in failed_sessions] == [failed_session_id]
|
||||
|
||||
response = await client.get("/api/voice-sessions/analytics?days=30")
|
||||
assert response.status_code == 200
|
||||
analytics = response.json()
|
||||
assert analytics["total_sessions"] == 4
|
||||
assert analytics["attention_sessions"] == 3
|
||||
assert analytics["confirmation_attention_sessions"] == 1
|
||||
assert analytics["safety_attention_sessions"] == 1
|
||||
assert analytics["failed_attention_sessions"] == 1
|
||||
assert analytics["failed_turns"] >= 1
|
||||
assert analytics["low_confidence_turns"] >= 1
|
||||
assert analytics["safety_interventions"] >= 1
|
||||
finally:
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
async def test_voice_session_list_orders_recent_sessions_first(
|
||||
db_session,
|
||||
auth_token,
|
||||
|
||||
Reference in New Issue
Block a user