chore: initialize medical consultation agent demo

This commit is contained in:
刘金宝
2026-06-01 09:25:26 +08:00
commit a7733243b2
139 changed files with 15764 additions and 0 deletions
+672
View File
@@ -0,0 +1,672 @@
# 后端 API 对接文档
## 1. 通用约定
Base URL
```text
http://127.0.0.1:8000/api/v1
```
必传 Header
| Header | 说明 |
|---|---|
| `X-User-Id` | 宿主系统传入的用户 ID,所有业务隔离依据 |
| `X-Entry-Scene` | 入口场景,前端 Demo 默认 `vue_demo` |
可传 Header
| Header | 说明 |
|---|---|
| `X-Tenant-Id` | 宿主系统租户/机构 ID |
| `X-Class-Id` | 教学班级 ID |
| `X-Role` | 用户角色 |
统一响应:
```json
{
"code": "OK",
"message": "success",
"data": {}
}
```
常见错误码:
| code | 含义 |
|---|---|
| `USER_ID_REQUIRED` | 缺少 `X-User-Id` |
| `CASE_NOT_FOUND` | 病例不存在或未启用 |
| `SESSION_NOT_FOUND` | 会话不存在或不属于当前用户 |
| `SESSION_STATUS_INVALID` | 当前阶段不允许该操作 |
| `INQUIRY_REQUIRED` | 完成问诊前至少需要一轮医生提问 |
| `DIAGNOSIS_REQUIRED` | 提交治疗前需要先提交诊断 |
| `TREATMENT_REQUIRED` | 生成评价前需要先提交治疗 |
| `ORDER_ITEM_NOT_FOUND` | 检查项目不存在 |
| `LLM_CALL_TIMEOUT` | LLM 普通调用超时 |
| `LLM_STREAM_TIMEOUT` | LLM 流式调用超时 |
| `LLM_STREAM_FAILED` | LLM 流式调用失败 |
| `CASE_SQL_FILE_INVALID` | 上传文件不是 `.sql` |
| `CASE_SQL_FILE_EMPTY` | 上传 SQL 文件为空 |
| `CASE_SQL_FILE_TOO_LARGE` | 上传 SQL 文件超过 5MB |
| `CASE_SQL_IMPORT_INVALID` | SQL 解析或字段映射校验失败 |
## 2. Agent Hello
| 项 | 内容 |
|---|---|
| Method | `GET` |
| Path | `/agent/hello` |
| Router | `agent.hello` |
| 用途 | 校验后端连接,返回当前用户上下文和功能开关 |
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_fallback_to_mock": false
}
}
```
## 3. 病例列表
| 项 | 内容 |
|---|---|
| Method | `GET` |
| Path | `/cases` |
| Router | `cases.list_cases` |
| Service | `CaseService.list_cases` |
| 表 | `case_base` |
Query
| 参数 | 说明 |
|---|---|
| `department_id` | 科室筛选 |
| `training_type` | 训练类型筛选 |
| `mode` | `practice``teaching` |
Response `data`
```json
{
"items": [
{
"id": 1,
"title": "支气管肺炎 - 6岁男性患儿",
"department_id": 1,
"department_name": "儿科",
"difficulty": "medium",
"chief_complaint": "发热、咳嗽4天,喘息1天。",
"patient_age": 6,
"patient_gender": "male",
"training_type": "case_analysis",
"supported_modes": ["practice", "teaching"],
"has_teaching_video": false,
"has_knowledge_points": true
}
]
}
```
## 4. 病例详情
| 项 | 内容 |
|---|---|
| Method | `GET` |
| Path | `/cases/{case_id}` |
| Router | `cases.get_case_detail` |
| Service | `CaseService.get_case_detail` |
| 表 | `case_base``traditional_case``teaching_case``case_exam_item` |
Response `data`
```json
{
"id": 1,
"title": "支气管肺炎 - 6岁男性患儿",
"department_name": "儿科",
"chief_complaint": "发热、咳嗽4天,喘息1天。",
"patient_age": 6,
"patient_gender": "male",
"supported_modes": ["practice", "teaching"],
"exam_item_count": 5,
"has_knowledge_points": true
}
```
病例详情不返回标准答案、隐藏病史和完整评分细则。
## 5. 创建训练会话
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions` |
| Router | `sessions.create_session` |
| Service | `SessionService.create_session` |
| 表 | `training_session` |
Request
```json
{
"case_id": 1,
"training_type": "case_analysis",
"mode": "practice",
"score_type": "percentage"
}
```
Response `data`
```json
{
"session_id": 10,
"session_code": "sess_20260528100000_abcd1234",
"status": "inquiry",
"patient_opening": "家长:医生,孩子发烧咳嗽好几天了..."
}
```
校验:
- `case_id` 必须存在并启用。
- `mode` 当前使用 `practice``teaching`
- 创建会话时初始化短期 memory。
## 6. 普通问诊
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/chat` |
| Router | `sessions.chat` |
| Service | `SessionService.chat` |
| Agent | `PatientAgent.reply` |
Request
```json
{
"message": "孩子发热几天了?最高体温多少?"
}
```
Response `data`
```json
{
"reply": "发热有4天了,最高烧到39度多。",
"latency_ms": 2500,
"model": "deepseek-v4-pro",
"fallback_used": false
}
```
校验:会话必须属于当前 `X-User-Id`,且状态为 `inquiry`
## 7. 流式问诊
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/chat/stream` |
| Router | `sessions.chat_stream` |
| Service | `SessionService.stream_chat` |
| Agent | `PatientAgent.stream_reply` |
Request 同普通问诊。
SSE 事件:
```text
event: message_delta
data: {"delta":"发热有"}
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_done``error` 后必须结束 pending。
## 8. 查看提示
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/hints` |
| Router | `sessions.generate_hints` |
| Service | `SessionService.generate_hints` |
| Agent | `HintAgent.generate` |
Request
```json
{
"last_user_message": "孩子发热几天了?",
"scope": "current_conversation"
}
```
Response `data`
```json
{
"hints": ["可以继续追问最高体温、热型和退热药反应。"],
"missing_dimensions": ["既往史", "严重程度评估"],
"next_questions": ["孩子以前有没有喘息或哮喘史?"],
"recommended_orders": [
{"item_code": "oxygen_saturation", "reason": "用于判断缺氧和病情严重程度"}
]
}
```
校验:当前仅允许 `practice` 模式且会话状态为 `inquiry`
## 9. 检查项目列表
| 项 | 内容 |
|---|---|
| Method | `GET` |
| Path | `/sessions/{session_id}/order-items` |
| Router | `sessions.list_order_items` |
| Service | `OrderService.list_order_items` |
| 表 | `case_exam_item` |
Response `data`
```json
{
"items": [
{
"item_code": "complete_blood_count",
"item_name": "血常规",
"item_type": "lab",
"category": "实验室检查",
"ordered": false
}
]
}
```
## 10. 申请检查
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/orders` |
| Router | `sessions.create_order` |
| Service | `OrderService.create_order` |
| 表 | `training_order` |
Request
```json
{
"item_code": "complete_blood_count"
}
```
Response `data`
```json
{
"order_id": 1,
"item_code": "complete_blood_count",
"item_name": "血常规",
"result_text": "WBC 12.4×10^9/L,中性粒细胞72%。",
"result_structured": {},
"already_ordered": false,
"context_written": true
}
```
同一 `session_id + item_code` 幂等,重复申请返回已有记录,不重复写入 memory。
## 11. 完成问诊
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/complete-inquiry` |
| Router | `sessions.complete_inquiry` |
| Service | `SessionService.complete_inquiry` |
Response `data`
```json
{
"session_id": 10,
"status": "diagnosis"
}
```
校验:至少存在一轮医生提问。
## 12. 提交诊断
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/diagnosis` |
| Router | `sessions.submit_diagnosis` |
| Service | `SessionService.submit_diagnosis` |
| 表 | `training_submission` |
Request
```json
{
"primary_diagnosis": "支气管肺炎",
"differential_diagnoses": ["毛细支气管炎", "哮喘急性发作"],
"diagnosis_basis": "发热、咳嗽、喘息,结合肺部体征、炎症指标、胸片和血氧情况。"
}
```
Response `data`
```json
{
"status": "treatment"
}
```
## 13. 提交治疗
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/treatment` |
| Router | `sessions.submit_treatment` |
| Service | `SessionService.submit_treatment` |
| 表 | `training_submission` |
Request
```json
{
"treatment_principle": "抗感染、平喘、改善氧合、严密观察。",
"treatment_measures": "根据病情选择抗感染治疗,必要时雾化吸入,监测体温、呼吸和血氧。",
"risk_plan": "关注低氧、呼吸困难加重、持续高热、精神反应差。",
"communication": "向家属说明病情、用药注意事项和复诊/住院指征。",
"follow_up": "治疗后复查体温、呼吸、血氧和炎症指标。"
}
```
Response `data`
```json
{
"status": "evaluating"
}
```
## 14. 生成评价
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/evaluation` |
| Router | `sessions.create_evaluation` |
| Service | `EvaluationService.create_evaluation` |
| Agent | `ScoringAgent.score``ReportAgent` |
| 表 | `scoring_rule``knowledge_chunks``training_record` |
Request
```json
{
"score_type": "percentage"
}
```
Response `data`
```json
{
"evaluation_id": 1,
"score_type": "percentage",
"total_score": 82,
"dimension_scores": [],
"errors": [],
"improvement_plan": [],
"evidence_summary": [],
"guideline_refs": [],
"overall_comment": "完成了主要诊断链路,但检查利用和沟通细节仍需加强。"
}
```
评价完成后释放短期 memory,并写入 `training_record`
## 15. 历史评价列表
| 项 | 内容 |
|---|---|
| Method | `GET` |
| Path | `/evaluations` |
| Router | `evaluations.list_evaluations` |
| Service | `EvaluationService.list_history` |
| 表 | `training_record` |
Response `data`
```json
{
"items": [
{
"evaluation_id": 1,
"case_title": "支气管肺炎 - 6岁男性患儿",
"score_type": "percentage",
"total_score": 82,
"created_at": "2026-05-28T10:00:00",
"pdf_exported": true
}
]
}
```
## 16. 评价详情
| 项 | 内容 |
|---|---|
| Method | `GET` |
| Path | `/evaluations/{evaluation_id}` |
| Router | `evaluations.get_evaluation_detail` |
| Service | `EvaluationService.get_detail` |
| 表 | `training_record` |
Response `data` 为完整评价报告,并包含 `session_id``case_id``case_title``created_at``pdf_file_path`
## 17. 导出 PDF
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/evaluations/{evaluation_id}/export-pdf` |
| Router | `evaluations.export_pdf` |
| Service | `PdfExportService.export` |
| 表 | `training_record` |
Response `data`
```json
{
"export_id": 1,
"file_path": "storage/reports/training_record_1_percentage_xxx.pdf"
}
```
## 18. 知识检索
| 项 | 内容 |
|---|---|
| Method | `GET` |
| Path | `/knowledge/search` |
| Router | `knowledge.search_knowledge` |
| Service | `KnowledgeService.search_guidelines` |
| 表 | `knowledge_sources``knowledge_documents``knowledge_chunks` |
Query
| 参数 | 说明 |
|---|---|
| `department_id` | 科室 ID |
| `training_type` | 训练类型 |
| `q` | 关键词,逗号分隔 |
用途:评价生成前检索科室/类型下的评分指南。前端第一版不需要主动调用。
## 19. 病例 SQL 导入预检
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/imports/case-sql/preview` |
| Router | `imports.preview_case_sql` |
| Service | `CaseSqlImportService.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": 1,
"scoring_rule": 5
},
"can_import": true,
"warnings": [],
"errors": [],
"preview_cases": [
{
"id": 1001,
"title": "儿童支气管肺炎",
"case_type": "diagnosis_treatment",
"difficulty": "medium"
}
]
}
```
校验逻辑:
- 必须携带 `X-User-Id`
- 文件后缀必须为 `.sql`,大小不超过 5MB。
- 只解析源 SQL 中的 `case_base``traditional_case``teaching_case``scoring_rule`
- 预检接口不执行源 SQL,不写入数据库。
- 源 SQL 中出现 `DROP TABLE``CREATE TABLE``ALTER TABLE` 等语句时只作为警告展示,导入器不会执行这些语句。
- 字段数量不匹配、非法字符串、JSON 字段非法时返回 `can_import=false``errors`
## 20. 病例 SQL 确认导入
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/imports/case-sql/apply` |
| Router | `imports.apply_case_sql` |
| Service | `CaseSqlImportService.apply` |
| 用途 | 预检通过后,将 SQL 中的病例源表数据映射写入当前数据库 |
| 表 | `case_base``traditional_case``teaching_case``scoring_rule``case_exam_item` |
Request
```text
Content-Type: multipart/form-data
file: case.sql
```
Response `data`
```json
{
"imported": true,
"file_name": "case.sql",
"encoding": "utf-8",
"inserted_or_updated_cases": 1,
"imported_traditional_cases": 1,
"imported_teaching_cases": 1,
"imported_scoring_rules": 5,
"generated_exam_items": 4,
"warnings": []
}
```
校验逻辑:
- 必须携带 `X-User-Id`
- 导入过程使用事务,任意表映射失败则整体回滚。
- `case_base``id` 更新或插入。
- `traditional_case``teaching_case``case_id` 更新或插入。
- `scoring_rule``case_id` 先删除旧规则再写入源规则。
- 源 SQL 缺少 `case_exam_item` 时,由后端根据病例文本生成基础检查项目,保障问诊训练链路可继续使用。
- 导入成功后前端刷新 `/cases`,新增病例即可进入训练。
## 21. LLM Fast 测试
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/llm/test/deepseek-fast` |
| Router | `llm_test.test_deepseek_fast` |
| Service | `OpenAICompatibleLLMClient.chat` |
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
}
```
## 22. LLM Reason 测试
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/llm/test/deepseek-reason` |
| Router | `llm_test.test_deepseek_reason` |
| Service | `OpenAICompatibleLLMClient.stream_chat`,流式不兼容时降级到 `chat` |
Request 同 Fast 测试。Response 字段同 Fast 测试。