177 lines
23 KiB
Markdown
177 lines
23 KiB
Markdown
# Server AI/LLM 患者上下文与接口安全审计
|
||
|
||
审计日期:2026-08-21
|
||
审计范围:`server/app`、`server/config` 及与真实请求拼装直接相关的 `app/src/doctor_workstation`。
|
||
方法:只读静态审计;未连接生产数据库、未请求任何模型服务、未修改生产代码或测试。行号以本次工作区内容为准。
|
||
|
||
## 1. 结论
|
||
|
||
当前不存在一套被所有患者 AI 入口复用的统一上下文。实际有三套相互独立的患者上下文:
|
||
|
||
1. `DiagnosisAiLogic`:诊单 AI 助手、诊单智能分析、诊单 AI 报告共用 `buildCaseContext()`,但它只基于**当前一张诊单详情**,并不聚合医生备注文字、跟踪记录、视频转写、聊天、已开处方或处方病历快照。证据:`server/app/adminapi/logic/tcm/DiagnosisAiLogic.php:349`、`:492`、`:564` 均调用 `buildCaseContext()`;该方法仅遍历诊单及附件数量,见 `:875-947`。
|
||
2. `PatientAiReportLogic`:患者纵向 AI 报告明确与 `DiagnosisAiLogic` 独立,见 `server/app/adminapi/logic/tcm/PatientAiReportLogic.php:18-22`。它覆盖历次诊单、备注、日常记录、聊天及视频转写,是当前最完整的一套,但**仍不读取 `tcm_prescription` 已开处方、药味和 `case_record`**。
|
||
3. `DailyDietAiLogic`:患者端饮食建议另建一套上下文,只含姓名、性别、年龄和近 30 天血糖统计/近 7 条明细,见 `server/app/api/logic/tcm/DailyDietAiLogic.php:279-339`。
|
||
|
||
此外,桌面端给诊单 AI 助手补上下文时存在两个确定的契约错误:跟踪接口返回 `blood_records`,客户端却读取 `blood_sugar`;处方接口返回 `Prescription` 数据类,客户端只接受 `Mapping`。因此界面声称附带的“每日血糖/处方记录”在真实远端数据形态下会缺失。证据见第 4 节。
|
||
|
||
最高优先级安全问题是 `AiChatService` 在阻塞与流式请求中都关闭 TLS 证书和主机名校验,会让患者姓名、血糖及问题内容面临中间人窃取或篡改风险:`server/app/common/service/AiChatService.php:48-59`、`:144-160`。
|
||
|
||
## 2. AI/LLM 入口清单
|
||
|
||
| 入口 | 是否调用 LLM | 上下文构造 | 备注 |
|
||
|---|---:|---|---|
|
||
| `tcm.diagnosis/aiAssistant`、`aiAssistantStream` | 是 | `DiagnosisAiLogic::buildCaseContext()` + 客户端把额外资料塞入 `prompt` | 控制器入口:`server/app/adminapi/controller/tcm/DiagnosisController.php:913-1020`;阻塞/流式最终都用同一 prepared context:`server/app/adminapi/logic/tcm/DiagnosisAiLogic.php:316-397` |
|
||
| `tcm.diagnosis/aiAnalysis` | 是 | `DiagnosisAiLogic::buildCaseContext()` | `server/app/adminapi/controller/tcm/DiagnosisController.php:1034-1047`;`server/app/adminapi/logic/tcm/DiagnosisAiLogic.php:481-516` |
|
||
| `tcm.diagnosis/generateAiReports` | 是,每次固定生成 qwen/openai 两份 | `DiagnosisAiLogic::buildCaseContext()` | `server/app/adminapi/controller/tcm/DiagnosisController.php:1087-1098`;`server/app/adminapi/logic/tcm/DiagnosisAiLogic.php:553-618` |
|
||
| `patientAiReports` | 否,只读历史 | 不组装上游请求 | `server/app/adminapi/controller/tcm/DiagnosisController.php:1052-1064` |
|
||
| `generatePatientAiReport` | 是 | `PatientAiReportLogic::buildSourceSnapshot()` | `server/app/adminapi/controller/tcm/DiagnosisController.php:1069-1082`;全量聚合见 `server/app/adminapi/logic/tcm/PatientAiReportLogic.php:335-398` |
|
||
| `aiReports`、`editAiReport`、`aiPatientOptions` | 否,只读/编辑/选择 | 不调用模型 | 控制器见 `server/app/adminapi/controller/tcm/DiagnosisController.php:879-926`、`:1104-1115` |
|
||
| `dailyDietAiRecommend`、`dailyDietAiAsk` 及两条 Stream | 是 | `DailyDietAiLogic::buildPatientContext()` | `server/app/api/controller/TcmController.php:900-979`;上下文见 `server/app/api/logic/tcm/DailyDietAiLogic.php:279-339` |
|
||
| 处方库 `generateAiReports` | 是,但不是患者入口 | 处方库名称、类型、最多 80 味有效药材 | `server/app/adminapi/logic/tcm/PrescriptionLibraryAiLogic.php:145-222`、`:400-426`;不应强行复用患者上下文 |
|
||
| `DailyBloodCareAiLogic` | 当前不可达 | 计划复用 DailyDiet | 全仓只有类自身引用,没有控制器/路由;且调用不存在的公开方法 `DailyDietAiLogic::getPatientContext()`,见 `server/app/api/logic/tcm/DailyBloodCareAiLogic.php:161-176`,实际方法是私有 `buildPatientContext()`:`server/app/api/logic/tcm/DailyDietAiLogic.php:279` |
|
||
|
||
全仓 PHP 搜索只发现两种上游客户端:`DifyChatService` 和 `AiChatService`。其调用者分别是 `DiagnosisAiLogic`、`PatientAiReportLogic`、`PrescriptionLibraryAiLogic`,以及 `DailyDietAiLogic`、未接线的 `DailyBloodCareAiLogic`。
|
||
|
||
## 3. “患者发给 AI”时各类资料的真实覆盖
|
||
|
||
符号:✅ 文字进入上游;△ 只有部分字段/数量元数据/依赖客户端;❌ 未进入。
|
||
|
||
| 资料类型 | 诊单 AI 助手/分析/诊单报告 | 患者纵向 AI 报告 | 患者端饮食 AI |
|
||
|---|---|---|---|
|
||
| 患者基本信息 | △ 性别、年龄、身高、体重、婚姻等;服务端不主动发送姓名/电话/身份证 | ✅ 最新诊单基本信息;上游发送前 `_id`、`_name` 等键脱敏 | △ 姓名、性别、年龄;姓名被直接发送 |
|
||
| 当前/现病信息 | △ 当前诊单的症状、既往史、当前用药、舌脉、治则等;仓库字段漂移导致实际 `prescription`、`doctor_advice` 漏掉 | ✅ 所有授权诊单的广泛字段,包含诊单上的 `prescription`、`doctor_advice` | ❌ 除血糖外不含症状、当前用药、过敏、肝肾风险、医嘱等 |
|
||
| 每日视频面诊转写 | ❌ 服务端不查;桌面端最多把一条转写塞入 320 字上下文 | ✅ 所有授权诊单通话及转写段,段落可重建 transcript | ❌ |
|
||
| 病例与记录病历 | △ 当前诊单字段;不含处方 `case_record` | △ 历次诊单完整,但不含已开处方的 `case_record` | ❌ |
|
||
| 医生备注/跟踪备注 | ❌ 只聚合备注里的图片,不聚合备注文字 | ✅ 全部医生备注和跟踪备注 | ❌ |
|
||
| 舌苔/舌象 | △ 舌苔/舌象文字 + 图片数量;不读取图片内容 | △ 文字 + 附件数量元数据;明确禁止声称做视觉识别 | ❌ |
|
||
| 检查报告 | △ 只发送附件数量和“未提供附件内容” | △ 只发送附件数量元数据,无 OCR/报告正文 | ❌ |
|
||
| 日常记录 | ❌ 服务端不查;桌面端本想补血糖但字段名错误,饮食/运动也未拼入 | ✅ 血糖血压、饮食、运动全量 | △ 近 30 天血糖统计、近 7 条逐日明细;不含饮食/运动记录 |
|
||
| 既往处方 | ❌ `tcm_diagnosis.prescription` 被字段白名单漏掉;不查 `tcm_prescription`;桌面端又因类型判断漏掉远端处方 | △ 有历次诊单的自由文本 `prescription`,但没有正式 `tcm_prescription`、药味、服法、`case_record` | ❌ |
|
||
| IM/企微聊天 | ❌ | ✅ 腾讯 IM 与企微聊天全量 | ❌ |
|
||
|
||
### 3.1 诊单 AI 共用的是“窄上下文”
|
||
|
||
`DiagnosisAiLogic::CASE_FIELDS` 定义于 `server/app/adminapi/logic/tcm/DiagnosisAiLogic.php:210-252`。它会加入血压、空腹血糖、身高体重、舌象/报告附件数量,再遍历白名单字段,见 `:875-923`。`DiagnosisLogic::detail()` 本身只从医生备注聚合舌象与报告图片,未加载备注 `content`,见 `server/app/adminapi/logic/tcm/DiagnosisLogic.php:274-352` 与 `server/app/adminapi/logic/doctor/DoctorNoteLogic.php:140-164`。
|
||
|
||
字段白名单存在明确漂移:仓库诊单表包含 `prescription` 和 `doctor_advice`,见 `server/sql/tcm_diagnosis.sql:13-21`;新增/编辑验证器也使用这两个名称,见 `server/app/adminapi/validate/tcm/DiagnosisValidate.php:111`。但 AI 白名单只找 `prescription_opinion`、`prescription_advice`,没有 `prescription`、`doctor_advice`,见 `server/app/adminapi/logic/tcm/DiagnosisAiLogic.php:244-251`。相反,白名单中的 `chief_complaint`、`present_illness` 等名称在本仓库 SQL 定义中未找到,应以生产表 `SHOW COLUMNS` 再核实;无论如何,这已说明上下文与真实字段没有单一契约。
|
||
|
||
舌象和检查报告只有数量元数据:`server/app/adminapi/logic/tcm/DiagnosisAiLogic.php:905-916` 明确写入“未提供附件内容”。医生备注实际保存 `content`、`tongue_images`、`report_files`,见 `server/app/adminapi/logic/doctor/DoctorNoteLogic.php:12-68`,但诊单 AI 没有读取 `content`。
|
||
|
||
### 3.2 患者纵向报告覆盖广,但漏正式处方
|
||
|
||
纵向报告先按 `patient_id` 和 `MyPatientLogic` 数据域查出全部有效诊单,不设日期或条数上限:`server/app/adminapi/logic/tcm/PatientAiReportLogic.php:254-296`。随后按这些诊单 ID 全量查询:
|
||
|
||
- 医生备注、跟踪备注、血糖血压、饮食、运动:`server/app/adminapi/logic/tcm/PatientAiReportLogic.php:342-366`;
|
||
- 腾讯 IM、企微聊天、视频通话:`:367-380`;
|
||
- 视频转写段:`:382-392`,并在 `:450-472` 重建每次通话的 `transcript_text`;
|
||
- 汇总对象确实包含 `doctor_notes`、`tracking_notes`、三类 daily records、两类 chat records 和 `video_calls`:`:495-520`。
|
||
|
||
但 `buildSourceSnapshot()` 完全没有查询 `tcm_prescription`。正式处方模型把 `herbs`、`case_record`、`aux_usage` 声明为 JSON 字段:`server/app/common/model/tcm/Prescription.php:14-21`;`case_record` 又是明确的“详细病历(诊单快照 JSON)”:`server/database/migrations/2026_03_19_add_prescription_case_record.sql:2`。已有按诊单、逐条可见性过滤的安全入口可复用:`server/app/adminapi/logic/tcm/PrescriptionLogic.php:937-963`。
|
||
|
||
上游脱敏总体正确:患者纵向报告会把 ID、`*_id`、`*_name`、账户标识替换为脱敏占位,并把附件 URL 替换成数量,见 `server/app/adminapi/logic/tcm/PatientAiReportLogic.php:820-848`;手机号、身份证、邮箱、URL 正则见 `:851-860`。提示词也明确禁止对附件和视频画面作视觉推断,只能使用文字、转写和元数据,见 `:739-744`。
|
||
|
||
### 3.3 患者端饮食 AI 的上下文过窄且发送姓名
|
||
|
||
`DailyDietAiLogic` 查询近 30 天血糖、不限显式行数,筛近 7 天后只取 7 条明细,见 `server/app/api/logic/tcm/DailyDietAiLogic.php:291-326`。返回上下文只有 `patient_name`、`age`、`gender_text`、血糖明细和 7/30 天统计,见 `:328-337`。推荐与问答提示词都将患者姓名直接发往模型:`:508-519`、`:560-569`、`:706-712`、`:795-803`。
|
||
|
||
对于会给出个体化饮食建议的糖尿病入口,未纳入诊单中的当前用药、过敏、肾脏/肝脏状况、医生医嘱、现有饮食/运动记录和正式处方,可能使建议与真实禁忌或治疗方案冲突。
|
||
|
||
## 4. 桌面端实际问答链路的两个确定缺口
|
||
|
||
诊单 AI 助手不是只用服务端诊单摘要。桌面端先把额外资料拼成最多 320 字的“患者综合资料”,再与医生问题合成最多 500 字的 `prompt`:`app/src/doctor_workstation/ui/dialogs/ai_consult.py:2682-2694`、`:2835-2928`。服务端把整个 `prompt` 当作 `<USER_QUESTION>`,同时另放自己构造的 `<CASE_DATA>`:`server/app/adminapi/logic/tcm/DiagnosisAiLogic.php:1052-1078`。真实流式请求体只有 `id`、`prompt`、`task`:`app/src/doctor_workstation/services/repository.py:1373-1391`。
|
||
|
||
确定缺口如下:
|
||
|
||
1. **每日血糖字段名不一致。** 客户端先读取 `tracking["blood_sugar"]`,再找 `entries|records|items`:`app/src/doctor_workstation/ui/dialogs/ai_consult.py:2710-2742`。但服务端跟踪接口返回的是 `blood_records`、`diet_records`、`exercise_records`:`server/app/adminapi/logic/tcm/DiagnosisLogic.php:4473-4502`;仓库层原样返回:`app/src/doctor_workstation/services/repository.py:2170-2191`。结果是逐日血糖不会进入 prompt,通常只剩诊单上的一次空腹血糖。
|
||
2. **远端处方对象类型不一致。** 客户端只处理 `Mapping`,否则 `continue`:`app/src/doctor_workstation/ui/dialogs/ai_consult.py:2789-2806`。远端仓库却把列表解析为 `Prescription` 数据类:`app/src/doctor_workstation/services/repository.py:1643-1649`;该类定义于 `app/src/doctor_workstation/core/models.py:577-620`。结果是正式环境返回的处方不会进入 prompt。当前脚本 `app/scripts/check_ai_context.py:252-267` 用字典模拟处方,无法覆盖此契约错误。
|
||
|
||
另有三个设计缺口:
|
||
|
||
- 工作区已加载医生备注 `notes`,见 `app/src/doctor_workstation/ui/dialogs/ai_consult.py:3782-3801`,但调用 `build_patient_ai_context()` 时没有 notes 参数,见 `:3928-3951`。
|
||
- 上下文顺序固定为血糖、舌脉、视频、历史 AI 报告、处方,并共享 320 字预算,见 `:2855-2898`;后面的处方更容易被预算耗尽,没有逐段保底或截断清单。
|
||
- “历史AI报告”会作为新一轮模型输入,见 `:2769-2786`,存在把旧模型推断当作新证据反复强化的来源污染;应至少明确标记为模型生成内容并默认不作为临床事实。
|
||
|
||
## 5. 鉴权与接口安全
|
||
|
||
### 5.1 已有的有效控制
|
||
|
||
- 管理端经过登录、权限认证等中间件:`server/app/adminapi/config/route.php:15-27`。诊单 AI 逻辑又独立校验精确权限和 `MyPatientLogic::canAccessDiagnosis()`,再读详情:`server/app/adminapi/logic/tcm/DiagnosisAiLogic.php:842-869`;权限匹配是精确小写 URI 白名单:`:744-755`。
|
||
- 患者纵向报告同时要求查看权限和生成权限,见 `server/app/adminapi/logic/tcm/PatientAiReportLogic.php:126-149`,并通过 `MyPatientLogic::applyScope()` 限定医生/医助/部门数据域,且对不存在和越权返回同一错误,见 `:254-296`。
|
||
- 患者端饮食四个入口不在 `notNeedLogin` 中:`server/app/api/controller/TcmController.php:36-42`;每个入口在调用 AI 前执行 `ensurePatientOwnsDiagnosis()`,通过当前用户的 `diagnosis_view_records` 验证归属:`:495-525`、`:904-977`。
|
||
- `aiAnalysis`、患者报告读/生成使用严格字段白名单,拒绝客户端传 provider、BASE_URL、凭据和自由来源正文:`server/app/adminapi/validate/tcm/DiagnosisValidate.php:196-213`、`:269-327`。AI 助手 task 受枚举限制,prompt 最长 500:`:27-53`。
|
||
- `DifyChatService` 只允许 qwen/openai profile、超时 1–300 秒,且启用 TLS 校验:`server/app/common/service/DifyChatService.php:13-50`、`:305-307`、`:329-343`、`:422-440`。它也不会把供应商原始错误正文和密钥回传给控制器。
|
||
|
||
### 5.2 缺口与风险等级
|
||
|
||
#### P0:患者端 AI 上游关闭 TLS 校验
|
||
|
||
`AiChatService` 阻塞和流式路径均设置 `CURLOPT_SSL_VERIFYPEER=false`、`CURLOPT_SSL_VERIFYHOST=false`:`server/app/common/service/AiChatService.php:48-59`、`:144-160`。该服务承载姓名、血糖和自由问题,风险为患者隐私泄露、模型响应被篡改以及服务端密钥被中间人获取。
|
||
|
||
最小修复:两处改为 `true`/`2`;对 `base_url` 做与 `DifyChatService::isValidBaseUrl()` 同等级校验;生产只允许 HTTPS。若企业内网使用私有 CA,应配置 CA bundle,不能关闭校验。
|
||
|
||
#### P1:上下文不统一且诊单助手缺失关键临床资料
|
||
|
||
诊单助手、分析和诊单报告虽然内部共用一份 builder,但这份 builder 没有患者级聚合能力;纵向报告和饮食 AI 又各自维护字段。这会造成同一患者在三个入口得到基于不同事实集的答案。
|
||
|
||
最小修复:新增服务端只读 `PatientAiContextBuilder`,先接收已鉴权的 diagnosis/patient scope,再用 profile 决定范围:
|
||
|
||
- `assistant`:当前诊单 + 最近 30/90 天日常记录 + 最近 N 条医生/跟踪备注 + 最近 N 次已完成视频转写 + 最近 N 张可见正式处方;
|
||
- `diagnosis_report`:当前诊单的完整字段、备注、处方及附件元数据;
|
||
- `longitudinal_report`:现有全病程分片策略,但补正式处方和来源清单;
|
||
- `daily_diet`:只取饮食决策所需的最小临床子集,避免姓名。
|
||
|
||
所有 profile 应返回 `source_manifest`、每类记录数量、时间窗和 `truncated_sections`,使 UI 与审计日志能准确说明模型看到了什么。
|
||
|
||
#### P1:患者纵向报告无限查询/无限调用成本
|
||
|
||
诊单查询和九类来源查询均未设置日期或行数上限:`server/app/adminapi/logic/tcm/PatientAiReportLogic.php:270-283`、`:342-392`。代码会按 120,000 字节切片、逐片调用模型,再最多做 8 轮归并,确保不静默截断:`:44-51`、`:630-729`。完整性设计是优点,但攻击者或异常大患者记录可触发大量数据库内存和模型请求,当前 AI 路径也未发现用户级/患者级频率限制或并发去重。
|
||
|
||
最小修复:在查询层分页/游标读取;设置总来源字节、最大 chunk 数和最大上游调用数;生成任务使用 `(patient_id, model, source_hash)` 幂等锁;按管理员/患者限流。达到上限时返回显式“资料过多,需要缩小时间范围”,不可仍标记 `snapshot_complete=true`。
|
||
|
||
#### P1:原始患者快照落库,保留范围过大
|
||
|
||
上游发送前会脱敏,但落库的 `source_snapshot` 是脱敏前的 `$sourceJson`:快照先在 `server/app/adminapi/logic/tcm/PatientAiReportLogic.php:163-166` 构造,脱敏只在 `generateUpstreamReport()` 的 `:638-640` 执行,而原始 JSON 在 `:203-225` 直接写入 `source_snapshot`。它包含姓名、内部 ID、聊天/转写正文和附件 URL。历史接口有意识地不读取这个大字段,见 `:1012-1025`,但数据库静态泄露与过度保留风险仍存在。
|
||
|
||
最小修复:若无需法律审计复现,仅存脱敏快照 + 哈希 + source manifest;若必须保存原文,则字段级加密、独立访问权限、明确 TTL/删除策略,并记录谁读取过原始快照。
|
||
|
||
#### P1:患者端饮食 AI 暴露不必要姓名且临床约束不足
|
||
|
||
姓名对 GI 推荐没有必要,但当前四套 prompt 均发送姓名,证据见第 3.3 节。与此同时,可能直接影响饮食安全的过敏、肾病/肾功能、当前用药和医生医嘱未进入模型。
|
||
|
||
最小修复:删除 `patient_name`;加入最小化的风险字段并设置“若过敏/肝肾/用药信息缺失,不给个体化禁忌结论”;将问答文本放进明确的不可信数据边界,防止把用户问题中的指令当系统指令。
|
||
|
||
#### P2:上游 URL/响应大小与中间件防线仍可加强
|
||
|
||
- `DifyChatService` 的 URL 校验允许 `http` 和任意主机/IP:`server/app/common/service/DifyChatService.php:287-303`。虽然 URL 只来自服务端配置,不是请求参数,因此不是直接请求型 SSRF,但生产误配会把患者资料发往明文或非批准主机。建议 HTTPS-only + 域名 allowlist;若确有内网模型,使用独立显式配置开关并拒绝重定向到私网/环回地址。
|
||
- 两个上游客户端都没有在传输阶段设置响应体最大字节数。`DifyChatService` 阻塞请求先整段 `RETURNTRANSFER`:`:329-354`,解析器的 32/64 KiB 限制发生在收完之后;流式路径也持续累积内容。建议在 write callback 中按字节中止,并区分“响应过大”错误。
|
||
- `AuthMiddleware` 对不在全局菜单 URI 集合中的路由直接放行:`server/app/adminapi/http/middleware/AuthMiddleware.php:73-83`。当前诊单/患者 AI 逻辑自带权限校验,故没有形成直接越权;但新 AI 路由若忘记逻辑层校验会失守。最小修复是对 `/ai*` 或配置的敏感控制器 fail-closed,并保留逻辑层二次校验。
|
||
|
||
#### P2:未接线的 DailyBloodCare 代码会在接线后立即失败
|
||
|
||
`DailyBloodCareAiLogic` 调用不存在的 `DailyDietAiLogic::getPatientContext()`,且自身没有患者归属校验。当前无路由所以不构成线上入口;未来若启用,应先改为共享的公开 context builder,并在控制器进入逻辑前复用 `ensurePatientOwnsDiagnosis()`。不要只修方法名后直接暴露。
|
||
|
||
## 6. 字段、时间范围和条数限制清单
|
||
|
||
| 路径 | 当前限制 | 审计判断 |
|
||
|---|---|---|
|
||
| 诊单 AI 助手 | task 枚举;医生 prompt 500 字;上下文字段逐项最多 800 字 | 输入边界明确,但 500 字中还混入客户端上下文,医生问题会被截短;字段截断无 manifest。证据:`server/app/adminapi/validate/tcm/DiagnosisValidate.php:49-53`、`server/app/adminapi/logic/tcm/DiagnosisAiLogic.php:1653-1661` |
|
||
| 诊单 AI 分析 | case 最多 16,000 字,响应最多 32,768 bytes,建议字段/风险条数均有限 | 输出校验较好;仍只看当前诊单且截断不告知模型/UI。证据:`server/app/adminapi/logic/tcm/DiagnosisAiLogic.php:21-58`、`:1025-1048` |
|
||
| 患者纵向报告 | 数据库查询全历史、无行数上限;每片 120,000 bytes;综合 180,000 bytes;最多 8 轮归并;响应 65,536 bytes | 不静默漏源,但数据库内存、延迟和费用无硬上限。证据:`server/app/adminapi/logic/tcm/PatientAiReportLogic.php:36-51`、`:630-729` |
|
||
| 患者端饮食推荐 | 血糖 30 天;近 7 天最多 7 条明细;问答 80 字;缓存推荐 1 天、问答 1 小时 | 时间窗合理,但 `refresh=1` 可反复绕过推荐缓存,未见限流;30 天查询无显式行数 cap。证据:`server/app/api/logic/tcm/DailyDietAiLogic.php:28-109`、`:291-326` |
|
||
| 处方库 AI | 最多 80 味有效药材;待生成列表最多 500 条 | 属于非患者入口,边界基本清晰。证据:`server/app/adminapi/logic/tcm/PrescriptionLibraryAiLogic.php:83-120`、`:604-625` |
|
||
| Dify 上游 | profile 仅 qwen/openai;timeout 1–300 秒 | 正向控制;缺传输级响应大小上限与生产域名 allowlist。证据:`server/app/common/service/DifyChatService.php:16-20`、`:305-307` |
|
||
|
||
## 7. 建议的最小修复顺序
|
||
|
||
1. **当天可改**:恢复 `AiChatService` TLS 校验;饮食 prompt 去掉患者姓名;为上游响应设置硬字节上限。
|
||
2. **第一批契约修复**:桌面端读取 `blood_records`;处方 helper 同时支持 `Prescription` 对象;把 notes 显式加入或由服务端统一拼装;补覆盖真实远端类型/字段名的测试。
|
||
3. **服务端临床完整性**:给 `DiagnosisAiLogic::CASE_FIELDS` 补真实 `prescription`、`doctor_advice`;接入最近医生/跟踪备注、视频转写、日常记录和经 `canViewPrescription()` 过滤的正式处方;不要把附件 URL 当内容,若有 OCR 则以独立、带来源的文本字段加入。
|
||
4. **统一 builder**:让 DiagnosisAi、PatientAiReport、DailyDiet 复用同一数据访问/脱敏/来源清单层,只在 profile 的时间窗和字段最小化上不同。
|
||
5. **资源与审计**:患者报告增加查询/调用/总字节上限、幂等锁和限流;调整原始 `source_snapshot` 的加密与保留策略;所有结果返回上下文版本、来源数量和截断信息。
|
||
|
||
## 8. 最终判断
|
||
|
||
目前“诊单 AI 助手会自动拿到患者完整资料”的说法不成立。服务端只自动拿当前诊单摘要;桌面端确实尝试补视频、血糖、处方等,但 320 字预算及两个契约错误使其远达不到完整上下文。患者纵向 AI 报告是唯一真正覆盖视频转写、备注、聊天和日常记录的入口,却没有正式处方/处方病历,并且全历史、无限行的实现带来显著资源与隐私保留风险。
|
||
|
||
建议把“统一上下文”定义为**统一的数据访问、鉴权、脱敏、来源清单和截断协议**,而不是要求所有入口发送同样多的数据。饮食问答应最小化;纵向报告可更完整;诊单助手应在可控时间窗内补齐临床关键项。这样才能同时解决答案一致性、隐私最小化和成本边界。
|