feat: refine voice studio attention workflow

This commit is contained in:
2026-04-21 14:19:51 +08:00
parent 8b50674d04
commit 9f74a93274
7 changed files with 1025 additions and 48 deletions

View File

@@ -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,