feat: 운영 안정성과 세션 음성 경험 개선
This commit is contained in:
parent
facc4ad2d9
commit
c788343467
95 changed files with 8431 additions and 1785 deletions
|
|
@ -71,12 +71,13 @@ from ..session_read_model import (
|
|||
session_share_payload as _session_share_payload,
|
||||
stage_label as _stage_label,
|
||||
)
|
||||
from ..store import InProcSession, store
|
||||
from ..store import InProcSession, TurnRecord, store
|
||||
|
||||
router = APIRouter(prefix="/sessions", tags=["sessions"])
|
||||
logger = logging.getLogger(__name__)
|
||||
_SESSION_EVALUATION_IN_FLIGHT: set[str] = set()
|
||||
_SESSION_EVALUATION_RECOVERY_TASK: asyncio.Task[int] | None = None
|
||||
_STREAM_TURN_EVALUATION_TASKS: set[asyncio.Task[None]] = set()
|
||||
|
||||
TheoryMode = Literal["humanistic", "cbt", "integrative"]
|
||||
EndStateValue = str | int | float | bool | None | dict[str, float]
|
||||
|
|
@ -670,9 +671,11 @@ async def _end_persisted_session(sess: InProcSession, carry: memory.CarryOver) -
|
|||
if _should_schedule_session_digest_worker(carry):
|
||||
asyncio.create_task(_run_session_digest_worker_for_session(sess.session_id))
|
||||
asyncio.create_task(_write_episodic_embeddings(sess))
|
||||
asyncio.create_task(engine_client.close_session(sess.session_id))
|
||||
return
|
||||
require_runtime_fallback_allowed("session end")
|
||||
store.end(sess.session_id)
|
||||
asyncio.create_task(engine_client.close_session(sess.session_id))
|
||||
|
||||
|
||||
def _should_schedule_session_digest_worker(carry: memory.CarryOver) -> bool:
|
||||
|
|
@ -770,6 +773,84 @@ async def _evaluate_stream_turn(
|
|||
return orchestrator.turn_evaluation_error_payload(ctx, exc)
|
||||
|
||||
|
||||
async def _evaluate_and_persist_stream_turn(
|
||||
*,
|
||||
sess: InProcSession,
|
||||
ctx: orchestrator.TurnContext,
|
||||
final_reply: str,
|
||||
result: orchestrator.TurnResult,
|
||||
learner_turn: TurnRecord,
|
||||
) -> None:
|
||||
"""응답 완료 뒤 fast-loop 평가를 저장해 다음 발화의 임계 경로에서 분리한다."""
|
||||
evaluation = await _evaluate_stream_turn(ctx, final_reply)
|
||||
if evaluation is None:
|
||||
return
|
||||
|
||||
if learner_turn.turn_id is not None:
|
||||
saved = await session_persistence.replace_turn_evaluation(
|
||||
turn_id=learner_turn.turn_id,
|
||||
evaluation=evaluation,
|
||||
)
|
||||
if not saved:
|
||||
logger.warning(
|
||||
"turn fast-loop evaluation was not saved: session_id=%s turn_id=%s",
|
||||
ctx.session_id,
|
||||
learner_turn.turn_id,
|
||||
)
|
||||
return
|
||||
|
||||
learner_turn.evaluation = evaluation
|
||||
cached = store.get(ctx.session_id)
|
||||
if cached is not None:
|
||||
for turn in cached.turns:
|
||||
if learner_turn.turn_id and turn.turn_id == learner_turn.turn_id:
|
||||
turn.evaluation = evaluation
|
||||
break
|
||||
if (
|
||||
learner_turn.turn_id is None
|
||||
and turn.speaker == "counselor"
|
||||
and turn.turn_seq == learner_turn.turn_seq
|
||||
):
|
||||
turn.evaluation = evaluation
|
||||
break
|
||||
|
||||
result.evaluation = evaluation
|
||||
await turn_runtime.maybe_recharge_live_coach_credit(sess, ctx, result)
|
||||
|
||||
|
||||
def _observe_stream_turn_evaluation_task(task: asyncio.Task[None]) -> None:
|
||||
_STREAM_TURN_EVALUATION_TASKS.discard(task)
|
||||
try:
|
||||
task.result()
|
||||
except asyncio.CancelledError:
|
||||
logger.info("turn fast-loop evaluation background task cancelled")
|
||||
except Exception:
|
||||
logger.exception("turn fast-loop evaluation background task crashed")
|
||||
|
||||
|
||||
def _schedule_stream_turn_evaluation(
|
||||
*,
|
||||
sess: InProcSession,
|
||||
ctx: orchestrator.TurnContext,
|
||||
final_reply: str,
|
||||
result: orchestrator.TurnResult,
|
||||
learner_turn: TurnRecord,
|
||||
) -> asyncio.Task[None]:
|
||||
task = asyncio.create_task(
|
||||
_evaluate_and_persist_stream_turn(
|
||||
sess=sess,
|
||||
ctx=ctx,
|
||||
final_reply=final_reply,
|
||||
result=result,
|
||||
learner_turn=learner_turn,
|
||||
),
|
||||
name=f"turn-evaluation:{ctx.session_id}:{result.turn_seq}",
|
||||
)
|
||||
_STREAM_TURN_EVALUATION_TASKS.add(task)
|
||||
task.add_done_callback(_observe_stream_turn_evaluation_task)
|
||||
return task
|
||||
|
||||
|
||||
def _stream_result_from_done(
|
||||
ctx: orchestrator.TurnContext,
|
||||
final_reply: str,
|
||||
|
|
@ -1649,15 +1730,22 @@ async def stream_turn(
|
|||
).model_dump(),
|
||||
}
|
||||
if not finalized_turn:
|
||||
evaluation = await _evaluate_stream_turn(ctx, final_reply)
|
||||
result = _stream_result_from_done(
|
||||
ctx, final_reply, data, evaluation
|
||||
ctx, final_reply, data, None
|
||||
)
|
||||
await turn_runtime.finalize_completed_turn(
|
||||
learner_turn = await turn_runtime.finalize_completed_turn(
|
||||
sess,
|
||||
ctx,
|
||||
result,
|
||||
context_prefix="session",
|
||||
recharge_live_coach=False,
|
||||
)
|
||||
_schedule_stream_turn_evaluation(
|
||||
sess=sess,
|
||||
ctx=ctx,
|
||||
final_reply=final_reply,
|
||||
result=result,
|
||||
learner_turn=learner_turn,
|
||||
)
|
||||
finalized_turn = True
|
||||
yield {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue