besfortmehmeti / tmbp-php-ai
AI abstraction layer for legacy PHP systems
Requires
- php: >=7.4
- guzzlehttp/psr7: ^2.8
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.0|^2.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- pestphp/pest: ^2.0
README
AI abstraction layer for legacy PHP systems.
This library provides a small, provider-agnostic API for:
- text generation
- multi-turn chat
- streaming responses
- switching between providers without changing calling code
Supported providers:
- OpenAI
- Anthropic
- Gemini
- Z.ai
It targets PHP >= 7.4 and uses PSR HTTP interfaces internally. The default runtime transport is cURL-based, so the PHP cURL extension should be available if you use the built-in transport.
Installation
composer require besfortmehmeti/tmbp-php-ai
Quick Start
<?php require_once __DIR__ . '/vendor/autoload.php'; use Tmbp\Ai\TmbpAi; $ai = TmbpAi::createManager([ 'default_provider' => 'openai', 'providers' => [ 'openai' => [ 'api_key' => getenv('OPENAI_API_KEY'), 'default_model' => 'gpt-4o-mini', ], ], ]); $text = $ai->text() ->prompt('Explain quantum computing in one sentence.') ->asText(); echo $text . PHP_EOL;
If you use the included bootstrap file:
require_once __DIR__ . '/bootstrap.php'; $ai = TmbpAi::createManager();
bootstrap.php also defines the global alias TmbpAi for legacy codebases that prefer it.
Configuration
The default config file is config/ai.php.
Example:
<?php return [ 'default_provider' => 'openai', 'providers' => [ 'openai' => [ 'api_key' => $_ENV['OPENAI_API_KEY'] ?? getenv('OPENAI_API_KEY'), 'default_model' => 'gpt-4o-mini', ], 'anthropic' => [ 'api_key' => $_ENV['ANTHROPIC_API_KEY'] ?? getenv('ANTHROPIC_API_KEY'), 'default_model' => 'claude-sonnet-4-5', ], 'gemini' => [ 'api_key' => $_ENV['GEMINI_API_KEY'] ?? getenv('GEMINI_API_KEY'), 'default_model' => 'gemini-2.0-flash', ], 'zai' => [ 'api_key' => $_ENV['ZAI_API_KEY'] ?? getenv('ZAI_API_KEY'), 'default_model' => 'glm-4-flash', ], ], ];
Important behavior:
- A provider is registered only if its
api_keyis present. default_provideris used only when you do not call->provider(...).- If you call
->provider('zai'), OpenAI is not involved. - If you call
->provider('zai')andzaihas noapi_key, the request throws a configuration error.
Usage
Plain text
$text = $ai->text() ->provider('openai', 'gpt-4o-mini') ->systemPrompt('You are a helpful assistant. Be concise.') ->prompt('What is the capital of France?') ->asText();
Full response metadata
$response = $ai->text() ->provider('openai', 'gpt-4o-mini') ->prompt('What is the capital of France?') ->asResponse(); echo $response->text(); echo $response->model(); echo $response->finishReason(); echo $response->usage()->totalTokens();
Multi-turn chat
$answer = $ai->chat() ->provider('anthropic', 'claude-haiku-4-5-20251001') ->systemPrompt('You are a helpful assistant. Be concise.') ->messages([ ['role' => 'user', 'content' => 'My name is Alex.'], ['role' => 'assistant', 'content' => 'Hello Alex, nice to meet you!'], ]) ->prompt('What is my name?') ->asText();
Streaming
foreach ( $ai->text() ->provider('openai', 'gpt-4o-mini') ->prompt('Count from 1 to 5, one number per line.') ->asStream() as $chunk ) { if ($chunk->isDone()) { $final = $chunk->finalResponse(); echo PHP_EOL . 'Total tokens: ' . $final->usage()->totalTokens() . PHP_EOL; } else { echo $chunk->text(); flush(); } }
Builder API
Shared builder methods:
provider(string $name, ?string $model = null)systemPrompt(string $prompt)temperature(float $value)maxTokens(int $tokens)options(array $options)asText()asResponse()asStream()
Text builder:
prompt(string $text)
Chat builder:
messages(array $messages)prompt(string $text)
Provider Selection
There are two ways to choose a provider:
Explicit:
$ai->text()->provider('gemini', 'gemini-2.0-flash')->prompt('Hello')->asText();
Implicit via default_provider:
$ai->text()->prompt('Hello')->asText();
Use explicit selection when:
- different flows use different providers
- migrations are in progress
- some environments only expose a subset of providers
Extension Points
TmbpAi::createManager() accepts optional dependencies:
public static function createManager( ?array $config = null, ?HttpTransport $transport = null, ?ProviderRegistryFactory $registryFactory = null ): Manager
This allows you to:
- swap the default cURL transport for your own PSR-18 / PSR-17 stack
- register custom providers
- change provider construction rules in one place
Custom transport
use GuzzleHttp\Psr7\HttpFactory; use Tmbp\Ai\Http\CurlSseStreamer; use Tmbp\Ai\Http\HttpTransport; use Tmbp\Ai\TmbpAi; $factory = new HttpFactory(); $transport = new HttpTransport( $myPsr18Client, $factory, $factory, new CurlSseStreamer() ); $ai = TmbpAi::createManager($config, $transport);
Custom provider registration
The default registry factory registers the built-in providers through provider factories. You can supply your own ProviderRegistryFactory to add or replace providers cleanly at the composition root.
Errors
Common configuration errors:
Default provider 'openai' is not registered...This happens when no explicit provider was selected and the configured default provider has no usableapi_key.Provider 'zai' is not registered...This happens when you explicitly select a provider that was not registered because its config is incomplete.
Provider API and network failures throw:
Tmbp\Ai\Exceptions\ProviderExceptionTmbp\Ai\Exceptions\StreamExceptionTmbp\Ai\Exceptions\ConfigurationException
Examples
See the runnable examples in:
- examples/openai.php
- examples/anthropic.php
- examples/gemini.php
- examples/zai.php
- examples/example.php
Development
Run tests:
vendor/bin/pest
Run code style checks:
vendor/bin/php-cs-fixer fix --dry-run --diff