chore: initialize medical consultation agent demo
This commit is contained in:
@@ -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 测试。
|
||||
Reference in New Issue
Block a user