add training configuration APIs
This commit is contained in:
@@ -28,6 +28,7 @@ from app.schemas.session import (
|
||||
)
|
||||
from app.services.audit_service import AuditService
|
||||
from app.services.runtime_memory import runtime_memory
|
||||
from app.services.training_config_service import TrainingConfigService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -48,6 +49,7 @@ class SessionService:
|
||||
if not case:
|
||||
raise AppError("CASE_NOT_FOUND", "case not found or inactive", 404)
|
||||
|
||||
patient_config = TrainingConfigService(self.db).normalize_patient_config(payload.patient_config)
|
||||
session_code = f"sess_{datetime.utcnow().strftime('%Y%m%d%H%M%S')}_{uuid.uuid4().hex[:8]}"
|
||||
memory_key = f"mem:{session_code}"
|
||||
session = self.session_repo.create_session(
|
||||
@@ -64,7 +66,7 @@ class SessionService:
|
||||
status="inquiry",
|
||||
started_at=datetime.utcnow(),
|
||||
memory_key=memory_key,
|
||||
metadata_={"source": "demo"},
|
||||
metadata_={"source": "demo", "patient_config": patient_config},
|
||||
)
|
||||
)
|
||||
patient_opening = case.patient_opening or "家长:医生,孩子这几天不舒服,想请您看看。"
|
||||
@@ -75,6 +77,7 @@ class SessionService:
|
||||
session_code=session.session_code,
|
||||
status=session.status,
|
||||
patient_opening=patient_opening,
|
||||
patient_config=patient_config,
|
||||
)
|
||||
|
||||
async def chat(self, ctx: UserContext, session_id: int, message: str) -> ChatResponse:
|
||||
@@ -225,6 +228,61 @@ class SessionService:
|
||||
self.audit.log(ctx, "session.hints", "training_session", str(session.id), session.id)
|
||||
return HintResponse(**result)
|
||||
|
||||
async def stream_hints(self, ctx: UserContext, session_id: int, payload: HintRequest) -> AsyncIterator[str]:
|
||||
"""流式练习提示:把结构化提示压缩成一句话并用 SSE 返回给前端。"""
|
||||
started_at = time.perf_counter()
|
||||
try:
|
||||
hint_result = await self.generate_hints(ctx, session_id, payload)
|
||||
sentence = self._build_hint_sentence(hint_result)
|
||||
except AppError as exc:
|
||||
error_message = exc.message
|
||||
error_code = exc.code
|
||||
|
||||
async def app_error_generator() -> AsyncIterator[str]:
|
||||
yield self._sse_error(error_message, error_code)
|
||||
|
||||
return app_error_generator()
|
||||
except Exception:
|
||||
logger.exception("hint_stream.failed session_id=%s", session_id)
|
||||
|
||||
async def error_generator() -> AsyncIterator[str]:
|
||||
yield self._sse_error("练习提示生成失败,请稍后重试", "HINT_STREAM_FAILED")
|
||||
|
||||
return error_generator()
|
||||
|
||||
async def event_generator() -> AsyncIterator[str]:
|
||||
if not sentence:
|
||||
yield self._sse_error("当前没有生成有效提示,请继续问诊后再试", "HINT_EMPTY")
|
||||
return
|
||||
for chunk in self._chunk_text(sentence, size=12):
|
||||
payload_text = json.dumps({"delta": chunk}, ensure_ascii=False)
|
||||
yield f"event: hint_delta\ndata: {payload_text}\n\n"
|
||||
await asyncio.sleep(0)
|
||||
done_payload = json.dumps({"latency_ms": int((time.perf_counter() - started_at) * 1000)}, ensure_ascii=False)
|
||||
yield f"event: hint_done\ndata: {done_payload}\n\n"
|
||||
|
||||
return event_generator()
|
||||
|
||||
def _build_hint_sentence(self, hint_result: HintResponse) -> str:
|
||||
"""提示压缩:从结构化提示中提炼适合前端流式展示的一句话。"""
|
||||
parts: list[str] = []
|
||||
if hint_result.missing_dimensions:
|
||||
parts.append(f"当前可补充{ '、'.join(hint_result.missing_dimensions[:3]) }")
|
||||
if hint_result.next_questions:
|
||||
parts.append(f"下一步可问:{hint_result.next_questions[0]}")
|
||||
elif hint_result.hints:
|
||||
parts.append(hint_result.hints[0])
|
||||
if hint_result.recommended_orders:
|
||||
order = hint_result.recommended_orders[0]
|
||||
item_code = order.get("item_code") or order.get("item_name") or "关键检查"
|
||||
reason = order.get("reason") or "用于完善病情判断"
|
||||
parts.append(f"可考虑申请{item_code},{reason}")
|
||||
return ";".join(parts) + ("。" if parts else "")
|
||||
|
||||
def _chunk_text(self, text: str, size: int) -> list[str]:
|
||||
"""文本切片:把一句练习提示拆成短片段,便于前端按 SSE 渐进展示。"""
|
||||
return [text[index : index + size] for index in range(0, len(text), size)]
|
||||
|
||||
def complete_inquiry(self, ctx: UserContext, session_id: int) -> SessionStatusResponse:
|
||||
"""完成问诊:校验至少一轮医生问诊后进入诊断阶段。"""
|
||||
session = self._get_session(session_id, ctx.user_id)
|
||||
|
||||
Reference in New Issue
Block a user