회기 연속성과 멀티 케이스 계약을 영속화
This commit is contained in:
parent
be08c0b573
commit
72353ecd82
26 changed files with 2170 additions and 127 deletions
|
|
@ -28,7 +28,15 @@ from ..deps import Principal, Role, require_role
|
|||
from ..engine_client import EngineError, engine_client
|
||||
from ..runtime_policy import runtime_fallback_allowed
|
||||
from ..session_evaluation_input import enriched_masked_turns
|
||||
from ..session_read_model import StageLabel, stage_label_or_none
|
||||
from ..session_evaluation_timeout import (
|
||||
session_evaluation_transport_timeout_seconds,
|
||||
)
|
||||
from ..session_read_model import (
|
||||
ReviewEvaluationFailure,
|
||||
StageLabel,
|
||||
stage_label_or_none,
|
||||
teacher_evaluation_failure,
|
||||
)
|
||||
from ..services import evaluator
|
||||
from ..services.evaluator import SessionEvaluation, TurnEvaluation
|
||||
from ..store import InProcSession
|
||||
|
|
@ -51,12 +59,13 @@ class TurnReevaluateRequest(BaseModel):
|
|||
|
||||
|
||||
class EvaluationSummary(BaseModel):
|
||||
"""회기 평가 조회 응답(분포 + deep 결과 합본)."""
|
||||
"""회기 평가 조회 응답(분포 + deep 결과 합본, provider 오류 원문 제외)."""
|
||||
|
||||
session_id: str
|
||||
stage: StageLabel | None = None
|
||||
status: str | None = None
|
||||
error: str | None = None
|
||||
failure: ReviewEvaluationFailure | None = None
|
||||
durable: bool = False
|
||||
deep: Optional[dict[str, Any]] = None
|
||||
distribution: dict[str, Any] = Field(default_factory=dict)
|
||||
|
|
@ -108,6 +117,15 @@ def _session_evaluation_error_status(error: str) -> int:
|
|||
return status.HTTP_502_BAD_GATEWAY
|
||||
|
||||
|
||||
def _safe_session_evaluation_retry_detail() -> str:
|
||||
"""재시도 HTTP 응답에서는 provider 예외 원문을 내보내지 않는다.
|
||||
|
||||
원인은 durable evaluation record에 서버 전용으로 보존하고, 교수자 화면은 review의
|
||||
안전 분류(evaluationFailure)로 다음 행동만 안내한다.
|
||||
"""
|
||||
return "AI 평가를 완료하지 못했습니다. 최신 평가 상태를 확인해 주세요."
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════════════════════
|
||||
# 회기 deep-loop 재평가 트리거 (교수자/관리자)
|
||||
# ════════════════════════════════════════════════════════════════════════════
|
||||
|
|
@ -120,7 +138,8 @@ async def reevaluate_session(
|
|||
"""회기 전체 deep-loop 재평가(슈퍼바이저 rationale/critique + 개선점 + 대안발화).
|
||||
|
||||
저장된 마스킹 축어록을 evaluator.evaluate_session 으로 평가한다.
|
||||
엔진 장애는 503 으로 변환(평가는 비치명적이지만 트리거는 사용자 명시 요청이라 에러 노출).
|
||||
엔진 장애는 503으로 변환한다. provider 예외 원문은 durable 기록에만 남기고 HTTP에는
|
||||
안전한 안내만 반환한다.
|
||||
"""
|
||||
sess = await _load_session_or_404(session_id, principal)
|
||||
counselor_identity = getattr(sess, "learner_label", None)
|
||||
|
|
@ -144,6 +163,7 @@ async def reevaluate_session(
|
|||
theory_mode=_theory_mode_of(sess),
|
||||
scope=body.scope if body.scope in ("session_end", "stage_transition") else "session_end",
|
||||
audit_hook=session_persistence.record_llm_call_audit,
|
||||
timeout=session_evaluation_transport_timeout_seconds(),
|
||||
)
|
||||
except EngineError as e:
|
||||
detail = f"engine unavailable: {e}"
|
||||
|
|
@ -163,7 +183,10 @@ async def reevaluate_session(
|
|||
session_id,
|
||||
write.error,
|
||||
)
|
||||
raise HTTPException(status.HTTP_503_SERVICE_UNAVAILABLE, detail=detail)
|
||||
raise HTTPException(
|
||||
status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=_safe_session_evaluation_retry_detail(),
|
||||
)
|
||||
|
||||
write = session_persistence.SessionEvaluationWrite.from_result(
|
||||
session_id=session_id,
|
||||
|
|
@ -178,7 +201,10 @@ async def reevaluate_session(
|
|||
logger.error("%s: session_id=%s status=%s", detail, session_id, write.status)
|
||||
raise HTTPException(status.HTTP_503_SERVICE_UNAVAILABLE, detail=detail)
|
||||
if result.error:
|
||||
raise HTTPException(_session_evaluation_error_status(result.error), detail=result.error)
|
||||
raise HTTPException(
|
||||
_session_evaluation_error_status(result.error),
|
||||
detail=_safe_session_evaluation_retry_detail(),
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
|
|
@ -256,7 +282,10 @@ async def reevaluate_turn(
|
|||
)
|
||||
learner.evaluation = result_payload
|
||||
if result.error:
|
||||
raise HTTPException(_session_evaluation_error_status(result.error), detail=result.error)
|
||||
raise HTTPException(
|
||||
_session_evaluation_error_status(result.error),
|
||||
detail=_safe_session_evaluation_retry_detail(),
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
|
|
@ -285,13 +314,18 @@ async def get_session_evaluation(
|
|||
distribution={},
|
||||
)
|
||||
payload = record.get("payload")
|
||||
deep = payload if isinstance(payload, dict) else {}
|
||||
deep = dict(payload) if isinstance(payload, dict) else {}
|
||||
# SessionEvaluation.to_dict()에는 server-side error가 함께 저장된다. deep 객체도
|
||||
# 교수자 API 경계에서는 같은 원칙으로 제거한다.
|
||||
deep.pop("error", None)
|
||||
distribution = deep.get("distribution")
|
||||
failure = teacher_evaluation_failure(record)
|
||||
return EvaluationSummary(
|
||||
session_id=session_id,
|
||||
stage=_summary_stage(record.get("stage") or deep.get("stage")),
|
||||
status=str(record.get("status") or "") or None,
|
||||
error=str(record.get("error") or "") or None,
|
||||
error=_safe_session_evaluation_retry_detail() if failure is not None else None,
|
||||
failure=failure,
|
||||
durable=durable,
|
||||
deep=deep,
|
||||
distribution=distribution if isinstance(distribution, dict) else {},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue