PHPackages                             knivey/amphp-openai - 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. knivey/amphp-openai

ActiveLibrary[API Development](/categories/api)

knivey/amphp-openai
===================

v1.2.0(1mo ago)012↓85.7%MITPHPPHP ^8.5CI passing

Since Jun 6Pushed 1mo agoCompare

[ Source](https://github.com/knivey/amphp-openai)[ Packagist](https://packagist.org/packages/knivey/amphp-openai)[ RSS](/packages/knivey-amphp-openai/feed)WikiDiscussions master Synced 1w ago

READMEChangelog (3)Dependencies (10)Versions (5)Used By (0)

knivey/amphp-openai
===================

[](#kniveyamphp-openai)

[![CI](https://github.com/knivey/amphp-openai/actions/workflows/ci.yml/badge.svg)](https://github.com/knivey/amphp-openai/actions/workflows/ci.yml)[![PHP Version](https://camo.githubusercontent.com/45832ca1fc7df02c8567cf9b8a96b2d256025cf2758006ebc1a023cffa07ede3/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6b6e697665792f616d7068702d6f70656e6169)](https://packagist.org/packages/knivey/amphp-openai)[![License](https://camo.githubusercontent.com/05d337b9cabd0786012ca548f25cb03911b8917fecc3d266a0896bacc4a77ccc/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6b6e697665792f616d7068702d6f70656e6169)](https://packagist.org/packages/knivey/amphp-openai)

An async PHP wrapper around the [OpenAI Chat Completions API](https://platform.openai.com/docs/api-reference/chat), built on [amphp v3](https://amphp.org/). Designed as a reusable library for any PHP project that needs OpenAI integration with full API coverage.

Requirements
------------

[](#requirements)

- PHP 8.5+
- [amphp/http-client](https://amphp.org/http-client) ^5.0

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

[](#installation)

```
composer require knivey/amphp-openai
```

Quick Start
-----------

[](#quick-start)

```
use Knivey\OpenAi\OpenAiClient;
use Knivey\OpenAi\Request\ChatRequest;
use Knivey\OpenAi\Request\Message;

$client = new OpenAiClient('sk-...');

$response = $client->chatCompletion(new ChatRequest(
    model: 'gpt-4o',
    messages: [
        Message::system('You are a helpful assistant.'),
        Message::user('Hello!'),
    ],
));

echo $response->choices[0]->message->content;
echo $response->usage->totalTokens . " tokens used\n";
```

Streaming
---------

[](#streaming)

```
$stream = $client->chatCompletionStream(new ChatRequest(
    model: 'gpt-4o',
    messages: [Message::user('Tell me a story')],
    streamOptions: new StreamingOptions(includeUsage: true),
));

foreach ($stream as $chunk) {
    echo $chunk->choices[0]->delta['content'] ?? '';
}
```

Multimodal (Images, Audio, Files)
---------------------------------

[](#multimodal-images-audio-files)

```
use Knivey\OpenAi\Request\Content\TextPart;
use Knivey\OpenAi\Request\Content\ImagePart;
use Knivey\OpenAi\Request\Content\AudioPart;
use Knivey\OpenAi\Request\Content\FilePart;

$response = $client->chatCompletion(new ChatRequest(
    model: 'gpt-4o',
    messages: [
        Message::user([
            new TextPart('What is in this image?'),
            // Pass a URL — OpenAI fetches it server-side, no download on your end
            ImagePart::url('https://example.com/photo.jpg', 'high'),
            // Or pass raw base64 bytes directly — wrapped into a data URI for you
            ImagePart::base64($base64Data, 'image/png'),
        ]),
    ],
));
```

Tool Calling
------------

[](#tool-calling)

### Manual (JSON Schema)

[](#manual-json-schema)

```
use Knivey\OpenAi\Request\Tool\FunctionTool;
use Knivey\OpenAi\Request\Tool\CustomTool;

$tools = [
    new FunctionTool(
        'get_weather',
        description: 'Get the current weather',
        parameters: [
            'type' => 'object',
            'properties' => [
                'location' => ['type' => 'string'],
            ],
            'required' => ['location'],
        ],
        strict: true,
    ),
    new CustomTool('my_custom_tool', description: 'A custom tool'),
];

$response = $client->chatCompletion(new ChatRequest(
    model: 'gpt-4o',
    messages: [Message::user('What is the weather in SF?')],
    tools: $tools,
    toolChoice: 'auto',
));

$toolCall = $response->choices[0]->message->toolCalls[0];
echo $toolCall->function['name'];     // get_weather
echo $toolCall->function['arguments']; // {"location":"San Francisco"}
```

### Reflection-Based (Auto Schema from PHP Callables)

[](#reflection-based-auto-schema-from-php-callables)

`ReflectionTool` uses PHP type hints to generate JSON Schema automatically:

```
use Knivey\OpenAi\Request\Tool\ReflectionTool;
use Knivey\OpenAi\Request\Tool\ToolRegistry;

$registry = ToolRegistry::create()
    ->add(ReflectionTool::fromCallable(
        fn(string $location, string $unit = 'celsius'): string => getWeather($location, $unit),
        name: 'get_weather',
        description: 'Get the current weather',
    ))
    ->add(ReflectionTool::fromCallable(
        fn(string $query, int $limit = 5): array => searchWeb($query, $limit),
        name: 'search_web',
        description: 'Search the web',
    ));

// Use the tools in a request — registry provides the schema array
$response = $client->chatCompletion(new ChatRequest(
    model: 'gpt-4o',
    messages: [Message::user('What is the weather in SF?')],
    tools: $registry->getTools(),
    toolChoice: 'auto',
));

// Dispatch tool calls back to your callables
foreach ($response->choices[0]->message->toolCalls as $toolCall) {
    $result = $registry->dispatch(
        $toolCall->function['name'],
        $toolCall->function['arguments'],
    );
}
```

**Type mapping:** `string` → `"string"`, `int` → `"integer"`, `float` → `"number"`, `bool` → `"boolean"`, `array` → `"object"`. Parameters without defaults are required; parameters with defaults include `"default"` in the schema.

**Backed enums** are auto-detected — the enum values populate the `"enum"` constraint:

```
enum Unit: string {
    case Celsius = 'celsius';
    case Fahrenheit = 'fahrenheit';
}

// fn(Unit $unit = Unit::Celsius) generates:
// {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius"}
```

### Attribute-Based Tools

[](#attribute-based-tools)

Use `#[ToolDescription]` and `#[ToolParam]` on class methods:

```
use Knivey\OpenAi\Request\Tool\Attribute\ToolDescription;
use Knivey\OpenAi\Request\Tool\Attribute\ToolParam;

class WeatherTools {
    #[ToolDescription('Get the current weather')]
    public function get_weather(
        string $location,
        #[ToolParam(description: 'Temperature unit', enum: ['celsius', 'fahrenheit'])]
        string $unit = 'celsius',
    ): string {
        return getWeather($location, $unit);
    }
}

$tools = new WeatherTools();
$registry = ToolRegistry::create()
    ->add(ReflectionTool::fromMethod([$tools, 'get_weather']));
```

### Automatic Tool-Call Loop

[](#automatic-tool-call-loop)

`chatCompletionWithTools` runs the full loop — dispatch tool calls, append results, re-send — until the API returns a final answer:

```
$response = $client->chatCompletionWithTools(
    new ChatRequest(
        model: 'gpt-4o',
        messages: [Message::user('What is the weather in SF and NYC?')],
        tools: $registry->getTools(),
        toolChoice: 'auto',
    ),
    $registry,
);

echo $response->choices[0]->message->content;
```

Handles parallel tool calls, configurable max iterations (default 10), and graceful error recovery — if a tool invocation fails, the error is sent back to the model so it can retry or adapt.

Audio Output
------------

[](#audio-output)

```
use Knivey\OpenAi\Request\Audio\AudioOutputOptions;

$response = $client->chatCompletion(new ChatRequest(
    model: 'gpt-4o-audio-preview',
    messages: [Message::user('Say hello in a friendly voice')],
    modalities: ['text', 'audio'],
    audio: new AudioOutputOptions('alloy', 'mp3'),
));

$audio = $response->choices[0]->message->audio;
echo $audio['transcript'];
```

Custom Base URL
---------------

[](#custom-base-url)

Works with any OpenAI-compatible API (Ollama, Together, Groq, etc.):

```
$client = new OpenAiClient(
    apiKey: 'your-key',
    baseUrl: 'https://api.groq.com/openai/v1',
);
```

Error Handling
--------------

[](#error-handling)

```
use Knivey\OpenAi\Exception\RateLimitException;
use Knivey\OpenAi\Exception\AuthenticationException;
use Knivey\OpenAi\Exception\ApiException;

try {
    $response = $client->chatCompletion($request);
} catch (RateLimitException $e) {
    // 429 — automatically retried up to 3 times with Retry-After header
} catch (AuthenticationException $e) {
    // 401/403
} catch (ApiException $e) {
    // All other HTTP errors
    echo $e->getStatusCode();
    echo $e->getResponseBody();
}
```

Cancellation
------------

[](#cancellation)

```
use Amp\Cancellation;

$response = $client->chatCompletion($request, cancellation: $cancellation);
```

All Request Parameters
----------------------

[](#all-request-parameters)

`ChatRequest` supports the full Chat Completions API surface:

ParameterTypeDescription`model``string`Model ID (required)`messages``list`Conversation messages (required)`temperature``?float`Sampling temperature (0-2)`topP``?float`Nucleus sampling threshold`maxTokens``?int`Max tokens to generate`maxCompletionTokens``?int`Upper bound including reasoning`n``?int`Number of choices`stop``string|list|null`Stop sequences`stream``?bool`Enable streaming (set automatically)`streamOptions``?StreamingOptions`Stream options (include\_usage, etc.)`tools``list|null`Available tools`toolChoice``string|array|null`Tool selection mode`parallelToolCalls``?bool`Allow parallel tool calls`responseFormat``?array`Structured output format`logprobs``?bool`Return log probabilities`topLogprobs``?int`Number of top logprobs (0-20)`logitBias``array|null`Token likelihood modification`seed``?int`Deterministic sampling seed`frequencyPenalty``?float`Frequency penalty (-2.0 to 2.0)`presencePenalty``?float`Presence penalty (-2.0 to 2.0)`modalities``list|null`Output modalities (text, audio)`audio``?AudioOutputOptions`Audio output configuration`store``?bool`Store completion for retrieval`metadata``?array`Developer metadata`serviceTier``?string`Processing tier`user``?string`End-user identifier`prediction``?array`Predicted output for latency`reasoningEffort``?string`Reasoning effort (low/medium/high)`webSearch``?array`Web search tool config`moderation``?array`Moderation settingsDevelopment
-----------

[](#development)

```
composer test        # Run tests
composer stan        # PHPStan level 9
composer cs:check    # PSR-12 check
composer cs:fix      # PSR-12 fix
composer check       # Run all checks
```

License
-------

[](#license)

MIT

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance90

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity54

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 ~0 days

Total

4

Last Release

49d ago

PHP version history (2 changes)v1.0.0PHP ^8.3

v1.1.1PHP ^8.5

### Community

Maintainers

![](https://www.gravatar.com/avatar/46a26dcd906261aed960a49fa54d83fc74046ced17d9bce0f68f751236f706de?d=identicon)[knivey](/maintainers/knivey)

---

Top Contributors

[![knivey](https://avatars.githubusercontent.com/u/4189618?v=4)](https://github.com/knivey "knivey (47 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/knivey-amphp-openai/health.svg)

```
[![Health](https://phpackages.com/badges/knivey-amphp-openai/health.svg)](https://phpackages.com/packages/knivey-amphp-openai)
```

###  Alternatives

[danog/madelineproto

Async PHP client API for the telegram MTProto protocol.

3.5k902.0k24](/packages/danog-madelineproto)[amphp/http-server

A non-blocking HTTP application server for PHP based on Amp.

1.3k6.7M115](/packages/amphp-http-server)[putyourlightson/craft-blitz

Intelligent static page caching for creating lightning-fast sites.

155484.7k37](/packages/putyourlightson-craft-blitz)[kelunik/acme

ACME library written in PHP.

121610.5k3](/packages/kelunik-acme)

PHPackages © 2026

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