Using an emotion recognition model on WooCommerce reviews

If you want to classify the emotion in a product review or a support ticket, you do not need to train an emotion recognition model yourself. Petr Koráb fine-tuned Mistral Small 3.1 on Google’s GoEmotions dataset and released the weights on Hugging Face under Apache 2.0, so the work left for a WordPress developer is the work we are good at: hooks, a queue, and somewhere to keep the scores. His walkthrough on Towards Data Science is worth reading end to end, but my take is that most of us should consume the model rather than rebuild it.

What the emotion recognition model is

GoEmotions is a human-annotated set of 54,263 comments from English-language subreddits, each tagged with any number of 27 emotions plus a neutral label. It lives on Google Research’s GitHub under Apache 2.0. It is multi-label, so one comment can be tagged with both amusement and annoyance. Its imbalance is brutal: neutral comments swamp every emotion, which is probably why Hugging Face had no Mistral model card for this task before now.

Koráb attacked the imbalance three ways. He thinned the neutral rows, generated synthetic samples for the minority classes up to 4,000 each with the 2025 ISMOTE algorithm, and trained with a focal loss that gives preferred labels a weight of 0.75 versus 0.25 for the rest. Training ran as LoRA on the 4-bit model, r=16 and alpha=32 across the attention and MLP projections, for 15 epochs, which took nine and a half hours on an RTX 6000 with 192 GB of VRAM. On the test set the model reaches a macro F1 of 0.8215, with most of the 15 target emotions above 0.7. Fear is the strongest at 0.94, annoyance the weakest at 0.61.

The released model is a plain classifier. Text is embedded before it reaches the backbone, the backbone runs with a sequence length of two, and a small projection layer plus a linear head emit 15 sigmoid scores. That shapes the deployment: you cannot drop it into a chat pipeline and prompt it. You send text to an endpoint and get probabilities back, which is what a REST call from PHP wants anyway.

Where it plugs into WordPress

On a WooCommerce store the interesting text lives in reviews, which are comments with a rating attached, and in order notes or ticket post types depending on the support setup. The pattern I would use is the same for all of them: catch the insert event, hand the text to a queued job, and store the scores as meta so no customer waits on inference.

<?php
add_action( 'comment_post', 'bbioon_queue_emotion_scan', 10, 3 );

function bbioon_queue_emotion_scan( $comment_id, $comment_approved, $commentdata ) {
    if ( 1 !== (int) $comment_approved ) {
        return;
    }
    // Reviews only, not every blog comment.
    $post_id = isset( $commentdata['comment_post_ID'] ) ? (int) $commentdata['comment_post_ID'] : 0;
    if ( ! $post_id || 'product' !== get_post_type( $post_id ) ) {
        return;
    }
    as_enqueue_async_action(
        'bbioon_scan_comment_emotion',
        array( 'comment_id' => $comment_id ),
        'bbioon-emotion'
    );
}

add_action( 'bbioon_scan_comment_emotion', 'bbioon_scan_comment_emotion' );

function bbioon_scan_comment_emotion( $comment_id ) {
    $comment = get_comment( $comment_id );
    if ( ! $comment ) {
        return;
    }
    $response = wp_remote_post( 'https://inference.internal/score', array(
        'timeout' => 20,
        'headers' => array( 'Content-Type' => 'application/json' ),
        'body'    => wp_json_encode( array( 'text' => $comment->comment_content ) ),
    ) );
    if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
        return; // the queue will retry
    }
    $scores = json_decode( wp_remote_retrieve_body( $response ), true );
    if ( is_array( $scores ) ) {
        update_comment_meta( $comment_id, 'bbioon_emotion_scores', $scores );
    }
}

The endpoint in that code should not live on the web server. A 24 billion parameter model, even quantized to 4-bit, needs far more memory than any shared or managed host gives PHP, so run it on its own box or a hosted inference endpoint and keep only the HTTP call inside WordPress. Action Scheduler, which ships with WooCommerce, retries the job when that endpoint is slow or down. If you are weighing this on a client store and want a second pair of eyes on the pipeline, this is the sort of work I do, and I’m happy to look at what you have.

Once the scores sit in comment meta, everything downstream is ordinary WordPress work: sort the review admin list by anger, flag disappointment above 0.8 for a human, trigger a win-back email on sadness. Nothing here needs the model on the site or slows a page load. For stores whose own data is too thin to train anything, a public classifier is the shortcut worth trying first, which is the same argument I made about small data machine learning on WooCommerce stores.

What won’t transfer

GoEmotions is English Reddit. Product reviews are a different dialect: shorter, politer, and full of phrases like “great product, fast shipping” that carry almost no emotional signal. The model card’s numbers come from a test set drawn from the same distribution the model trained on, so expect lower scores on your own reviews. Before building anything on top, hand-label a hundred of them, run the model over that set, and look at what it gets wrong.

Neutral is a blind spot too. It was undersampled in training and it is not one of the 15 output labels, so mildly positive text has nowhere neutral to land and its probability gets smeared across emotions that are only vaguely right. (I also don’t know how it behaves on two-word reviews, since the embedding step happened outside the fine-tune and the article doesn’t test that.)

And ask early whether you need 15 classes. For support triage, anger and disappointment against everything else covers most of the decision. The remaining taxonomy serves reporting more than routing, and reports nobody acts on are how these projects quietly die.

The question I still can’t answer is whether the extra classes change behavior. If a store owner responds the same way to an annoyed customer and an angry one, a two-way classifier or even a keyword list would have been enough. If they respond differently, the granularity is the whole product. I would want to watch one store’s review queue for a month before betting either way.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.

Leave a Comment