Files
fastapi/docs/03_api_design.md
T
2026-06-05 12:57:02 +08:00

1095 lines
32 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` 分支当前版本
> 本文档以当前真实代码行为为准,用于正式 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");
}
```
## 3. 当前接口总览
### 3.0 训练页面交付接口表
以下表格为前端训练页面当前交付接口。`url` 使用公网网关前缀,前端实际调用时统一在 `api` 前拼接 `http://8.160.178.88/fastapi`
| 模块 | 接口名称 | url | api | methods | params | response | 说明 |
|---|---|---|---|---|---|---|---|
| 训练页面 | 推荐配置信息 | `http://8.160.178.88/fastapi/api/v1/training-config/recommended` | `/api/v1/training-config/recommended` | `GET` | Query`case_id` | `recommended``recommended_labels``options` | 初始化训练页病人配置,默认选中门诊、青年、高等教育、平和。 |
| 训练页面 | 训练配置信息 | `http://8.160.178.88/fastapi/api/v1/training-config/options` | `/api/v1/training-config/options` | `GET` | Query`case_id` | `recommended``recommended_labels``options` | 返回自定义配置全部可选项,用于前端渲染就诊环境、年龄段、文化程度、性格。 |
| 训练页面 | 新建会话 | `http://8.160.178.88/fastapi/api/v1/sessions` | `/api/v1/sessions` | `POST` | Body`case_id``training_type``mode``score_type``patient_config` | `session_id``session_code``status``patient_opening``patient_config` | 创建训练会话并把病人初始化配置写入会话 metadata。 |
| 训练页面 | 流式会话 | `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``message_done``error` | 医学生问诊,AI 病人按病例和配置流式回复。 |
| 训练页面 | 练习提示 | `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` | SSE`hint_delta``hint_done``error` | 返回一句话形式的练习提示,前端点击后按需展示。旧 JSON 接口 `/hints` 保留兼容。 |
| 训练页面 | 体格检查列表获取 | `http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/physical-exams` | `/api/v1/sessions/{session_id}/physical-exams` | `GET` | Path`session_id` | `items[]` | 从当前病例检查项中筛选体格检查类项目。暂无独立表,复用 `case_exam_item`。 |
| 训练页面 | 辅助检查列表获取 | `http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/auxiliary-exams` | `/api/v1/sessions/{session_id}/auxiliary-exams` | `GET` | Path`session_id` | `items[]` | 从当前病例检查项中筛选辅助检查类项目。暂无独立表,复用 `case_exam_item`。 |
| 训练页面 | 体格检查某项结果 | `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` | 检查结果结构 | 返回数据库固定检查结果,并写入本次会话上下文。 |
| 训练页面 | 辅助检查某项结果 | `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` | 检查结果结构 | 返回数据库固定检查结果,并写入本次会话上下文。 |
| 训练页面 | 完成问诊 | `http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/complete-inquiry` | `/api/v1/sessions/{session_id}/complete-inquiry` | `POST` | Path`session_id` | `session_id``status` | 从问诊阶段进入诊断阶段。 |
| 训练页面 | 提交诊断 | `http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/diagnosis` | `/api/v1/sessions/{session_id}/diagnosis` | `POST` | Path`session_id`Body:诊断内容 | `status` | 保存主要诊断、鉴别诊断和诊断依据。 |
| 训练页面 | 提交治疗 | `http://8.160.178.88/fastapi/api/v1/sessions/{session_id}/treatment` | `/api/v1/sessions/{session_id}/treatment` | `POST` | Path`session_id`Body:治疗内容 | `status` | 保存治疗原则、措施、风险预案、沟通和随访。 |
| 训练页面 | 生成评价 | `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` | 评价报告结构 | 调用评分 Agent,写入 `training_record``training_score_detail`。 |
| 训练页面 | 获取评价(详情) | `http://8.160.178.88/fastapi/api/v1/evaluations/{evaluation_id}` | `/api/v1/evaluations/{evaluation_id}` | `GET` | Path`evaluation_id` | 评价详情和评分明细 | 按当前 token 对应用户隔离查询。 |
| 训练页面 | 下载 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` | `export_id``file_path` | 生成本地 PDF 报告路径。 |
| 训练页面 | 历史评价列表 | `http://8.160.178.88/fastapi/api/v1/evaluations` | `/api/v1/evaluations` | `GET` | 无 | `items[]` | 查询当前 token 对应用户的完整训练评价历史。 |
### 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` | `/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
```
会话状态流转:
```text
inquiry -> diagnosis -> treatment -> evaluating -> completed
```
| 状态 | 允许操作 |
|---|---|
| `inquiry` | 问诊、提示、检查申请、完成问诊。 |
| `diagnosis` | 提交诊断、继续申请检查。 |
| `treatment` | 提交治疗、继续申请检查。 |
| `evaluating` | 生成评价。 |
| `completed` | 查看评价、导出 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"]
}
}
```
## 7. 创建训练会话
### 7.1 推荐配置信息
```http
GET /api/v1/training-config/recommended?case_id=2
```
响应:
```json
{
"code": "OK",
"message": "success",
"data": {
"case_id": 2,
"recommended": {
"visit_environment": "outpatient",
"age_group": "youth",
"education_level": "higher",
"personality": "calm"
},
"recommended_labels": {
"visit_environment": "门诊",
"age_group": "青年",
"education_level": "高等教育",
"personality": "平和"
},
"options": {
"visit_environment": [
{"value": "outpatient", "label": "门诊", "description": "适合常规问诊训练"}
],
"age_group": [],
"education_level": [],
"personality": []
}
}
}
```
### 7.2 训练配置信息
```http
GET /api/v1/training-config/options?case_id=2
```
该接口返回和推荐配置信息相同的结构,前端使用 `options` 渲染自定义配置按钮,使用 `recommended` 初始化默认选中项。
### 7.3 新建会话
```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": "youth",
"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` | 就诊环境。 |
| `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`
响应:
```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": "youth",
"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 练习提示
#### 8.3.1 JSON 兼容接口
```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` 状态可调用。
- 前端默认不自动展示提示,由用户点击后调用。
#### 8.3.2 SSE 一句话提示
```http
POST /api/v1/sessions/{session_id}/hints/stream
Content-Type: application/json
Accept: text/event-stream
```
请求:
```json
{
"last_user_message": "孩子发热几天了?最高体温多少?",
"scope": "current_conversation"
}
```
事件格式:
```text
event: hint_delta
data: {"delta":"当前可补充既往史、严重程度评估;"}
event: hint_done
data: {"latency_ms":1200}
event: error
data: {"code":"HINT_STREAM_FAILED","message":"练习提示生成失败,请稍后重试"}
```
前端规则:
- 该接口用于训练页面“查看提示”的新交互。
- 返回内容为一句话形式,适合在提示区域流式展示。
- 如果收到 `error`,前端结束 loading 并展示错误。
- 旧 JSON 接口 `/hints` 保留,用于需要结构化缺失维度和推荐检查的页面。
## 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
GET /api/v1/sessions/{session_id}/physical-exams
```
响应结构与 `/order-items` 相同。当前没有独立体格检查表,后端按 `item_type``category``item_name``case_exam_item` 中识别体格检查类项目。
### 9.3 获取辅助检查列表
```http
GET /api/v1/sessions/{session_id}/auxiliary-exams
```
响应结构与 `/order-items` 相同。当前没有独立辅助检查表,非体格检查类项目均归入辅助检查。
### 9.4 申请检查
```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` 状态执行。
### 9.5 获取体格检查某项结果
```http
POST /api/v1/sessions/{session_id}/physical-exams/{item_code}
```
响应结构与 `/orders` 相同。该接口用于前端按“体格检查”分区调用,内部仍复用检查申请逻辑。
### 9.6 获取辅助检查某项结果
```http
POST /api/v1/sessions/{session_id}/auxiliary-exams/{item_code}
```
响应结构与 `/orders` 相同。该接口用于前端按“辅助检查”分区调用,内部仍复用检查申请逻辑。
## 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 并返回服务器内部文件路径,没有提供浏览器文件下载流。前端功能测试阶段展示“导出成功”和文件路径即可。正式下载接口需要后续增加受鉴权保护的文件响应接口。
## 12. 调试接口
### 12.1 知识检索
```http
GET /api/v1/knowledge/search?department_id=2&training_type=diagnosis_treatment&q=,
```
### 12.2 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 生成失败。 |
## 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,确认接口返回文件路径。
## 16. 当前前端必须了解的限制
- 无会话详情和聊天恢复接口,页面刷新后无法恢复短期问诊。
- 问诊聊天只保存在 Redis 短期 memory 中,评价完成后释放。
- PDF 导出当前只返回服务器文件路径,不直接下载文件。
- 病例库为只读数据源;新增、解析、修改和删除由外部病例管理系统完成。
- 所有检查项必须从 `/order-items` 动态读取,不能写死。
- 正式训练模式只有 `practice``teaching``novice` 仅为旧接口兼容值。