← All articles
9 min read

How to Build Custom AI Inside Magento (Without an External Service)

In Part 1 we trained a small model that flags AI-generated product reviews. Training a model is the part everyone writes about. This post is about the part almost nobody does: getting a custom model to actually run inside Magento.

The fake-review detector is the worked example, but the question is general — you have a model (a classifier, a ranker, a scorer) and you need it to do its job inside a Magento store, on real data, in the admin or on the storefront. How do you wire it in? There are two honest answers, and most of the work is choosing between them.

The two ways to integrate a model into Magento

Two architectures for custom models in a store: (A) Magento calling an external AI service/API over HTTP, with network latency, infrastructure cost, independent scaling and data-egress security; (B) an all-in-one extension where Magento scores the integrated model in pure PHP, with zero network, instant inference, cost efficiency and simple deployment.
Every custom-model integration is one of these two shapes. A is the default reach; B is the one worth knowing about, because for a large class of models it is simpler and cheaper.
A · External serviceB · In-process (PHP)
How it runsModel behind an HTTP API (Python/GPU)Model file loaded and scored in PHP
Per-call costNetwork round-trip + computeA function call
Extra infraA service to deploy, scale, monitorNone — ships in the module
Works offlineNoYes
Best forBig models — LLMs, deep nets, GPUSmall models — linear, trees, small nets

Pattern A is the right call when the model genuinely needs a GPU or gigabytes of weights — an LLM, a vision model, a large transformer. You wrap it in a small service, call it over HTTP, and accept the network and ops cost because there is no alternative.

But a huge number of useful store models are not that. Fraud scores, review filters, lead scoring, simple recommenders, churn flags — these are often linear models, gradient-boosted trees, or tiny networks. For those, Pattern A is over-engineering. The model is a few hundred kilobytes of numbers, and you can run it where the data already is: inside the PHP request. That is what we did.

Why a linear model ports to PHP so easily

The detector is TF-IDF + logistic regression. Once it is trained, scoring a review is not "machine learning" in any heavy sense — it is a weighted sum. No matrix library, no runtime, no Python.

Inference is just arithmeticReviewraw textTokens1–2 gramstf · idfL2 normaliseP(fake)·coef + sigmoidNo matrix library required — a loop over the words in one review.
Turn the review into word + word-pair counts, weight them, take a dot product with the model's coefficients, squash with a sigmoid. That is the entire forward pass.

So the integration trick is simply moving the numbers. Export everything PHP needs from the trained scikit-learn pipeline into a small JSON file: each vocabulary term mapped to its IDF weight and model coefficient, plus the intercept and a couple of preprocessing flags.

{
  "format": "fakereviews-linear-v1",
  "intercept": -7.74,
  "params": { "ngram_max": 2, "sublinear_tf": true, "norm": "l2" },
  "vocab": {
    "love it": [6.43, 2.11],   // term -> [idf, coefficient]
    "i love":  [5.88, 1.40],
    "...":     [0.0,  0.0]
  }
}

The export is one command in the Python repo, then a copy into the module:

python -m fakereviews.export_php          # -> model/model.json (~900 KB)
cp model/model.json app/code/WisWes/FakeReviews/Model/model.json

On the PHP side, scoring is a faithful re-implementation of what scikit-learn does — tokenize the same way, apply the same TF-IDF and L2 normalisation, dot with the coefficients, sigmoid. The core of it is short:

$score = $intercept;
$weights = [];
$norm = 0.0;

// tf-idf weight per in-vocab term (sublinear tf, like sklearn)
foreach ($this->termCounts($text) as $term => $count) {
    if (!isset($vocab[$term])) {
        continue;                       // out-of-vocabulary: ignore
    }
    [$idf, $coef] = $vocab[$term];
    $w = (1 + log($count)) * $idf;
    $weights[$term] = $w;
    $norm += $w * $w;
}
$norm = sqrt($norm);

// L2-normalise, then dot with the coefficients
foreach ($weights as $term => $w) {
    $score += ($w / $norm) * $vocab[$term][1];
}

$pFake = 1 / (1 + exp(-$score));        // sigmoid -> probability

The detail that earns trust: verify the port against the original. Score the same reviews in Python and in PHP and compare. Ours agrees with scikit-learn to within ~0.0001 — the only gap is rounding in the exported weights. Same verdicts, same explanation tokens. If your PHP port and your training code disagree, the integration is a bug, not a feature.

CheckResult
Max probability difference (PHP vs Python)~0.00004
Labels match on the test reviews100%
Explanation signals matchYes

Where it plugs into Magento

With a Classifier service that takes text and returns a probability, the rest is ordinary Magento extension work. One model, reused from three entry points:

One classifier, three entry pointsClassifier.phploads model.jsonGrid columnobserver + rendererCLI scanbin/magento commandStore configenable · threshold
The model is a plain service. Everything else — the admin column, the CLI, the config — just calls it. This is the shape of most custom-model integrations once the inference problem is solved.

The product reviews live under Marketing → All Reviews, which in Magento is still a legacy grid (not a UI component). You cannot add a computed column with a UI-component XML file. The reliable hook is an event that fires just before the grid builds its columns — backend_block_widget_grid_prepare_grid_before — where addColumn() is allowed:

public function execute(Observer $observer): void
{
    $grid = $observer->getEvent()->getData('grid');
    if (!$grid instanceof \Magento\Review\Block\Adminhtml\Grid) {
        return;
    }
    $grid->addColumn('wiswes_fake', [
        'header'   => __('Fake?'),
        'index'    => 'detail',                 // the review text column
        'renderer' => Fake::class,              // scores + renders the badge
        'filter'   => false,
        'sortable' => false,
    ]);
}

The column renderer pulls the review text off the row, calls the classifier, and prints a coloured verdict badge — green real 5% or red FAKE 94% — with the words that drove the score in the hover title. The grid is paginated, so only the ~20 rows on screen are scored per page load: in-process inference makes that free.

The same service backs a CLI command, handy for a one-off audit of the whole table:

bin/magento wiswes:fakereviews:scan            # flag at the configured threshold
bin/magento wiswes:fakereviews:scan -t 0.35   # higher recall, per run

And a small Stores → Configuration section exposes the one knob that matters — the probability threshold above which a review is flagged — so a merchant tunes sensitivity without touching code. The grid column and the CLI both read it.

The module, at a glance

Nothing here is exotic Magento. If you have shipped a module, this is the familiar furniture — the only unusual file is the model itself:

PieceRole
Model/model.jsonThe exported weights — the actual AI, ~900 KB
Model/Classifier.phpPure-PHP inference: text in, probability + signals out
Observer + Block/.../RendererAdds and renders the "Fake?" grid column
Console/CommandThe bin/magento scan command
etc/system|config|acl.xmlThe enable toggle + threshold setting

When you should reach for a service instead

For everything that fits in memory and scores in microseconds, in-process wins: no network, no extra service, no per-call bill, and it works on a merchant's server with nothing else installed.

What's next

The detector now lives where the reviews are. Part 3 takes the same exported model to Shopify, where the integration shape is different — an app and webhooks rather than an in-process PHP module — and the in-process-vs-service tradeoff plays out all over again.

Both pieces are open source: the model and exporter at github.com/wiswes/fakereviews, and the Magento extension at github.com/wiswes/fakereviews_magento.

WisWes builds AI that runs inside your store — answering shoppers, recommending products, and keeping your reviews honest. This series is us building one piece of it in the open.

Turn questions into checkout.

WisWes drops into your store and guides shoppers from browsing to buying. 14-day free trial — no card.