Building a PHP SDK for Replicate AI
Tags: php, ai, sdk, tutorial
Published: 2024-02-15 15:36:08

If you haven’t already been introduced to [Replicate AI](https://replicate.com), it’s an awesome API that allows you to deploy custom AI models and access them through a HTTP interface.

However, there is no PHP SDK! Which, we cannot have. So I thought we should set about building one, and I can walk you through it as we go.

What spurred me to start building this, was I came across [PhotoAI](https://photoai.com/) on my hunt for cool things. After a few conversations with people I found out that it uses Replicate to do all of its image generation which was awesome!

So, SDKs. What are they? The technical term is “Software Development Kit”, but that doesn’t really mean much to us right. What it actually is, at least in this context, is that it is a library that allows you to perform actions on an external application using HTTP. Sounds more accurate right?

Where do we start when building an SDK? If you, like me, just though “at the beginning” then I salute you. We are one and the same. The first thing to do when starting to build a new SDK, is understand the resources we are dealing with. To achieve this, we need to read the API documentation for the API in question. You can find the Replicate API docs [here](https://replicate.com/docs/reference/http). I am not going to build a full PHP SDK right now, but I will build enough to show you how we go about doing it. This will also be open-source, so if you want to contribute and use it yourself you are welcome to!

Before we dive too deeply into the resources, let’s talk a little about what Replicate is so we know what our expectations are.

Replicate will allow you to build, train, and deploy open-source and custom AI models that run on CPUs and GPUs. Much like any cloud service, you pay based on CPU and/or GPU usage. It isn’t anything super new, but it is something that is very interesting! When building an SDK, you need to know why you are building it. In this scenario I want to build an SDK that will allow me to manage my AI models that I have deployed. Nothing I couldn’t do through the admin interface, however if I am running and deploying multiple models I might want to add an orchestration layer where I can programmatically control everything. On the other hand, I am an absolute nerd - so this sort of thing interests me.

Back to resources, we have the following:

- Models
- Versions
- Predications

All sounds very AI right? A Model is something like stable-diffusion or any open-source AI model (including one you upload yourself). A Version is a specific version of the Model, say we want a specific version of the Mistral AI Model, we would specifically choose this version. Predications are a little like requests. We get a prediction when we successfully ask a Model to do something. We can then query the prediction for the response with any content that the AI model needs to give us.

Overall, relatively simple. Looking at the documentation for Replicate the Node and Python SDKs are first party and well supported (nothing new there right). This is great for when you want to build i these languages. But in PHP. What would an integration look like in PHP? I am not talking about specific frameworks here, I mean the language itself. What I like to do is start by trying to understand the developer experience of how I would want this to work. So, let’s start with that

```php
// usual vendor autoloads and stuff goes here.

$replicate = new Replicate(
    auth: 'our-api-token',
);
```

Great, we have a place to start. So let’s so some experiments before we dive into some more specifics.

```php
// Let's say we want to get a list of models
$models = $replicate->models()->list();
```

For this we would expect that we can get a list of possible models to work with. In the API documentation this is: `https://api.replicate.com/v1/models` which is what I would have expected.

Next we may want to get a specific collection of models, say super resolution models for images.

```php
$models = $replicate->models()->collection('super-resolution');
```

So by now we know what we will have a resource called `Model` which we want to be able to call. Now that we have the potential models, we can choose one and move on. This is where we might want to look at a dedicated resource, or focus on sub-resources for versions. Which of the following makes the most sense to you:

```php
$version = $replicate->versions()->get('model-owner', 'model-name', 'version-id');

// OR

$version = $replicate->models()->for('model-owner', 'model-name')->version('version-id');
```

To me, I kind of like the first option. In PHP I like to use named arguments so it offers me the shortest path to get what I want, while allowing me to control the outcome using the parameters. The URL for this is: `https://api.replicate.com/v1/models/{model-owner}/{model-name}/versions/{version-id}`

If we think of this simply as building up the URL we could maybe do the following:

```php
$version = $replicate->models('model-owner')->for('model-name')->versions()->get('version-id');
```

But, the purpose of the SDK isn’t just to wrap the API up in accessors in a dumb way. We can use SDK-generators for that. Instead we want to make it feel almost conversational. That is where good developer experience comes into it. If we think about what we want to do, we would say:

> I want to get version X of model from this publisher.

To look at a real life example

> I want to get version `5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa` of the `hello-world` model from `replicate`.

In which case our original idea works well.

```php
$version = $replicate->versions()->get(
    owner: 'replicate',
    model: 'hello-world',
    version: '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa',
);
```

When you add the named parameters, and some realistic data is when you can tell whether it makes sense or not. This to me works well.

The last thing we need to think about is the Predictions. Predictions are tied to the user account, so only you can access them. Which makes it a top level resource in my opinion. Let’s flesh this one out.

```php
// Get all of my predictions
$predications = $replicate->predictions()->list();

// Get a specific prediction
$prediction = $replicate->predications()->get('prediction-id');

// Cancel a running prediction
$cancelled = $replicate->predictions()->cancel('prediction_id');

// Create a new prediction
$predication = $replicate->predictions()->create('version-id', 'input');
```

This to me makes a lot of sense. We want to work with predications, so we want to be able to list the ones we have, get a specific one, cancel a prediction, or create a new one. One thing to note here though is that when creating a new prediction each model will have a specific payload it expects - it isn’t a completely unified API, but it pretty close!

Now that we know what we want to achieve, let’s start the process.

For this I am going to be using a library I created some time ago now, that people have asked me about examples for. I built [PHP-SDK](https://github.com/JustSteveKing/php-sdk) as a “framework” for building SDKs in PHP that were interoperable with any potential PSRs.

This next part I will focus solely on the implementation, I won’t write tests or any ancillary files that I would typically add to a project; such as PhpStan configuration etc.

We start by building our SDK “client” which is the entry way to your SDK. It is something that will either be statically created, or injected using Dependency Injection. Everyone has their own approach that they prefer, and I am not here to judge.

```php
use JustSteveKing\Sdk\Client;

final class Replicate extends Client
{
    //
}
```

Believe it or not, that is all we need to do for a basic SDK. The base `Client` has methods you can override if you want to be specific about the HTTP client you want to use. However this “framework” uses [PSR-18](https://www.php-fig.org/psr/psr-18/) under the hood so that it can auto-discover the installed HTTP Client in your application. Now we have this in place, let’s create our first Resource class and add it to our client.

```php
use JustSteveKing\Sdk\Concerns\Resources;

final class Model
{
    use Resources\CanAccessClient;
    use Resources\CanCreateDataObjects;
    use Resources\CanCreateRequests;

    public function list()
    {
        // send the request.
    }

    public function collection(string $name)
    {
        // send the request
    }
}
```

Then we can add it to the Client class we created.

```php
use JustSteveKing\Sdk\Client;

final class Replicate extends Client
{
    public function models(): Model
    {
        return new Model(
            client: $this,
        );
    }
}
```

But wait, what about authenticating and all of that stuff? The beauty of a constructor.

```php
$replicate = new Replicate(
    apiToken: 'your-api-token',
    url: 'https://api.replicate.com',
);
```

When you extend the SDK Client itself, it comes with these parameters by default - meaning you don’t need to add them. With most of the SDKs I have built I have never needed much more than this for working with an API. Yes if you need a full OAuth flow then perhaps this framework isn’t right for you and you might want to look at how you work with this.

Back to the resource.

```php
use JustSteveKing\Sdk\Concerns\Resources;
use JustSteveKing\Tools\Http\Enums\Method;
use Ramsey\Collection\Collection;
use Throwable;

final class Model
{
    use Resources\CanAccessClient;
    use Resources\CanCreateDataObjects;
    use Resources\CanCreateRequests;

    public function list()
    {
        $request = $this->request(
	    method: Method::GET,
	    uri: '/models',
	);

	try {
	    $response = $this->client->send(
		request: $request,
	    );
	} catch (Throwable $exception) {
            throw new FailedToFetchModels(
                message: 'Failed to get a list of models from the API.',
                previous: $exception,
            );
        }

        return new Collection(
            collectionType: ModelObject::class,
            data: array_map(
                callback: static fn (array $data): ModelObject => ModelObject::make(
                    data: $data,
                ),
                array: (array) json_decode(
                    json: $response->getBody()->getContents(),
                    associative: true,
                    flags: JSON_THROW_ON_ERROR,
                ),
            ),
        );
    }
}
```

As you can see, we build the request then try to get a response. If an error occurs then we want to send a contextual exception with a relevant message - while passing back the exception that was actually triggered. Finally we are using a collection library by Ben Ramsey instead of the Laravel one, because it is a lot more lightweight. We map over the PSR-7 response body that has been transformed into an array, and call the static constructor of a data object for this resource.

All in all, it is smooth, simple, and fast. We can add more methods to call more endpoints, more resources to add more capabilities to our SDK, and sub-resources to our resources if we want to be able to chain method until the cows come home.

So, that is how I would go about building a PHP SDK for the Replicate AI API. I will publish the repository on GitHub once it is complete and tested. But hopefully this points you in the right direction for creating PHP SDKs for your own APIs, or 3rd party APIs that don’t have PHP support yet.