webman-php/openai异步调用必须传回调函数,不返回响应对象,也不支持await或同步等待;所有方法如completions()均需显式传入success和error回调,由workerman事件驱动模型决定结果只能通过回调触发。

webman-php/openai 的异步调用必须传回调函数
它不返回响应对象,也不支持 await 或同步阻塞等待。所有方法(如 chat->completions())都要求你显式传入 success 和 error 回调。这是由 Workerman 的事件驱动模型决定的——主线程不能挂起,所以结果只能靠回调触发。
常见错误是照搬官方 Python SDK 写法,试图这样写:
php $result = $client->chat->completions($messages); // ❌ 返回 null,永远拿不到数据
正确做法是把后续逻辑(比如推送给前端、存数据库)全部放进 success 回调里:
-
success回调接收两个参数:$response(完整响应)和$stream(流式 chunk 处理器) - 若要实现流式输出,必须用
$stream回调,而不是等success整体返回 -
error回调里不要只打日志,建议主动 emit 错误事件或关闭连接,避免客户端一直等待
流式响应需手动处理 chunk 并分段推送
OpenAI 的 stream=true 响应是 Server-Sent Events(SSE)格式,每行一个 JSON 对象,以 data: 开头。webman-php/openai 会自动解析这些 chunk,但不会自动广播给前端——你需要自己调用 websocket->send() 或 http-response->write()。
典型漏点:只在 success 回调里发一次完整回复,完全没用 $stream。结果就是用户看到“思考中…”十几秒后突然刷出整段回答,失去实时感。
-
$stream回调每收到一个 chunk,就立即调用$connection->send(['chunk' => $content]) - 注意判断
$chunk->choices[0]->delta->content是否为空,避免发空字符串干扰前端解析 - 前端需用
EventSource或 WebSocket 接收,不能用普通fetch直接读取流(会缓存)
Webman 生命周期下 token 计算和上下文管理得自己兜底
这个库不维护对话历史,也不帮你做 prompt 截断或 token 统计。每次调用都是无状态的裸请求,messages 数组全靠你传入——如果历史太长导致超限,OpenAI 直接返回 400 Bad Request,错误信息是 "This model's maximum context length is 16384 tokens"。
容易踩的坑是直接把整个对话数组堆进去,尤其开启多轮后很快爆掉。PHP 里没有现成的 token 计算器,得自己处理:
- 用
openai/tokenizer的 PHP 移植版(如symfony/serializer+ 手动映射)粗略估算长度 - 更稳妥的做法是按字符数保守截断:中文按 2 字符 ≈ 1 token,英文单词平均 1.3 token,留至少 500 token 余量
- 把系统提示词(
systemrole)和最近 5–8 轮对话拼成messages,老消息丢弃或摘要压缩
WebSocket 连接不稳定时流式中断很难重试
Webman 的 WebSocket 连接是长连接,但网络抖动、客户端切后台、Nginx 代理超时都会导致中断。而 OpenAI 流式响应一旦开始,中途断开就没有续传机制——你没法告诉它“从第 12 个 chunk 继续”。
这不是库的问题,是协议限制。能做的只有防御性设计:
- 服务端加心跳检测,
onClose里清理未完成的异步任务(调用$client->cancel()) - 前端收到中断后,发新请求时带上上次最后一条
user消息的 ID,服务端据此重建上下文 - 避免在
$stream回调里做耗时操作(如 DB 写入),否则可能拖慢流速,加剧断连
真正难处理的是中间 chunk 丢失——它不像 HTTP 可重试,流式本质是一次性管道。所以重点不在恢复,而在让单次流尽可能稳:缩短单次请求的上下文、控制生成长度、避开高峰期调用。











