3.7 KiB
3.7 KiB
桌面端 AI 助手:服务端流式链路审查
日期:2026-10-17
结论
- 正常链路已经逐帧转发:
DifyChatService::sendStreamRequest()在CURLOPT_WRITEFUNCTION内解析 SSE 并立即调用onDelta;DiagnosisController::runAssistantSse()对每个 deltaecho后执行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.phpserver/tests/DifyChatStreamContractTest.phpserver/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:
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
验证均离线进行,没有调用生产模型、读取凭据或测试生产代理配置;因此不提供生产首字时延数值。