DEV Community

Akram Bakhouche
Akram Bakhouche

Posted on • Originally published at bak-dev.com

5 places to bolt an AI agent onto a PHP/Laravel app without rebuilding

If you run a Laravel or PrestaShop app, "add AI to it" arrived on your roadmap
sometime in the last twelve months. Probably from a CEO who saw a Claude demo,
or a customer-success person watching support tickets pile up.

The wrong answer is to rewrite. The right answer is to find the 5 places in
your existing stack where an LLM call collapses 20 lines of brittle code into
2 lines of Claude SDK — and start there.

These are the 5 I keep returning to in client work, ranked roughly by how
fast they break even.

1. Support-inbox triage

The use case. Every incoming support email goes through Claude before a
human sees it. The model assigns a category, severity, and language; drafts a
suggested reply; and routes it to the right inbox. Your team opens 50 emails
and sees 50 pre-categorized rows with draft answers, not 50 unparsed strings.

The failure mode if you do it badly. You let Claude auto-send replies.
Three months in, the model hallucinates a refund policy that doesn't exist and
costs you a five-figure chargeback. The fix is a hard rule: draft yes,
auto-send no.
Humans approve the draft. The savings come from time-to-first-
draft, not from removing the human.

The integration point. A queued job per inbound email.

// app/Jobs/TriageSupportEmail.php

public function handle(Anthropic $claude): void
{
    $analysis = $claude->messages()->create([
        'model'    => 'claude-haiku-4-5',
        'system'   => $this->triagePrompt(),  // cached, 600 tokens
        'tools'    => $this->triageTools(),   // assign_category, set_severity
        'messages' => [
            ['role' => 'user', 'content' => $this->email->raw_body],
        ],
    ]);

    $this->email->update([
        'category'        => $analysis->tool('assign_category')->args['name'],
        'severity'        => $analysis->tool('set_severity')->args['level'],
        'draft_reply'     => $analysis->content_text(),
        'triaged_at'      => now(),
    ]);
}
Enter fullscreen mode Exit fullscreen mode

Cost: roughly $0.0003/email on Haiku 4.5 with prompt caching. A team
processing 5,000 support emails a month spends $1.50 to give every one of them
a pre-categorized draft.

2. Product description generation (e-commerce)

The use case. Admin panel gets a "Generate description" button next to
the product editor. The button calls Claude with the product specs, the
store's tone-of-voice guide, and any existing description. Claude returns 3
variants. Editor picks one, edits if needed, saves.

This is the lowest-hanging fruit I see for PrestaShop and Shopify stores.
Catalog managers spend hours a week rewriting descriptions. This collapses to
minutes.

The failure mode if you do it badly. You let Claude invent specs ("100%
organic cotton" when you sold synthetic). The fix: pass the specs as a
structured fact list in the system prompt, and instruct Claude to
never invent attributes outside that list. Production-tested constraint
that actually works.

The integration point. A single controller action, idempotent by product
ID + tone.

// app/Http/Controllers/Admin/DescriptionGeneratorController.php

public function generate(Product $product, Request $request): JsonResponse
{
    $variants = $this->claude->messages()->create([
        'model'    => 'claude-sonnet-4-6',
        'system'   => $this->copywriterPrompt($request->tone),
        'messages' => [[
            'role'    => 'user',
            'content' => $product->fact_sheet(),  // structured: name, attrs, materials
        ]],
        'max_tokens' => 600,
    ]);

    return response()->json([
        'variants' => $this->parseVariants($variants->content_text()),
    ]);
}
Enter fullscreen mode Exit fullscreen mode

The tone is the parameter that matters. Premium · friendly · technical ·
minimalist · playful. Same product, five wildly different angles, picker UI.

3. Semantic-search fallback

The use case. Your existing keyword search is fine for 70% of queries.
For the other 30% — typos, synonyms, intent-based searches — it returns
nothing and your bounce rate goes up. You add an embedding-based fallback:
when keyword search returns fewer than N results, the query gets embedded,
matched against pre-computed product embeddings, and the top results are
appended.

You don't replace keyword search. You augment it.

The failure mode if you do it badly. You replace keyword search wholesale.
Now exact-SKU lookups stop working because embedding similarity ranks
"BLACK-M-FRONT-9382" lower than "black t-shirt". The fix: augment, never
replace
. Keyword is fast, exact, and deterministic. Embeddings are slow,
fuzzy, and probabilistic. They're complements.

The integration point. A Laravel scout-driver decorator.

// app/Search/HybridSearch.php

public function search(string $query, int $threshold = 5): Collection
{
    $keyword = Product::search($query)->take(20)->get();

    if ($keyword->count() >= $threshold) {
        return $keyword;  // keyword had enough, fast path
    }

    $embedding = $this->embed($query);  // cached, batched
    $semantic  = $this->vectorIndex->similar($embedding, take: 20 - $keyword->count());

    return $keyword->merge($semantic)->unique('id');
}
Enter fullscreen mode Exit fullscreen mode

Cheap to run: embeddings cost about $0.00002/query. A million queries a
month is twenty bucks.

4. Daily admin summary

The use case. Every morning at 7am, the admin panel home screen loads
with a 3-paragraph summary of the previous day's activity: orders, refunds,
support tickets, unusual signals. Written by Claude, in your brand voice,
referencing real numbers.

This is the highest perceived value per dollar of any pattern on this list.
The owner opens the admin, sees a coherent paragraph saying "Yesterday: 47
orders (+8% vs Tuesday avg), 2 refund requests both for size issues on
SKU-9382, support volume normal, one anomaly: 4 abandoned carts at the
checkout step in the last 3 hours — worth checking the payment provider", and
the AI bill paid for itself before they finish their coffee.

The failure mode if you do it badly. You feed Claude raw database dumps
and ask it to "find something interesting". It invents trends from noise. The
fix: you do the aggregation, Claude does the narrative. Pre-compute the
stats. Pass them as a structured digest. Ask Claude to turn them into
English, not to discover them.

The integration point. A scheduled task, run before 7am.

// app/Console/Commands/DailyAdminDigest.php

public function handle(): void
{
    $stats = [
        'orders'           => $this->dailyOrders(),
        'refunds'          => $this->dailyRefunds(),
        'support_volume'   => $this->dailySupportVolume(),
        'unusual_signals'  => $this->detectAnomalies(),
    ];

    $narrative = $this->claude->messages()->create([
        'model'    => 'claude-haiku-4-5',
        'system'   => $this->editorPrompt(),
        'messages' => [['role' => 'user', 'content' => json_encode($stats)]],
    ]);

    AdminDashboard::cacheTodayDigest($narrative->content_text());
}
Enter fullscreen mode Exit fullscreen mode

5. Outbound email personalization

The use case. Your abandoned-cart and re-engagement emails currently use
templated copy: "Hi {{first_name}}, you left items in your cart". Open rates
are mid-single-digits.

You add a Claude step: the model gets the customer's last 5 orders, the items
they abandoned, their average order value, the season. It rewrites the email
body to lean into the angle most likely to convert this customer.

Same template structure, same call-to-action button. Just the body paragraph
is dynamic per recipient.

The failure mode if you do it badly. You let Claude generate the subject
line too — and your spam complaint rate triples because the model used
clickbait phrases your sender reputation can't sustain. The fix: keep the
subject line and the CTA pinned
. Let Claude work on the body only.

The integration point. A queued personalizer running before the
deliverability tool sends the email.

// app/Mail/Personalizers/AbandonedCartPersonalizer.php

public function personalize(Cart $cart, User $user): string
{
    $context = [
        'abandoned_items'     => $cart->items->toFactSheet(),
        'recent_orders'       => $user->recentOrders(5)->toFactSheet(),
        'avg_order_value_eur' => $user->avgOrderValue(),
    ];

    return $this->claude->messages()->create([
        'model'      => 'claude-haiku-4-5',
        'system'     => $this->copywriterPrompt(template: 'abandoned_cart'),
        'messages'   => [['role' => 'user', 'content' => json_encode($context)]],
        'max_tokens' => 240,
    ])->content_text();
}
Enter fullscreen mode Exit fullscreen mode

A/B test it against the template baseline before rolling out fully. I have
seen lift between 18% and 40% in open-to-click conversion on abandoned-cart
sequences, but your results depend entirely on the quality of your customer
data.

What unites all five patterns

Look at the code. None of them needed a new framework, a vector database, a
"AI platform", or a rewrite. Each is a single class with a single Claude
call, dropped into the existing Laravel app like any other Service.

That's the actual playbook for adding AI to a production app:

  1. Find the place where the existing code is brittle, slow, or expensive.
  2. Wrap the brittle bit in a queued job or controller action.
  3. Replace its body with one Claude SDK call.
  4. Add structured constraints in the system prompt so the model can't invent things outside the facts you pass it.
  5. Keep the human in the loop anywhere a hallucination would cost more than a draft.

If you have a Laravel or PrestaShop app shipping revenue and you want to add
one of these patterns without burning down what works,
email me a brief.
I do exactly this for a living: scope, build, ship, inside your codebase.
2–4 weeks fixed-scope. The shape is documented on the
hire-me page.

Or, if you want to see what the same patterns look like across the full
agent-system stack instead of one feature at a time, the source for
Career-OS — the AI-agent dashboard
I run my own job search through — is MIT-licensed on GitHub.


Originally published on bak-dev.com. Find more build-in-public posts at bak-dev.com/blog.

Top comments (0)