PHP client for bellmand, the Bellman LLM proxy. One URL and one API key give you OpenAI, Anthropic, Gemini (VertexAI), VoyageAI, Ollama, vLLM and xAI behind the same interface.
Requires PHP 8.1+.
composer require modfin/bellmanIf the package is not on Packagist (or you want to pin to the GitHub repo directly),
add the repository to your composer.json first:
{
"repositories": [
{ "type": "vcs", "url": "https://github.com/modfin/bellman-php" }
],
"require": {
"modfin/bellman": "^0.1"
}
}use Bellman\Client;
use Bellman\Models\Anthropic;
use Bellman\Models\OpenAI;
$bellman = new Client(getenv('BELLMAN_URL'), 'my-app', getenv('BELLMAN_TOKEN'));
// or, with a combined key: Client::fromKey(getenv('BELLMAN_URL'), getenv('BELLMAN_KEY'))
$res = $bellman->generator()
->model(OpenAI::GenModel_gpt4o_mini())
->prompt('What company made you?');
echo $res->text(); // OpenAIThe key name (my-app) identifies the caller in bellmand's logs and metrics, the
token is the API key configured in bellmand.
Generators are immutable, every setter returns a new instance, so configure once and reuse.
$llm = $bellman->generator()
->model(Anthropic::GenModel_5_5_sonnet_latest())
->system('You are a helpful assistant. Answer briefly.')
->temperature(0.2)
->maxTokens(1000);
$a = $llm->prompt('Why is the sky blue?');
$b = $llm->temperature(1.0)->prompt('Write a haiku about Stockholm.'); // $llm is unchangedAlso available: topP(), topK(), frequencyPenalty(), presencePenalty(),
stopAt(), thinkingBudget() and includeThinkingParts().
$res = $llm
->output([
'type' => 'object',
'properties' => [
'name' => ['type' => 'string'],
'founded' => ['type' => 'integer'],
],
'required' => ['name', 'founded'],
])
->prompt('Which company makes the Monitor IR platform?');
$data = $res->json(); // ['name' => '...', 'founded' => ...]use Bellman\Prompt\Mime;
use Bellman\Prompt\Prompt;
$res = $llm->prompt(
Prompt::asUserWithData(Mime::APPLICATION_PDF, file_get_contents('report.pdf')),
'Summarize this report in three bullet points.',
);Pass raw bytes, the client takes care of base64 encoding. Plain strings passed to
prompt() are user messages.
Append $res->turn to the prompts to continue a conversation. It holds the
assistant's reply in replay-ready form, including thinking signatures that some
providers require you to send back.
$history = [Prompt::asUser('Pick a random Nordic capital.')];
$res = $llm->prompt(...$history);
$history = [...$history, ...$res->turn, Prompt::asUser('What is its population?')];
$res = $llm->prompt(...$history);use Bellman\Embed\Type;
use Bellman\Models\VoyageAI;
$model = VoyageAI::EmbedModel_voyage_3_5();
$vector = $bellman->embed($model->withType(Type::QUERY), 'search query')->single();
$vectors = $bellman->embed($model->withType(Type::DOCUMENT), ['first text', 'second text'])->embeddings;
// Contextualized chunk embeddings
$chunks = $bellman->embedDocument(VoyageAI::EmbedModel_voyage_context_4(), $documentChunks)->embeddings;All models known to the Go library are available as static constructors on
Bellman\Models\Anthropic, OpenAI, VertexAI, VoyageAI, Ollama, VLLM,
XAI and OMLX, with the same names as in Go:
OpenAI::GenModel_gpt4o_mini();
VertexAI::EmbedModel_text_005();
OpenAI::genModels(); // all OpenAI generative models
Catalog::gen('OpenAI/gpt-4o'); // look up by "provider/name", null if unknownAny model bellmand can route to works, including ones not in the catalog:
use Bellman\Gen\Model;
new Model('VertexAI', 'gemini-2.5-flash', config: ['region' => 'us-central1']);
Model::fromFqn('OpenAI/some-new-model');$res->metadata->inputTokens;
$res->metadata->outputTokens;
$res->metadata->totalTokens;Every failure throws Bellman\BellmanException. $e->statusCode holds the HTTP
status from bellmand (null if it was never reached), and $e->isRateLimited()
and $e->isUnauthorized() cover the common cases.
Guzzle is used by default with a 300 second timeout. Any PSR-18 client can be passed instead:
$bellman = new Client($url, 'my-app', $token, http: $myPsr18Client);// config/services.php
'bellman' => [
'url' => env('BELLMAN_URL'),
'name' => env('BELLMAN_KEY_NAME', 'my-app'),
'token' => env('BELLMAN_TOKEN'),
],
// AppServiceProvider::register()
$this->app->singleton(\Bellman\Client::class, fn () => new \Bellman\Client(
config('services.bellman.url'),
config('services.bellman.name'),
config('services.bellman.token'),
));Then inject Bellman\Client wherever you need it.
composer install
composer testsrc/Models is generated from the Go library's model definitions. To update it after
models change in bellman, run this from the repo root, with a bellman checkout next to it:
go run modelgen/main.go -bellman ../bellman -out src/ModelsNot yet supported: streaming, and tools / function calling.