Search by

A lightweight OpenAI-compatible AI client for PHP. Supports streaming and direct responses, works with any provider that implements the OpenAI API (OpenAI, DeepSeek, Moonshot, Qwen, Zhipu, Ollama, etc.).

Package info

gitee.com/mh-code/openai

pkg:composer/mh-code/ai

Statistics

Installs: 9

Dependents: 0

Suggesters: 0

v1.0.4 2026-09-21 05:43 UTC

This package is not auto-updated.

Last update: 2026-09-21 05:43:57 UTC


README

一个轻量级、无第三方依赖的 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-curlext-json

流式输出已知问题

Swoole 协程 + cURL 环境下,流式输出是否正常与底层 libc 有关。

目前已测试:

环境PHPSwoolelibc流式输出
原生 PHP + Swoole 协程8.4.216.0.2glibc❌ 不支持
Swoole CLI (sw6)8.4.x6.2.0musl✅ 正常
Swoole CLI (sw5)8.1.x5.1.5musl✅ 正常

结论:

Swoole Coroutine + cURL 的流式输出依赖底层 libc 环境。

目前测试结果表明:

  • musl:流式输出正常
  • glibc:流式输出异常

因此,在 Hyperf / Swoole 协程环境中使用无法正常流式输出,请先检查当前 PHP 使用的是 musl 还是 glibc

查看 libc

php -i | grep -iE 'libc|musl|glibc'



## 安装

composer require mh-code/ai


## 快速开始

配置在**初始化时传入**,不会从任何动态/全局配置中读取。

<?php

require DIR . '/vendor/autoload.php';

use MhCode\Ai\Client; use MhCode\Ai\Config;

$client = new Client(new Config(

apiKey: 'sk-xxxxxxxx',                          // 无需 key 的本地服务可留空
baseUrl: 'https://api.deepseek.com/v1',         // 替换为目标服务地址
model: 'deepseek-chat',                          // 默认模型

));


## 直接获取返回值

// 返回完整响应数组 $response = $client->chat([

['role' => 'user', 'content' => '介绍一下你自己'],

]);

// 直接返回生成的文本 $text = $client->chatText([

['role' => 'user', 'content' => '写一首关于春天的诗'],

]); echo $text;


## 流式输出

`chatStream()` 将每个 SSE 数据块解码后回调,可逐字输出;回调返回 `false` 可提前中止。

$client->chatStream(

[['role' => 'user', 'content' => '讲一个笑话']],
function (array $chunk) {
    $delta = $chunk['choices'][0]['delta']['content'] ?? '';
    if ($delta !== '') {
        echo $delta;
        flush();
    }
},

);


`chatStreamText()` 边流式边累积文本,结束时返回完整内容,可选回调接收每个增量。

$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()` 回调收到的是完整的原始数据块,可分别读取两个字段并做区分展示:

$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]` |

## 更多接口

// 每个请求可覆盖模型与传入任意参数 $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`:

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)