PHPackages                             sentience/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. sentience/ai

ActiveLibrary

sentience/ai
============

The AI connector of Sentience

v1.4.0(1mo ago)09PHPPHP ^8.3

Since Jul 15Pushed 1mo agoCompare

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

READMEChangelog (5)Dependencies (1)Versions (6)Used By (0)

Sentience AI
============

[](#sentience-ai)

The AI connector for the Sentience framework.

Sentience AI is a small, opinionated PHP library for talking to large language models. It targets PHP 8.3 and wraps two underlying API shapes - the OpenAI chat completions API and the Anthropic messages API - behind one consistent interface. Anything that speaks OpenAI's protocol (OpenRouter, local inference servers, compatible gateways) works through the OpenAI driver by pointing it at a different base URI.

The goal is not to be a kitchen-sink SDK. It is to give a PHP application a clean way to:

- send a prompt to a model,
- attach images and files,
- expose tools the model can call,
- ask for a structured JSON response,
- enable streaming over a PHP `Generator`, reading SSE chunks incrementally,
- and have tool calls loop to completion without writing that loop yourself.

All of that is one fluent call chain on a `Prompt` object, returning a `Generator` that yields `ResponseInterface` values through each round of the conversation.

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

[](#installation)

```
composer require sentience/ai
```

Requires PHP 8.3 or newer. The only runtime dependency is `guzzlehttp/guzzle`.

Providers
---------

[](#providers)

Three providers ship out of the box, defined on the `Sentience\Ai\Api` enum:

ProviderDriverNotes`OpenAI``OpenAIApi`The OpenAI chat completions API.`OpenRouter``OpenAIApi`OpenAI-compatible; point the base URI at `https://openrouter.ai`.`Anthropic``AnthropicApi`The Anthropic messages API.Both drivers share `ApiAbstract`, so the message-building, attachment formatting, and structured-output handling live in one place. The per-provider classes only deal with the wire format differences (message roles, tool call shapes, image content blocks, and SSE streaming events).

Usage
-----

[](#usage)

### Connect

[](#connect)

```
use Sentience\Ai\Api;
use Sentience\Ai\Ai;

$ai = Ai::connect(
    Api::OpenAI,
    baseUri: 'https://api.openai.com/v1/',
    apiKey: getenv('OPENAI_API_KEY')
);
```

For OpenRouter, swap the enum and the base URI:

```
$ai = Ai::connect(
    Api::OpenRouter,
    baseUri: 'https://openrouter.ai/api/v1/',
    apiKey: getenv('OPENROUTER_API_KEY')
);
```

For Anthropic:

```
$ai = Ai::connect(
    Api::Anthropic,
    baseUri: 'https://api.anthropic.com/',
    apiKey: getenv('ANTHROPIC_API_KEY')
);
```

### A basic prompt

[](#a-basic-prompt)

```
foreach ($ai
    ->prompt('gpt-4o-mini', 'Summarise the plot of Moby-Dick in two sentences.')
    ->execute() as $response
) {
    echo $response->getContent();
}
```

Models can be passed as a plain string or as any backed enum; backed enums are resolved to their `.value` automatically, which is handy when models live in your own enum.

### System prompt and conversation history

[](#system-prompt-and-conversation-history)

```
foreach ($ai
    ->prompt('claude-3-5-sonnet-latest', 'What did I just ask you about?')
    ->withSystemPrompt('You are a terse assistant. Answer in one sentence.')
    ->withPreviousMessages($priorTurns)
    ->execute() as $response
) {
    echo $response->getContent();
}
```

`withPreviousMessages` accepts an array of message objects (`UserMessage`, `AssistantMessage`, `ToolMessage`). These are the same message types the library produces internally, so you can round-trip a previous response through `AssistantMessage::fromResponse($response)` and feed it back in.

### Attachments

[](#attachments)

```
foreach ($ai
    ->prompt('gpt-4o', 'What is in this image?')
    ->withAttachment('/path/to/photo.png')
    ->withAttachment('/path/to/notes.txt')
    ->execute() as $response
) {
    echo $response->getContent();
}
```

Images (`png`, `jpg`, `jpeg`, `gif`, `webp`, `bmp`) are sent as image content blocks. Anything else is decoded and embedded as a fenced text block so the model sees the file contents directly. There are also `withBase64Attachment`and `withRawAttachment` entry points for when the bytes are already in memory.

### Tools

[](#tools)

Tools can be a closure, a callable, or any class implementing `ToolInterface`. The simplest form is a closure:

```
foreach ($ai
    ->prompt('gpt-4o', 'What is the weather in Amsterdam?')
    ->withTool(
        name: 'get_weather',
        tool: function (string $city): string {
            return "{$city}: 14C, light rain";
        },
        description: 'Get the current weather for a city.'
    )
    ->execute() as $response
) {
    echo $response->getContent();
}
```

If you do not supply an explicit schema, the library reflects on the closure's parameters and builds one for you. Supported parameter types are `bool`, `int`, `float`, `string`, and `array`. Nullable types are honoured. This is enough for the vast majority of tools; for anything fancier, pass a `Schemable` schema explicitly.

When `execute()` is called, the library will:

1. send the prompt,
2. collect any tool calls in the response,
3. run them against the registered tools,
4. append the assistant message and each tool result to the conversation,
5. and re-send, repeating until the model stops calling tools.

The tool-call loop runs automatically inside the generator. Each round of the conversation yields a `ResponseInterface` that you can inspect or consume before the loop continues to the next round.

For class-based tools, implement `ToolInterface` and register with `withToolInterface`:

```
final class SearchTool implements ToolInterface
{
    public function name(): string { return 'search'; }
    public function description(): string { return 'Search the knowledge base.'; }
    public function schema(): array { /* ... */ }
    public function execute(array $arguments): string { /* ... */ }
}

foreach ($ai
    ->prompt('claude-3-5-sonnet-latest', '...')
    ->withToolInterface(new SearchTool())
    ->execute() as $response
) {
    echo $response->getContent();
}
```

### Structured output

[](#structured-output)

Ask for a JSON object back by passing an `ObjectType` schema:

```
use Sentience\Ai\Schema\Schema;

$schema = Schema::object([
    'title'       => Schema::string(),
    'summary'     => Schema::string()->maxLength(280),
    'tags'        => Schema::array(Schema::string()),
    'sentiment'   => Schema::enum(['positive', 'neutral', 'negative']),
    'confidence'  => Schema::float()->nullable(),
]);

foreach ($ai
    ->prompt('gpt-4o', 'Analyse this article: ...')
    ->withStructuredOutput($schema)
    ->execute() as $response
) {
    $data = $response->getStructuredOutput();
}
```

The schema is injected as a system message instructing the model to return minified JSON conforming to the schema. On the way back, `getStructuredOutput`parses the response, handling both raw JSON and ````json` fenced blocks, and returns the decoded array (or `null` if the response was not marked as structured).

### Streaming

[](#streaming)

```
use Sentience\Ai\Apis\Length;

$generator = $ai
    ->prompt('gpt-4o-mini', 'Write a short essay on tide pools.')
    ->withStream()
    ->execute();

foreach ($generator as $response) {
    $response->read(Length::Small);

    echo $response->getContent();
}
```

Calling `withStream()` enables streaming mode on the HTTP request. The `execute()` method now returns a `Generator` that yields `ResponseInterface`objects. Each iteration yields the current response, and you call `read()` on it to pull the next chunk of SSE data from the stream. The `read()` method accepts a `Length` enum value (or an `int` byte count) to control chunk size:

CaseBytes`ExtraSmall`1024`Small`4096`Medium`8192`Large`16384`ExtraLarge`65536The same loop handles content deltas, reasoning deltas (where the model emits them), tool-call assembly, and the tool-call loop itself. When the model requests tools, the generator automatically executes them, appends the results to the conversation, and yields the next round — all within the same `foreach`.

For a non-streaming request, call `readAll()` on the yielded response to consume the full body at once:

```
$generator = $ai
    ->prompt('gpt-4o-mini', 'Write a short essay on tide pools.')
    ->execute();

foreach ($generator as $response) {
    $response->readAll();
}

echo $response->getContent();
```

Schemas
-------

[](#schemas)

The `Schema` facade produces JSON Schema fragments. Every type comes in required and optional variants; the `required` flag on the factory selects between them. Required properties end up in the resulting object's `required`array automatically.

Available types:

- `Schema::bool()`
- `Schema::int()`
- `Schema::float()`
- `Schema::string()` - supports `minLength`, `maxLength`, `pattern`, `format`
- `Schema::enum(array $values)`
- `Schema::array(Schemable|array $items)`
- `Schema::object(array $properties)`

Every type is fluent: `->nullable()`, `->description(string)`, and the string constraints chain. Anything implementing `Schemable` can be passed in places that ask for a schema, which is how custom tool schemas and structured output share the same machinery.

### Continuing the Conversation

[](#continuing-the-conversation)

If your application needs to maintain state across multiple user interactions (e.g., a chat interface), you can use `->continue()` on the generator. This method adds a new user message to the conversation history and re-sends the request, picking up exactly where the previous call left off.

```
$generator = $ai
    ->prompt('gpt-4o', 'I have two dogs: Buster and Daisy.')
    ->execute();

foreach ($generator as $response) {
    echo $response->getContent();
}

$newGenerator = $ai->continue('Can you think of cool last names for these dogs', 'gpt-3.5');

foreach ($newGenerator as $response) {
    echo $response->getContent();
}
```

Architecture
------------

[](#architecture)

The public surface is small and the layering is intentional:

```
Ai                                  // entry point, picks a driver
  -> ApiInterface (OpenAI|Anthropic) // translates to the wire format
       -> Prompt                      // fluent builder
            -> ResponseGenerator       // owns the tool-call loop, yields responses
                 -> ResponseInterface  // content, reasoning, tool calls, finish reason

```

- `Sentience\Ai\Ai` - the facade. Connects, dispatches on the `Api` enum, and starts prompts.
- `Sentience\Ai\Apis\ApiAbstract` - shared behaviour: image detection, MIME handling, multipart content building, the structured-output system message, and the `/v1/models` listing.
- `Sentience\Ai\Apis\OpenAI\OpenAIApi` and `Sentience\Ai\Apis\Anthropic\AnthropicApi`
    - per-provider message and attachment formatting plus SSE stream parsing.
- `Sentience\Ai\Prompt` - the builder. Holds the prompt, system prompt, conversation history, attachments, tools, max tokens, structured output, and the stream flag. `execute()` returns a `Generator` via `ResponseGenerator`.
- `Sentience\Ai\Apis\ResponseGenerator` - an `IteratorAggregate` that wraps the tool-call loop. It yields `ResponseInterface` objects, re-sending with tool results until the model stops. In streaming mode, each yielded response can be consumed incrementally with `read()`.
- `Sentience\Ai\Apis\ResponseAbstract` - base class for provider responses. Handles both streaming and non-streaming HTTP bodies. Streaming reads SSE data via `read()` (with `Length` enum chunk sizes) and exposes `readAll()`for full consumption. Each provider subclass implements `handleSseData()`, `isStreamEnd()`, and `finalizeStream()`.
- `Sentience\Ai\Apis\Length` - an enum of chunk sizes (`ExtraSmall` through `ExtraLarge`) that controls how many bytes are read per `read()` call.
- `Sentience\Ai\Schema` - the schema factory and type hierarchy.
- `Sentience\Ai\Tools` - `Tool` (closure-backed, with reflection-driven schema generation) and `ToolInterface` for class-based tools.
- `Sentience\Ai\Messages` - `UserMessage`, `AssistantMessage`, `ToolMessage`, `SystemMessage`, and the `Role` enum.
- `Sentience\Ai\Attachments\Base64Attachment` - file and inline attachment helper.

Adding a provider means implementing `ApiInterface` (or extending `ApiAbstract`) and adding a case to the `Api` enum plus a branch in `Ai`'s constructor. Everything above the driver - prompt building, schemas, tools, attachments, the loop - is shared.

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance91

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity52

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

Total

5

Last Release

42d ago

### Community

Maintainers

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

---

Top Contributors

[![UniForceMusic](https://avatars.githubusercontent.com/u/51342446?v=4)](https://github.com/UniForceMusic "UniForceMusic (10 commits)")

### Embed Badge

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

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

###  Alternatives

[aws/aws-sdk-php

AWS SDK for PHP - Use Amazon Web Services in your PHP project

6.4k567.5M2.9k](/packages/aws-aws-sdk-php)[neuron-core/neuron-ai

The PHP Agentic Framework.

2.1k1.0M59](/packages/neuron-core-neuron-ai)[craftcms/cms

Craft CMS

3.6k3.7M3.5k](/packages/craftcms-cms)[tencentcloud/tencentcloud-sdk-php

TencentCloudApi php sdk

3751.3M49](/packages/tencentcloud-tencentcloud-sdk-php)[tempest/framework

The PHP framework that gets out of your way.

2.3k42.4k21](/packages/tempest-framework)[eslazarev/wildberries-sdk

Wildberries OpenAPI clients (generated).

353.6k](/packages/eslazarev-wildberries-sdk)

PHPackages © 2026

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