Files
zyt/artifacts/desktop-ai-stream-backend-review.md
T
2026-10-17 16:08:24 +08:00

56 lines
3.7 KiB
Markdown
Raw 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.
# 桌面端 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
```
验证均离线进行,没有调用生产模型、读取凭据或测试生产代理配置;因此不提供生产首字时延数值。