ez-php/ai
Composer 安装命令:
composer require ez-php/ai
包简介
Multi-provider AI client for ez-php — unified driver-based abstraction over OpenAI, Anthropic, Gemini, and Mistral
README 文档
README
Multi-provider AI client for ez-php. Supports chat completions, streaming, tool calling, and embeddings across OpenAI, Anthropic, Gemini, Mistral, and Grok.
Installation
composer require ez-php/ai
Requires PHP 8.5 and ez-php/http-client.
Configuration
Register AiServiceProvider in your application and add config/ai.php:
// config/ai.php return [ 'driver' => env('AI_DRIVER', 'openai'), 'openai' => [ 'api_key' => env('OPENAI_API_KEY', ''), 'model' => env('OPENAI_MODEL', 'gpt-4o-mini'), 'base_url' => env('OPENAI_BASE_URL', 'https://api.openai.com'), ], 'anthropic' => [ 'api_key' => env('ANTHROPIC_API_KEY', ''), 'model' => env('ANTHROPIC_MODEL', 'claude-sonnet-4-6'), 'api_version' => env('ANTHROPIC_API_VERSION', '2023-06-01'), ], 'gemini' => [ 'api_key' => env('GEMINI_API_KEY', ''), 'model' => env('GEMINI_MODEL', 'gemini-2.0-flash'), ], 'mistral' => [ 'api_key' => env('MISTRAL_API_KEY', ''), 'model' => env('MISTRAL_MODEL', 'mistral-small-latest'), 'base_url' => env('MISTRAL_BASE_URL', 'https://api.mistral.ai'), ], 'grok' => [ 'api_key' => env('GROK_API_KEY', ''), 'model' => env('GROK_MODEL', 'grok-3-mini'), 'base_url' => env('GROK_BASE_URL', 'https://api.x.ai'), ], 'log' => [ 'inner_driver' => env('AI_LOG_INNER_DRIVER', 'openai'), ], ];
Driver options
AI_DRIVER value |
Description |
|---|---|
openai |
OpenAI chat completions API |
anthropic |
Anthropic Messages API |
gemini |
Google Gemini generateContent API |
mistral |
Mistral AI (OpenAI-compatible) |
grok |
xAI Grok (OpenAI-compatible) |
log |
Decorates another driver with error_log output |
null |
Returns empty responses; useful in tests |
Environment variables
| Variable | Default | Description |
|---|---|---|
AI_DRIVER |
null |
Active driver |
OPENAI_API_KEY |
— | OpenAI API key |
OPENAI_MODEL |
gpt-4o-mini |
Default OpenAI model |
OPENAI_BASE_URL |
https://api.openai.com |
Base URL (Azure / proxy support) |
ANTHROPIC_API_KEY |
— | Anthropic API key |
ANTHROPIC_MODEL |
claude-sonnet-4-6 |
Default Anthropic model |
ANTHROPIC_API_VERSION |
2023-06-01 |
anthropic-version header value |
GEMINI_API_KEY |
— | Google AI API key |
GEMINI_MODEL |
gemini-2.0-flash |
Default Gemini model |
MISTRAL_API_KEY |
— | Mistral API key |
MISTRAL_MODEL |
mistral-small-latest |
Default Mistral model |
MISTRAL_BASE_URL |
https://api.mistral.ai |
Mistral base URL |
GROK_API_KEY |
— | xAI (Grok) API key |
GROK_MODEL |
grok-3-mini |
Default Grok model |
GROK_BASE_URL |
https://api.x.ai |
Grok base URL |
AI_LOG_INNER_DRIVER |
openai |
Driver wrapped by the log driver |
Basic usage
Static facade
use EzPhp\Ai\Ai; use EzPhp\Ai\Request\AiRequest; $response = Ai::complete(AiRequest::make('What is the capital of France?')); echo $response->content(); // "Paris"
Direct driver injection
use EzPhp\Ai\AiClientInterface; use EzPhp\Ai\Request\AiRequest; class MyService { public function __construct(private AiClientInterface $ai) {} public function ask(string $question): string { $response = $this->ai->complete(AiRequest::make($question)); return $response->content(); } }
Building requests
AiRequest is immutable. All wither methods return new instances.
use EzPhp\Ai\Request\AiRequest; use EzPhp\Ai\Message\AiMessage; // Single user message $request = AiRequest::make('Hello'); // Explicit message list $request = AiRequest::withMessages( AiMessage::system('You are a helpful assistant.'), AiMessage::user('What is 2 + 2?'), ); // Chain withers $request = AiRequest::make('Explain async/await') ->withModel('gpt-4o') ->withTemperature(0.7) ->withMaxTokens(500) ->withSystemPrompt('You are a concise technical writer.'); // Append a message $request = $request->addMessage(AiMessage::user('Give an example in PHP.'));
Messages
use EzPhp\Ai\Message\AiMessage; use EzPhp\Ai\Message\ContentPart; // Plain text AiMessage::user('Hello'); AiMessage::assistant('Hi there!'); AiMessage::system('You are a helpful assistant.'); // Multimodal (text + image URL) AiMessage::userWithParts([ ContentPart::text('What is in this image?'), ContentPart::imageUrl('https://example.com/image.png'), ]);
Streaming
Drivers that implement StreamingAiClientInterface support streaming responses.
use EzPhp\Ai\Ai; use EzPhp\Ai\Request\AiRequest; use EzPhp\Ai\StreamingAiClientInterface; $client = Ai::getClient(); if ($client instanceof StreamingAiClientInterface) { $stream = $client->stream(AiRequest::make('Tell me a story.')); foreach ($stream as $chunk) { echo $chunk->content(); if ($chunk->isFinal()) { echo PHP_EOL; echo 'Finish reason: ' . $chunk->finishReason()?->value . PHP_EOL; } } } // Or collect the full text at once $text = $stream->collect();
All five production drivers (OpenAI, Anthropic, Gemini, Mistral, Grok) implement StreamingAiClientInterface.
Note: Streaming uses SSE post-hoc parsing — the full response body is buffered, then parsed line-by-line. True chunked transfer is not supported.
Tool calling
Define tools, attach them to the request, and handle tool calls in a loop.
use EzPhp\Ai\Ai; use EzPhp\Ai\Request\AiRequest; use EzPhp\Ai\Message\AiMessage; use EzPhp\Ai\Response\FinishReason; use EzPhp\Ai\Tool\ToolDefinition; $getWeather = new ToolDefinition( name: 'get_weather', description: 'Returns the current weather for a city.', parameters: [ 'type' => 'object', 'properties' => [ 'city' => ['type' => 'string', 'description' => 'The city name'], ], 'required' => ['city'], ], ); $request = AiRequest::make('What is the weather in Berlin?') ->withTools($getWeather); $response = Ai::complete($request); // Agentic loop while ($response->finishReason() === FinishReason::TOOL_CALL) { $toolMessages = []; foreach ($response->toolCalls() as $call) { $result = match ($call->name()) { 'get_weather' => json_encode(['temp' => '18°C', 'condition' => 'Cloudy']), default => 'Unknown tool', }; $toolMessages[] = AiMessage::tool($result, $call->id()); } $request = $request ->addMessage(AiMessage::assistantWithToolCalls(...$response->toolCalls())) ->addMessage(...$toolMessages); // may need multiple addMessage calls $response = Ai::complete($request); } echo $response->content();
Gemini note: Gemini does not assign separate IDs to tool calls. The function name is used as the call ID. Use the function name as
toolCallIdin tool result messages for Gemini conversations.
Streaming + tool calling: Tool calls are only parsed in
complete(). Thestream()path yields text chunks only.
Embeddings
Use OpenAiEmbeddingDriver or GeminiEmbeddingDriver directly — embeddings are not wired through AiServiceProvider or the Ai facade.
use EzPhp\Ai\Driver\OpenAiConfig; use EzPhp\Ai\Driver\OpenAiEmbeddingDriver; use EzPhp\HttpClient\CurlTransport; use EzPhp\HttpClient\HttpClient; $driver = new OpenAiEmbeddingDriver( new HttpClient(new CurlTransport()), new OpenAiConfig(apiKey: $_ENV['OPENAI_API_KEY']), ); // Returns float[] $vector = $driver->embed('The quick brown fox'); // Override model $vector = $driver->embed('Hello world', 'text-embedding-3-large');
use EzPhp\Ai\Driver\GeminiConfig; use EzPhp\Ai\Driver\GeminiEmbeddingDriver; use EzPhp\HttpClient\CurlTransport; use EzPhp\HttpClient\HttpClient; $driver = new GeminiEmbeddingDriver( new HttpClient(new CurlTransport()), new GeminiConfig(apiKey: $_ENV['GEMINI_API_KEY']), ); // Default model: text-embedding-004 $vector = $driver->embed('The quick brown fox');
| Driver | Default model | Endpoint |
|---|---|---|
OpenAiEmbeddingDriver |
text-embedding-3-small |
POST /v1/embeddings |
GeminiEmbeddingDriver |
text-embedding-004 |
POST /v1beta/models/{model}:embedContent |
Response object
$response = Ai::complete($request); $response->content(); // string — generated text $response->finishReason(); // FinishReason enum: STOP, LENGTH, TOOL_CALL, CONTENT_FILTER, ERROR $response->usage(); // TokenUsage|null $response->toolCalls(); // list<ToolCall> — non-empty when finishReason === TOOL_CALL $response->hasToolCalls(); // bool $response->rawBody(); // string — raw JSON from the provider $usage = $response->usage(); if ($usage !== null) { $usage->inputTokens(); // int $usage->outputTokens(); // int $usage->totalTokens(); // int }
Logging decorator
Wrap any driver to log every request and response via error_log:
// config/ai.php return [ 'driver' => 'log', 'log' => ['inner_driver' => 'openai'], 'openai' => ['api_key' => env('OPENAI_API_KEY')], ];
Or construct LogDriver manually with a custom logger closure:
use EzPhp\Ai\Driver\LogDriver; $driver = new LogDriver( $innerDriver, function (string $level, string $message, array $context): void { $this->logger->log($level, $message, $context); }, );
OpenAI-compatible proxies and Azure
OpenAiDriver and MistralDriver accept a base_url config key, making them compatible with Azure OpenAI and any OpenAI-compatible proxy:
// config/ai.php — Azure OpenAI 'openai' => [ 'api_key' => env('AZURE_OPENAI_API_KEY'), 'model' => 'gpt-4o', 'base_url' => env('AZURE_OPENAI_ENDPOINT'), // e.g. https://my-resource.openai.azure.com ],
Testing
In unit tests, inject NullDriver or use FakeTransport from ez-php/http-client:
use EzPhp\Ai\Driver\NullDriver; use EzPhp\Ai\Request\AiRequest; $driver = NullDriver::withContent('Paris'); $response = $driver->complete(AiRequest::make('What is the capital of France?')); assertEquals('Paris', $response->content());
use EzPhp\Ai\Driver\OpenAiDriver; use EzPhp\Ai\Driver\OpenAiConfig; use EzPhp\Ai\Request\AiRequest; use EzPhp\HttpClient\FakeTransport; use EzPhp\HttpClient\HttpClient; $fake = new FakeTransport(); $fake->queue(200, '{"choices":[{"message":{"role":"assistant","content":"Paris"},"finish_reason":"stop"}],"usage":{"prompt_tokens":10,"completion_tokens":5,"total_tokens":15}}'); $driver = new OpenAiDriver( new HttpClient($fake), new OpenAiConfig('test-key'), ); $response = $driver->complete(AiRequest::make('Capital of France?')); assertEquals('Paris', $response->content());
Use Ai::resetClient() in tearDown() when tests touch the static facade to prevent state leaking between test cases.
Quality suite
# Inside Docker docker compose exec app composer full # Individual steps docker compose exec app composer analyse # PHPStan level 9 docker compose exec app composer cs # php-cs-fixer docker compose exec app composer test # PHPUnit
Start the development shell:
./start.sh
ez-php/ai 适用场景与选型建议
ez-php/ai 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 19 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 04 月 19 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「php」 「ai」 「Gemini」 「openai」 「gpt」 「llm」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 ez-php/ai 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 ez-php/ai 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 ez-php/ai 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
PHP 8.0+ OpenAI API client with fully typed/documented requests+responses models, guzzle and symfony/http-client support and async/parallel requests.
Estimate AI API costs before making expensive calls
A powerful package that seamlessly integrates OpenAI's advanced AI capabilities into your Laravel applications. This package offers quick setup and intuitive configuration to leverage AI models for chat, embeddings, and more.
AI Agent templates for Laravel development - Skills, Agents, and Workflows for enhanced coding assistance
AI discussion summaries for Flarum with real-time streaming.
Alfabank REST API integration
统计信息
- 总下载量: 19
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 42
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-04-19