# 后端 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 测试。