feat: 운영 안정성과 세션 음성 경험 개선

This commit is contained in:
Yun Chan 2026-07-31 00:13:08 +09:00
parent facc4ad2d9
commit c788343467
95 changed files with 8431 additions and 1785 deletions

View file

@ -2,6 +2,7 @@
from __future__ import annotations
import asyncio
import json
import unittest
from types import SimpleNamespace
@ -609,6 +610,8 @@ class SessionTurnPersistenceTest(unittest.IsolatedAsyncioTestCase):
principal,
)
await _consume_event_source(response)
if sessions._STREAM_TURN_EVALUATION_TASKS:
await asyncio.gather(*tuple(sessions._STREAM_TURN_EVALUATION_TASKS))
self.assertEqual(len(sess.turns), 2)
learner_turn, client_turn = sess.turns
@ -620,6 +623,65 @@ class SessionTurnPersistenceTest(unittest.IsolatedAsyncioTestCase):
)
self.assertIsNone(client_turn.evaluation)
async def test_stream_turn_done_does_not_wait_for_fast_loop_evaluation(
self,
) -> None:
principal = _principal()
sess = _session(principal)
evaluation_started = asyncio.Event()
release_evaluation = asyncio.Event()
async def successful_stream(ctx, engine, **kwargs):
assert ctx.state_after is not None
yield orchestrator.StreamEvent("token", {"text": "지금 답할게요."})
yield orchestrator.StreamEvent(
"done",
{
"session_id": ctx.session_id,
"turn_seq": ctx.state_after.turn_seq,
"safety_flagged": False,
"llm_provider": "claude_cli",
"model": "gateway-default",
},
)
async def slow_eval_hook(ctx, client_reply):
evaluation_started.set()
await release_evaluation.wait()
return {
"loop": "fast",
"turn_seq": ctx.state_after.turn_seq,
"stage": ctx.state_after.stage.value,
"appropriateness": "pos",
}
with (
patch.object(sessions.orchestrator, "run_turn_stream", successful_stream),
patch.object(
sessions.evaluator,
"make_eval_hook",
return_value=slow_eval_hook,
),
):
response = await sessions.stream_turn(
sess.session_id,
sessions.TurnRequest(text="평가를 기다리지 않는 발화"),
principal,
)
consume_task = asyncio.create_task(_consume_event_source(response))
await asyncio.wait_for(evaluation_started.wait(), timeout=1)
body = await asyncio.wait_for(consume_task, timeout=0.2)
self.assertIn("'event': 'done'", body.decode("utf-8"))
self.assertEqual(len(sess.turns), 2)
self.assertIsNone(sess.turns[0].evaluation)
release_evaluation.set()
if sessions._STREAM_TURN_EVALUATION_TASKS:
await asyncio.gather(*tuple(sessions._STREAM_TURN_EVALUATION_TASKS))
self.assertEqual(sess.turns[0].evaluation["appropriateness"], "pos")
async def test_stream_turn_surfaces_fast_loop_evaluation_failure_on_review(
self,
) -> None:
@ -662,6 +724,8 @@ class SessionTurnPersistenceTest(unittest.IsolatedAsyncioTestCase):
principal,
)
await _consume_event_source(response)
if sessions._STREAM_TURN_EVALUATION_TASKS:
await asyncio.gather(*tuple(sessions._STREAM_TURN_EVALUATION_TASKS))
self.assertEqual(len(sess.turns), 2)
learner_turn = sess.turns[0]