texhub / openai
Full-featured OpenAI SDK for any PHP framework with first-class Laravel support: chat, tools/function-calling, vision, assistants (v2), files, images, audio, embeddings, models and usage analytics.
Requires
- php: ^8.2
- ext-curl: *
- ext-json: *
Requires (Dev)
- illuminate/support: ^11.0 || ^12.0 || ^13.0
- phpunit/phpunit: ^11.0 || ^12.0
Suggests
- illuminate/support: Required to use the package inside a Laravel application (service provider, facade, config publishing).
README
🌐 English · Русский
A complete, framework-agnostic OpenAI SDK for PHP — chat, function-calling/tools, vision, assistants (v2), files, images, audio, embeddings, moderations, models and usage analytics — with first-class Laravel support.
Designed to make working with OpenAI проще простого: a one-liner for a quick prompt, full power when you need it.
Reference: https://platform.openai.com/docs/api-reference
✨ What's covered
| Area | Methods |
|---|---|
| Chat | text(), ask(), create(), vision(), json(), stream(), streamText() |
| Tools / functions | Tool::function(), code_interpreter, file_search, tool calls in responses |
| Responses API | responses()->create() / stream() (modern unified endpoint) |
| Assistants (v2) | create / retrieve / update / list / delete |
| Threads & runs | messages, runs, run steps, tool outputs, poll-until-done |
| Files | upload (disk or memory), list, retrieve, download, delete |
| Images | generate, edit, variation (DALL·E / gpt-image-1) |
| Audio | transcribe, translate, text-to-speech |
| Embeddings | create(), vector() |
| Moderations | create(), isFlagged() |
| Models | list, ids, retrieve, delete |
| Usage | token & cost analytics (admin key) |
| Escape hatch | ->http() to call any endpoint |
📦 Installation
composer require texhub/openai
Requirements: PHP ≥ 8.2 with the curl and json extensions.
🚀 Quick start
use TexHub\OpenAI\OpenAI; $ai = OpenAI::make('sk-...'); // or with org/project: OpenAI::make('sk-...', 'org-...', 'proj-...') // Simplest possible — just text in, text out: echo $ai->chat()->text('Объясни квантовую запутанность одним предложением.'); // With a system prompt and options: $response = $ai->chat()->ask( 'Напиши слоган для кофейни', system: 'Ты креативный копирайтер.', options: ['model' => 'gpt-4o', 'temperature' => 0.9], ); echo $response->content(); echo $response->totalTokens();
Pick the model per-call via options: ['model' => '...'], or set a default in config.
🧰 Function calling (tools)
use TexHub\OpenAI\Builders\Tool; use TexHub\OpenAI\Builders\Message; $response = $ai->chat()->create([ 'messages' => [Message::user('Какая погода в Душанбе?')], 'tools' => [ Tool::function('get_weather', 'Получить погоду по городу', Tool::objectSchema( properties: ['city' => ['type' => 'string']], required: ['city'], )), ], ]); if ($response->hasToolCalls()) { foreach ($response->toolCalls() as $call) { $args = json_decode($call['function']['arguments'], true); $result = myWeatherLookup($args['city']); // send the result back: $final = $ai->chat()->create([ 'messages' => [ Message::user('Какая погода в Душанбе?'), $response->message(), Message::toolResult($call['id'], json_encode($result)), ], 'tools' => [/* same tools */], ]); echo $final->content(); } }
👁 Vision (images in)
use TexHub\OpenAI\Builders\Message; // Remote URL: $ai->chat()->vision('Что на картинке?', ['https://example.com/photo.jpg']); // Local file as a data URL: $ai->chat()->vision('Опиши фото', [Message::imageDataUrl('/path/to/photo.jpg')]);
🧾 JSON mode & streaming
$json = $ai->chat()->json([Message::user('Верни {"city","country"} для Душанбе')]); $ai->chat()->streamText('Расскажи историю', function (string $delta) { echo $delta; // tokens arrive live });
🤖 Assistants (v2)
use TexHub\OpenAI\Builders\Tool; // 1) Create a reusable assistant $assistant = $ai->assistants()->create('gpt-4o', [ 'name' => 'Data Analyst', 'instructions' => 'Ты аналитик. Отвечай кратко, считай через code interpreter.', 'tools' => [Tool::codeInterpreter()], ]); // 2) Create a thread, add a message, run, and wait for completion $thread = $ai->threads()->create(); $ai->threads()->addMessage($thread->id(), 'Посчитай факториал 10'); $run = $ai->threads()->runAndPoll($thread->id(), $assistant->id()); // 3) Read the assistant's reply foreach ($ai->threads()->messages($thread->id())->data() as $message) { // newest first }
Function tools inside a run
When a run returns status: requires_action, submit your tool outputs:
$ai->threads()->submitToolOutputs($threadId, $runId, [ ['tool_call_id' => 'call_1', 'output' => json_encode(['result' => 42])], ]);
📁 Files (and file_search)
$file = $ai->files()->upload('/path/to/manual.pdf', purpose: 'assistants'); $ai->files()->list(['purpose' => 'assistants']); $content = $ai->files()->download($file->id()); $ai->files()->delete($file->id()); // In-memory upload: $ai->files()->uploadContents($csvString, 'data.csv');
🖼 Images
$image = $ai->images()->generate('Кот-космонавт, акварель', 'gpt-image-1', [ 'size' => '1024x1024', 'n' => 1, ]); $image->firstUrl(); // when response_format=url $image->base64(); // when response_format=b64_json $ai->images()->edit('/path/in.png', 'Добавь радугу', maskPath: '/path/mask.png'); $ai->images()->variation('/path/in.png');
🔊 Audio
// Speech → text $ai->audio()->transcribe('/path/voice.mp3', options: ['language' => 'ru'])->get('text'); // Text → speech (returns mp3 bytes, or write to file) $ai->audio()->speechToFile('Привет, мир!', '/path/out.mp3', voice: 'alloy');
🧮 Embeddings & moderation
$vector = $ai->embeddings()->vector('текст для поиска'); // array<float> $flagged = $ai->moderations()->isFlagged('какой-то текст'); // bool
📊 Usage & analytics
// Per-request token usage is on every response: $ai->chat()->ask('hi')->usage(); // ['prompt_tokens'=>..,'completion_tokens'=>..,'total_tokens'=>..] // Org-wide analytics (requires an ADMIN api key, sk-admin-...): $ai->usage()->completionsSince(strtotime('-7 days')); $ai->usage()->costs(['start_time' => strtotime('-30 days'), 'bucket_width' => '1d']);
🧯 Error handling
use TexHub\OpenAI\Exceptions\ApiException; use TexHub\OpenAI\Exceptions\TransportException; try { $ai->chat()->text('hi'); } catch (ApiException $e) { $e->httpStatus; // 401, 429, 500, ... $e->errorType; // invalid_request_error, rate_limit_exceeded, ... $e->errorCode; // e.g. invalid_api_key $e->isRateLimit(); $e->isRetryable(); } catch (TransportException $e) { // network failure }
🧩 Any endpoint (escape hatch)
$ai->http()->post('moderations', ['input' => 'text', 'model' => 'omni-moderation-latest']); $ai->http()->get('models');
🧩 Laravel
Auto-discovered. Publish config:
php artisan vendor:publish --tag=openai-config
.env:
OPENAI_API_KEY=sk-... OPENAI_DEFAULT_MODEL=gpt-4o-mini # optional: OPENAI_ORGANIZATION= OPENAI_PROJECT= OPENAI_BASE_URL=https://api.openai.com/v1 OPENAI_TIMEOUT=60
Facade:
use TexHub\OpenAI\Laravel\OpenAI; echo OpenAI::chat()->text('Привет из Laravel!'); OpenAI::assistants()->create('gpt-4o', ['name' => 'Bot']);
…or inject \TexHub\OpenAI\OpenAI anywhere.
🧪 Testing
Inject the fake transport to test without network calls:
use TexHub\OpenAI\OpenAI; use TexHub\OpenAI\Config; use TexHub\OpenAI\Tests\Support\FakeTransport; $t = (new FakeTransport())->push([ 'choices' => [['message' => ['content' => 'Hi!'], 'finish_reason' => 'stop']], ]); $ai = new OpenAI(new Config('sk-test'), $t); $ai->chat()->text('hello'); // "Hi!" // assert on $t->lastJson(), $t->lastHeaders(), $t->lastRequest()
Run the suite:
composer install
composer test
📚 Architecture
src/
├── OpenAI.php # entry — chat()/assistants()/threads()/files()/images()/audio()/…
├── Config.php # immutable configuration
├── Http/ # Transport, CurlTransport (JSON/multipart/SSE), HttpClient, FileParam
├── Builders/ # Message (incl. vision), Tool (functions, code_interpreter, file_search)
├── Resources/ # Chat, Responses, Models, Embeddings, Files, Images, Audio,
│ # Moderations, Assistants, Threads, Usage
├── Responses/ # Response (ArrayAccess), ChatResponse, ImageResponse, ListResponse
├── Exceptions/ # ApiException, TransportException, ConfigurationException
└── Laravel/ # ServiceProvider + Facade
License
MIT © TexHub Pro — built by Mahmudi Shodmehr.