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

Groq Php Laravel Package

lucianotonet/groq-php

A PHP SDK for the GroqCloud API that lets developers add fast LLM inference to any PHP app, with chat completions, tool calling, vision, reasoning, audio transcription, text-to-speech, batch processing, and RAG support.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and key up

composer require lucianotonet/groq-php
GROQ_API_KEY=gsk_...
# GROQ_API_BASE=https://api.groq.com/openai/v1  # only if proxying

The SDK reads GROQ_API_KEY from the environment itself (the README uses getenv()), which means it works the moment you drop the key in .env — no config publishing, no service provider, no facade. That's the first thing to internalize: this is a framework-agnostic PHP SDK, not a Laravel package. There is a separate lucianotonet/groq-laravel wrapper if you want a facade and config file, but for a codebase you own, a hand-rolled binding is usually less risk than a second low-adoption dependency.

Make it injectable in Laravel

Don't sprinkle new Groq(...) through controllers. Bind it once:

// config/services.php
'groq' => [
    'key'  => env('GROQ_API_KEY'),
    'base' => env('GROQ_API_BASE', 'https://api.groq.com/openai/v1'),
],

// app/Providers/AppServiceProvider.php
$this->app->singleton(Groq::class, fn () => new Groq(config('services.groq.key')));

Reading the key through config() rather than letting the SDK hit getenv() matters once you run php artisan config:cache — see Gotchas.

First use case: a 10-minute smoke test

Before building anything, verify the key and model availability from php artisan tinker:

$groq = app(\LucianoTonet\GroqPHP\Groq::class);

$groq->models()->list()['data']; // confirm the key works and see live model IDs

$groq->chat()->completions()->create([
    'model' => 'openai/gpt-oss-20b',
    'messages' => [['role' => 'user', 'content' => 'Say hello in five words.']],
])['choices'][0]['message']['content'];

Where to look first

  1. examples/ in the repo — the README repeatedly defers to files there (tool-calling-advanced.php, audio-transcriptions.php) for the parts the README abbreviates. Read those before writing tool-calling or audio code.
  2. $groq->models()->list() — model IDs churn fast on GroqCloud (openai/gpt-oss-20b, openai/gpt-oss-120b, whisper-large-v3, canopylabs/orpheus-v1-english in the docs are already a mix of generations). Never hardcode an ID you haven't listed.
  3. The README's response-shape blocks — they're accurate and are your only schema documentation.

Implementation Patterns

Wrap the SDK in one application service

The SDK returns raw arrays with no typed DTOs. Keep those arrays out of your domain:

final class Assistant
{
    public function __construct(private Groq $groq) {}

    public function answer(string $question): string
    {
        try {
            $r = $this->groq->chat()->completions()->create([
                'model' => config('services.groq.chat_model', 'openai/gpt-oss-20b'),
                'messages' => [
                    ['role' => 'system', 'content' => 'You are a concise support assistant.'],
                    ['role' => 'user', 'content' => $question],
                ],
                'temperature' => 0.2,
                'max_completion_tokens' => 400,
            ]);
        } catch (GroqException $e) {
            report($e);
            throw new AssistantUnavailable(previous: $e);
        }

        return $r['choices'][0]['message']['content'] ?? '';
    }
}

Every controller, job, and Livewire component then depends on Assistant, and you can fake it in tests without touching the SDK.

Structured output instead of parsing prose

For anything you'll store or branch on, use response_format with json_schema and strict: true — constrained decoding means you can json_decode without defensive regex. Reserve json_object mode for exploratory work, and remember it wants the word "JSON" in the prompt.

'response_format' => [
    'type' => 'json_schema',
    'json_schema' => [
        'name' => 'ticket',
        'strict' => true,
        'schema' => [
            'type' => 'object',
            'properties' => [
                'category' => ['type' => 'string', 'enum' => ['billing','bug','other']],
                'priority' => ['type' => 'integer'],
            ],
            'required' => ['category','priority'],
            'additionalProperties' => false,
        ],
    ],
],

Then map into a real class: TicketData::from(json_decode($content, true)). Strict mode is not supported by every model, so keep a fallback model ID in config.

Streaming to the browser

Use Laravel's response()->stream() (or streamDownload) and let the SDK iterate:

return response()->stream(function () use ($groq, $prompt) {
    $stream = $groq->chat()->completions()->create([
        'model' => 'openai/gpt-oss-20b',
        'messages' => [['role' => 'user', 'content' => $prompt]],
        'stream' => true,
    ]);

    foreach ($stream->chunks() as $chunk) {
        $delta = $chunk['choices'][0]['delta']['content'] ?? '';
        if ($delta !== '') {
            echo "data: " . json_encode(['delta' => $delta]) . "\n\n";
            ob_flush(); flush();
        }
    }
    echo "data: [DONE]\n\n";
}, 200, ['Content-Type' => 'text/event-stream', 'X-Accel-Buffering' => 'no']);

Streaming is the single biggest UX win with Groq (sub-second first token). Use it for chat UIs; don't use it for background jobs, where a blocking call plus a queue is simpler and resumable.

Tool calling as an explicit agent loop

The README's two-pass example is the pattern, but it doesn't scale past one tool. Structure it as a bounded loop:

$messages = [...];
for ($i = 0; $i < 5; $i++) {
    $res = $groq->chat()->completions()->create([
        'model' => 'openai/gpt-oss-120b',
        'messages' => $messages,
        'tools' => $tools,
        'tool_choice' => 'auto',
    ]);
    $msg = $res['choices'][0]['message'];
    $messages[] = $msg;

    if (empty($msg['tool_calls'])) {
        return $msg['content'];
    }

    foreach ($msg['tool_calls'] as $call) {
        $args = json_decode($call['function']['arguments'], true) ?? [];
        $result = $this->registry->call($call['function']['name'], $args);
        $messages[] = [
            'role' => 'tool',
            'tool_call_id' => $call['id'],
            'name' => $call['function']['name'],
            'content' => is_string($result) ? $result : json_encode($result),
        ];
    }
}

Key detail: you must append the assistant message with its tool_calls back into $messages before appending tool results — omitting it produces confusing 400s. Always cap the loop and validate tool names against an allowlist; never dispatch by string name to arbitrary container resolution.

Push slow modalities to the queue

Audio, vision, and batch operations take seconds to minutes. A job per operation, with the result written to the DB and dispatched as a broadcast/notification, is the correct Laravel shape:

final class TranscribeRecording implements ShouldQueue
{
    public function handle(Groq $groq): void
    {
        $result = $groq->audio()->transcriptions()->create([
            'file' => Storage::disk('local')->path($this->path),
            'model' => 'whisper-large-v3',
            'response_format' => 'verbose_json',
            'language' => 'en',
        ]);
        // store $result['text'] and $result['segments']
    }
}

Same for vision()->analyze($pathOrUrl, $question) and TTS ->save(Storage::disk('public')->path('tts/foo.wav')).

Config-driven models and a cost line

Put model IDs in config/services.php and log usage.total_tokens from each response. Groq is cheap and fast, but token accounting is how you'll notice a runaway agent loop or a prompt that grew to 30k tokens.

Testing

Because everything is arrays over an HTTP client, the clean seam is your own service class:

$this->mock(Assistant::class, fn ($m) => $m->shouldReceive('answer')->andReturn('stubbed'));

For the SDK layer itself, use Http::fake() only if the SDK goes through Laravel's client (it doesn't — it has its own), so instead assert on your service contract and keep one or two @group external integration tests that hit the real API with a small model.

Gotchas and Tips

  • config:cache breaks getenv()-style keys. The SDK reads GROQ_API_KEY from the process environment; in production with cached config, env vars are often not present at runtime. Pass the key explicitly to the constructor from config('services.groq.key') and you sidestep the whole class of "works locally, 401 in prod" bugs.
  • Only one exception type is documented. GroqException covers API errors, but transport failures (DNS, timeouts, cURL) may surface as other throwables from the underlying HTTP client. Catch Throwable at your service boundary, and add ->retry()/backoff on 429 in the job, since Groq rate limits are per-minute and spiky batch traffic will hit them.
  • Streamed chunks are not completions. In stream mode the payload is choices[0].delta, not .message, and there's a terminal [DONE]. Any code path that handles both modes needs two branches, not one.
  • Streaming gets swallowed by middleware. Laravel Debugbar, response caching middleware, and gzip in Nginx will buffer your stream until the end, producing "it works in artisan serve but not on the server." Send X-Accel-Buffering: no, and verify with curl -N.
  • Tool call arguments is a JSON string, not an array. json_decode every time, and guard against malformed/empty strings from small models. parallel_tool_calls must currently be forced false per the README — don't rely on the model batching calls.
  • Strict JSON schema is fussy. Every property must be in required, and additionalProperties: false is mandatory. If you get a 400, it's almost always the schema, not the prompt.
  • Audio constraints bite early. response_format supports json, verbose_json, text — not vtt or srt, so subtitle generation needs your own segment→SRT conversion from verbose_json. language is the source language. Pass a real filesystem path (Storage::path() / $upload->getRealPath()), not an UploadedFile object.
  • TTS is aggressively limited. Orpheus models cap input at ~200 characters and only emit wav. Long text means chunking sentences and concatenating audio yourself — plan for it rather than discovering it in production.
  • models()->list() is your source of truth. Model IDs are deprecated and swapped on GroqCloud with little notice; a hardcoded llama-3.3-70b style ID in a config file is a future outage. Store a default plus a fallback and refresh the list on a schedule.
  • No prompt-side caching magic. Prompt caching is a feature of the platform's behavior with long stable prefixes — keep your system prompt byte-identical across calls (no timestamps, no per-user interpolation at the top) or you lose the benefit.
  • Adoption risk is real. 80 stars, zero dependents, one maintainer. Pin the version in composer.json ("lucianotonet/groq-php": "^1.0" or an exact tag), keep the SDK behind your own service class, and keep a second integration test that would fail loudly if a documented response shape changed. The escape hatch — a raw Http::withToken() call to the same OpenAI-compatible endpoint — is a 20-line rewrite, which is exactly why the wrapper abstraction is worth the effort.
  • Don't log prompts. These requests carry user data. Log model, token counts, latency, and a request ID only; use the moderation capability before persisting or displaying generated content in user-facing surfaces.
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.
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support