# openai **Repository Path**: mh-code/openai ## Basic Information - **Project Name**: openai - **Description**: 支持openai的AI接口 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-07 - **Last Updated**: 2026-09-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # mh-code/ai 一个轻量级、无第三方依赖的 PHP OpenAI 兼容客户端。支持**流式输出**与**直接返回**两种调用方式,可用于所有实现了 OpenAI HTTP API 的 AI 服务: - OpenAI / Azure OpenAI - DeepSeek(深度求索) - Moonshot / Kimi(月之暗面) - 通义千问 Qwen(DashScope 兼容模式) - 智谱 GLM(BigModel) - Groq - Ollama / LM Studio 等本地服务 - 以及其他任何兼容 OpenAI 接口的服务 ## 环境要求 - PHP >= 8.0 - 扩展:`ext-curl`、`ext-json` ## 流式输出已知问题 在 **Swoole 协程 + cURL** 环境下,流式输出是否正常与底层 `libc` 有关。 目前已测试: | 环境 | PHP | Swoole | libc | 流式输出 | |---|---|---|---|---| | 原生 PHP + Swoole 协程 | 8.4.21 | 6.0.2 | glibc | ❌ 不支持 | | Swoole CLI (`sw6`) | 8.4.x | 6.2.0 | musl | ✅ 正常 | | Swoole CLI (`sw5`) | 8.1.x | 5.1.5 | musl | ✅ 正常 | **结论:** `Swoole Coroutine + cURL` 的流式输出依赖底层 `libc` 环境。 目前测试结果表明: - `musl`:流式输出正常 - `glibc`:流式输出异常 因此,在 **Hyperf / Swoole 协程环境**中使用无法正常流式输出,请先检查当前 PHP 使用的是 `musl` 还是 `glibc`。 ### 查看 libc ```bash php -i | grep -iE 'libc|musl|glibc' ## 安装 ```bash composer require mh-code/ai ``` ## 快速开始 配置在**初始化时传入**,不会从任何动态/全局配置中读取。 ```php chat([ ['role' => 'user', 'content' => '介绍一下你自己'], ]); // 直接返回生成的文本 $text = $client->chatText([ ['role' => 'user', 'content' => '写一首关于春天的诗'], ]); echo $text; ``` ## 流式输出 `chatStream()` 将每个 SSE 数据块解码后回调,可逐字输出;回调返回 `false` 可提前中止。 ```php $client->chatStream( [['role' => 'user', 'content' => '讲一个笑话']], function (array $chunk) { $delta = $chunk['choices'][0]['delta']['content'] ?? ''; if ($delta !== '') { echo $delta; flush(); } }, ); ``` `chatStreamText()` 边流式边累积文本,结束时返回完整内容,可选回调接收每个增量。 ```php $text = $client->chatStreamText( [['role' => 'user', 'content' => '写一篇短文']], options: ['temperature' => 0.8], onPartial: fn (string $delta) => print($delta), ); ``` ## 区分深度思考与正文 深度思考模型(如 DeepSeek 的 `deepseek-reasoner`)在流式返回中带有两种内容: | 字段 | 含义 | | --- | --- | | `delta.reasoning_content` | 模型的深度思考(推理)过程 | | `delta.content` | 最终回答正文 | `chatStream()` 回调收到的是完整的原始数据块,可分别读取两个字段并做区分展示: ```php $thinking = ''; // 深度思考内容 $answer = ''; // 最终正文 $phase = 'thinking'; // 当前阶段:thinking(思考中)/ answer(回答中) $client->chatStream( [['role' => 'user', 'content' => '你好,请给我春江花月夜的原文']], function (array $chunk) use (&$thinking, &$answer, &$phase) { $choice = $chunk['choices'][0] ?? []; $delta = $choice['delta'] ?? []; // 深度思考内容 $reasoning = $delta['reasoning_content'] ?? ''; if ($reasoning !== '') { $thinking .= $reasoning; echo '[思考] ' . $reasoning; flush(); return true; } // 正文内容 $content = $delta['content'] ?? ''; if ($content !== '') { if ($phase === 'thinking') { $phase = 'answer'; echo PHP_EOL . '-------- 思考结束,正式回答 --------' . PHP_EOL; } $answer .= $content; echo $content; flush(); return true; } // 结束标记 if (($choice['finish_reason'] ?? null) !== null) { return false; // 提前中止流 } return true; }, ); ``` 完整可运行示例见 [examples/text.php](examples/text.php)。 ## 配置项 `Config` 构造函数全部参数(均可在初始化时指定): | 参数 | 类型 | 必填/选填 | 默认值 | 说明 | | --- | --- | --- | --- | --- | | `apiKey` | `string` | 必填 | 无 | API Key;本地服务可传空字符串 | | `model` | `string` | 必填 | 无 | 默认模型,单个请求可覆盖 | | `baseUrl` | `string` | 必填 | 无 | OpenAI 兼容接口地址 | | `timeout` | `int` | 选填 | `60` | 请求总超时(秒) | | `connectTimeout` | `?int` | 选填 | `null` | 连接超时(秒),null 使用 libcurl 默认 | | `verifySsl` | `bool` | 选填 | `true` | 是否校验 SSL 证书 | | `organization` | `?string` | 选填 | `null` | OpenAI Organization(可选) | | `headers` | `array` | 选填 | `[]` | 额外请求头;`Authorization` 可覆盖默认 Bearer | | `defaultOptions` | `array` | 选填 | `[]` | 默认合并到 chat/embeddings 请求体的参数(优先级最低,可被单次 `$options` 覆盖),如 `['temperature' => 0.7]` | ## 更多接口 ```php // 每个请求可覆盖模型与传入任意参数 $client->chat($messages, [ 'model' => 'gpt-4o', 'temperature' => 0.5, 'max_tokens' => 2048, 'tools' => [...], // function calling ]); // 向量化 $client->embeddings('你好,世界'); $client->embeddings(['文本1', '文本2'], ['model' => 'text-embedding-3-small']); // 模型列表 $client->models(); // 通用请求:images / audio / moderation 等任意 OpenAI 兼容接口 $client->request('POST', '/images/generations', [ 'prompt' => '一只戴帽子的猫', 'n' => 1, ]); ``` ## 错误处理 接口返回非 2xx 状态时抛出 `MhCode\Ai\Exception\ApiException`: ```php use MhCode\Ai\Exception\ApiException; try { $client->chatText([['role' => 'user', 'content' => 'hi']]); } catch (ApiException $e) { echo $e->getStatusCode(); // 429 echo $e->getMessage(); // 错误信息 var_dump($e->getErrorBody()); // 完整响应体 } ``` ## 支持的服务示例 只需更换 `baseUrl`(部分服务路径需带 `/v1`): | 服务 | baseUrl | | --- | --- | | OpenAI | `https://api.openai.com/v1` | | DeepSeek | `https://api.deepseek.com/v1` | | Moonshot / Kimi | `https://api.moonshot.cn/v1` | | 通义千问(兼容模式) | `https://dashscope.aliyuncs.com/compatible-mode/v1` | | 智谱 GLM | `https://open.bigmodel.cn/api/paas/v4` | | Groq | `https://api.groq.com/openai/v1` | | Ollama(本地) | `http://localhost:11434/v1` | | LM Studio(本地) | `http://localhost:1234/v1` | ## 目录结构 ``` src/ ├── Client.php # 主客户端 ├── Config.php # 初始化配置 └── Exception/ └── ApiException.php # 接口异常 ``` ## License [MIT](LICENSE)