chore: finalize backend feature scope
This commit is contained in:
@@ -9,11 +9,11 @@ from app.models.training import SessionOrder, TrainingSession
|
||||
|
||||
|
||||
class HintAgent:
|
||||
"""新手提示 Agent:基于病例、对话和检查结果调用快速模型生成结构化提示。"""
|
||||
"""练习提示 Agent:基于病例、对话和检查结果调用快速模型生成结构化提示。"""
|
||||
|
||||
def __init__(self, llm: DeepSeekClient | None = None) -> None:
|
||||
self.llm = llm or DeepSeekClient()
|
||||
self.template_path = Path(__file__).resolve().parents[1] / "prompts" / "hint" / "novice_case_hint.md"
|
||||
self.template_path = Path(__file__).resolve().parents[1] / "prompts" / "hint" / "practice_case_hint.md"
|
||||
|
||||
async def generate(
|
||||
self,
|
||||
@@ -85,7 +85,7 @@ class HintAgent:
|
||||
}
|
||||
|
||||
def _load_template(self) -> str:
|
||||
"""提示词读取:加载新手模式病例提示模板。"""
|
||||
"""提示词读取:加载练习模式病例提示模板。"""
|
||||
if self.template_path.exists():
|
||||
return self.template_path.read_text(encoding="utf-8")
|
||||
return "你是医疗问诊训练提示 Agent,只输出合法 JSON。"
|
||||
|
||||
@@ -1,37 +1,34 @@
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
from app.agents.llm_adapter import LLMResponse, LLMStreamChunk, OpenAICompatibleLLMClient
|
||||
from app.agents.llm_adapter import LLMStreamChunk, OpenAICompatibleLLMClient
|
||||
from app.core.config import settings
|
||||
from app.schemas.learning_assistant import LearningAssistantSource
|
||||
|
||||
|
||||
class LearningAssistantAgent:
|
||||
"""AI学习助手 Agent:根据 RAG 来源生成带循证出处的医学学习回答。"""
|
||||
"""AI 学习助手 Agent:根据 RAG 来源和短期上下文生成带循证出处的医学学习回答。"""
|
||||
|
||||
def __init__(self, llm_client: OpenAICompatibleLLMClient | None = None) -> None:
|
||||
self.llm_client = llm_client or OpenAICompatibleLLMClient()
|
||||
|
||||
async def answer(self, question: str, sources: list[LearningAssistantSource]) -> LLMResponse:
|
||||
"""非流式回答:把问题和检索来源拼接后调用快速模型生成标准回答。"""
|
||||
return await self.llm_client.chat(
|
||||
self._messages(question, sources),
|
||||
model=settings.llm_fast_model,
|
||||
thinking_enabled=settings.llm_fast_thinking_enabled,
|
||||
max_tokens=1200,
|
||||
)
|
||||
|
||||
async def stream_answer(self, question: str, sources: list[LearningAssistantSource]) -> AsyncIterator[LLMStreamChunk]:
|
||||
async def stream_answer(
|
||||
self,
|
||||
question: str,
|
||||
sources: list[LearningAssistantSource],
|
||||
history: list[dict] | None = None,
|
||||
) -> AsyncIterator[LLMStreamChunk]:
|
||||
"""流式回答:输出 AI 学习助手增量文本,前端可直接渲染。"""
|
||||
async for chunk in self.llm_client.stream_chat(
|
||||
self._messages(question, sources),
|
||||
self._messages(question, sources, history or []),
|
||||
model=settings.llm_fast_model,
|
||||
thinking_enabled=settings.llm_fast_thinking_enabled,
|
||||
max_tokens=1200,
|
||||
):
|
||||
yield chunk
|
||||
|
||||
def _messages(self, question: str, sources: list[LearningAssistantSource]) -> list[dict]:
|
||||
"""提示词拼接:命中知识库时必须引用来源,未命中时必须声明未找到参考。"""
|
||||
def _messages(self, question: str, sources: list[LearningAssistantSource], history: list[dict]) -> list[dict]:
|
||||
"""提示词拼接:命中知识库时强制引用来源,未命中时必须声明未找到机构参考。"""
|
||||
history_text = self._history_text(history)
|
||||
if sources:
|
||||
context = "\n\n".join(
|
||||
(
|
||||
@@ -42,17 +39,41 @@ class LearningAssistantAgent:
|
||||
for index, source in enumerate(sources, start=1)
|
||||
)
|
||||
system = (
|
||||
"你是医学学习助手,只用于医学教育学习,不替代临床诊疗。"
|
||||
"请优先依据给定知识库片段回答,回答要清晰、准确、分点。"
|
||||
"你是医学学习助手,用于医学教育、课程学习和临床思维训练,不替代临床诊疗。"
|
||||
"优先依据给定知识库片段回答,回答要清晰、准确、分点。"
|
||||
"每个关键结论后标注对应来源编号,例如【来源1】。"
|
||||
"不得编造不存在的PDF、页码或指南来源。"
|
||||
"不得编造不存在的 PDF、页码或指南来源。"
|
||||
)
|
||||
user = (
|
||||
f"{history_text}"
|
||||
f"用户当前问题:{question}\n\n"
|
||||
f"可用知识库片段:\n{context}\n\n"
|
||||
"请给出带来源的学习回答。"
|
||||
)
|
||||
user = f"用户问题:{question}\n\n可用知识库片段:\n{context}\n\n请给出带来源的学习回答。"
|
||||
else:
|
||||
system = (
|
||||
"你是医学学习助手,只用于医学教育学习,不替代临床诊疗。"
|
||||
"当前没有检索到机构知识库参考,回答开头必须写:未检索到本机构知识库参考,以下为大模型通用学习回答。"
|
||||
"不得伪造PDF来源、页码或指南名称。"
|
||||
"你是医学学习助手,用于医学教育、课程学习和临床思维训练,不替代临床诊疗。"
|
||||
"当前没有检索到机构知识库参考,回答开头必须写:"
|
||||
"未检索到本机构知识库参考,以下为大模型通用学习回答。"
|
||||
"不得伪造 PDF 来源、页码或指南名称。"
|
||||
)
|
||||
user = (
|
||||
f"{history_text}"
|
||||
f"用户当前问题:{question}\n\n"
|
||||
"请给出通用学习回答,并提醒用户以课程教材、指南和临床医生判断为准。"
|
||||
)
|
||||
user = f"用户问题:{question}\n\n请给出通用学习回答,并提醒用户以课程教材和临床规范为准。"
|
||||
return [{"role": "system", "content": system}, {"role": "user", "content": user}]
|
||||
|
||||
def _history_text(self, history: list[dict]) -> str:
|
||||
"""上下文摘要:把当前学习助手会话最近几轮问答压缩为提示词上下文。"""
|
||||
if not history:
|
||||
return ""
|
||||
lines: list[str] = []
|
||||
for item in history[-settings.learning_assistant_history_limit :]:
|
||||
role = "用户" if item.get("role") == "user" else "助手"
|
||||
content = str(item.get("content") or "").strip()
|
||||
if content:
|
||||
lines.append(f"{role}:{content[:500]}")
|
||||
if not lines:
|
||||
return ""
|
||||
return "当前会话最近上下文:\n" + "\n".join(lines) + "\n\n"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
from app.agents.llm_adapter import LLMResponse, LLMStreamChunk
|
||||
from app.agents.llm_adapter import LLMStreamChunk
|
||||
from app.agents.hint_agent import HintAgent
|
||||
from app.agents.patient_agent import PatientAgent
|
||||
from app.agents.report_agent import ReportAgent
|
||||
@@ -18,10 +18,6 @@ class MedicalConsultationOrchestrator:
|
||||
self.scoring_agent = ScoringAgent()
|
||||
self.report_agent = ReportAgent()
|
||||
|
||||
async def patient_reply(self, session: TrainingSession, case: CaseBase, memory_messages: list[dict], message: str) -> LLMResponse:
|
||||
"""问诊编排:调用 Patient Agent 生成 AI 病人回复。"""
|
||||
return await self.patient_agent.reply(case, memory_messages, message, session.mode, self._patient_config(session))
|
||||
|
||||
async def patient_stream_reply(
|
||||
self,
|
||||
session: TrainingSession,
|
||||
@@ -84,7 +80,7 @@ class MedicalConsultationOrchestrator:
|
||||
orders: list[SessionOrder],
|
||||
last_user_message: str | None = None,
|
||||
) -> dict:
|
||||
"""新手提示编排:基于当前会话上下文生成轻量训练提醒。"""
|
||||
"""练习提示编排:基于当前会话上下文生成轻量训练提醒。"""
|
||||
return await self.hint_agent.generate(session, case, memory_messages, orders, last_user_message)
|
||||
|
||||
def _patient_config(self, session: TrainingSession) -> dict | None:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
from app.agents.llm_adapter import DeepSeekClient, LLMResponse, LLMStreamChunk
|
||||
from app.agents.llm_adapter import DeepSeekClient, LLMStreamChunk
|
||||
from app.core.config import settings
|
||||
from app.models.source_case import CaseBase
|
||||
|
||||
@@ -11,23 +11,6 @@ class PatientAgent:
|
||||
def __init__(self, llm: DeepSeekClient | None = None) -> None:
|
||||
self.llm = llm or DeepSeekClient()
|
||||
|
||||
async def reply(
|
||||
self,
|
||||
case: CaseBase,
|
||||
memory_messages: list[dict],
|
||||
user_message: str,
|
||||
mode: str,
|
||||
patient_config: dict | None = None,
|
||||
) -> LLMResponse:
|
||||
"""问诊回复:拼接病例上下文、短期记忆和用户输入后调用 Patient Agent。"""
|
||||
messages = self._build_messages(case, memory_messages, user_message, mode, patient_config)
|
||||
return await self.llm.chat(
|
||||
messages,
|
||||
settings.llm_fast_model,
|
||||
thinking_enabled=settings.llm_fast_thinking_enabled,
|
||||
max_tokens=settings.llm_fast_max_tokens,
|
||||
)
|
||||
|
||||
async def stream_reply(
|
||||
self,
|
||||
case: CaseBase,
|
||||
@@ -58,11 +41,7 @@ class PatientAgent:
|
||||
profile = case.ai_patient_profile or {}
|
||||
hidden_info = case.hidden_patient_info or {}
|
||||
config_rule = self._build_patient_config_rule(patient_config)
|
||||
mode_rule = {
|
||||
"novice": "新手模式:回答清楚,必要时可提示医生继续追问症状、既往史或检查。",
|
||||
"practice": "练习模式:只回答被问到的信息,不主动给诊断建议。",
|
||||
"teaching": "教学模式:保持患者身份,允许在回答后补充简短学习提示。",
|
||||
}.get(mode, "只回答被问到的信息。")
|
||||
mode_rule = "练习模式:只回答被问到的信息,不主动给诊断建议。"
|
||||
system = f"""
|
||||
你是一名标准化 AI 病人或患儿家属,只能基于病例资料回答。
|
||||
病例主诉:{case.chief_complaint}
|
||||
|
||||
Reference in New Issue
Block a user