laybot / ai-sdk
Provider-independent AI and speech SDK for PHP/Webman
Requires
- php: >=8.1
- ext-json: *
- ext-zlib: *
- laybot/request-sdk: ^2.0.6
- psr/log: ^3.0
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^10.5 || ^11.0
- vlucas/phpdotenv: ^5.6
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
灵语智教 · 多模型厂商与实时语音能力聚合 SDK
Ark · DeepSeek · OpenAI-Compatible · Responses · TTS · ASR · Webman Async
Chat · Responses · SSE · Tool Calls · Files · TTS · AUC · SAUC · WebSocket
Powered by LayBot LingTeach AI · Laybot 现代 MPA 工程平台
laybot/ai-sdk是面向 PHP 8.1+ 的多供应商 AI 与语音协议 SDK。
它在laybot/request-sdk之上统一 Chat、Responses、流式文本、Tool Call、Usage、Files、TTS、ASR 和供应商异常。
1. 项目简介
在企业 AI 项目中,接入大模型通常不只是发送一次 HTTP 请求。
实际系统还需要处理:
- 多供应商模型切换;
- 同步与异步调用;
- SSE 增量文本;
- Reasoning 输出;
- Tool Call 参数归并;
- Responses API;
- 图片、PDF 和 Files API;
- Token Usage;
- Request ID 和供应商日志 ID;
- HTTP Chunked、NDJSON 和二进制协议;
- 实时 TTS 和 ASR;
- WebSocket Session 状态机;
- 连接、请求和流空闲超时;
- 取消、背压和资源释放;
- 供应商错误码和重复计费风险;
- Webman 常驻进程中的租户配置隔离。
laybot/ai-sdk 将这些能力收敛为统一的 PHP API:
业务项目
↓
laybot/ai-sdk
↓
laybot/request-sdk
↓
HTTP / SSE / NDJSON / WebSocket / TLS
↓
模型与语音供应商
它解决的不是单纯的“调用一个模型”,而是:
如何在 PHP 企业项目中,以可维护、可观测、可取消、可扩展的方式统一接入模型和实时语音供应商。
2. LayBot 体系
本 SDK 脱胎于:
LayBot 系列组件包括:
| 项目 | 职责 |
|---|---|
laybot/request-sdk |
HTTP、SSE、NDJSON、WebSocket、TLS、Proxy、Retry、Cancellation |
laybot/ai-sdk |
模型消息、流式状态、Tool Call、Usage、TTS、ASR、供应商协议 |
laybot/storage-sdk |
文件和对象存储相关能力 |
| 业务项目 | 用户、权限、消息、计费、任务、数据库、OSS 和业务审计 |
推荐分层:
Webman / Laravel / ThinkPHP / Symfony / CLI
↓
业务Service、Worker、Process
↓
laybot/ai-sdk
↓
laybot/request-sdk
↓
供应商API
3. 核心能力
3.1 大模型
- OpenAI-Compatible Chat Completions
- 非流式 Chat
- 同步 SSE Chat
- Workerman 异步 SSE Chat
- 正文增量
- Reasoning 增量
- 加密 Reasoning 元数据
- Tool Call 增量与归并
- Usage 统一映射
- Finish Reason
- Request ID
- Responses API
- Responses SSE
- 图片、PDF 和文件输入
- Files 上传、查询、列表、下载与删除
- Embeddings、Images、Audio、Batches、Fine-tuning 资源入口
- 模型能力注册表
3.2 火山语音
- AUC 文件语音识别
- AUC Submit / Query
- SAUC Async 实时识别
- SAUC Nostream 流式输入识别
- HTTP SSE TTS
- HTTP Chunked / JSON Lines TTS
- 双向 WebSocket TTS
- 连续文本
TaskRequest - PCM、MP3 等音频输出参数
- 声音复刻音色状态查询
- Sentence 事件
- TTS Usage
- WebSocket 背压、取消和正常关闭
3.3 工程能力
- PHP 8.1+ 强类型 DTO
- 同步和异步 API 语义分离
- 生成式请求默认不透明重试
- 供应商配置不可变覆盖
- 多租户 API Key 隔离
- 连接超时、请求超时和流空闲超时
- Header 安全合并
- API Key 日志脱敏
- 供应商异常统一映射
- 部分输出与可能计费标记
- Webman/Workerman 常驻进程适配
- PHPUnit 和 PHPStan 验证
4. 当前供应商状态
4.1 已通过真实账号验收
| 供应商 | 能力 | 状态 |
|---|---|---|
| Ark / 豆包方舟 | Chat 非流式 | 已验证 |
| Ark / 豆包方舟 | Chat SSE | 已验证 |
| Ark / 豆包方舟 | Responses 非流式 | 已验证 |
| Ark / 豆包方舟 | Responses SSE | 已验证 |
| Ark / 豆包方舟 | 公网图片 URL | 已验证 |
| Ark / 豆包方舟 | 公网 PDF URL | 已验证 |
| Ark / 豆包方舟 | Files 上传和 file_id |
已验证 |
| DeepSeek | Chat 非流式 | 已验证 |
| DeepSeek | Chat SSE | 已验证 |
| DeepSeek | Reasoner | 已验证 |
| 火山语音 | 已有声音复刻音色查询 | 已验证 |
| 火山语音 | HTTP SSE TTS | 已验证 |
| 火山语音 | 双向 WebSocket TTS | 已验证 |
| 火山语音 | AUC 文件识别 | 已验证 |
| 火山语音 | SAUC Async | 已验证 |
| 火山语音 | SAUC Nostream | 已验证 |
4.2 已实现但尚未完成真实账号验收
| 供应商/能力 | 状态 |
|---|---|
| OpenAI Chat / Responses / Files / Resources | 协议已实现,待真实账号验收 |
| Gemini Native API | 协议已实现,待真实账号验收 |
| Anthropic / Claude Messages | 协议已实现,待真实账号验收 |
| Qwen OpenAI-Compatible | 待真实账号验收 |
| xAI OpenAI-Compatible | 待真实账号验收 |
| Groq OpenAI-Compatible | 待真实账号验收 |
| 火山 HTTP Chunked TTS | 已实现,待正式账号专项验收 |
| LayBot 平台扩展资源 | 由实际平台端点决定 |
“协议已实现”不等同于“已完成目标账号生产验收”。
正式项目应依据自身模型、区域、配额和供应商版本执行集成测试。
5. 框架支持
| 运行环境 | 同步 Chat | 同步流 | 异步 HTTP/SSE | 实时 TTS/ASR WebSocket |
|---|---|---|---|---|
| Webman / Workerman | 支持 | 支持 | 支持 | 支持 |
| Laravel | 支持 | 支持 | 需 Workerman 事件循环 | 需 Workerman 事件循环 |
| ThinkPHP | 支持 | 支持 | 需 Workerman 事件循环 | 需 Workerman 事件循环 |
| Symfony | 支持 | 支持 | 需 Workerman 事件循环 | 需 Workerman 事件循环 |
| Yii | 支持 | 支持 | 需 Workerman 事件循环 | 需 Workerman 事件循环 |
| PHP CLI | 支持 | 支持 | 需启动 Workerman | 需启动 Workerman |
| PHP-FPM | 支持 | 受网关缓冲和执行时间影响 | 不建议 | 不适用 |
| Swoole / Hyperf | 同步能力可用 | 可能阻塞协程 | 暂无原生适配 | 暂无原生适配 |
SDK 不会根据环境自动改变方法语义:
调用同步方法 → 一定同步
调用异步方法 → 一定异步,并要求有效的Workerman事件循环
6. 运行要求
基础要求:
PHP >= 8.1
ext-json
ext-zlib
laybot/request-sdk ^2.0.6
psr/log ^3.0
异步 HTTP、SSE、TTS 和 ASR 需要底层 Workerman 事件循环支持。
推荐生产环境:
PHP 8.4
当前稳定版Webman
Workerman 5.2+
laybot/request-sdk 2.0.6+
Linux
7. 安装
composer require laybot/ai-sdk:^2.0
本地开发:
git clone https://github.com/laybot/ai-sdk.git
cd ai-sdk
composer install
8. 配置
8.1 创建统一 AiClient
<?php use LayBot\Ai\AiClient; $ai = new AiClient([ 'default_provider' => 'ark', 'providers' => [ 'ark' => [ 'api_key' => getenv('ARK_API_KEY'), 'options' => [ 'log_bodies' => false, ], ], 'deepseek' => [ 'api_key' => getenv( 'DEEPSEEK_API_KEY' ), 'options' => [ 'log_bodies' => false, ], ], 'volc-speech' => [ 'api_key' => getenv( 'VOLC_SPEECH_API_KEY' ), 'options' => [ 'tts_resource_id' => getenv( 'VOLC_TTS_RESOURCE_ID' ), 'tts_model' => getenv( 'VOLC_TTS_MODEL' ), 'auc_resource_id' => getenv( 'VOLC_AUC_RESOURCE_ID' ) ?: 'volc.seedasr.auc', 'sauc_resource_id' => getenv( 'VOLC_SAUC_RESOURCE_ID' ) ?: 'volc.seedasr.sauc.duration', 'log_bodies' => false, ], ], ], ]);
8.2 Provider 配置项
[
'driver' => 'openai',
'base_uri' => 'https://api.example.com',
'api_key' => 'server-side-key',
'headers' => [
'X-App-Name' => 'xiaoyi-ai',
],
'sensitive_headers' => [
'X-Private-Token',
],
'endpoints' => [
'chat' => '/v1/chat/completions',
],
'options' => [
'require_done_token' => true,
'log_bodies' => false,
],
'verify_tls' => true,
'proxy' => null,
]
支持通过别名配置同一协议:
$ai = new AiClient([ 'providers' => [ 'primary-chat' => [ 'driver' => 'openai', 'base_uri' => 'https://internal-gateway.example.com', 'api_key' => getenv('PRIMARY_CHAT_KEY'), ], ], ]); $result = $ai ->provider('primary-chat') ->chat() ->create([...]);
9. Chat 快速开始
9.1 Ark 非流式 Chat
use LayBot\Ai\Vendor\Ark; $ark = new Ark([ 'api_key' => getenv('ARK_API_KEY'), ]); $result = $ark->chat()->create([ 'model' => getenv('ARK_CHAT_MODEL'), 'messages' => [ [ 'role' => 'system', 'content' => '你是一名专业的教学助手。', ], [ 'role' => 'user', 'content' => '为初二学生讲解浮力定律。', ], ], 'stream' => false, 'max_completion_tokens' => 1024, ]); echo $result->text; echo $result->reasoningText; echo $result->usage->totalTokens; echo $result->providerRequestId;
9.2 DeepSeek 非流式 Chat
use LayBot\Ai\Vendor\DeepSeek; $deepSeek = new DeepSeek([ 'api_key' => getenv('DEEPSEEK_API_KEY'), ]); $result = $deepSeek->chat()->create([ 'model' => getenv('DEEPSEEK_CHAT_MODEL'), 'messages' => [ [ 'role' => 'user', 'content' => '请解释什么是二叉搜索树。', ], ], 'stream' => false, 'max_tokens' => 1024, ]); echo $result->text;
10. 统一 completions() 入口
completions() 根据请求中的 stream 决定返回类型:
$result = $ai ->chat('ark') ->completions( request: [ 'model' => getenv('ARK_CHAT_MODEL'), 'messages' => [ [ 'role' => 'user', 'content' => '你好', ], ], 'stream' => true, ], callbacks: [ 'on_start' => static function (): void { echo '[START]'; }, 'on_text_delta' => static function (string $text): void { echo $text; }, 'on_reasoning_delta' => static function (string $text): void { // 按业务要求决定是否展示。 }, 'on_usage' => static function ($usage): void { echo $usage->totalTokens; }, 'on_finish' => static function (?string $reason): void { echo $reason ?? 'unknown'; }, 'on_complete' => static function ($result): void { // 最终归并结果。 }, 'on_error' => static function (\Throwable $error): void { report($error); }, ] );
返回类型:
stream=false → ChatResult
stream=true → ChatStreamResult
11. 同步流式 Chat
use LayBot\Ai\DTO\StreamEvent; use LayBot\Ai\Enum\StreamEventType; $result = $ai ->chat('ark') ->stream( request: [ 'model' => getenv('ARK_CHAT_MODEL'), 'messages' => [ [ 'role' => 'user', 'content' => '请分步骤讲解勾股定理。', ], ], 'stream' => true, 'max_completion_tokens' => 2048, ], onEvent: static function ( StreamEvent $event ): void { match ($event->type) { StreamEventType::TEXT_DELTA => print($event->textDelta), StreamEventType::REASONING_DELTA => null, StreamEventType::USAGE => null, StreamEventType::FINISH => print(PHP_EOL . '[DONE]'), default => null, }; }, options: [ 'connect_timeout' => 10, 'request_timeout' => 0, 'idle_timeout' => 120, ] ); echo $result->text;
流式结果包含:
$result->text; $result->reasoningText; $result->encryptedReasoning; $result->model; $result->providerRequestId; $result->finishReason; $result->usage; $result->durationMs; $result->firstTokenDurationMs; $result->receivedAnyDelta; $result->completed; $result->possiblyCharged; $result->toolCalls;
12. Webman 异步流式 Chat
在 Webman/Workerman 已运行的 Worker 中:
use LayBot\Ai\DTO\ChatStreamResult; use LayBot\Ai\DTO\StreamEvent; use LayBot\Ai\Enum\StreamEventType; $handle = $ai ->chat('ark') ->streamAsync( request: [ 'model' => getenv('ARK_CHAT_MODEL'), 'messages' => [ [ 'role' => 'user', 'content' => '请讲解牛顿第二定律。', ], ], 'stream' => true, 'max_completion_tokens' => 2048, ], onEvent: static function ( StreamEvent $event ): void { if ( $event->type === StreamEventType::TEXT_DELTA ) { // 推送给App或浏览器WebSocket。 echo $event->textDelta; } }, onComplete: static function ( ChatStreamResult $result ): void { echo 'Completed: ', $result->text; }, onError: static function ( \Throwable $error ): void { echo $error->getMessage(); }, options: [ 'connect_timeout' => 10, 'request_timeout' => 0, 'idle_timeout' => 120, ] );
取消:
$handle->cancel('user stopped generation');
在已经运行的 Webman Worker 中不要再次调用:
Workerman\Worker::runAll();
13. ChatRequest DTO
use LayBot\Ai\DTO\ChatRequest; $request = new ChatRequest( model: 'runtime-model', messages: [ [ 'role' => 'user', 'content' => '你好', ], ], parameters: [ 'temperature' => 0.7, 'max_completion_tokens' => 1024, 'tools' => [], ] ); $result = $ai ->chat('ark') ->create($request);
规则:
model和messages由DTO明确字段决定
其他供应商参数通过parameters自由传入
调用方可以动态传入任何运行时模型和消息,SDK 不会写死模型。
14. Tool Calls
OpenAI-Compatible 请求:
$result = $ai ->chat('deepseek') ->create([ 'model' => getenv('DEEPSEEK_CHAT_MODEL'), 'messages' => [ [ 'role' => 'user', 'content' => '查询北京天气。', ], ], 'tools' => [ [ 'type' => 'function', 'function' => [ 'name' => 'get_weather', 'description' => '查询城市天气', 'parameters' => [ 'type' => 'object', 'properties' => [ 'city' => [ 'type' => 'string', ], ], 'required' => ['city'], ], ], ], ], ]); foreach ($result->toolCalls as $toolCall) { $name = $toolCall['function']['name'] ?? ''; $arguments = $toolCall['function']['arguments'] ?? '{}'; }
流式 Tool Call 会按 choice_index 和 index 归并。
统一结果当前只支持单候选:
OpenAI-Compatible:n=1
Gemini:candidate_count=1
15. Ark Responses API
15.1 文本
use LayBot\Ai\Vendor\Ark; $ark = new Ark([ 'api_key' => getenv('ARK_API_KEY'), ]); $result = $ark->responses()->create( [ 'model' => getenv('ARK_RESPONSES_MODEL'), 'input' => [ [ 'role' => 'user', 'content' => [ [ 'type' => 'input_text', 'text' => '请简要解释光合作用。', ], ], ], ], 'max_output_tokens' => 2048, ], [ 'connect_timeout' => 15, 'request_timeout' => 120, ] ); echo $result->status; echo $result->text; echo $result->usage->totalTokens;
completed 只在供应商返回:
status=completed
时为 true。
如果供应商返回:
status=incomplete
SDK 不会将其伪装成成功。
15.2 公网图片 URL
$result = $ark->responses()->create([ 'model' => getenv('ARK_RESPONSES_MODEL'), 'input' => [ [ 'role' => 'user', 'content' => [ [ 'type' => 'input_image', 'image_url' => 'https://example.com/image.png', 'detail' => 'high', ], [ 'type' => 'input_text', 'text' => '请描述图片内容。', ], ], ], ], ]);
15.3 公网 PDF URL
$result = $ark->responses()->create( [ 'model' => getenv('ARK_RESPONSES_MODEL'), 'input' => [ [ 'role' => 'user', 'content' => [ [ 'type' => 'input_file', 'file_url' => 'https://example.com/document.pdf', ], [ 'type' => 'input_text', 'text' => '请概括PDF主题。', ], ], ], ], /* * 推理模型需要同时为Reasoning和可见正文 * 预留输出预算。 */ 'max_output_tokens' => 4096, ], [ 'connect_timeout' => 15, 'request_timeout' => 120, ] );
15.4 上传文件并使用 file_id
$uploaded = $ark ->files() ->upload( __DIR__ . '/document.pdf', 'user_data' ); $fileId = $uploaded->id(); if ($fileId === null) { throw new RuntimeException( 'Ark upload response has no file ID' ); } $file = $ark->files()->retrieve($fileId); $result = $ark->responses()->create( [ 'model' => getenv('ARK_RESPONSES_MODEL'), 'input' => [ [ 'role' => 'user', 'content' => [ [ 'type' => 'input_file', 'file_id' => $fileId, ], [ 'type' => 'input_text', 'text' => '请概括上传的文件。', ], ], ], ], 'max_output_tokens' => 4096, ], [ 'connect_timeout' => 15, 'request_timeout' => 120, ] );
文件上传后可能暂时处于:
processing
pending
queued
业务项目应轮询文件状态,不能对所有错误盲目重试。
15.5 Responses SSE
$result = $ark->responses()->stream( request: [ 'model' => getenv('ARK_RESPONSES_MODEL'), 'input' => '请介绍LayBot。', ], onEvent: static function ( \LayBot\Ai\DTO\StreamEvent $event ): void { if ( $event->type === \LayBot\Ai\Enum\StreamEventType::TEXT_DELTA ) { echo $event->textDelta; } }, options: [ 'request_timeout' => 0, 'idle_timeout' => 120, ] );
异步:
$handle = $ark->responses()->streamAsync( request: [...], onEvent: $onEvent, onComplete: $onComplete, onError: $onError, options: [ 'request_timeout' => 0, 'idle_timeout' => 120, ] );
16. 火山语音配置
use LayBot\Ai\Vendor\VolcSpeech; $speech = new VolcSpeech([ 'api_key' => getenv( 'VOLC_SPEECH_API_KEY' ), 'options' => [ 'tts_resource_id' => getenv( 'VOLC_TTS_RESOURCE_ID' ), 'tts_model' => getenv( 'VOLC_TTS_MODEL' ), 'auc_resource_id' => getenv( 'VOLC_AUC_RESOURCE_ID' ) ?: 'volc.seedasr.auc', 'sauc_resource_id' => getenv( 'VOLC_SAUC_RESOURCE_ID' ) ?: 'volc.seedasr.sauc.duration', ], ]);
火山语音 API Key 与 Ark API Key 是不同凭证。
所有凭证都只能保存在服务端,不要放入:
- 浏览器 JavaScript;
- uni-app 前端源码;
- Electron 渲染进程;
- Git 仓库;
- App 安装包;
- 可公开下载的配置文件。
17. HTTP SSE TTS
use LayBot\Ai\DTO\Tts\TtsStreamEvent; use LayBot\Ai\Enum\TtsStreamEventType; $audio = ''; $result = $speech ->tts() ->sse() ->streamSse( request: [ 'text' => '你好,这是一段语音合成测试。', 'speaker' => getenv( 'VOLC_TTS_SPEAKER' ), 'audio' => [ 'format' => 'mp3', 'sample_rate' => 24000, 'bit_rate' => 64000, ], 'additions' => [ 'tone_fidelity' => true, ], ], onEvent: static function ( TtsStreamEvent $event ) use (&$audio): void { if ( $event->type === TtsStreamEventType::AUDIO_CHUNK && $event->audio !== null ) { $audio .= $event->audio->audio; } }, options: [ 'connect_timeout' => 10, 'request_timeout' => 0, 'idle_timeout' => 90, ] ); if (!$result->completed) { throw new RuntimeException( 'TTS did not complete' ); } file_put_contents( __DIR__ . '/speech.mp3', $audio );
结果包含:
$result->provider; $result->status; $result->clientRequestId; $result->providerRequestId; $result->providerLogId; $result->usage->textWords; $result->audioBytes; $result->audioChunks; $result->durationMs; $result->completed; $result->cancelled; $result->sentences;
大音频不要长期拼接在内存中,应在 AUDIO_CHUNK 回调中持续写入临时文件。
18. HTTP Chunked TTS
$result = $speech ->tts() ->chunked() ->streamChunked( request: [ 'text' => '你好', 'speaker' => getenv( 'VOLC_TTS_SPEAKER' ), ], onEvent: static function ( TtsStreamEvent $event ): void { if ( $event->audio !== null ) { // 消费二进制音频。 } } );
HTTP Chunked 使用 JSON Lines 解析。
由于不同火山产品和账号可能返回不同外层结构,正式上线前必须使用目标账号执行协议验收。
19. 查询已有复刻音色
$voice = $speech ->tts() ->voice() ->get( speakerId: getenv( 'VOLC_TTS_SPEAKER' ) ); if (!$voice->availableForSynthesis()) { throw new RuntimeException(sprintf( 'voice status is %d', $voice->status )); } if ($voice->supportsClone20()) { echo 'Clone 2.0 is available'; }
当前 SDK 支持:
查询已有音色状态
判断音色是否可合成
判断speaker_status是否包含复刻2.0模型类型
当前未提供完整的:
训练音频上传
创建新音色
重新训练
删除音色
20. 双向 WebSocket TTS
双向 TTS 需要 Workerman 事件循环。
use LayBot\Ai\Contract\BidirectionalTtsConnectionInterface; use LayBot\Ai\Contract\BidirectionalTtsSessionInterface; use LayBot\Ai\DTO\Tts\TtsResult; use LayBot\Ai\DTO\Tts\TtsStreamEvent; use LayBot\Ai\Enum\TtsStreamEventType; use LayBot\Ai\Provider\VolcSpeech\Tts\BidirectionalTtsListener; use LayBot\Request\DTO\WebSocketCloseInfo; $listener = new class extends BidirectionalTtsListener { public function onConnectionReady( BidirectionalTtsConnectionInterface $connection ): void { $connection->startSession([ 'speaker' => getenv( 'VOLC_TTS_SPEAKER' ), 'audio' => [ 'format' => 'pcm', 'sample_rate' => 24000, 'bit_rate' => null, ], ]); } public function onSessionStarted( BidirectionalTtsConnectionInterface $connection, BidirectionalTtsSessionInterface $session ): void { $session->appendText('你好,'); $session->appendText('这是双向流式语音。'); $session->finish(); } public function onEvent( BidirectionalTtsConnectionInterface $connection, BidirectionalTtsSessionInterface $session, TtsStreamEvent $event ): void { if ( $event->type === TtsStreamEventType::AUDIO_CHUNK && $event->audio !== null ) { $pcm = $event->audio->audio; // 立即转发给App或写入播放器。 } } public function onSessionCompleted( BidirectionalTtsConnectionInterface $connection, BidirectionalTtsSessionInterface $session, TtsResult $result ): void { if ($result->completed) { $connection->finish(); } else { $connection->cancel( 'TTS Session did not complete' ); } } public function onError( BidirectionalTtsConnectionInterface $connection, \Throwable $error ): void { report($error); $connection->cancel( 'TTS failed' ); } public function onClose( BidirectionalTtsConnectionInterface $connection, WebSocketCloseInfo $info ): void { // 清理业务资源。 } }; $connection = $speech ->tts() ->bidirectional() ->connectAsync( request: [ 'connect_timeout' => 10, 'idle_timeout' => 180, 'ping_interval' => 30, 'close_timeout' => 5, ], listener: $listener );
连接状态:
CONNECTING
↓
STARTING
↓
READY
↓
FINISHING
↓
CLOSED
Session 状态:
NEW
↓
STARTING
↓
STREAMING
↓
FINISHING
↓
FINISHED
同一连接同时只允许一个活动 Session。
只有收到当前 Session 的终态后,才能创建下一个 Session。
21. Ark 流式文本接入实时 TTS
推荐链路:
Ark文本Delta
├── 推送App显示
└── appendText()发送给双向TTS
↓
音频Chunk
↓
推送App播放
注意:
- 只发送最终正文
TEXT_DELTA; - 不发送 Reasoning;
- 不重复发送 Delta;
- 不为每个 Delta 新建 TTS Session;
- 一条 AI 回复使用一个 TTS Session;
- AI 失败时取消 TTS Session;
- AI 完成后调用 TTS Session
finish(); - 等 TTS
SessionFinished后再结束 Connection。
22. AUC 文件语音识别
$submit = $speech ->asr() ->auc() ->submit([ 'audio' => [ 'url' => 'https://example.com/audio.mp3', 'format' => 'mp3', 'codec' => 'raw', 'rate' => 24000, 'bits' => 16, 'channel' => 1, ], 'request' => [ 'model_name' => 'bigmodel', 'enable_itn' => true, 'enable_punc' => true, 'show_utterances' => true, ], ]); $taskId = $submit->taskId; do { usleep(1_500_000); $result = $speech ->asr() ->auc() ->query( taskId: $taskId, resourceId: $submit->resourceId ); } while (!$result->state->terminal()); if (!$result->successful()) { throw new RuntimeException( $result->message ?? 'AUC recognition failed' ); } echo $result->text; foreach ($result->utterances as $utterance) { echo $utterance->text; }
上述轮询示例适用于 CLI 或专用任务 Worker。
在 Webman WebSocket Worker 中,不要使用 usleep() 阻塞事件循环,应使用:
Workerman\Timer
或将 AUC 轮询放入独立任务进程。
23. SAUC 实时 ASR
SAUC 使用双向 WebSocket 上传音频。
use LayBot\Ai\Contract\RealtimeAsrSessionInterface; use LayBot\Ai\DTO\Asr\RealtimeAsrResult; use LayBot\Ai\Provider\VolcSpeech\Asr\RealtimeAsrListener; use LayBot\Request\DTO\WebSocketCloseInfo; $listener = new class extends RealtimeAsrListener { public function onReady( RealtimeAsrSessionInterface $session ): void { /* * 初始化确认已经收到。 * 现在可以开始发送PCM音频。 */ } public function onResult( RealtimeAsrSessionInterface $session, RealtimeAsrResult $result ): void { echo $result->text; } public function onComplete( RealtimeAsrSessionInterface $session, RealtimeAsrResult $result ): void { echo 'Final: ', $result->text; } public function onBufferFull( RealtimeAsrSessionInterface $session ): void { // 暂停麦克风或上游音频生产。 } public function onBufferDrain( RealtimeAsrSessionInterface $session ): void { // 恢复音频生产。 } public function onError( RealtimeAsrSessionInterface $session, \Throwable $error ): void { report($error); } public function onClose( RealtimeAsrSessionInterface $session, WebSocketCloseInfo $info ): void { // 清理录音与业务状态。 } }; $session = $speech ->asr() ->realtime() ->connectAsync( request: [ 'endpoint' => 'async', 'audio' => [ 'format' => 'pcm', 'codec' => 'raw', 'rate' => 16000, 'bits' => 16, 'channel' => 1, ], 'request' => [ 'model_name' => 'bigmodel', 'enable_itn' => true, 'enable_punc' => true, 'show_utterances' => true, 'enable_nonstream' => false, ], ], listener: $listener, options: [ 'connect_timeout' => 10, 'idle_timeout' => 60, 'ping_interval' => 20, 'close_timeout' => 5, ] );
收到 onReady() 后发送音频:
$session->sendAudio($pcmChunk); $session->sendAudio( $lastPcmChunk, last: true );
或者:
$session->finish($lastPcmChunk);
last=true 时 SDK 会发送火山协议要求的最后一个负序号音频包。
24. AUC 与 SAUC 的选择
| 场景 | 推荐 |
|---|---|
| 已上传到 OSS 的完整录音 | AUC |
| 边录音边显示识别结果 | SAUC Async |
| 流式上传、整句返回 | SAUC Nostream |
| 业务正式存档识别 | AUC |
| 实时输入体验 | SAUC |
一期业务可以采用:
App录音
↓
私有OSS
↓
服务端AUC
↓
正式识别结果
实时增强:
App采集PCM
↓
WebSocket
↓
服务端SAUC
↓
临时识别结果
如果同时执行 AUC 和 SAUC,应评估双份识别费用。
25. 超时配置
支持:
| 配置 | 说明 |
|---|---|
connect_timeout |
TCP、代理和 TLS 连接超时 |
request_timeout |
请求总超时 |
idle_timeout |
流连续无数据超时 |
Chat 流推荐:
[
'connect_timeout' => 10,
'request_timeout' => 0,
'idle_timeout' => 120,
]
Ark PDF/Files 推荐:
[
'connect_timeout' => 15,
'request_timeout' => 120,
]
实时语音推荐:
[
'connect_timeout' => 10,
'idle_timeout' => 180,
'ping_interval' => 30,
'close_timeout' => 5,
]
request_timeout=0 表示不限制流总持续时间,但仍受 idle_timeout 保护。
26. Retry 与重复计费
生成式请求默认:
'retry' => false
这是为了避免:
- Chat 重复生成;
- Responses 重复计费;
- TTS 重复合成和播放;
- AUC 重复创建任务;
- 文件重复上传;
- Tool Call 重复执行。
只有明确幂等的查询才应启用重试:
$result = $speech ->asr() ->auc() ->query( taskId: $taskId, resourceId: $resourceId, options: [ 'idempotent' => true, 'retry' => [ 'max_attempts' => 3, ], ] );
如果已经收到部分文本或音频,不应自动重新执行完整生成请求。
ProviderException 会提供:
$error->retryable(); $error->partialOutputReceived(); $error->possiblyCharged();
27. 配置覆盖与多租户
27.1 单次网络覆盖
$result = $ai ->chat('ark') ->create( request: [...], options: [ 'base_uri' => 'https://internal-gateway.example.com', 'headers' => [ 'X-Tenant-Id' => 'tenant-1001', ], 'connect_timeout' => 5, 'request_timeout' => 60, 'proxy' => 'http://127.0.0.1:7890', ] );
27.2 单次 Endpoint 覆盖
Endpoint 应放在调用选项,不是模型请求 Body:
$result = $ai ->chat('openai') ->create( request: [ 'model' => 'model-name', 'messages' => [...], ], options: [ 'endpoint' => '/v1/chat/completions', ] );
27.3 多租户 API Key
$tenantChat = $ai ->provider( 'ark', [ 'api_key' => $tenantApiKey, ] ) ->chat();
存在实例级覆盖时,AiClient 不会缓存该 ProviderClient,防止租户 Key、Base URI 和 Header 污染其他调用。
28. 常驻进程与并发建议
AiClient 可以在单个 Webman Worker 生命周期内复用:
final class AiService { public function __construct( private readonly \LayBot\Ai\AiClient $ai ) { } }
以下对象必须按请求或会话创建:
Chat流状态机
Responses流状态机
TTS Session
ASR Session
Listener业务状态
音频临时文件
Cancellation Handle
不要:
- 在不同用户间共享同一个可变 Listener;
- 将 Session 保存为全局静态变量;
- 在多个 Worker 进程间传递连接对象;
- 在 WebSocket Worker 中执行长时间同步请求;
- 在回调内执行大量 CPU 密集任务;
- 忽略
onBufferFull(); - 将音频 Base64 放入普通 JSON WebSocket。
服务端向 App 转发实时音频时推荐:
控制消息:JSON文本帧
音频Chunk:WebSocket二进制帧
29. 异常体系
基础异常:
AiException
├── ConfigurationException
├── ValidationException
├── ProtocolException
├── ProviderException
└── UnsupportedCapabilityException
示例:
use LayBot\Ai\Exception\ProviderException; use LayBot\Ai\Exception\ValidationException; try { $result = $ai ->chat('ark') ->create([...]); } catch (ValidationException $error) { // 请求参数错误。 } catch (ProviderException $error) { echo $error->provider(); echo $error->providerCode(); echo $error->requestId(); echo $error->httpStatus(); if ($error->possiblyCharged()) { // 不要直接重复生成。 } if ( $error->retryable() && !$error->partialOutputReceived() ) { // 由业务幂等策略决定是否重试。 } }
30. 日志与敏感信息
SDK 默认注册以下敏感 Header:
Authorization
Proxy-Authorization
X-Api-Key
X-Api-Token
X-Goog-Api-Key
X-Api-App-Key
X-Api-Access-Key
自定义:
[
'sensitive_headers' => [
'X-Gateway-Credential',
'X-Tenant-Secret',
],
'options' => [
'log_bodies' => false,
],
]
生产环境不要记录:
- 完整 Prompt;
- 用户心理咨询内容;
- 音频二进制;
- Base64 音频;
- 文件签名 URL;
- API Key;
- 原始身份证明;
- 未脱敏的模型响应。
31. 模型能力注册
未知模型能力默认不会被武断阻断:
$capabilities = $ai ->provider('ark') ->capabilities('new-model');
项目可以注册能力:
$ai = new \LayBot\Ai\AiClient([ 'capabilities' => [ 'ark' => [ 'custom-model' => [ 'supports_vision' => true, 'supports_tools' => true, 'supports_reasoning' => true, 'max_context_tokens' => 32768, ], ], ], 'providers' => [ 'ark' => [ 'api_key' => getenv('ARK_API_KEY'), ], ], ]);
能力状态:
yes
no
unknown
unknown 默认不阻断新模型。
32. LayBot 灵语智教平台能力
通过 LayBot Vendor 可以访问平台能力:
use LayBot\Ai\Vendor\LayBot; $laybot = new LayBot([ 'api_key' => getenv('LAYBOT_API_KEY'), ]);
Chat:
$result = $laybot->chat()->create([ 'model' => 'LB-Cosmos', 'messages' => [ [ 'role' => 'user', 'content' => '为初二学生讲解浮力定律。', ], ], 'edu_features' => [ 'difficulty' => 'middle_school', 'tiered' => true, ], ]);
平台资源入口:
$laybot->files(); $laybot->batches(); $laybot->embeddings(); $laybot->fineTuning(); $laybot->images(); $laybot->audio(); $laybot->portal(); $laybot->api();
教育扩展 API 包括:
文档提取
作文批改
口语评测
听力填空
习题生成
智能组卷
学习报告
路径推荐
知识追踪
数学批改
OCR
具体可用模型、计费、端点和合规能力以:
- LayBot 灵语智教官网
- 实际控制台
- 对应服务协议
为准。
33. 旗舰教育模型示例
| 教育场景 | 方案 | 模型示例 | 代号 |
|---|---|---|---|
| 口语评测 | 实时识别与表达分析 | 灵语·语韵 | LB-Phona |
| 能力素养测评 | 综合分析与成长建议 | 灵语·慧学 | LB-Skillwise |
| K12 分层教学 | 梯度内容生成 | 明心·洞玄 | LB-Insight |
| 智能组卷批改 | 图文试卷处理 | 灵语·玄穹 | LB-Aethel |
| 学业诊断 | 薄弱点定位 | 灵语·太初 | LB-Primordius |
| 跨学科教学 | 知识关联推理 | 灵语·寰宇 | LB-Cosmos |
模型名称仅作为 LayBot 平台产品示例。
实际开放范围、价格和能力以控制台为准。
34. 与 Laybot MPA 前端集成
本项目是服务端 PHP SDK,不应将供应商 API Key 写入前端组件。
推荐:
<laybot-ai-chat endpoint="/api/ai/chat" model="LB-Cosmos" prompt="用高中难度讲解牛顿第二定律" ></laybot-ai-chat>
正确链路:
Laybot MPA组件
↓ 用户登录态和业务Ticket
Webman业务接口
↓
laybot/ai-sdk
↓
模型供应商
禁止:
<laybot-ai-chat api-key="真实供应商Key">
Laybot MPA/SPA 前端框架相关信息:
- 官网:https://www.laybot.cn
- 发明专利申请号:
2025108367676
35. 安全与合规边界
SDK 提供:
- 服务端凭证管理入口;
- Header 脱敏;
- TLS 验证;
- 请求大小限制;
- 异常边界;
- 请求 ID;
- 重试安全默认值;
- 多租户配置隔离。
SDK 本身不自动完成:
- GDPR 法律合规;
- K12 内容审核;
- 用户授权;
- 数据保留策略;
- 医疗或心理咨询安全审查;
- 供应商账单结算;
- 敏感词业务规则;
- 内容版权审核。
这些能力应由:
LayBot平台服务
业务项目
部署区域
供应商配置
组织合规制度
共同完成。
36. 当前限制
当前 2.x 的明确边界:
- 不管理业务数据库;
- 不管理用户额度;
- 不负责 OSS;
- 不保存聊天消息;
- 不负责浏览器 CORS;
- 不向 App 签发火山临时 Token;
- 不提供声音复刻训练完整生命周期;
- 不自动恢复中断的 WebSocket Session;
- 不对生成式请求自动重试;
- 不支持同一统一结果中的多候选;
- 异步 API 依赖 Workerman 事件循环;
- 暂无 Swoole、Amp、ReactPHP 原生 Transport;
- 供应商未公开的私有鉴权协议不会在 SDK 中猜测实现。
37. 测试
37.1 静态检查与单元测试
composer validate --strict
composer dump-autoload \
--optimize \
--strict-psr
php tools/lint.php
composer test
composer analyse
当前验收基线:
PHPUnit Unit:63 tests
Assertions:177
PHPStan:153/153
37.2 小毅 AI 集成测试
vendor/bin/phpunit \ --configuration phpunit.xml.dist \ --testsuite integration-xiaoyi \ --colors=always \ --display-skipped \ --fail-on-skipped
当前验收基线:
17 tests
93 assertions
0 failures
0 errors
0 skipped
已连续执行三轮通过。
37.3 全部集成测试
composer test:integration
38. 生产发布检查
composer validate --strict
composer dump-autoload --optimize --strict-psr
php tools/lint.php
composer test
composer analyse
composer audit
git diff --check
检查敏感文件:
git ls-files | grep -E \ '(^|/)(\.env|.*\.pid|.*\.log)$'
检查 Worker 源码目录污染:
find tests/Integration/VolcSpeech/bin \ -maxdepth 1 \ -type f \ \( \ -name '*.pid' \ -o -name '*.pid.lock' \ -o -name '*.status' \ -o -name '*.log' \ -o -name '*.stdout.log' \ \) \ -print
发布前清理:
rm -rf \ .phpunit.cache \ .phpstan.cache \ tests/Integration/.runtime
真实 API Key 必须保存在本地 .env 或密钥管理系统中,不能进入版本库。
39. Webman 生产建议
普通短请求
可以使用同步 API:
$result = $ai ->chat('deepseek') ->create([...]);
长时间 Chat、Responses、实时 TTS/ASR
优先使用异步 API:
streamAsync()
responses()->streamAsync()
tts()->bidirectional()->connectAsync()
asr()->realtime()->connectAsync()
进程隔离
推荐:
WebSocket Gateway
AI Chat Worker
TTS Stream Gateway
ASR Stream Gateway
AUC Task Worker
不要让一个 Worker 同时承担:
- 大文件解析;
- CPU 密集 Embedding;
- 大量实时音频连接;
- 阻塞式模型请求;
- 用户 WebSocket 心跳。
40. 路线图
- 更多供应商真实账号矩阵;
- OpenAI、Gemini、Anthropic 完整集成验收;
- 火山 HTTP Chunked TTS 专项验收;
- 声音复刻训练管理;
- 更多语音格式和字幕 DTO;
- WebSocket 故障注入测试;
- 异步并发压力测试;
- Webman Service Provider;
- Laravel、ThinkPHP、Symfony 集成示例;
- 指标和 OpenTelemetry;
- 更多供应商 Embedding 与 Rerank;
- PHP 8.1~8.4 CI 矩阵。
41. 贡献指南
git clone https://github.com/laybot/ai-sdk.git cd ai-sdk composer install composer validate --strict composer test composer analyse composer audit
提交代码前必须确保:
Composer Validate通过
严格PSR-4通过
PHP Lint通过
PHPUnit通过
PHPStan通过
没有提交真实API Key
没有提交用户Prompt和音频
没有提交签名URL
没有提交运行日志和PID
欢迎提交 Issue 和 Pull Request。
42. LayBot 系列项目
LayBot 专注于现代 Web 工程、教育智能、知识管理和 AI 基础设施。
- LayBot 灵语智教 AI
- Laybot 现代 MPA 工程平台
laybot/request-sdklaybot/ai-sdklaybot/storage-sdk
如果本项目对你的 PHP AI 工程有帮助,欢迎 Star、反馈和参与建设。
43. License、NOTICE 与署名
本项目采用 Apache License 2.0 开源协议。
Copyright © 2025-2026 Larry / LayBot
LayBot LingTeach AI
https://ai.laybot.cn
https://www.laybot.cn
Apache License 2.0 允许:
- 商业使用;
- 修改;
- 分发;
- 专利授权范围内使用;
- 在遵守许可证的情况下闭源集成。
分发本项目或其衍生版本时,应按照 Apache License 2.0 的要求:
- 保留
LICENSE; - 保留适用的版权声明;
- 标明修改内容;
- 如果发行包包含
NOTICE,保留其中适用的归属说明。
LayBot、Laybot、灵语智教、LayBot LingTeach AI 及相关标识属于其权利人。Apache License 2.0 不授予商标使用权。
技术与产品信息:
- LayBot 灵语智教:https://ai.laybot.cn
- Laybot MPA:https://www.laybot.cn
LayBot · 灵语智教
稳定连接模型,让 PHP 专注业务。