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

1175 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 医疗问诊 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 主机内部地址
```text
http://127.0.0.1:9000
```
该地址只用于服务器本机检查,不提供给远程前端。
## 2. 前端接入规则
### 2.1 认证链路
医疗问诊 Agent 不实现登录注册,也不接受前端传入 `user_id`
```text
前端从宿主系统获得 access token
-> 请求 FastAPI 时携带 Authorization: Bearer <access_token>
-> FastAPI 将 token 转发给 Django /api/user/users/me/
-> Django 返回 200 和用户资料
-> FastAPI 使用 Django 返回的 id 隔离会话、检查、提交和评价记录
```
前端禁止传递或信任 `X-User-Id`
### 2.2 通用请求头
除健康检查外,所有业务接口必须携带:
```http
Authorization: Bearer <access_token>
X-Entry-Scene: vue_frontend
X-Request-Id: <ID>
```
| Header | 必填 | 说明 |
|---|---:|---|
| `Authorization` | 是 | Django 用户中心 access token。 |
| `X-Entry-Scene` | 否 | 入口场景,例如 `vue_frontend``production_vue`。 |
| `X-Request-Id` | 否 | 前端生成的请求追踪 ID。 |
### 2.3 统一响应
除 SSE 流式问诊外,接口统一返回:
```json
{
"code": "OK",
"message": "success",
"data": {}
}
```
前端必须同时判断 HTTP 状态码和业务 `code`
```ts
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。例如:
```env
CORS_ALLOW_ORIGINS=http://localhost:5173,http://192.168.2.100:5173
CORS_ALLOW_ORIGIN_REGEX=
```
修改服务器 `.env` 后重新创建 FastAPI 容器:
```bash
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. 前端完整业务流程
```text
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
```
会话状态流转:
```text
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` | Header`Authorization` 必填,`X-Entry-Scene` 建议传;Body`case_id` 必填,`training_type` 必填,`mode` 必填,`score_type` 可选,`patient_config` 可选。 | `code``message``data.session_id``data.session_code``data.status``data.patient_opening``data.patient_config`。 |
| 流式会话 | `http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/chat/stream` | `/api/v1/sessions/{session_id}/chat/stream` | `POST` | Path`session_id` 必填;Body`message` 必填。 | SSE`message_delta` 返回 `delta``message_done` 返回 `latency_ms``first_token_ms``model``fallback_used`;异常返回 `error`。 |
| 练习提示 | `http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/hints/stream` | `/api/v1/sessions/{session_id}/hints/stream` | `POST` | Path`session_id` 必填;Body`last_user_message` 可选,`scope=current_conversation`。仅 `practice``inquiry` 状态可调用。 | SSE`hint_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` | Path`session_id` 必填。 | `data.items[]`,包含 `item_code``item_name``item_type`。不返回检查结果。 |
| 辅助检查列表获取 | `http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/auxiliary-exams` | `/api/v1/sessions/{session_id}/auxiliary-exams` | `GET` | Path`session_id` 必填。 | `data.items[]`,包含 `item_code``item_name``item_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` | Path`session_id` 必填,`item_code` 必填。`item_code` 必须属于体格检查项。 | `data.item_code``item_name``item_type``result_text``result_structured``is_key``is_abnormal``context_written``already_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` | Path`session_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` | Path`session_id` 必填。至少完成一轮医生问诊后可调用。 | `data.session_id``data.status=diagnosis`。 |
| 提交诊断 | `http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/diagnosis` | `/api/v1/sessions/{session_id}/diagnosis` | `POST` | Path`session_id` 必填;Body`primary_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` | Path`session_id` 必填;Body`treatment_principle` 必填,`treatment_measures` 必填,`risk_plan``communication``follow_up` 可选。 | `data.status=evaluating`。 |
| 生成评价 | `http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/evaluation` | `/api/v1/sessions/{session_id}/evaluation` | `POST` | Path`session_id` 必填;Body`score_type` 可选,允许 `percentage``five_point`。必须已提交治疗。 | `data.evaluation_id``score_type``total_score``dimension_scores``score_details``errors``improvement_plan``evidence_summary``guideline_refs``overall_comment`。 |
| 获取评价(详情) | `http://8.160.178.88/fastapi/api/v1/evaluations/{evaluation_id}` | `/api/v1/evaluations/{evaluation_id}` | `GET` | Path`evaluation_id` 必填。只允许当前 token 对应用户访问自己的评价。 | 评价完整详情,包含 `session_id``case_id``case_title``created_at``pdf_file_path`。 |
| 生成 PDF | `http://8.160.178.88/fastapi/api/v1/evaluations/{evaluation_id}/export-pdf` | `/api/v1/evaluations/{evaluation_id}/export-pdf` | `POST` | Path`evaluation_id` 必填。只允许当前 token 对应用户导出自己的评价。 | `data.export_id``data.file_path`。 |
| 下载 PDF 文件流 | `http://8.160.178.88/fastapi/api/v1/evaluations/{evaluation_id}/download-pdf` | `/api/v1/evaluations/{evaluation_id}/download-pdf` | `GET` | Path`evaluation_id` 必填。Header`Authorization` 必填,`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 获取当前用户
```http
GET /api/v1/auth/me
Authorization: Bearer <access_token>
```
成功响应:
```json
{
"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 能力
```http
GET /api/v1/agent/hello
Authorization: Bearer <access_token>
```
响应核心结构:
```json
{
"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 病例列表
```http
GET /api/v1/cases?department_id=2&mode=practice
```
Query 参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---:|---|
| `department_id` | integer | 否 | 科室 ID。 |
| `training_type` | string | 否 | `case_analysis``diagnosis_treatment``consultation`。 |
| `mode` | string | 否 | `practice``teaching`。 |
响应:
```json
{
"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 病例详情
```http
GET /api/v1/cases/{case_id}
```
响应不会返回标准诊断、标准治疗、隐藏病史、评分规则和检查结果:
```json
{
"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=emergency``personality=anxious`
```http
GET /api/v1/training-config/recommended?case_id=2
```
入参:
```json
{
"case_id": 2
}
```
返回:
```json
{
"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` 传给创建会话接口。
```http
GET /api/v1/training-config/options?case_id=2
```
返回字段与推荐配置信息一致,`options` 包含以下可选值:
| 配置字段 | 可选值 | 说明 |
|---|---|---|
| `visit_environment` | `outpatient``emergency``ward` | 门诊、急诊、病房。 |
| `age_group` | `child``youth``middle_aged``elderly` | 儿童、青年、中年、老年。 |
| `education_level` | `primary_or_below``secondary``higher` | 小学及以下、中等教育、高等教育。 |
| `personality` | `calm``anxious``impatient``cooperative``suspicious` | 平和、焦虑、急躁、配合、多疑。 |
## 7. 创建训练会话
```http
POST /api/v1/sessions
Content-Type: application/json
```
请求:
```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_analysis``diagnosis_treatment``consultation` | 训练类别。 |
| `mode` | `practice``teaching` | 正式前端使用的两种模式。 |
| `score_type` | `percentage``five_point` | 百分制或五分制。 |
| `patient_config.visit_environment` | `outpatient``emergency``ward` | 就诊环境,影响 AI 病人沟通节奏。 |
| `patient_config.age_group` | `child``youth``middle_aged``elderly` | 年龄段,影响病人或家属表达方式。 |
| `patient_config.education_level` | `primary_or_below``secondary``higher` | 文化程度,影响医学术语理解和表达清晰度。 |
| `patient_config.personality` | `calm``anxious``impatient``cooperative``suspicious` | 性格,影响情绪和配合度。 |
后端仍接受旧值 `novice`,但会自动转换为 `practice`。正式前端不要继续使用 `novice`
`patient_config` 为可选字段;未传时后端会根据当前病例自动使用推荐配置。前端训练页已调用推荐配置接口时,应把用户最终选中的配置传入本接口。
响应:
```json
{
"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 非流式问诊
```http
POST /api/v1/sessions/{session_id}/chat
Content-Type: application/json
```
请求:
```json
{
"message": "孩子发热几天了?最高体温多少?"
}
```
响应:
```json
{
"code": "OK",
"message": "success",
"data": {
"reply": "发热有4天了,最高烧到39度多,吃了退烧药能降下来,但过几个小时又会烧。",
"latency_ms": 2500,
"model": "deepseek-chat",
"fallback_used": false
}
}
```
### 8.2 SSE 流式问诊
```http
POST /api/v1/sessions/{session_id}/chat/stream
Content-Type: application/json
Accept: text/event-stream
```
请求体与非流式接口相同。
事件格式:
```text
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。
```ts
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 练习提示
```http
POST /api/v1/sessions/{session_id}/hints
```
请求:
```json
{
"last_user_message": "孩子发热几天了?最高体温多少?",
"scope": "current_conversation"
}
```
响应:
```json
{
"code": "OK",
"message": "success",
"data": {
"hints": ["可以继续追问热型、退热药反应和呼吸困难表现。"],
"missing_dimensions": ["既往史", "严重程度评估"],
"next_questions": [
"孩子以前有没有喘息、哮喘或过敏史?",
"孩子现在有没有呼吸困难或口唇发紫?"
],
"recommended_orders": [
{
"item_code": "spo2",
"reason": "用于评估低氧和病情严重程度"
}
]
}
}
```
约束:
-`practice` 模式可调用。
-`inquiry` 状态可调用。
- 前端默认不自动展示提示,由用户点击后调用。
## 9. 检查与检验
### 9.1 获取当前病例检查项
```http
GET /api/v1/sessions/{session_id}/order-items
```
响应:
```json
{
"code": "OK",
"message": "success",
"data": {
"items": [
{
"item_code": "blood_routine",
"item_name": "血常规",
"item_type": "lab"
}
]
}
}
```
前端不得写死检查项编码。不同病例拥有不同检查项,必须使用本接口返回的 `item_code`
### 9.2 申请检查
```http
POST /api/v1/sessions/{session_id}/orders
```
请求:
```json
{
"item_code": "blood_routine"
}
```
响应:
```json
{
"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` 去重;点击后立即禁用,避免重复请求。
- 检查申请允许在 `inquiry``diagnosis``treatment` 状态执行。
## 10. 阶段提交
### 10.1 完成问诊
```http
POST /api/v1/sessions/{session_id}/complete-inquiry
```
请求体:无。
响应:
```json
{
"code": "OK",
"message": "success",
"data": {
"session_id": 10,
"status": "diagnosis"
}
}
```
至少完成一轮医生提问,否则返回 `INQUIRY_REQUIRED`
### 10.2 提交诊断
```http
POST /api/v1/sessions/{session_id}/diagnosis
```
请求:
```json
{
"primary_diagnosis": "支气管肺炎",
"differential_diagnoses": [
"支气管哮喘急性发作",
"上呼吸道感染"
],
"diagnosis_basis": "结合发热、咳嗽、喘息、肺部体征、炎症指标、胸片和血氧结果,符合儿童支气管肺炎表现。"
}
```
成功响应:
```json
{
"code": "OK",
"message": "success",
"data": {
"status": "treatment"
}
}
```
### 10.3 提交治疗
```http
POST /api/v1/sessions/{session_id}/treatment
```
请求:
```json
{
"treatment_principle": "抗感染、止咳平喘、改善氧合并严密观察病情变化。",
"treatment_measures": "根据病情进行抗感染治疗,必要时雾化缓解喘息,监测体温、呼吸和血氧。",
"risk_plan": "关注低氧、呼吸困难加重、持续高热、精神反应差和脱水。",
"communication": "向家属说明病情、用药注意事项、危险信号和复诊指征。",
"follow_up": "治疗后复查体温、呼吸、血氧和必要的炎症指标。"
}
```
成功响应:
```json
{
"code": "OK",
"message": "success",
"data": {
"status": "evaluating"
}
}
```
## 11. AI 评价、历史记录与 PDF
### 11.1 生成评价
```http
POST /api/v1/sessions/{session_id}/evaluation
```
请求:
```json
{
"score_type": "percentage"
}
```
响应:
```json
{
"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_record``training_score_detail`
- 当前 Redis 短期聊天 memory 被释放。
- 相同会话再次调用评价接口时返回已存在的评价,不重复创建。
### 11.2 历史评价列表
```http
GET /api/v1/evaluations
```
响应:
```json
{
"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 评价详情
```http
GET /api/v1/evaluations/{evaluation_id}
```
返回字段包含完整评价结构,并额外包含:
```json
{
"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
```http
POST /api/v1/evaluations/{evaluation_id}/export-pdf
```
响应:
```json
{
"code": "OK",
"message": "success",
"data": {
"export_id": 101,
"file_path": "/app/storage/reports/training_record_101_percentage_xxx.pdf"
}
}
```
该接口只生成 PDF 并返回服务器内部文件路径,适合前端展示“导出成功”和保存导出记录。
### 11.5 下载 PDF 文件流
```http
GET /api/v1/evaluations/{evaluation_id}/download-pdf
Authorization: Bearer <access_token>
X-Entry-Scene: vue_frontend
```
成功响应不是统一 JSON 包装,而是 PDF 文件流:
```http
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="training_record_101_percentage_xxx.pdf"
```
错误响应仍然使用统一 JSON
```json
{
"code": "EVALUATION_NOT_FOUND",
"message": "evaluation not found",
"data": null
}
```
前端通过浏览器直接下载时不能使用普通 `<a href>` 携带 Bearer token,应使用 `fetch``axios` 的 blob 请求,然后创建临时下载链接:
```ts
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 预检
```http
POST /api/v1/imports/case-sql/preview
Content-Type: multipart/form-data
```
FormData
```text
file=<病例SQL文件>
```
只读取以下四张源表:
```text
case_base
traditional_case
teaching_case
scoring_rule
```
预检不会写入数据库。
### 12.2 确认导入
```http
POST /api/v1/imports/case-sql/apply
Content-Type: multipart/form-data
```
后端根据导入病例生成或更新 `case_exam_item`,导入后病例列表可立即查询。
### 12.3 病例删除
```http
GET /api/v1/cases/{case_id}/delete-preview
DELETE /api/v1/cases/{case_id}
```
删除请求:
```json
{
"confirm": true,
"delete_training_data": true
}
```
删除会级联清理病例、病例扩展数据、检查项、评分规则以及相关训练数据。学生前端不得开放该功能。
### 12.4 知识检索
```http
GET /api/v1/knowledge/search?department_id=2&training_type=diagnosis_treatment&q=,
```
### 12.5 LLM 测试
```http
POST /api/v1/llm/test/deepseek-fast
POST /api/v1/llm/test/deepseek-reason
```
请求:
```json
{
"message": "请用一句话说明医疗问诊训练的用途。"
}
```
## 13. 前端 Axios 封装示例
```ts
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=real``runtime_memory_backend=redis`
4. 获取病例列表和病例详情。
5. 创建 `practice` 会话。
6. 测试普通问诊和 SSE 流式问诊。
7. 点击查看提示,确认返回动态提示。
8. 动态读取检查项并申请检查,重复申请时确认 `already_ordered=true`
9. 完成问诊、提交诊断、提交治疗。
10. 生成评价,确认包含 `dimension_scores``score_details`
11. 查询历史列表和评价详情。
12. 生成 PDF,确认接口返回文件路径。
13. 下载 PDF 文件流,确认浏览器触发文件下载。
## 16. 当前前端必须了解的限制
- 无会话详情和聊天恢复接口,页面刷新后无法恢复短期问诊。
- 问诊聊天只保存在 Redis 短期 memory 中,评价完成后释放。
- PDF 支持两种调用:`export-pdf` 返回服务器文件路径,`download-pdf` 返回 `application/pdf` 文件流并触发浏览器下载。
- 病例导入和删除接口尚未增加管理员角色授权,学生前端必须隐藏。
- 所有检查项必须从 `/order-items` 动态读取,不能写死。
- 正式训练模式只有 `practice``teaching``novice` 仅为旧接口兼容值。