PHPackages                             sitehostnz/sthai - 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. sitehostnz/sthai

ActiveLibrary

sitehostnz/sthai
================

PHP client for the SiteHost AI Platform: inference, embeddings and reranking

v1.1.1(today)02↑2900%MITPHP &gt;=7.4

Since Jul 18Compare

[ Source](https://github.com/sitehostnz/sthai-php)[ Packagist](https://packagist.org/packages/sitehostnz/sthai)[ RSS](/packages/sitehostnz-sthai/feed)WikiDiscussions Synced today

READMEChangelog (4)Dependencies (3)Versions (7)Used By (0)

sthai-php
=========

[](#sthai-php)

[![Tests](https://github.com/sitehostnz/sthai-php/actions/workflows/tests.yml/badge.svg)](https://github.com/sitehostnz/sthai-php/actions/workflows/tests.yml)

A PHP client for the [SiteHost AI Platform](https://kb.sitehost.nz/ai-platform): inference, embeddings and reranking, with no runtime dependencies beyond `ext-curl` and `ext-json`. A port of the Python client, [sthai-py](https://github.com/sitehostnz/sthai-py), matching its functionality and wire format.

The SiteHost AI Platform
------------------------

[](#the-sitehost-ai-platform)

The [SiteHost AI Platform](https://kb.sitehost.nz/ai-platform) serves capable open-weight models at their full context windows - not heavily quantised cut-downs - with full control over system prompts and outputs. Everything runs on SiteHost's own hardware in their own New Zealand data centres, so your data never leaves the country, and request bodies are never stored: only usage metrics are kept for billing and performance monitoring.

Models are stable targets, too: each served model has a minimum one-year retention window and at least three months' deprecation notice, with guidance on any adjustments needed.

The platform currently serves three models, one per capability (see the [models page](https://kb.sitehost.nz/ai-platform/models) for the source of truth):

ModelPurposeContext window`Qwen/Qwen3.6-27B`Inference (chat, multimodal, thinking)262K`Qwen/Qwen3-VL-Embedding-8B`Embeddings (multimodal, Matryoshka, 4096 dims)32K`Qwen/Qwen3-VL-Reranker-8B`Reranking (multimodal, instruction-trained)32KInstallation
------------

[](#installation)

Requires PHP 7.4 or newer with `ext-curl` and `ext-json`. Install via [Composer](https://getcomposer.org/):

```
composer require sitehostnz/sthai
```

On PHP 8.0+ every optional parameter can be passed as a named argument, which the examples below use. On PHP 7.4 pass them positionally.

Getting started
---------------

[](#getting-started)

Create an API key in the SiteHost Control Panel (see [API keys](https://kb.sitehost.nz/ai-platform/api-keys)). The client reads it from the `STHAI_KEY` environment variable, or you can pass it explicitly:

```
use SthAI\Client;

$client = new Client();  // or new Client('your-api-key')
$response = $client->chat("What's the tallest mountain in New Zealand?");
echo $response->output()->text;
```

Usage
-----

[](#usage)

### Chat and history

[](#chat-and-history)

`chat()` keeps a conversation going: each successful call appends the user and assistant turns to the client's history, and later calls send it back. The system prompt is applied per call rather than stored.

```
$client->chat("I'm planning a tramping trip to Fiordland.");
$client->chat('What should I pack?');  // the model sees the earlier turn

$client->chat('Standalone question.', useHistory: false);  // neither sends nor records
$client->clearHistory();

$client->chat('Be brief: why is the sky blue?', systemPrompt: 'You are terse.');
```

The stored history can be read with `history()` and restored with `setHistory()`, so a conversation can be persisted and picked up later. `setWriteHistory(false)` stops `chat()` recording new turns (the stored history is kept and still sent); `writeHistory()` reads the current setting.

`historyUsage()` totals token usage across the calls that built the stored history. Each call resends the conversation so far, so input tokens count what the server processed (as billed), not unique tokens. `clearHistory()` and `setHistory()` reset the tally along with the turns it covered.

Thinking models can reason before answering; the reasoning rides along on the response:

```
$response = $client->chat('What is 17 * 23?', useThinking: true, maxTokens: 2000);
echo $response->output()->reasoning;  // or $client->lastReasoning()
echo $response->output()->text;
```

### Images

[](#images)

Chat and embedding inputs can include images, given as URLs or local files (PNG, JPEG, GIF or WEBP - files are inlined as data URIs):

```
$client->chat("What's in this image?", imageFiles: ['photo.png']);
$client->chat('Compare these.', imageUrls: [
    'https://example.com/a.jpg',
    'https://example.com/b.jpg',
]);
```

Raw image bytes can be converted with `SthAI\Image::dataUriFromBytes($bytes)` and passed via `imageUrls` (a data URI is a URL).

### One-off and structured responses

[](#one-off-and-structured-responses)

`response()` mirrors `chat()` without the back-and-forth: the stored history is neither sent nor updated. `structuredResponse()` adds structured output - pass a JSON schema as a PHP array and the server enforces it during generation, with the decoded array returned:

```
$city = $client->structuredResponse(
    'Give me basic facts about Wellington.',
    schema: [
        'type' => 'object',
        'properties' => [
            'name' => ['type' => 'string'],
            'country' => ['type' => 'string'],
            'population' => ['type' => 'integer'],
        ],
        'required' => ['name', 'country', 'population'],
    ],
    schemaName: 'CityInfo',
);
echo $city['population'];

// With no schema, the output is only constrained to valid JSON
$data = $client->structuredResponse('List three NZ birds as JSON.');
```

The client does not re-validate the result against the schema. If the output is cut off by the token limit, or (rarely, with thinking enabled) the server skips the schema, parsing throws a `SthAI\Exception\ResponseParseException` naming the cause. A schema needing an empty JSON object value must use `new \stdClass()` rather than `[]` (which encodes as an empty array).

### Embeddings

[](#embeddings)

`embed()` turns one input - text, images, or both - into a single vector. The embedding model is instruction-trained: document embedding is the default, and `query: true` switches to the query instruction for search-style lookups. `dimensions` truncates the vector server-side (Matryoshka - powers of two work best):

```
$vector = $client->embed("The Beehive is New Zealand's parliament building.");
$queryVector = $client->embed('Where does NZ parliament sit?', query: true);
$small = $client->embed('Compact vector, please.', dimensions: 512);
```

`batchEmbed()` embeds many texts in one request, returning one vector per text in order:

```
$vectors = $client->batchEmbed([
    'Wellington is the capital of New Zealand.',
    'Auckland is the largest city in New Zealand.',
]);
```

### Reranking

[](#reranking)

`rerank()` scores each document against a query and returns results sorted by relevance, with each result's `index` mapping back to your input list:

```
$results = $client->rerank(
    'What is the capital of New Zealand?',
    [
        'The capital of New Zealand is Wellington.',
        'Auckland has the largest population in New Zealand.',
        "The All Blacks are New Zealand's national rugby team.",
    ],
    topN: 2,
);
foreach ($results as $result) {
    echo $result->relevanceScore, ' ', $result->document->text, PHP_EOL;
}
```

Pass `instruction:` to steer relevance for a specific task; the model applies a sensible default otherwise.

### Response helpers

[](#response-helpers)

Every response type has `usage()` (input/output/cached token counts) and `output()` (the useful payload), and `toArray()` exposes the complete decoded payload. The full response from the most recent inference call is available via `lastResponse()`:

```
$response = $client->chat('Hello!');
echo $response->usage()->inputTokens, ' ', $response->usage()->outputTokens;
echo $client->lastResponse()->model;
```

### Sessions

[](#sessions)

Pinning requests to a server session keeps routing consistent and helps caching. Pass `sessionPin:` with your own identifier, or let the client generate one:

```
$client = new Client(autoSession: true);
echo $client->sessionPin();        // the pin in use, e.g. to persist it

$client->setSessionPin('my-pin');  // pin to a known session (null unpins)
$pin = $client->newSession();      // switch to a freshly generated pin
```

### Models and health

[](#models-and-health)

```
$client->healthy();  // true if the server is up
foreach ($client->models() as $card) {
    echo $card->id, PHP_EOL;
}
```

### Errors

[](#errors)

Everything the library throws implements `SthAI\Exception\SthAIException`, so catching it is enough to handle anything the client can throw:

```
use SthAI\Client;
use SthAI\Exception\ApiException;
use SthAI\Exception\ClientException;
use SthAI\Exception\InputException;
use SthAI\Exception\ResponseException;
use SthAI\Exception\TransportException;

$client = new Client();
try {
    $response = $client->chat('hello', model: 'no-such-model');
} catch (ClientException $e) {
    echo $e->getStatusCode(), ' ', $e->getErrorType(), ' ', $e->getMessage();
    // 404 invalid_request_error 404: model not found (invalid_request_error)
} catch (ApiException $e) {
    // 5xx: the server had a problem
} catch (TransportException $e) {
    // the request never completed: connection failure, timeout, TLS error
} catch (InputException $e) {
    // bad arguments to a client method
} catch (ResponseException $e) {
    // the server returned something the client couldn't use
}
```

`ClientException` (4xx) and `ApiException` (5xx) both extend `ApiStatusException`, which carries `getStatusCode()`, `getResponseBody()`, `getServerMessage()` and `getErrorType()` (parsed from the server's error body when present). `ResponseParseException`, thrown by `structuredResponse()`'s parsing, extends `ResponseException`.

Development
-----------

[](#development)

The test suite runs entirely offline against fixtures captured from the live API (shared with sthai-py):

```
git clone https://github.com/sitehostnz/sthai-php.git
cd sthai-php
composer install
composer test      # phpunit
composer stan      # phpstan
composer cs        # php-cs-fixer (dry run)
```

Licence
-------

[](#licence)

[MIT](LICENSE)

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance100

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity37

Early-stage or recently created project

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

0d ago

Major Versions

v0.1.0 → v1.0.02026-07-19

### Community

Maintainers

![](https://www.gravatar.com/avatar/421a423fc4022199ae6db68bff770831ce780112bcfc14e3d37a553ba74f1290?d=identicon)[sitehostnz](/maintainers/sitehostnz)

---

Tags

inferenceaillmembeddingssitehostvllmreranking

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/sitehostnz-sthai/health.svg)

```
[![Health](https://phpackages.com/badges/sitehostnz-sthai/health.svg)](https://phpackages.com/packages/sitehostnz-sthai)
```

###  Alternatives

[cognesy/instructor-php

The complete AI toolkit for PHP: unified LLM API, structured outputs, agents, and coding agent control

318123.0k1](/packages/cognesy-instructor-php)[wordpress/ai-provider-for-anthropic

AI Provider for Anthropic for the PHP AI Client SDK. Works as both a Composer package and WordPress plugin.

191.0k](/packages/wordpress-ai-provider-for-anthropic)[wordpress/ai-provider-for-google

AI Provider for Google for the PHP AI Client SDK. Works as both a Composer package and WordPress plugin.

171.2k](/packages/wordpress-ai-provider-for-google)

PHPackages © 2026

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