Files
fastapi/docs/03_api_design.md
T
2026-06-01 10:39:07 +08:00

810 lines
20 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.
# 前端 API 对接文档
本文档面向 Vue 前端与后续正式前端开发,描述当前第一版 Demo 已实现的后端接口、请求头、状态流转、字段结构和错误处理规则。
## 1. 通用约定
### 1.1 Base URL
本地开发默认地址:
```text
http://127.0.0.1:8000/api/v1
```
前端默认配置:
```text
Vite dev server: http://127.0.0.1:5173
API base: http://127.0.0.1:8000/api/v1
```
### 1.2 必传 Header
所有业务接口都必须携带以下 Header:
| Header | 类型 | 说明 |
|---|---:|---|
| `X-User-Id` | string | 宿主系统传入的用户标识。后端按该字段隔离会话、提交、评价和历史记录。 |
| `X-Entry-Scene` | string | 入口场景。Demo 前端默认 `vue_demo`。 |
可选 Header
| Header | 类型 | 说明 |
|---|---:|---|
| `X-Tenant-Id` | string | 租户、机构或项目 ID。第一版只透传和审计。 |
| `X-User-Role` | string | 用户角色。第一版只透传和审计。 |
| `X-Class-Id` | string | 班级或课程 ID。第一版只透传和审计。 |
| `X-Request-Id` | string | 请求链路 ID。前端有链路追踪需求时传入。 |
### 1.3 统一响应结构
除 SSE 流式接口外,后端统一返回:
```json
{
"code": "OK",
"message": "success",
"data": {}
}
```
前端处理规则:
- `code === "OK"`:读取 `data`
- `code !== "OK"`:展示 `message`,不要直接展示底层异常。
- HTTP 401 通常表示缺少 `X-User-Id`
- HTTP 404 通常表示资源不存在或不属于当前 `user_id`
- HTTP 400 通常表示当前状态不允许操作或入参不合法。
### 1.4 常见错误码
| code | 场景 | 前端处理 |
|---|---|---|
| `USER_ID_REQUIRED` | 缺少 `X-User-Id` | 回到入口设置页,提示填写 user_id。 |
| `CASE_NOT_FOUND` | 病例不存在、未启用或已删除 | 刷新病例列表。 |
| `CASE_DELETE_CONFIRM_REQUIRED` | 删除病例未传 `confirm=true` | 保持确认弹窗,不执行删除。 |
| `CASE_DELETE_TRAINING_DATA_EXISTS` | 病例存在训练数据但未允许删除训练数据 | 当前前端固定传 `delete_training_data=true`。 |
| `SESSION_NOT_FOUND` | 会话不存在或不属于当前用户 | 回到病例页重新创建会话。 |
| `SESSION_STATUS_INVALID` | 当前阶段不允许执行该操作 | 根据 `status` 禁用按钮并提示原因。 |
| `INQUIRY_REQUIRED` | 完成问诊前没有医生提问 | 提示至少完成一轮问诊。 |
| `DIAGNOSIS_REQUIRED` | 提交治疗前未提交诊断 | 引导到诊断表单。 |
| `TREATMENT_REQUIRED` | 生成评价前未提交治疗 | 引导到治疗表单。 |
| `ORDER_ITEM_NOT_FOUND` | 检查项不存在 | 刷新检查项列表。 |
| `LLM_CALL_TIMEOUT` | 非流式模型调用超时 | 提示重试。 |
| `LLM_STREAM_TIMEOUT` | 流式模型调用超时 | 结束 pending,提示重试或关闭流式。 |
| `LLM_STREAM_FAILED` | 流式模型调用失败 | 结束 pending,提示模型服务异常。 |
| `CASE_SQL_FILE_INVALID` | 上传文件不是 `.sql` | 提示选择 SQL 文件。 |
| `CASE_SQL_FILE_EMPTY` | SQL 文件为空 | 提示重新导出文件。 |
| `CASE_SQL_FILE_TOO_LARGE` | 文件超过 5MB | 提示压缩或拆分。 |
| `CASE_SQL_IMPORT_INVALID` | SQL 解析或字段映射失败 | 展示 `errors`,不允许确认导入。 |
## 2. 前端主流程
```text
入口页
-> GET /agent/hello
病例页
-> GET /cases
-> GET /cases/{case_id}
-> GET /cases/{case_id}/delete-preview
-> DELETE /cases/{case_id}
训练配置页
-> POST /sessions
Chat 页
-> POST /sessions/{session_id}/chat 或 /chat/stream
-> POST /sessions/{session_id}/hints
-> GET /sessions/{session_id}/order-items
-> POST /sessions/{session_id}/orders
提交页
-> POST /sessions/{session_id}/complete-inquiry
-> POST /sessions/{session_id}/diagnosis
-> POST /sessions/{session_id}/treatment
报告页
-> POST /sessions/{session_id}/evaluation
-> POST /evaluations/{evaluation_id}/export-pdf
历史页
-> GET /evaluations
-> GET /evaluations/{evaluation_id}
导入页
-> POST /imports/case-sql/preview
-> POST /imports/case-sql/apply
LLM 测试页
-> POST /llm/test/deepseek-fast
-> POST /llm/test/deepseek-reason
```
训练会话状态流转:
```text
inquiry -> diagnosis -> treatment -> evaluating -> evaluated
```
前端按钮启用规则:
| 阶段 | 允许操作 |
---|---|
| `inquiry` | 问诊、查看提示、申请检查、完成问诊 |
| `diagnosis` | 提交诊断 |
| `treatment` | 提交治疗方案 |
| `evaluating` | 生成评价报告 |
| `evaluated` | 查看报告、导出 PDF、查看历史 |
## 3. Agent Hello
### `GET /agent/hello`
用途:检查后端连接,返回当前用户上下文和 Demo 能力开关。
Response `data`
```json
{
"user": {
"user_id": "demo_user_001",
"tenant_id": null,
"role": null
},
"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-v4-pro",
"llm_reason_model": "deepseek-v4-pro",
"runtime_memory_backend": "redis"
}
}
```
前端展示说明:
- `stream_chat`:是否支持 SSE 流式问诊。
- `score_types`:评分输出类型,`percentage` 为百分制,`five_point` 为五分制。
- `pdf_export`:是否支持 PDF 导出。
- `knowledge_search`:是否已接入评分参考指南检索。
- `llm_mock_enabled`:是否强制使用 mock 模型。
- `llm_fallback_to_mock`:真实模型失败时是否回退 mock。
- `runtime_memory_backend`:短期记忆后端,通常为 `redis``memory`
## 4. 病例接口
### `GET /cases`
用途:获取病例列表。不会返回标准答案、隐藏病史、评分细则。
Query
| 参数 | 类型 | 必填 | 说明 |
|---|---:|---:|---|
| `department_id` | number | 否 | 科室筛选。 |
| `training_type` | string | 否 | 训练类别筛选:`case_analysis``diagnosis_treatment``consultation`。 |
| `mode` | string | 否 | 模式筛选:`practice``teaching`。 |
Response `data`
```json
{
"items": [
{
"id": 2,
"case_code": "SRC_2",
"department_id": 1,
"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
}
]
}
```
### `GET /cases/{case_id}`
用途:获取病例训练入口详情。不会返回标准诊断、标准治疗、隐藏病史。
Response `data`
```json
{
"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": ["lab", "imaging", "vital_sign"]
}
```
### `GET /cases/{case_id}/delete-preview`
用途:删除病例前统计影响范围。前端删除弹窗先调用该接口。
Response `data`
```json
{
"case_id": 2,
"case_title": "支气管肺炎 - 6岁男性患儿",
"can_delete": true,
"affected": {
"case_base": 1,
"traditional_case": 1,
"teaching_case": 0,
"scoring_rule": 1,
"case_exam_item": 6,
"training_session": 1,
"training_order": 6,
"training_submission": 1,
"training_record": 1
}
}
```
### `DELETE /cases/{case_id}`
用途:确认后删除病例及关联业务数据。审计日志只记录删除行为,不反删。
Request
```json
{
"confirm": true,
"delete_training_data": true
}
```
Response `data`
```json
{
"deleted": true,
"case_id": 2,
"deleted_counts": {
"training_order": 6,
"training_submission": 1,
"training_record": 1,
"training_session": 1,
"case_exam_item": 6,
"scoring_rule": 1,
"traditional_case": 1,
"teaching_case": 0,
"case_base": 1
}
}
```
前端交互规则:
- 删除按钮只放在病例详情区,不放在病例卡片上。
- 点击删除后先调用 `delete-preview`
- 弹窗展示 `affected`
- 用户输入固定确认文案后再调用 `DELETE`
- 删除成功后清空当前病例、会话、检查结果、报告缓存,并刷新 `/cases`
## 5. 会话与问诊接口
### `POST /sessions`
用途:创建训练会话,初始化短期 memory。
Request
```json
{
"case_id": 2,
"training_type": "diagnosis_treatment",
"mode": "practice",
"score_type": "percentage"
}
```
字段说明:
| 字段 | 允许值 | 说明 |
|---|---|---|
| `training_type` | `case_analysis``diagnosis_treatment``consultation` | 训练类别。 |
| `mode` | `practice``teaching` | 训练模式。后端兼容旧值 `novice`,会归一为 `practice`。 |
| `score_type` | `percentage``five_point` | 评分输出类型。 |
Response `data`
```json
{
"session_id": 10,
"session_code": "sess_20260528100000_abcd1234",
"status": "inquiry",
"patient_opening": "家长:医生,孩子发热咳嗽好几天了,昨天开始喘得厉害,精神也不太好。"
}
```
### `POST /sessions/{session_id}/chat`
用途:普通非流式问诊。
Request
```json
{
"message": "孩子发热几天了?最高体温多少?"
}
```
Response `data`
```json
{
"reply": "发热有4天了,最高烧到39度多,吃了退烧药能降下来,但过几个小时又会烧。",
"latency_ms": 2500,
"model": "deepseek-v4-pro",
"fallback_used": false
}
```
校验:
- 会话必须属于当前 `X-User-Id`
- 当前状态必须为 `inquiry`
- `message` 长度 1 到 2000。
### `POST /sessions/{session_id}/chat/stream`
用途:SSE 流式问诊。当前 Chat 页面优先使用该接口。
Request 同普通问诊。
SSE 事件:
```text
event: message_delta
data: {"delta":"发热有4天了,"}
event: message_done
data: {"latency_ms":3200,"first_token_ms":800,"model":"deepseek-v4-pro","fallback_used":false}
event: error
data: {"code":"LLM_STREAM_TIMEOUT","message":"AI 病人回复超时,请重试"}
```
前端处理规则:
- 收到 `message_delta`:追加到当前 AI 病人气泡。
- 收到 `message_done`:结束 pending,启用发送按钮。
- 收到 `error`:结束 pending,展示错误。
- fetch abort 或 reader done 但未收到 `message_done`:结束 pending,提示“AI 病人回复超时或失败,请重试”。
### `POST /sessions/{session_id}/hints`
用途:练习模式下手动生成提示。提示基于病例、当前对话和已申请检查动态生成。
Request
```json
{
"last_user_message": "孩子发热几天了?最高体温多少?",
"scope": "current_conversation"
}
```
Response `data`
```json
{
"hints": [
"可以继续追问最高体温、热型和退热药反应。"
],
"missing_dimensions": [
"既往史",
"严重程度评估"
],
"next_questions": [
"孩子以前有没有喘息、哮喘或过敏史?",
"现在血氧是多少?有没有呼吸困难?"
],
"recommended_orders": [
{
"item_code": "oxygen_saturation",
"reason": "用于判断低氧和病情严重程度"
}
]
}
```
前端处理规则:
- 提示不自动弹出。
- 只在练习模式中显示“查看提示”按钮。
- 练习模式中是否点击提示不影响评分链路。
- 教学互动模式当前不显示提示入口。
## 6. 检查/检验接口
### `GET /sessions/{session_id}/order-items`
用途:获取当前病例可申请的检查项目。只返回名称和类型,不返回检查结果。
Response `data`
```json
{
"items": [
{
"item_code": "complete_blood_count",
"item_name": "血常规",
"item_type": "lab"
}
]
}
```
### `POST /sessions/{session_id}/orders`
用途:申请检查/检验,结果必须来自数据库 `case_exam_item`,不允许 LLM 编造。
Request
```json
{
"item_code": "complete_blood_count"
}
```
Response `data`
```json
{
"item_code": "complete_blood_count",
"item_name": "血常规",
"item_type": "lab",
"result_text": "WBC 12.4×10^9/L,中性粒细胞72%。",
"result_structured": {},
"is_key": true,
"is_abnormal": true,
"context_written": true,
"already_ordered": false
}
```
幂等规则:
- 同一 `session_id + item_code` 只写入一次。
- 重复申请返回已有结果,`already_ordered=true`
- 重复申请不重复写入 runtime memory。
- 前端按 `item_code` 去重展示。
## 7. 阶段提交接口
### `POST /sessions/{session_id}/complete-inquiry`
用途:完成问诊,进入诊断阶段。
Response `data`
```json
{
"session_id": 10,
"status": "diagnosis"
}
```
校验:至少存在一轮医生提问。
### `POST /sessions/{session_id}/diagnosis`
用途:提交诊断。
Request
```json
{
"primary_diagnosis": "支气管肺炎",
"differential_diagnoses": ["毛细支气管炎", "支气管哮喘急性发作", "上呼吸道感染"],
"diagnosis_basis": "结合发热、咳嗽、喘息、肺部体征、炎症指标升高、胸片异常和血氧情况,符合儿童支气管肺炎表现。"
}
```
Response `data`
```json
{
"status": "treatment"
}
```
### `POST /sessions/{session_id}/treatment`
用途:提交治疗方案。
Request
```json
{
"treatment_principle": "抗感染、止咳平喘、改善氧合、严密观察病情变化。",
"treatment_measures": "根据病情选择抗感染治疗,必要时雾化吸入缓解喘息,监测体温、呼吸、血氧和精神反应。",
"risk_plan": "关注低氧、呼吸困难加重、持续高热、精神反应差、脱水等情况。",
"communication": "向家属说明肺炎病情、用药注意事项、观察指标和复诊/住院指征。",
"follow_up": "治疗后复查体温、呼吸、血氧和必要炎症指标,症状加重时及时就诊。"
}
```
Response `data`
```json
{
"status": "evaluating"
}
```
## 8. 评价与报告接口
### `POST /sessions/{session_id}/evaluation`
用途:生成 AI 评价报告。后端读取评分规则、知识检索结果、检查申请、问诊过程和提交内容后调用 Scoring Agent。
Request
```json
{
"score_type": "percentage"
}
```
Response `data`
```json
{
"evaluation_id": 1,
"score_type": "percentage",
"total_score": 82,
"dimension_scores": [
{
"dimension": "信息采集",
"score": 18,
"max_score": 25,
"comment": "已覆盖发热和咳嗽,但既往喘息史和家族过敏史追问不足。",
"evidence": ["询问发热天数和最高体温", "申请血常规和CRP"],
"deductions": ["未充分询问既往喘息史"],
"improvement": "补充既往史、过敏史、严重程度评估相关问题。"
}
],
"errors": [],
"improvement_plan": ["加强儿童肺炎严重程度评估训练。"],
"evidence_summary": ["检查结果已写入评分依据。"],
"guideline_refs": [],
"overall_comment": "诊断方向正确,检查利用和沟通细节仍需加强。"
}
```
评价完成后:
- 写入 `training_record`
- 释放当前会话短期 memory。
- 历史记录只保存评价报告,不长期保存完整聊天记录。
### `GET /evaluations`
用途:按当前 `X-User-Id` 查询历史评价。
Response `data`
```json
{
"items": [
{
"evaluation_id": 1,
"case_title": "支气管肺炎 - 6岁男性患儿",
"score_type": "percentage",
"total_score": 82,
"created_at": "2026-05-29T10:00:00",
"pdf_exported": true
}
]
}
```
### `GET /evaluations/{evaluation_id}`
用途:获取评价详情。只能读取当前用户自己的评价。
Response `data`:继承评价报告字段,并额外包含:
```json
{
"session_id": 10,
"case_id": 2,
"case_title": "支气管肺炎 - 6岁男性患儿",
"created_at": "2026-05-29T10:00:00",
"pdf_file_path": "storage/reports/training_record_1_percentage_xxx.pdf"
}
```
### `POST /evaluations/{evaluation_id}/export-pdf`
用途:生成本地 PDF 报告并保存文件路径。
Response `data`
```json
{
"export_id": 1,
"file_path": "storage/reports/training_record_1_percentage_xxx.pdf"
}
```
## 9. 病例 SQL 导入接口
### `POST /imports/case-sql/preview`
用途:上传接口解析后的 SQL 文件,只解析和校验,不写入数据库。
Request
```text
Content-Type: multipart/form-data
file: case.sql
```
Response `data`
```json
{
"file_name": "case.sql",
"encoding": "utf-8",
"tables": {
"case_base": 1,
"traditional_case": 1,
"teaching_case": 0,
"scoring_rule": 1
},
"can_import": true,
"warnings": [
"源 SQL 未包含 case_exam_item,导入器会按当前业务规则处理。"
],
"errors": [],
"preview_cases": [
{
"id": 2,
"title": "支气管肺炎 - 6岁男性患儿",
"case_type": "traditional",
"difficulty": "medium"
}
]
}
```
规则:
- 只识别 `case_base``traditional_case``teaching_case``scoring_rule`
- 不执行源 SQL 中的 `DROP TABLE``CREATE TABLE``ALTER TABLE``LOCK TABLES`
- 字段数量不匹配、JSON 损坏、字符串未闭合时返回 `can_import=false`
- `preview` 不写库。
### `POST /imports/case-sql/apply`
用途:确认导入,将 SQL 中的病例源表数据映射写入当前数据库。
Request 同预检接口。
Response `data`
```json
{
"imported": true,
"file_name": "case.sql",
"encoding": "utf-8",
"inserted_or_updated_cases": 1,
"imported_traditional_cases": 1,
"imported_teaching_cases": 0,
"imported_scoring_rules": 1,
"generated_exam_items": 6,
"warnings": []
}
```
前端处理:
- 先调用 `preview`,只有 `can_import=true` 才允许点击“确认导入”。
- 导入成功后刷新病例列表。
- 如果源 SQL 缺少 `case_exam_item`,后端会生成基础检查项,保证新病例可训练。
## 10. 知识检索接口
### `GET /knowledge/search`
用途:按科室、训练类别和关键词检索评分参考指南。第一版主要供评价链路和调试使用。
Query
| 参数 | 类型 | 必填 | 说明 |
|---|---:|---:|---|
| `department_id` | number | 是 | 科室 ID。 |
| `training_type` | string | 是 | 训练类别。 |
| `q` | string | 否 | 关键词,多个关键词用英文逗号分隔。 |
Response `data`
```json
{
"matched_chunks": [],
"source_refs": [],
"no_match": true
}
```
## 11. LLM 测试接口
### `POST /llm/test/deepseek-fast`
用途:测试快速模型耗时。
Request
```json
{
"message": "请用一句话说明医疗问诊训练 Demo 的用途。"
}
```
Response `data`
```json
{
"model": "deepseek-v4-pro",
"first_token_ms": null,
"total_latency_ms": 3000,
"stream": false,
"mock_mode": false,
"fallback_used": false,
"thinking_enabled": false,
"reasoning_effort": null
}
```
### `POST /llm/test/deepseek-reason`
用途:测试 reason 模型耗时。接口内部会优先按配置执行,流式不兼容时降级为非流式,不影响问诊主链路。
Request 同 Fast 测试。
Response 字段同 Fast 测试。
## 12. 前端字段枚举
| 字段 | 允许值 |
|---|---|
| `score_type` | `percentage``five_point` |
| `mode` | `practice``teaching` |
| `training_type` | `case_analysis``diagnosis_treatment``consultation` |
| `session.status` | `inquiry``diagnosis``treatment``evaluating``evaluated` |
| `patient.gender` | `male``female``null` |
## 13. 前端联调注意事项
1. 所有请求必须带 `X-User-Id`,否则后端返回 `USER_ID_REQUIRED`
2. 当前 Demo 不做登录注册,`user_id` 由宿主系统或测试页传入。
3. 病例详情不返回标准答案,避免前端泄露训练答案。
4. 检查结果只来自数据库,不来自 LLM。
5. 聊天记录只在 runtime memory 中短期保存,评价完成后释放。
6. 历史页读取的是 `training_record`,只展示完整训练结束后的评价。
7. 删除病例会级联删除该病例训练数据,前端必须保留二次确认。
8. `.env` 不进入 Git,前端不能写死 API Key。