Files
zyt/app/research/ai_server_context_audit_20260821.md
2026-08-22 08:51:35 +08:00

177 lines
23 KiB
Markdown
Raw Permalink 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.
# 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、超时 1300 秒,且启用 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/openaitimeout 1300 秒 | 正向控制;缺传输级响应大小上限与生产域名 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 报告是唯一真正覆盖视频转写、备注、聊天和日常记录的入口,却没有正式处方/处方病历,并且全历史、无限行的实现带来显著资源与隐私保留风险。
建议把“统一上下文”定义为**统一的数据访问、鉴权、脱敏、来源清单和截断协议**,而不是要求所有入口发送同样多的数据。饮食问答应最小化;纵向报告可更完整;诊单助手应在可控时间窗内补齐临床关键项。这样才能同时解决答案一致性、隐私最小化和成本边界。