Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Open Ai Laravel Package

orhanerday/open-ai

A popular PHP SDK for OpenAI's API, orhanerday/open-ai offers simple methods for ChatGPT, GPT-4, DALL-E, Whisper, embeddings, and moderation. Framework-agnostic with Laravel support, streaming, and easy Composer installation.

Deep Wiki
Context7

Getting Started

Install and pin the SDK:

composer require orhanerday/open-ai

The whole surface area is one class: Orhanerday\OpenAi\OpenAi. Read src/OpenAi.php first — every public method is a thin cURL wrapper that takes an associative array and returns a JSON string (or false/null on transport failure). There are no DTOs, no request objects, and no validation. The examples/ directory in the repo and the README are the fastest way to see payload shapes; after that, the OpenAI API reference is your real documentation, because this package passes your array through almost untouched.

Register it once in Laravel so you are not re-reading env() deep in your code:

// config/services.php
'openai' => [
    'key' => env('OPENAI_API_KEY'),
    'org' => env('OPENAI_ORG'),
],

// App\Providers\AppServiceProvider::register()
use Orhanerday\OpenAi\OpenAi;

$this->app->singleton(OpenAi::class, fn () => new OpenAi(
    config('services.openai.key'),
    '',                                  // custom base URL
    config('services.openai.org') ?? ''  // OpenAI-Organization header
));

First use case — a chat completion from a controller or job:

$raw = $open_ai->chat([
    'model' => 'gpt-4o-mini',
    'messages' => [
        ['role' => 'system', 'content' => 'You are a terse assistant.'],
        ['role' => 'user', 'content' => 'Summarise this ticket: ...'],
    ],
    'temperature' => 0.3,
    'max_tokens' => 800,
]);

$data = json_decode($raw, true) ?? [];
if (isset($data['error'])) {
    throw new RuntimeException($data['error']['message'] ?? 'OpenAI error');
}
$text = $data['choices'][0]['message']['content'] ?? '';

If that call works, everything else (embeddings, images, audio, files, moderation, fine-tunes, assistants/threads) is the same pattern with a different method name and payload.

Implementation Patterns

Wrap the SDK in your own service class. Never let OpenAi leak into controllers. A ChatService with methods like ask(string $prompt): string and stream(ChatRequest $r) gives you one place for retries, logging, token budgeting, and test doubles. Because the package has no mocks of its own, this wrapper is also how you keep your test suite fast.

Chat with persisted history. Store messages as JSON on a conversation model, append the user turn, trim to a token budget you control, then send. The SDK keeps no state between calls — $open_ai is stateless apart from constructor config — so history, tools, and system prompts are entirely your responsibility.

Streaming. chatStream($payload, $callback) is the day-to-day win for chat UIs:

return response()->stream(function () use ($open_ai, $payload) {
    $open_ai->chatStream($payload, function ($curlInfo, $data) {
        echo $data;
        return strlen($data); // must return byte length or the stream stalls
    });
}, 200, [
    'Content-Type' => 'text/event-stream',
    'Cache-Control' => 'no-cache',
    'X-Accel-Buffering' => 'no',
]);

On the client, parse data: lines, stop on [DONE], and accumulate choices[0].delta.content. If you need to persist the assistant reply, buffer deltas server-side inside the callback and write after the stream closes.

Tool / function calling. Pass tools and tool_choice in the payload, then loop: send → inspect choices[0].message.tool_calls → execute your PHP function → append a role: "tool" message with tool_call_id → resend. Cap the loop (3–5 iterations) and set parallel_tool_calls => false if your handlers are not concurrency-safe. The package gives you nothing here; the loop is yours.

Structured output. Use 'response_format' => ['type' => 'json_object'] (and mention JSON in the prompt), then json_decode and validate with a Laravel FormRequest or a DTO. Anything the model returns is untrusted input — never eval it or bind it straight to an Eloquent fill().

Embeddings and RAG. embedding([...]) returns vectors you store in Postgres/pgvector, Pinecone, or similar. Batch documents (hundreds per request, under the model's input limit) in a queued job, and rate-limit with Redis::throttle so you do not blow through TPM on a backfill.

Vision and audio. Send content as an array of {type: "text"} / {type: "image_url"} parts for vision; use transcribe() with a CURLFile and purpose for audio. Base64 for small local images, a signed URL for anything large.

Queue long work. Chat completions, embeddings backfills, and fine-tune polling belong in jobs. HTTP requests here are blocking cURL with a generous default timeout — a slow request in a web request cycle will hang a PHP-FPM worker.

Config knobs worth setting. setTimeout(60), setProxy(...), setHeader('OpenAI-Project: proj_...'), and setBaseURL('https://your-gateway/v1/') for a proxy, LiteLLM, or Azure-compatible endpoint.

Gotchas and Tips

  • Everything is a JSON string. chat() returns a string on success, false/null on transport errors, and a JSON body containing error on API errors. A single helper that decodes, checks error, and throws is the highest-value thing you will write.
  • Debugging a failed call. $open_ai->getCURLInfo() after a failed request exposes HTTP status, timings, and the cURL error. Log it alongside the request payload (minus the API key) — 400s from tool schemas and image formats are otherwise opaque.
  • The streaming callback's return value is a contract. Return strlen($data). Returning true, null, or a trimmed string causes truncated or stalled streams that look like network problems.
  • Disable buffering everywhere. ob_end_flush() / flush() in the callback, X-Accel-Buffering: no for nginx, and set_time_limit(0) for long generations. Laravel's response()->stream() handles most of it, but any middleware that wraps output will swallow the stream.
  • Http::fake() does not intercept this package. It uses raw cURL, not Guzzle, so Laravel's HTTP client fakes are invisible to it. Test through your wrapper interface, or point setBaseURL() at a local stub server.
  • No retries or backoff. 429s and 5xx are yours to handle. Read Retry-After when present, use exponential backoff, and only retry idempotent calls (chat and embeddings are safe; file uploads and fine-tune creation less so).
  • No token counting. There is no tokenizer in the package. Add yethee/tiktoken (or estimate ~4 chars/token) to trim history and size max_tokens against the model's context window, or you will hit 400s in production with long threads.
  • Payload typos fail silently or with a generic 400. Nothing validates your array keys. A misspelled temprature is simply ignored by the API. Keep payloads in typed builder methods rather than inline arrays scattered across controllers.
  • max_tokens is not universal. Reasoning-era models want max_completion_tokens, and some reject temperature/top_p. Check the model before copying a payload from an older example — the README examples still lean on legacy shapes.
  • Azure / gateways. The SDK always sets an Authorization: Bearer header. Azure expects api-key; the common workaround is setHeader('api-key: ...') plus a base URL containing the deployment path, and stripping the bearer header if your gateway rejects dual auth. Verify against your gateway with a trivial listModels() call first.
  • complete(), engine, and the fine-tune endpoints are legacy. They still work, but new code should start at chat() and embedding(). Treat assistants/threads as experimental in this SDK — the API moved to Responses, and the wrapper has not kept up.
  • Pin the version. The release cadence is irregular and there is no changelog discipline; a minor bump has historically changed constructor arguments and method signatures. composer require orhanerday/open-ai:^4.0 with a lockfile is safer than floating.
  • Watch memory on large payloads. Base64 images and long embeddings responses are held as strings in memory. Stream uploads with CURLFile instead of base64 where the API allows, and chunk embedding batches.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource