Files
fastapi/docs/03_api_design.md
T
2026-06-08 15:16:07 +08:00

36 KiB
Raw Blame History

医疗问诊 Agent 前端联调 API 文档

文档版本:2026-06-04 对应后端:FastAPI main 分支,提交 b46e43a 之后版本 本文档以当前真实代码行为为准,用于正式 Vue 前端功能联调。

1. 联调地址

1.1 当前公网测试环境

项目 地址
网关根地址 http://8.160.178.88/fastapi
API Base URL http://8.160.178.88/fastapi/api/v1
Swagger http://8.160.178.88/fastapi/docs
OpenAPI JSON http://8.160.178.88/fastapi/openapi.json
存活检查 http://8.160.178.88/fastapi/health/live
就绪检查 http://8.160.178.88/fastapi/health/ready

当前 /health/ready 已验证 MySQL、Redis 和生产配置处于就绪状态。

如果服务器关闭 Nginx 的 /fastapi/ 公网代理,公网地址将不可用,前端需要改用局域网、VPN、SSH 隧道或新的测试网关。

1.2 Docker 主机内部地址

http://127.0.0.1:9000

该地址只用于服务器本机检查,不提供给远程前端。

2. 前端接入规则

2.1 认证链路

医疗问诊 Agent 不实现登录注册,也不接受前端传入 user_id

前端从宿主系统获得 access token
  -> 请求 FastAPI 时携带 Authorization: Bearer <access_token>
  -> FastAPI 将 token 转发给 Django /api/user/users/me/
  -> Django 返回 200 和用户资料
  -> FastAPI 使用 Django 返回的 id 隔离会话、检查、提交和评价记录

前端禁止传递或信任 X-User-Id

2.2 通用请求头

除健康检查外,所有业务接口必须携带:

Authorization: Bearer <access_token>
X-Entry-Scene: vue_frontend
X-Request-Id: <可选的请求追踪ID>
Header 必填 说明
Authorization Django 用户中心 access token。
X-Entry-Scene 入口场景,例如 vue_frontendproduction_vue
X-Request-Id 前端生成的请求追踪 ID。

2.3 统一响应

除 SSE 流式问诊外,接口统一返回:

{
  "code": "OK",
  "message": "success",
  "data": {}
}

前端必须同时判断 HTTP 状态码和业务 code

if (!response.ok || body.code !== "OK") {
  throw new Error(body.message || body.code || "request failed");
}

2.4 CORS 配置

正式前端通过 http://8.160.178.88/app/ 访问 API 时,与 /fastapi/ 同源,不会触发跨域限制。

Mac 上使用 Vite 开发服务器直接调用公网 API 时,服务器 .env 必须允许前端 Origin。例如:

CORS_ALLOW_ORIGINS=http://localhost:5173,http://192.168.2.100:5173
CORS_ALLOW_ORIGIN_REGEX=

修改服务器 .env 后重新创建 FastAPI 容器:

cd /home/code/medical-ai
docker compose up -d --force-recreate fastapi

不需要重新构建镜像。

3. 当前接口总览

3.1 无需认证

Method Path 用途
GET /health/live FastAPI 进程存活检查。
GET /health/ready MySQL、Redis 和关键配置就绪检查。

3.2 需要 Bearer Token

以下路径均以 /api/v1 为前缀。

Method Path 用途 前端范围
GET /auth/me 校验 token 并获取当前用户。 必须
GET /agent/hello 获取 Agent 能力开关。 必须
GET /cases 获取病例列表。 必须
GET /cases/{case_id} 获取病例入口详情。 必须
GET /training-config/recommended 获取训练页推荐病人初始化配置。 必须
GET /training-config/options 获取训练页自定义配置选项。 必须
POST /sessions 创建训练会话。 必须
POST /sessions/{session_id}/chat 非流式问诊。 必须
POST /sessions/{session_id}/chat/stream SSE 流式问诊。 必须
POST /sessions/{session_id}/hints 练习模式提示。 必须
POST /sessions/{session_id}/hints/stream SSE 流式练习提示。 必须
GET /sessions/{session_id}/order-items 获取病例可申请检查项。 必须
POST /sessions/{session_id}/orders 申请检查并返回结果。 必须
GET /sessions/{session_id}/physical-exams 获取体格检查列表。 必须
GET /sessions/{session_id}/auxiliary-exams 获取辅助检查列表。 必须
POST /sessions/{session_id}/physical-exams/{item_code} 获取体格检查某项结果。 必须
POST /sessions/{session_id}/auxiliary-exams/{item_code} 获取辅助检查某项结果。 必须
POST /sessions/{session_id}/complete-inquiry 完成问诊。 必须
POST /sessions/{session_id}/diagnosis 提交诊断。 必须
POST /sessions/{session_id}/treatment 提交治疗方案。 必须
POST /sessions/{session_id}/evaluation 生成 AI 评价。 必须
GET /evaluations 查询当前用户历史评价。 必须
GET /evaluations/{evaluation_id} 查询评价详情和评分明细。 必须
POST /evaluations/{evaluation_id}/export-pdf 生成 PDF 报告。 必须
GET /evaluations/{evaluation_id}/download-pdf 生成并下载 PDF 报告文件流。 必须
GET /knowledge/search 调试知识检索。 调试
POST /llm/test/deepseek-fast 测试快速模型。 调试
POST /llm/test/deepseek-reason 测试 Reason 模型。 调试

4. 前端完整业务流程

GET /auth/me
  -> GET /agent/hello
  -> GET /cases
  -> GET /cases/{case_id}
  -> POST /sessions
  -> 多轮问诊、提示、检查申请
  -> POST /sessions/{session_id}/complete-inquiry
  -> POST /sessions/{session_id}/diagnosis
  -> POST /sessions/{session_id}/treatment
  -> POST /sessions/{session_id}/evaluation
  -> GET /evaluations/{evaluation_id}
  -> POST /evaluations/{evaluation_id}/export-pdf
  -> GET /evaluations/{evaluation_id}/download-pdf

会话状态流转:

inquiry -> diagnosis -> treatment -> evaluating -> completed
状态 允许操作
inquiry 问诊、提示、检查申请、完成问诊。
diagnosis 提交诊断、继续申请检查。
treatment 提交治疗、继续申请检查。
evaluating 生成评价。
completed 查看评价、导出 PDF、查看历史记录。

当前没有“获取会话详情”“恢复聊天记录”接口。页面刷新或中断后,前端不能恢复短期对话,应重新创建训练会话。

4.1 训练页面当前使用接口明细

下表为正式前端训练页面当前需要直接调用的接口。url 为公网联调完整地址,api 为前端代码中拼接的相对路径。除健康检查外,以下接口都需要携带 Authorization: Bearer <access_token>X-Entry-Scene 建议固定传 vue_frontend 或实际入口场景。

接口名称 url api methods params(入参) response(返回参数)
新建会话 http://8.160.178.88/fastapi/api/v1/sessions /api/v1/sessions POST HeaderAuthorization 必填,X-Entry-Scene 建议传;Bodycase_id 必填,training_type 必填,mode 必填,score_type 可选,patient_config 可选。 codemessagedata.session_iddata.session_codedata.statusdata.patient_openingdata.patient_config
流式会话 http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/chat/stream /api/v1/sessions/{session_id}/chat/stream POST Pathsession_id 必填;Bodymessage 必填。 SSEmessage_delta 返回 deltamessage_done 返回 latency_msfirst_token_msmodelfallback_used;异常返回 error
练习提示 http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/hints/stream /api/v1/sessions/{session_id}/hints/stream POST Pathsession_id 必填;Bodylast_user_message 可选,scope=current_conversation。仅 practiceinquiry 状态可调用。 SSEhint_delta 返回一句话提示增量;hint_done 返回耗时;异常返回 error
体格检查列表获取 http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/physical-exams /api/v1/sessions/{session_id}/physical-exams GET Pathsession_id 必填。 data.items[],包含 item_codeitem_nameitem_type。不返回检查结果。
辅助检查列表获取 http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/auxiliary-exams /api/v1/sessions/{session_id}/auxiliary-exams GET Pathsession_id 必填。 data.items[],包含 item_codeitem_nameitem_type。不返回检查结果。
体格检查某项结果 http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/physical-exams/{item_code} /api/v1/sessions/{session_id}/physical-exams/{item_code} POST Pathsession_id 必填,item_code 必填。item_code 必须属于体格检查项。 data.item_codeitem_nameitem_typeresult_textresult_structuredis_keyis_abnormalcontext_writtenalready_ordered
辅助检查某项结果 http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/auxiliary-exams/{item_code} /api/v1/sessions/{session_id}/auxiliary-exams/{item_code} POST Pathsession_id 必填,item_code 必填。item_code 必须属于辅助检查项。 同体格检查结果。检查结果来自数据库,并写入本次会话短期 memory。重复申请返回已有结果,already_ordered=true
完成问诊 http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/complete-inquiry /api/v1/sessions/{session_id}/complete-inquiry POST Pathsession_id 必填。至少完成一轮医生问诊后可调用。 data.session_iddata.status=diagnosis
提交诊断 http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/diagnosis /api/v1/sessions/{session_id}/diagnosis POST Pathsession_id 必填;Bodyprimary_diagnosis 必填,diagnosis_basis 必填,differential_diagnoses 可选数组。 data.status=treatment
提交治疗 http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/treatment /api/v1/sessions/{session_id}/treatment POST Pathsession_id 必填;Bodytreatment_principle 必填,treatment_measures 必填,risk_plancommunicationfollow_up 可选。 data.status=evaluating
生成评价 http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/evaluation /api/v1/sessions/{session_id}/evaluation POST Pathsession_id 必填;Bodyscore_type 可选,允许 percentagefive_point。必须已提交治疗。 data.evaluation_idscore_typetotal_scoredimension_scoresscore_detailserrorsimprovement_planevidence_summaryguideline_refsoverall_comment
获取评价(详情) http://8.160.178.88/fastapi/api/v1/evaluations/{evaluation_id} /api/v1/evaluations/{evaluation_id} GET Pathevaluation_id 必填。只允许当前 token 对应用户访问自己的评价。 评价完整详情,包含 session_idcase_idcase_titlecreated_atpdf_file_path
生成 PDF http://8.160.178.88/fastapi/api/v1/evaluations/{evaluation_id}/export-pdf /api/v1/evaluations/{evaluation_id}/export-pdf POST Pathevaluation_id 必填。只允许当前 token 对应用户导出自己的评价。 data.export_iddata.file_path
下载 PDF 文件流 http://8.160.178.88/fastapi/api/v1/evaluations/{evaluation_id}/download-pdf /api/v1/evaluations/{evaluation_id}/download-pdf GET Pathevaluation_id 必填。HeaderAuthorization 必填,X-Entry-Scene 建议传。只允许当前 token 对应用户下载自己的评价报告。 成功时返回 application/pdf 文件流,响应头包含 Content-Disposition: attachment;失败时返回统一错误 JSON。

常见错误码:

code 场景
AUTH_CREDENTIAL_REQUIRED 缺少 Authorization
SESSION_NOT_FOUND 会话不存在或不属于当前用户。
SESSION_STATUS_INVALID 当前会话状态不允许该操作。
INQUIRY_REQUIRED 未完成至少一轮医生问诊就完成问诊。
ORDER_ITEM_NOT_FOUND 当前病例下不存在该检查项。
ORDER_ITEM_TYPE_MISMATCH 用体格检查接口申请辅助检查,或用辅助检查接口申请体格检查。
TREATMENT_REQUIRED 未提交治疗就生成评价。
EVALUATION_NOT_FOUND 评价不存在或不属于当前用户。
PDF_EXPORT_FAILED PDF 生成失败。

5. 认证与 Agent

5.1 获取当前用户

GET /api/v1/auth/me
Authorization: Bearer <access_token>

成功响应:

{
  "code": "OK",
  "message": "success",
  "data": {
    "user_id": "37",
    "source": "django_user_center",
    "username": "13700000099",
    "display_name": "测试用户",
    "tenant_id": "1",
    "role": "student",
    "phone": "13700000099",
    "avatar": null,
    "gender": 0,
    "institution": 1,
    "institution_id": 1,
    "institution_name": "测试机构",
    "department": 2,
    "department_id": 2,
    "department_name": "儿科",
    "title_name": null,
    "major": null,
    "training_stage": null,
    "learning_target": null,
    "competency_profile": {},
    "weak_dimensions": [],
    "strong_dimensions": [],
    "ai_preference": {},
    "total_training_count": 0,
    "total_case_count": 0,
    "current_level": null,
    "status": 1
  }
}

前端规则:

  • 进入 Agent 后首先调用本接口。
  • 返回 200 且 code=OK 才允许进入训练页面。
  • 后续所有请求继续携带相同 token。
  • user_id 只用于展示或前端状态标识,不能由前端覆盖。

5.2 获取 Agent 能力

GET /api/v1/agent/hello
Authorization: Bearer <access_token>

响应核心结构:

{
  "code": "OK",
  "message": "success",
  "data": {
    "user": {
      "user_id": "37",
      "role": "student",
      "source": "django_user_center",
      "username": "13700000099",
      "display_name": "测试用户"
    },
    "features": {
      "stream_chat": true,
      "score_types": ["percentage", "five_point"],
      "pdf_export": true,
      "knowledge_search": true,
      "llm_mock_enabled": false,
      "llm_mode": "real",
      "llm_fallback_to_mock": false,
      "llm_fast_model": "deepseek-chat",
      "llm_reason_model": "deepseek-reasoner",
      "runtime_memory_backend": "redis",
      "auth_validate_enabled": true,
      "auth_source": "django_user_center"
    }
  }
}

6. 病例接口

6.1 病例列表

GET /api/v1/cases?department_id=2&mode=practice

Query 参数:

参数 类型 必填 说明
department_id integer 科室 ID。
training_type string case_analysisdiagnosis_treatmentconsultation
mode string practiceteaching

响应:

{
  "code": "OK",
  "message": "success",
  "data": {
    "items": [
      {
        "id": 2,
        "case_code": "SRC_2",
        "department_id": 2,
        "title": "支气管肺炎 - 6岁男性患儿",
        "difficulty": "medium",
        "chief_complaint": "发热、咳嗽4天,喘息1天",
        "supported_training_type": "diagnosis_treatment",
        "supported_mode": "free_chat",
        "has_teaching_video": false,
        "has_knowledge_points": true,
        "has_quiz": false
      }
    ]
  }
}

6.2 病例详情

GET /api/v1/cases/{case_id}

响应不会返回标准诊断、标准治疗、隐藏病史、评分规则和检查结果:

{
  "code": "OK",
  "message": "success",
  "data": {
    "id": 2,
    "case_code": "SRC_2",
    "title": "支气管肺炎 - 6岁男性患儿",
    "department": "儿科",
    "difficulty": "medium",
    "patient": {
      "name": null,
      "age": 6,
      "gender": "male",
      "occupation": null
    },
    "chief_complaint": "发热、咳嗽4天,喘息1天",
    "supported_training_type": "diagnosis_treatment",
    "supported_mode": "free_chat",
    "has_teaching_video": false,
    "has_knowledge_points": true,
    "has_quiz": false,
    "order_item_types": ["imaging", "lab", "vital_sign"]
  }
}

6.3 推荐配置信息

推荐配置信息用于训练页初始化病人沟通风格。后端会读取病例主表内容,根据病例年龄、标题、主诉、描述和标签推断默认值。例如儿科患儿病例默认推荐 age_group=child;急诊、危重、低氧等关键词会推荐 visit_environment=emergencypersonality=anxious

GET /api/v1/training-config/recommended?case_id=2

入参:

{
  "case_id": 2
}

返回:

{
  "code": "OK",
  "message": "success",
  "data": {
    "case_id": 2,
    "recommended": {
      "visit_environment": "outpatient",
      "age_group": "child",
      "education_level": "higher",
      "personality": "calm"
    },
    "recommended_labels": {
      "visit_environment": "门诊",
      "age_group": "儿童",
      "education_level": "高等教育",
      "personality": "平和"
    },
    "options": {}
  }
}

6.4 训练配置信息

训练配置信息用于前端渲染自定义配置页面,返回推荐值和全部可选项。前端可以直接使用 recommended 作为默认选中项,用户修改后把 patient_config 传给创建会话接口。

GET /api/v1/training-config/options?case_id=2

返回字段与推荐配置信息一致,options 包含以下可选值:

配置字段 可选值 说明
visit_environment outpatientemergencyward 门诊、急诊、病房。
age_group childyouthmiddle_agedelderly 儿童、青年、中年、老年。
education_level primary_or_belowsecondaryhigher 小学及以下、中等教育、高等教育。
personality calmanxiousimpatientcooperativesuspicious 平和、焦虑、急躁、配合、多疑。

7. 创建训练会话

POST /api/v1/sessions
Content-Type: application/json

请求:

{
  "case_id": 2,
  "training_type": "diagnosis_treatment",
  "mode": "practice",
  "score_type": "percentage",
  "patient_config": {
    "visit_environment": "outpatient",
    "age_group": "child",
    "education_level": "higher",
    "personality": "calm"
  }
}

字段:

字段 允许值 说明
training_type case_analysisdiagnosis_treatmentconsultation 训练类别。
mode practiceteaching 正式前端使用的两种模式。
score_type percentagefive_point 百分制或五分制。
patient_config.visit_environment outpatientemergencyward 就诊环境,影响 AI 病人沟通节奏。
patient_config.age_group childyouthmiddle_agedelderly 年龄段,影响病人或家属表达方式。
patient_config.education_level primary_or_belowsecondaryhigher 文化程度,影响医学术语理解和表达清晰度。
patient_config.personality calmanxiousimpatientcooperativesuspicious 性格,影响情绪和配合度。

后端仍接受旧值 novice,但会自动转换为 practice。正式前端不要继续使用 novicepatient_config 为可选字段;未传时后端会根据当前病例自动使用推荐配置。前端训练页已调用推荐配置接口时,应把用户最终选中的配置传入本接口。

响应:

{
  "code": "OK",
  "message": "success",
  "data": {
    "session_id": 10,
    "session_code": "sess_20260604100000_abcd1234",
    "status": "inquiry",
    "patient_opening": "家长:医生,孩子发热咳嗽好几天了,昨天开始喘得厉害,精神也不太好。",
    "patient_config": {
      "values": {
        "visit_environment": "outpatient",
        "age_group": "child",
        "education_level": "higher",
        "personality": "calm"
      },
      "labels": {
        "visit_environment": "门诊",
        "age_group": "儿童",
        "education_level": "高等教育",
        "personality": "平和"
      }
    }
  }
}

前端创建新会话时必须清空上一轮 Chat、检查结果、诊断、治疗和评价状态。

8. 问诊接口

8.1 非流式问诊

POST /api/v1/sessions/{session_id}/chat
Content-Type: application/json

请求:

{
  "message": "孩子发热几天了?最高体温多少?"
}

响应:

{
  "code": "OK",
  "message": "success",
  "data": {
    "reply": "发热有4天了,最高烧到39度多,吃了退烧药能降下来,但过几个小时又会烧。",
    "latency_ms": 2500,
    "model": "deepseek-chat",
    "fallback_used": false
  }
}

8.2 SSE 流式问诊

POST /api/v1/sessions/{session_id}/chat/stream
Content-Type: application/json
Accept: text/event-stream

请求体与非流式接口相同。

事件格式:

event: message_delta
data: {"delta":"发热有4天了,"}

event: message_done
data: {"latency_ms":3200,"first_token_ms":800,"model":"deepseek-chat","fallback_used":false}

event: error
data: {"code":"LLM_STREAM_TIMEOUT","message":"AI 病人首段回复超时,请重试或关闭流式模式"}

前端必须处理:

事件 处理
message_delta delta 追加到当前 AI 气泡。
message_done 结束 pending,启用发送按钮。
error 结束 pending,显示错误信息。
请求中断或流结束但没有 message_done 结束 pending,提示重试。

流式请求使用 fetch,不要使用原生 EventSource,因为该接口是 POST 且需要请求体和 Authorization。

async function streamChat(baseUrl: string, token: string, sessionId: number, message: string) {
  const response = await fetch(`${baseUrl}/sessions/${sessionId}/chat/stream`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Accept: "text/event-stream",
      Authorization: `Bearer ${token}`,
      "X-Entry-Scene": "vue_frontend",
    },
    body: JSON.stringify({ message }),
  });

  if (!response.ok || !response.body) {
    throw new Error(`stream request failed: ${response.status}`);
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  let completed = false;

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    const blocks = buffer.split("\n\n");
    buffer = blocks.pop() || "";

    for (const block of blocks) {
      const event = block.match(/^event:\s*(.+)$/m)?.[1];
      const rawData = block.match(/^data:\s*(.+)$/m)?.[1];
      if (!event || !rawData) continue;
      const data = JSON.parse(rawData);

      if (event === "message_delta") {
        // appendAiText(data.delta)
      } else if (event === "message_done") {
        completed = true;
      } else if (event === "error") {
        throw new Error(data.message);
      }
    }
  }

  if (!completed) throw new Error("AI 流式回复未正常结束");
}

8.3 练习提示

POST /api/v1/sessions/{session_id}/hints

请求:

{
  "last_user_message": "孩子发热几天了?最高体温多少?",
  "scope": "current_conversation"
}

响应:

{
  "code": "OK",
  "message": "success",
  "data": {
    "hints": ["可以继续追问热型、退热药反应和呼吸困难表现。"],
    "missing_dimensions": ["既往史", "严重程度评估"],
    "next_questions": [
      "孩子以前有没有喘息、哮喘或过敏史?",
      "孩子现在有没有呼吸困难或口唇发紫?"
    ],
    "recommended_orders": [
      {
        "item_code": "spo2",
        "reason": "用于评估低氧和病情严重程度"
      }
    ]
  }
}

约束:

  • practice 模式可调用。
  • inquiry 状态可调用。
  • 前端默认不自动展示提示,由用户点击后调用。

9. 检查与检验

9.1 获取当前病例检查项

GET /api/v1/sessions/{session_id}/order-items

响应:

{
  "code": "OK",
  "message": "success",
  "data": {
    "items": [
      {
        "item_code": "blood_routine",
        "item_name": "血常规",
        "item_type": "lab"
      }
    ]
  }
}

前端不得写死检查项编码。不同病例拥有不同检查项,必须使用本接口返回的 item_code

9.2 申请检查

POST /api/v1/sessions/{session_id}/orders

请求:

{
  "item_code": "blood_routine"
}

响应:

{
  "code": "OK",
  "message": "success",
  "data": {
    "item_code": "blood_routine",
    "item_name": "血常规",
    "item_type": "lab",
    "result_text": "WBC 12.5×10^9/L,中性粒细胞比例72%,提示感染及炎症反应。",
    "result_structured": {
      "wbc": "12.5×10^9/L",
      "neutrophil": "72%"
    },
    "is_key": true,
    "is_abnormal": true,
    "context_written": true,
    "already_ordered": false
  }
}

规则:

  • 检查结果只来自数据库,不由 LLM 生成。
  • 检查结果会写入本次会话上下文和评分依据。
  • 同一会话重复申请相同 item_code 时,返回已有结果并设置 already_ordered=true
  • 前端按 item_code 去重;点击后立即禁用,避免重复请求。
  • 检查申请允许在 inquirydiagnosistreatment 状态执行。

10. 阶段提交

10.1 完成问诊

POST /api/v1/sessions/{session_id}/complete-inquiry

请求体:无。

响应:

{
  "code": "OK",
  "message": "success",
  "data": {
    "session_id": 10,
    "status": "diagnosis"
  }
}

至少完成一轮医生提问,否则返回 INQUIRY_REQUIRED

10.2 提交诊断

POST /api/v1/sessions/{session_id}/diagnosis

请求:

{
  "primary_diagnosis": "支气管肺炎",
  "differential_diagnoses": [
    "支气管哮喘急性发作",
    "上呼吸道感染"
  ],
  "diagnosis_basis": "结合发热、咳嗽、喘息、肺部体征、炎症指标、胸片和血氧结果,符合儿童支气管肺炎表现。"
}

成功响应:

{
  "code": "OK",
  "message": "success",
  "data": {
    "status": "treatment"
  }
}

10.3 提交治疗

POST /api/v1/sessions/{session_id}/treatment

请求:

{
  "treatment_principle": "抗感染、止咳平喘、改善氧合并严密观察病情变化。",
  "treatment_measures": "根据病情进行抗感染治疗,必要时雾化缓解喘息,监测体温、呼吸和血氧。",
  "risk_plan": "关注低氧、呼吸困难加重、持续高热、精神反应差和脱水。",
  "communication": "向家属说明病情、用药注意事项、危险信号和复诊指征。",
  "follow_up": "治疗后复查体温、呼吸、血氧和必要的炎症指标。"
}

成功响应:

{
  "code": "OK",
  "message": "success",
  "data": {
    "status": "evaluating"
  }
}

11. AI 评价、历史记录与 PDF

11.1 生成评价

POST /api/v1/sessions/{session_id}/evaluation

请求:

{
  "score_type": "percentage"
}

响应:

{
  "code": "OK",
  "message": "success",
  "data": {
    "evaluation_id": 101,
    "score_type": "percentage",
    "total_score": 82,
    "dimension_scores": [
      {
        "dimension": "信息获取",
        "score": 18,
        "max_score": 25,
        "comment": "已覆盖主要症状,但既往喘息史追问不足。",
        "evidence": ["询问发热天数和最高体温"],
        "deductions": ["未充分询问既往喘息史"],
        "improvement": "补充既往史、过敏史和严重程度评估。"
      }
    ],
    "score_details": [
      {
        "id": 501,
        "record_id": 101,
        "rule_id": 1,
        "dimension": "信息获取",
        "score": 18,
        "deducted_reason": "未充分询问既往喘息史",
        "evidence_message_ids": ["询问发热天数和最高体温"],
        "ai_confidence": 0.85,
        "comment": "补充既往史和过敏史。"
      }
    ],
    "errors": [],
    "improvement_plan": ["加强儿童肺炎严重程度评估训练。"],
    "evidence_summary": ["检查结果已写入评分依据。"],
    "guideline_refs": [],
    "overall_comment": "诊断方向正确,问诊完整性和沟通细节仍需加强。"
  }
}

评价成功后:

  • 会话状态变为 completed
  • 写入 training_recordtraining_score_detail
  • 当前 Redis 短期聊天 memory 被释放。
  • 相同会话再次调用评价接口时返回已存在的评价,不重复创建。

11.2 历史评价列表

GET /api/v1/evaluations

响应:

{
  "code": "OK",
  "message": "success",
  "data": {
    "items": [
      {
        "evaluation_id": 101,
        "case_title": "支气管肺炎 - 6岁男性患儿",
        "score_type": "percentage",
        "total_score": 82,
        "created_at": "2026-06-04T10:00:00",
        "pdf_exported": true
      }
    ]
  }
}

11.3 评价详情

GET /api/v1/evaluations/{evaluation_id}

返回字段包含完整评价结构,并额外包含:

{
  "session_id": 10,
  "case_id": 2,
  "case_title": "支气管肺炎 - 6岁男性患儿",
  "created_at": "2026-06-04T10:00:00",
  "pdf_file_path": "/app/storage/reports/training_record_101_percentage_xxx.pdf"
}

11.4 导出 PDF

POST /api/v1/evaluations/{evaluation_id}/export-pdf

响应:

{
  "code": "OK",
  "message": "success",
  "data": {
    "export_id": 101,
    "file_path": "/app/storage/reports/training_record_101_percentage_xxx.pdf"
  }
}

该接口只生成 PDF 并返回服务器内部文件路径,适合前端展示“导出成功”和保存导出记录。

11.5 下载 PDF 文件流

GET /api/v1/evaluations/{evaluation_id}/download-pdf
Authorization: Bearer <access_token>
X-Entry-Scene: vue_frontend

成功响应不是统一 JSON 包装,而是 PDF 文件流:

HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="training_record_101_percentage_xxx.pdf"

错误响应仍然使用统一 JSON

{
  "code": "EVALUATION_NOT_FOUND",
  "message": "evaluation not found",
  "data": null
}

前端通过浏览器直接下载时不能使用普通 <a href> 携带 Bearer token,应使用 fetchaxios 的 blob 请求,然后创建临时下载链接:

async function downloadEvaluationPdf(baseUrl: string, token: string, evaluationId: number) {
  const response = await fetch(`${baseUrl}/evaluations/${evaluationId}/download-pdf`, {
    method: "GET",
    headers: {
      Authorization: `Bearer ${token}`,
      "X-Entry-Scene": "vue_frontend",
    },
  });

  if (!response.ok) {
    const error = await response.json().catch(() => null);
    throw new Error(error?.message || `PDF 下载失败:${response.status}`);
  }

  const blob = await response.blob();
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = `evaluation_${evaluationId}.pdf`;
  document.body.appendChild(a);
  a.click();
  a.remove();
  URL.revokeObjectURL(url);
}

12. 管理与调试接口

12.1 病例 SQL 预检

POST /api/v1/imports/case-sql/preview
Content-Type: multipart/form-data

FormData

file=<病例SQL文件>

只读取以下四张源表:

case_base
traditional_case
teaching_case
scoring_rule

预检不会写入数据库。

12.2 确认导入

POST /api/v1/imports/case-sql/apply
Content-Type: multipart/form-data

后端根据导入病例生成或更新 case_exam_item,导入后病例列表可立即查询。

12.3 病例删除

GET /api/v1/cases/{case_id}/delete-preview
DELETE /api/v1/cases/{case_id}

删除请求:

{
  "confirm": true,
  "delete_training_data": true
}

删除会级联清理病例、病例扩展数据、检查项、评分规则以及相关训练数据。学生前端不得开放该功能。

12.4 知识检索

GET /api/v1/knowledge/search?department_id=2&training_type=diagnosis_treatment&q=肺炎,血氧

12.5 LLM 测试

POST /api/v1/llm/test/deepseek-fast
POST /api/v1/llm/test/deepseek-reason

请求:

{
  "message": "请用一句话说明医疗问诊训练的用途。"
}

13. 前端 Axios 封装示例

import axios from "axios";

export const apiClient = axios.create({
  baseURL: "http://8.160.178.88/fastapi/api/v1",
  timeout: 60000,
});

apiClient.interceptors.request.use((config) => {
  const token = sessionStorage.getItem("access_token");
  if (token) config.headers.Authorization = `Bearer ${token}`;
  config.headers["X-Entry-Scene"] = "vue_frontend";
  config.headers["X-Request-Id"] = crypto.randomUUID();
  return config;
});

apiClient.interceptors.response.use(
  (response) => {
    if (response.data?.code !== "OK") {
      return Promise.reject(new Error(response.data?.message || response.data?.code));
    }
    return response;
  },
  (error) => {
    if (error.response?.status === 401) {
      // 返回宿主系统登录页或触发 token 刷新
    }
    return Promise.reject(error);
  },
);

14. 常见错误码

HTTP code 说明
401 AUTH_CREDENTIAL_REQUIRED 缺少 Authorization。
401 AUTH_USER_INVALID token 无效、过期或 Django 返回非 200。
403 AUTH_USER_DISABLED Django 用户状态被禁用。
503 AUTH_USER_CENTER_UNAVAILABLE Django 用户中心超时或不可达。
404 CASE_NOT_FOUND 病例不存在、未发布或已停用。
404 SESSION_NOT_FOUND 会话不存在或不属于当前用户。
400 SESSION_STATUS_INVALID 当前状态不允许执行该操作。
400 INQUIRY_REQUIRED 完成问诊前没有医生提问。
400 DIAGNOSIS_REQUIRED 提交治疗前没有提交诊断。
400 TREATMENT_REQUIRED 生成评价前没有提交治疗。
404 ORDER_ITEM_NOT_FOUND 当前病例不存在该检查项。
404 EVALUATION_NOT_FOUND 评价不存在或不属于当前用户。
504 LLM_CALL_TIMEOUT 非流式问诊超时。
SSE error LLM_STREAM_TIMEOUT 流式问诊首段或总耗时超时。
SSE error LLM_STREAM_FAILED 流式模型调用失败。
SSE error LLM_EMPTY_RESPONSE 模型未返回有效文本。
500 PDF_EXPORT_FAILED PDF 生成失败。
400 CASE_SQL_IMPORT_INVALID 病例 SQL 预检或导入失败。

15. 前端功能验收顺序

  1. 打开 /health/ready,确认返回 status=ready
  2. 使用真实 token 调用 /auth/me,确认返回 Django 用户 id
  3. 调用 /agent/hello,确认 llm_mode=realruntime_memory_backend=redis
  4. 获取病例列表和病例详情。
  5. 创建 practice 会话。
  6. 测试普通问诊和 SSE 流式问诊。
  7. 点击查看提示,确认返回动态提示。
  8. 动态读取检查项并申请检查,重复申请时确认 already_ordered=true
  9. 完成问诊、提交诊断、提交治疗。
  10. 生成评价,确认包含 dimension_scoresscore_details
  11. 查询历史列表和评价详情。
  12. 生成 PDF,确认接口返回文件路径。
  13. 下载 PDF 文件流,确认浏览器触发文件下载。

16. 当前前端必须了解的限制

  • 无会话详情和聊天恢复接口,页面刷新后无法恢复短期问诊。
  • 问诊聊天只保存在 Redis 短期 memory 中,评价完成后释放。
  • PDF 支持两种调用:export-pdf 返回服务器文件路径,download-pdf 返回 application/pdf 文件流并触发浏览器下载。
  • 病例导入和删除接口尚未增加管理员角色授权,学生前端必须隐藏。
  • 所有检查项必须从 /order-items 动态读取,不能写死。
  • 正式训练模式只有 practiceteachingnovice 仅为旧接口兼容值。