php curl 启用 mistral 流式响应需设 curlopt_returntransfer 为 false 并配合 curlopt_writefunction 回调解析 sse 格式(data: 开头、空行分隔),禁用 curlopt_header,添加 accept: text/event-stream 头,同时注意 utf-8 多字节截断问题。

PHP cURL 如何正确启用 Mistral 流式响应
必须显式关闭 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true),否则整个响应体被缓冲,根本收不到流式数据。Mistral 的 API(如 /v1/chat/completions)在启用 stream=true 后,会以 text/event-stream 格式逐块返回 data: {...} 行,PHP 默认行为会等连接结束才吐出全部内容。
实操要点:
- 设
CURLOPT_RETURNTRANSFER为false,让输出直接流向stdout或自定义回调 - 必须设
CURLOPT_WRITEFUNCTION回调处理每段收到的原始字节,不能依赖curl_exec()返回值 - 加上
CURLOPT_HTTPHEADER包含Accept: text/event-stream和Content-Type: application/json - 禁用
CURLOPT_HEADER,否则响应头会混入数据流,干扰data:解析
如何解析 Mistral 的 SSE 响应格式
Mistral 返回的不是纯 JSON,而是标准 Server-Sent Events(SSE),每条消息以 data: 开头,可能跨多行,末尾用空行分隔。常见错误是直接 json_decode($line),但实际要先提取 data: 后的内容,再合并连续的 data: 行(Mistral 有时把一个 JSON 拆成多行发送)。
关键逻辑:
- 用
preg_match('/^data:\s*(.*)$/', $line, $matches)提取有效载荷 - 跳过空行、
event:、id:、retry:等非 data 行 - 遇到连续
data:行时,需累积拼接,直到遇到空行再统一json_decode() - 注意
data: [DONE]是终止信号,不是 JSON,需单独判断
PHP stream_socket_client 与 cURL 的选择权衡
用 curl 更省心:自动处理重定向、gzip、SSL 验证;但对流控粒度低,比如无法精确控制每次读多少字节。用 stream_socket_client + fread() 可做到字节级响应捕获,适合需要低延迟渲染或带宽受限场景,但得自己实现 HTTP 头解析、chunked 编码识别、SSL 上下文配置。
推荐策略:
- 业务逻辑简单、只关心完整 token 流 → 用
cURL+WRITEFUNCTION - 需实时高亮关键词、做 token 级限速、或嵌入 WebSockets 中继 → 用
stream_socket_client - 无论哪种,都必须设
stream_set_timeout($fp, 0, 50000)(50ms 超时),避免卡死在fread() -
stream_socket_client连 Mistral 时,tls://api.mistral.ai:443必须配['ssl' => ['verify_peer' => true, 'cafile' => '/path/to/cacert.pem']]
常见阻塞和超时错误怎么定位
最典型的是 cURL error 56: Failure when receiving data from the peer,表面是连接中断,实际常因 PHP 输出缓冲未关(ob_implicit_flush(true) 没开)、或 Nginx 的 proxy_buffering off 漏配。本地 CLI 运行正常但 Web 服务卡住,基本可锁定是中间代理吞了流。
排障步骤:
- CLI 下跑脚本,加
curl_setopt($ch, CURLOPT_VERBOSE, true)看真实收发日志 - 检查
output_buffering是否为Off(php.ini 或运行时ini_set('output_buffering', 'Off')) - Nginx 配置中确认有
proxy_buffering off;和proxy_cache off; - Mistral 的
max_tokens设太小会导致流提前结束,误判为异常,建议先设2048排除截断干扰
流式响应真正难的不是接收,而是保持连接稳定的同时,把碎片化的 delta.content 拼成语义连贯的文本——Mistral 不保证每个 chunk 都是 UTF-8 完整字符,边界处可能截断多字节序列,这点容易被忽略。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











