LLaMA lokaal draaien leek me aanvankelijk een project waar ik weken aan zou besteden, maar uiteindelijk viel de integratie mee. In mijn werk begon ik aan een Laravel-applicatie die AI-gegenereerde content moest produceren, en na wat experimenteren kwam ik erachter dat Ollama een perfecte tussenlaag is. De setup bleek verrassend eenvoudig, hoewel je wel moet nadenken over architectuurkeuzes die later lastig te wijzigen zijn.
Eigenlijk draaide mijn hele aanpak om één vraag: ga ik het model lokaal hosten, of besteed ik de inferentie uit aan een serverless omgeving? Die beslissing bepaalt niet alleen je hosting-strategie, maar ook hoe je de rest van je applicatie inricht. Lokaal betekent meer controle en lagere kosten per request, maar ook meer complexiteit in deployment. Serverless geeft je schaalbaarheid, maar brengt latency en cold start-problemen met zich mee.
LLaMA koppelen aan Laravel
Communiceren met een lokaal draaiend LLaMA-model via Ollama doe ik met Laravel's ingebouwde HTTP-client. Extra packages zijn niet nodig omdat Ollama een simpele REST API aanbiedt. Het basispatroon ziet er zo uit:
use Illuminate\Support\Facades\Http;
$response = Http::post('http://localhost:11434/api/chat', [
'model' => 'llama3.1:8b',
'messages' => [
['role' => 'user', 'content' => $prompt],
],
'stream' => false,
]);
$content = $response->json('message.content');
Rechtstreeks HTTP-calls in controllers vind ik echter rommelig. Daarom bouw ik altijd een service class die de communicatie abstraheert en configureerbaar maakt via environment variables. Deze service bind ik aan Laravel's container via een custom ServiceProvider:
use Illuminate\Support\ServiceProvider;
class LLaMAServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(LLaMAService::class, function () {
return new LLaMAService(
url: config('ai.llama_url', 'http://localhost:11434'),
model: config('ai.llama_model', 'llama3.1:8b'),
);
});
}
}
Deze aanpak heeft twee grote voordelen. Ten eerste kan ik in development naar een lokale Ollama-instantie wijzen terwijl productie een remote endpoint gebruikt. Ten tweede voorkom ik hardcoded URLs in mijn codebase, wat deployment een stuk flexibeler maakt. Elke omgeving heeft zijn eigen .env configuratie zonder dat ik code hoef aan te passen.
Het model zelf kies ik afhankelijk van de use case. Voor eenvoudige taken zoals het samenvatten van tekst of het genereren van SEO-descriptions gebruik ik vaak llama3.1:8b. Complexere taken waarbij redeneervermogen belangrijker is, vereisen soms llama3.1:70b, maar dat betekent wel aanzienlijk meer compute-tijd per request.
Serverless deployment
Serverless en LLaMA zijn een lastige combinatie. AWS Lambda en Google Cloud Functions zijn gebouwd voor snelle, stateless taken, maar LLaMA-inferentie kan honderden milliseconden tot seconden duren. Cold starts maken dit alleen maar erger, vooral als je model niet continu warm gehouden wordt.
Mijn oplossing is de inferentie niet in de Lambda zelf uit te voeren, maar in een dedicated container die draait op ECS of Cloud Run. Lambda fungeert dan als een dunne proxy die requests doorstuurt naar de inferentiecontainer. API Gateway vormt de voorkant van deze architectuur:
aws lambda create-function \
--function-name llama-proxy \
--runtime provided.al2 \
--role arn:aws:iam::123456789012:role/lambda-execution-role \
--handler bootstrap \
--zip-file fileb://lambda.zip
Deze splitsing geeft me het beste van beide werelden. Lambda handelt HTTP-routing af en zorgt voor authenticatie, terwijl de inferentiecontainer zich volledig kan focussen op het draaien van het model. Schaalbaarheid wordt hierdoor ook een stuk eleganter: ik kan de inferentielaag onafhankelijk schalen zonder impact op de rest van de applicatie.
Kosten zijn bij deze setup wel een aandachtspunt. Container-instanties die continu draaien zijn duurder dan serverless functies die alleen actief zijn tijdens requests. Voor applicaties met onvoorspelbaar traffic kan dit flink uitlopen, dus goede monitoring van gebruik versus kosten is essentieel.
Prestaties en caching
Elke LLaMA-aanroep kost tijd, en die tijd stapelt zich op als je veel requests hebt. Voor use cases waarbij de output redelijk deterministisch is, cache ik de responsen agressief. Een hash van de prompt gebruik ik als cache-sleutel:
$result = cache()->remember(
'llama:' . md5($prompt),
now()->addHours(24),
fn () => $this->llama->complete($prompt)
);
Deze strategie werkt uitstekend voor vaste use cases zoals het genereren van productbeschrijvingen of SEO-teksten. Bezoekers krijgen binnen milliseconden antwoord op vragen die al eerder gesteld zijn. Voor dynamische content zoals chatbots is dit natuurlijk te rigide, maar voor veel business-toepassingen is het perfect.
Redis gebruik ik als cache-backend omdat het sneller is dan database-caching en beter schaalt bij meerdere applicatie-instanties. De TTL stel ik in afhankelijk van hoe snel de onderliggende data wijzigt. SEO-content kan dagen gecached worden, productinformatie misschien maar een uur.
Warmup-strategieën zijn ook belangrijk. Ik laat vaak een background job draaien die populaire prompts pre-cached, zodat echte gebruikers nooit wachten op de eerste generatie van content. Dit kost wat extra compute, maar de gebruikerservaring verbetert er dramatisch door.
Testen
Testen van AI-integraties brengt unieke uitdagingen met zich mee. Het model zelf wil ik niet aanroepen in tests omdat dat traag is en onbetrouwbare resultaten geeft. In plaats daarvan gebruik ik Laravel's Http::fake() om de network calls te mocken:
Http::fake([
'localhost:11434/*' => Http::response(['message' => ['content' => 'Test response']], 200),
]);
Voor de applicatielogica schrijf ik gewone feature tests die controleren of de eindpunten correct reageren:
use Tests\TestCase;
class LLaMATest extends TestCase
{
public function testLLamaResponse(): void
{
$response = $this->get('/llama');
$response->assertStatus(200);
}
}
Wat ik wel test is de error handling. Wat gebeurt er als Ollama offline is? Hoe reageer je op malformed JSON responses? Die edge cases zijn kritiek omdat AI-output inherent onvoorspelbaar is. Een goede fallback-strategie voorkomt dat je applicatie crasht als het model onverwachte antwoorden geeft.
Integration tests draai ik tegen een apart test-model dat snel en lichtgewicht is. Dit geeft me vertrouwen dat de integratie werkt zonder dat ik wacht op zware inferentie-operaties. Voor productie-deployments doe ik altijd een smoke test tegen het echte model om te controleren of alles nog werkt.
Prompts zelf zijn moeilijk te unit testen omdat de output varieert. Wat ik wel doe is controleren of de output voldoet aan verwachte patronen. Als ik JSON verwacht, parse ik de response om te zien of het valide JSON is. Voor gestructureerde tekst check ik of bepaalde keywords aanwezig zijn.
Het echte werk zit niet in de technische integratie, maar in het verfijnen van prompts en het omgaan met de onvoorspelbaarheid van AI-output. Een strikte JSON-modus en robuuste error handling zijn geen luxe maar absolute noodzaak als echte gebruikers met je applicatie werken.