# 桌面端 AI 助手:服务端流式链路审查 日期:2026-10-17 ## 结论 - 正常链路已经逐帧转发:`DifyChatService::sendStreamRequest()` 在 `CURLOPT_WRITEFUNCTION` 内解析 SSE 并立即调用 `onDelta`;`DiagnosisController::runAssistantSse()` 对每个 delta `echo` 后执行 `ob_flush()` / `flush()`,没有等模型全文完成才发送。 - 控制器清理 PHP 输出缓冲、关闭输出压缩、设置 `X-Accel-Buffering: no` 和 `Cache-Control: no-cache, no-transform`,并先发送 2048 字节注释和 start 事件。 - 首字之前仍有真实耗时:`prepareAssistant()` 在 SSE headers 前完成权限、DataScope、病例聚合和模型预检。病例正文超过 120,000 字节时,`fitContextForPrompt()` 同步调用 `PatientAiReportLogic::compactSourceForPrompt()`;后者依次通过 blocking 请求读取全部分片并可能归并摘要。保留这一流程以保持全量临床来源覆盖,不能用截断病例来换速度。 - 兼容重试也可能增加首字等待:通用 `/v1` 地址存在协议回退;`streamPreparedAssistant()` 在尚未交付任何 delta 且流失败为 `UPSTREAM_REJECTED` / `INCOMPLETE_RESPONSE` / `EMPTY_RESPONSE` 时,会额外做一次 blocking 兼容尝试。已经交付文本后不会重复生成。 - 因此前端需要在用户提交时立即反馈并开始计秒,不能等 SSE start 才开始计时。`AssistantSseTiming` 已提供不含患者正文的 prepare、first_delta、upstream_done 等日志,可定位实际延迟阶段。 ## 修复 发现 SSE 分帧正则 `(?:\r\n|\r|\n){2}` 允许回溯,把一个 CRLF 拆成两个换行;仅收到一行 data 时就会提前解析。合法的多行 CRLF data 事件会被拆坏并丢失文本,可能进一步触发无 delta 的兼容重试。 将换行匹配改为原子组 `(?>\r\n|\r|\n){2}`,使 CRLF 始终作为一个换行。事件到达空行即转发,不等待 message_end 或网络 EOF。接口、权限、事件格式、错误码和凭据保护保持兼容。 修改文件: - `server/app/common/service/DifyChatService.php` - `server/tests/DifyChatStreamContractTest.php` - `server/tests/DifyChatStreamDeliveryTest.php`(新增) 新增行为覆盖: - data 行尚未结束事件时不解析;完整首帧在终止事件及 EOF 前立即交付。 - 多行 CRLF data 在 1 字节、2 字节及整段传输切块下保持为同一事件。 - 离线模拟实际 cURL header/write 回调:每帧在下一次网络读取之前、`curl_exec()` 返回之前到达消费者;Dify / OpenAI 均覆盖。 - HTTP 错误正文不进入 delta;消费者拒绝后停止继续读取和交付。 新增回归在修复前实际失败:`FAIL: a data line alone is not a complete SSE event`;应用修复后通过。 ## 验证 以下命令全部退出码 0: ```powershell php server/tests/DifyChatStreamContractTest.php php server/tests/DifyChatStreamDeliveryTest.php php server/tests/DifyChatAncientBooksReuseContractTest.php php server/tests/DiagnosisAiAssistantStreamContractTest.php php server/tests/DiagnosisAiAssistantTimingTest.php php server/tests/DiagnosisAiAssistantContractTest.php php server/tests/DiagnosisAiPrescriptionGeneratePermissionTest.php php server/tests/DiagnosisAiAnalysisSecurityTest.php php server/tests/DiagnosisAiAnalysisContractTest.php php server/tests/PrescriptionAiUpstreamContractTest.php php -l server/app/common/service/DifyChatService.php php -l server/tests/DifyChatStreamContractTest.php php -l server/tests/DifyChatStreamDeliveryTest.php git diff --check -- server/app/common/service/DifyChatService.php server/tests/DifyChatStreamContractTest.php server/tests/DifyChatStreamDeliveryTest.php ``` 验证均离线进行,没有调用生产模型、读取凭据或测试生产代理配置;因此不提供生产首字时延数值。