런타임 계약과 학습자 흐름 보강
This commit is contained in:
parent
f456b8997a
commit
206018b088
56 changed files with 4306 additions and 1008 deletions
|
|
@ -10,6 +10,7 @@ from __future__ import annotations
|
|||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import secrets
|
||||
from datetime import datetime
|
||||
from typing import Literal, Optional
|
||||
|
|
@ -25,7 +26,16 @@ from ..deps import CurrentPrincipal, Principal, Role
|
|||
from ..engine_client import EngineError, engine_client
|
||||
from ..persona_repository import get_catalog_persona
|
||||
from ..runtime_policy import require_runtime_fallback_allowed
|
||||
from ..services import evaluator, guardrail, live_coach, memory, orchestrator, rag, state_machine
|
||||
from ..services import (
|
||||
evaluator,
|
||||
guardrail,
|
||||
live_coach,
|
||||
memory,
|
||||
orchestrator,
|
||||
rag,
|
||||
session_digest_worker,
|
||||
state_machine,
|
||||
)
|
||||
from ..session_read_model import (
|
||||
LearnerDashboardResponse,
|
||||
LearnerSessionsResponse,
|
||||
|
|
@ -58,6 +68,7 @@ from ..session_read_model import (
|
|||
from ..store import InProcSession, TurnRecord, store
|
||||
|
||||
router = APIRouter(prefix="/sessions", tags=["sessions"])
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TheoryMode = Literal["humanistic", "cbt", "integrative"]
|
||||
EndStateValue = str | int | float | bool | None | dict[str, float]
|
||||
|
|
@ -394,10 +405,12 @@ async def _prepare_turn_context(
|
|||
card=sess.persona,
|
||||
state=sess.state,
|
||||
learner_text=learner_text,
|
||||
recall_summary=recall.recall_summary,
|
||||
pinned_facts=recall.pinned_facts,
|
||||
recent_turns=sess.recent_turns(visible_to="client"),
|
||||
kb_behavior_cues=kb_cues,
|
||||
memory=orchestrator.TurnMemory(
|
||||
recall_summary=recall.recall_summary,
|
||||
pinned_facts=recall.pinned_facts,
|
||||
recent_turns=sess.recent_turns(visible_to="client"),
|
||||
kb_behavior_cues=kb_cues,
|
||||
),
|
||||
theory_mode=sess.theory_mode,
|
||||
)
|
||||
assert ctx.state_after is not None
|
||||
|
|
@ -502,12 +515,52 @@ async def _end_persisted_session(sess: InProcSession, carry: memory.CarryOver) -
|
|||
sess.ended = True
|
||||
sess.ended_at = datetime.now().timestamp()
|
||||
store.put(sess)
|
||||
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))
|
||||
return
|
||||
require_runtime_fallback_allowed("session end")
|
||||
store.end(sess.session_id)
|
||||
|
||||
|
||||
def _should_schedule_session_digest_worker(carry: memory.CarryOver) -> bool:
|
||||
return bool(settings.session_digest_worker_enabled and carry.compression_job is not None)
|
||||
|
||||
|
||||
async def _run_session_digest_worker_for_session(session_id: str) -> None:
|
||||
"""Best-effort M2 LLM digest compressor.
|
||||
|
||||
The DB connection is held only for load/apply. Engine generation runs outside
|
||||
the transaction so a slow provider cannot pin the pool.
|
||||
"""
|
||||
|
||||
try:
|
||||
db.get_pool()
|
||||
async with db.acquire(role="admin") as conn:
|
||||
loaded = await session_digest_worker.load_session_digest_job(conn, session_id)
|
||||
if loaded is None:
|
||||
return
|
||||
model = settings.session_digest_worker_model.strip() or None
|
||||
worker = await session_digest_worker.run_session_digest_worker(
|
||||
loaded.job,
|
||||
engine_client,
|
||||
existing_case_digest=loaded.existing_case_digest,
|
||||
model=model,
|
||||
audit_hook=session_persistence.record_llm_call_audit,
|
||||
)
|
||||
if worker.apply_plan is None:
|
||||
return
|
||||
async with db.acquire(role="admin") as conn:
|
||||
await session_digest_worker.apply_session_digest_plan(
|
||||
conn,
|
||||
worker.apply_plan,
|
||||
learner_id=loaded.learner_id,
|
||||
)
|
||||
except Exception:
|
||||
logger.warning("session digest worker failed for session_id=%s", session_id, exc_info=True)
|
||||
return
|
||||
|
||||
|
||||
async def _write_episodic_embeddings(sess: InProcSession) -> None:
|
||||
"""Best-effort M2 episodic writer.
|
||||
|
||||
|
|
@ -601,27 +654,22 @@ async def _generate_and_save_session_evaluation(sess: InProcSession) -> None:
|
|||
),
|
||||
timeout=min(float(settings.engine_timeout), 45.0),
|
||||
)
|
||||
status_value = "error" if result.error else "ready"
|
||||
await session_persistence.save_session_evaluation(
|
||||
session_id=sess.session_id,
|
||||
learner_id=sess.learner_id,
|
||||
status=status_value,
|
||||
source="engine",
|
||||
scope=result.scope,
|
||||
stage=result.stage,
|
||||
payload=result.to_dict(),
|
||||
error=result.error,
|
||||
session_persistence.SessionEvaluationWrite.from_result(
|
||||
session_id=sess.session_id,
|
||||
learner_id=sess.learner_id,
|
||||
result=result,
|
||||
)
|
||||
)
|
||||
except Exception as exc:
|
||||
await session_persistence.save_session_evaluation(
|
||||
session_id=sess.session_id,
|
||||
learner_id=sess.learner_id,
|
||||
status="error",
|
||||
source="engine",
|
||||
scope="session_end",
|
||||
stage=_stage_label(sess.state.stage),
|
||||
payload={},
|
||||
error=str(exc),
|
||||
session_persistence.SessionEvaluationWrite.from_error(
|
||||
session_id=sess.session_id,
|
||||
learner_id=sess.learner_id,
|
||||
scope="session_end",
|
||||
stage=_stage_label(sess.state.stage),
|
||||
error=str(exc),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue