PHPackages                             ez-php/ai - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. ez-php/ai

ActiveLibrary[API Development](/categories/api)

ez-php/ai
=========

Multi-provider AI client for ez-php — unified driver-based abstraction over OpenAI, Anthropic, Gemini, and Mistral

1.11.1(4w ago)019MITPHPPHP ^8.5CI passing

Since Apr 19Pushed 4w agoCompare

[ Source](https://github.com/ez-php/ai)[ Packagist](https://packagist.org/packages/ez-php/ai)[ Docs](https://github.com/ez-php/ai)[ RSS](/packages/ez-php-ai/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (8)Versions (12)Used By (0)

ez-php/ai
=========

[](#ez-phpai)

Multi-provider AI client for ez-php. Supports chat completions, streaming, tool calling, and embeddings across OpenAI, Anthropic, Gemini, and Mistral.

---

Installation
------------

[](#installation)

```
composer require ez-php/ai
```

Requires PHP 8.5 and `ez-php/http-client`.

---

Configuration
-------------

[](#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'),
    ],

    'log' => [
        'inner_driver' => env('AI_LOG_INNER_DRIVER', 'openai'),
    ],
];
```

### Driver options

[](#driver-options)

`AI_DRIVER` valueDescription`openai`OpenAI chat completions API`anthropic`Anthropic Messages API`gemini`Google Gemini generateContent API`mistral`Mistral AI (OpenAI-compatible)`log`Decorates another driver with `error_log` output`null`Returns empty responses; useful in tests### Environment variables

[](#environment-variables)

VariableDefaultDescription`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`AI_LOG_INNER_DRIVER``openai`Driver wrapped by the `log` driver---

Basic usage
-----------

[](#basic-usage)

### Static facade

[](#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

[](#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
-----------------

[](#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
--------

[](#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
---------

[](#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 four production drivers (OpenAI, Anthropic, Gemini, Mistral) 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
------------

[](#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 `toolCallId` in tool result messages for Gemini conversations.

> **Streaming + tool calling:** Tool calls are only parsed in `complete()`. The `stream()` path yields text chunks only.

---

Embeddings
----------

[](#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');
```

DriverDefault modelEndpoint`OpenAiEmbeddingDriver``text-embedding-3-small``POST /v1/embeddings``GeminiEmbeddingDriver``text-embedding-004``POST /v1beta/models/{model}:embedContent`---

Response object
---------------

[](#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 — 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
-----------------

[](#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
-----------------------------------

[](#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
-------

[](#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
-------------

[](#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
```

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance94

Actively maintained with recent releases

Popularity9

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity58

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

11

Last Release

29d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/122030400?v=4)[AU9500](/maintainers/AU9500)[@AU9500](https://github.com/AU9500)

---

Top Contributors

[![AU9500](https://avatars.githubusercontent.com/u/122030400?v=4)](https://github.com/AU9500 "AU9500 (23 commits)")

---

Tags

phpaiopenaiGeminiclaudellmanthropicgptmistralez-php

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/ez-php-ai/health.svg)

```
[![Health](https://phpackages.com/badges/ez-php-ai/health.svg)](https://phpackages.com/packages/ez-php-ai)
```

###  Alternatives

[theodo-group/llphant

LLPhant is a library to help you build Generative AI applications.

1.7k371.6k5](/packages/theodo-group-llphant)[vizra/vizra-adk

Vizra Agent Development Kit - A comprehensive Laravel package for building intelligent AI agents.

29431.7k](/packages/vizra-vizra-adk)[mozex/anthropic-laravel

Laravel integration for the Anthropic API: facade, config publishing, install command, testing fakes, messages, streaming, tool use, thinking, and batches.

74287.1k1](/packages/mozex-anthropic-laravel)[mozex/anthropic-php

PHP client for the Anthropic API: messages, streaming, tool use, thinking, web search, code execution, batches, and more.

49480.9k16](/packages/mozex-anthropic-php)[claude-php/claude-php-sdk-laravel

Laravel integration for the Claude PHP SDK - Anthropic Claude API

5219.2k](/packages/claude-php-claude-php-sdk-laravel)[sbsaga/toon

🧠 TOON for Laravel — a compact, human-readable, and token-efficient data format for AI prompts &amp; LLM contexts. Perfect for ChatGPT, Gemini, Claude, Mistral, and OpenAI integrations (JSON ⇄ TOON).

6342.4k](/packages/sbsaga-toon)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
