更新
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
# 桌面端 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
|
||||
```
|
||||
|
||||
验证均离线进行,没有调用生产模型、读取凭据或测试生产代理配置;因此不提供生产首字时延数值。
|
||||
Reference in New Issue
Block a user