add training configuration APIs

This commit is contained in:
刘金宝
2026-06-05 12:57:02 +08:00
parent 7f1803f9fa
commit 41a2851120
14 changed files with 1541 additions and 10 deletions
+59 -1
View File
@@ -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)