Fco Javier Fernández Rumí · LinkedIn · GitHub (@JaviRomano) · Code and reports · Español

How does an AI respond to a hotel's difficult reviews?

A hotel receives hundreds of reviews, and one badly written sentence can admit a fault nobody has verified. This system interprets the review, drafts the reply, checks it and decides whether it is published on its own or goes to a person.

I spent years in hospitality handling guest complaints, and I know how delicate it is to do well.

Contact

Cases

Start with the guided tour: 9 cases, from a clean success to the failure the judge misses.

Review received (in Spanish)

Generated reply · draft, in Spanish

          
        
Classifier and retrieved context

How it works

1. Understand the review

A classifier decides whether the review is negative, neutral or positive, and how confident it is. It is mRoBERTa, a model trained for Spanish that I fine-tuned on real reviews, averaged over three seeds. On 11,414 reviews it never saw in training, it reaches a macro F1 of 0.781: a score from 0 to 1 that weighs all three classes equally. The neutral class, the hardest, stays at 0.549.

2. Find what it can say

A language model writes well, but it tends to make things up. To limit that, it gets the hotel's full fact sheet and, through RAG, the policies and approved replies closest to this review. RAG (Retrieval-Augmented Generation) here means finding the relevant documents and handing them to the model before it writes. For policies, it combines search by keywords (BM25) and by meaning (bge-m3 embeddings).

3. Draft and check

Qwen3.6 27B, an LLM running locally, works out what the guest is talking about and drafts the reply. Two filters then check it: fixed rules (services missing from the fact sheet, compensation, people's names) and a second model, Gemma 4 12B, which I call the judge. It comes from another family so it does not share the generator's biases: it answers yes or no to 8 criteria and quotes the sentence behind each failure.

4. Decide who publishes

The gate makes the final decision. Negative and neutral reviews, doubtful ones (confidence below 0.85), traps and any accident or legal risk go to a person. Of the 54 reviews in the test, 8 pass every check. Of the 24 real 1 to 3★ reviews, none was published without review. It is not auto-publishing: it prepares drafts, at a median of 48 s per review on one PC.

What I learned along the way

The first figures were worthless

This project began as an earlier version that appeared to work, but the result was very poor, so I audited it: 50.8% of the final exam (the test set) also appeared, word for word, in the training data. The model already knew the answers. This is called data leakage (reproduction). It also picked the model with that same exam and had never seen a 4★ review. I rebuilt it from scratch: duplicates removed before splitting, and a separate validation set.

More variety did not mean better replies

Almost every reply opens the same way, so I tried more varied versions of the prompt, the model's instructions. Comparing them blind, without knowing which was which, I preferred the current one on 15 reviews and the newer one on 10, a difference that could be chance (p = 0.42). I kept the current one, and the replies are still repetitive. I will try again when I have more reviews, so as not to fit the prompt to these same cases and overfit.

An automatic evaluator has to be evaluated too

I compared the judge with my own blind labels on 24 replies. It failed 7 and I failed 4, all of them among its 7: it missed none of mine, with 3 false alarms. The sample is small, and reading other replies I found implicit admissions of fault that it lets through (p08 and rag-carteles). So anything delicate always has to go to a person.

What has not been tested

Evidence

Every figure on this page comes from a file in the repository. Here they all are, with their sample size and source. I built the project with Claude Code: what I decided and what was done with the tool.

All the results

Each figure links to its file. Intervals on proportions are Wilson intervals; those on F1 come from each report's bootstrap. Calibration used rubric v2, and production uses v2g.

SetnMetricValue95% CISource
AHR, never used for training11,414F1 macro0.7810.770 to 0.791phase3b_realista.json
AHR11,414F1 of the neutral class0.549—phase3b_realista.json
AHR, confidence ≥ 0.858,487Error264 of 8,487 (3.1%)2.8% to 3.5%phase3b_realista.json
Clean COAH test269F1 macro0.7970.732 to 0.853phase3b_realista.json
COAH, confidence ≥ 0.85191Error2 of 191 (1.0%)0.3% to 3.7%phase3b_realista.json
SFU, another website50Correct39 of 50 (78%)64.8% to 87.2%phase3b_realista.json
Calibration, rubric v224Judge precision4 of 7 (57%)25.0% to 84.2%phase6_calibracion.md
Calibration, rubric v224Judge recall4 of 4 (100%)51.0% to 100.0%phase6_calibracion.md
Calibration, rubric v224Kappa on “does not admit fault”0.70—phase6_calibracion.md
Seeds, rubric v210The person's failures the judge detects1 of 3 (33%)6.1% to 79.2%phase6_semillas.json
Demo queue54Pass every check8 of 54 (15%)7.7% to 26.6%pipeline_cola.json
Real 1 to 3★ reviews24Published without review (by design)0 of 24 (0.0%)0.0% to 13.8%pipeline_cola.json
Blind comparison, v6 and v925Prefers v6, excluding 3 ties (p = 0.42)15 of 25 (60%)40.7% to 76.6%phase9_ciegas.md
Blind comparison, v630Publishable as it stands8 of 30 (27%)14.2% to 44.4%phase9_ciegas.md
Latency on one PC15Median time to verdict48.2 s (p90 52.2 s)—phase9_latencia.md
The judge against my rating

Only 6 cases have my verdict, with the criteria written before generating, and 1 fails: the matrix shows which cases they are, it does not measure the judge. Each ID opens the case in the viewer.

Me: passesMe: fails
Judge approvesp01 p09 p18 p20p08
Judge failsp02None
Evaluation sets

They measure different things and barely overlap, so the system has no single “n”.

SetSizeOriginUse
Clean COAH test269Real, TripAdvisorMeasure the classifier
AHR11,414Real, TripAdvisor, 2021Measure the classifier on unseen reviews
SFU50Real, ciao.esMeasure it on another website
Demo queue5414 synthetic and 40 real from AHRFull system and gate
Blind comparison30, 28 ratedWritten by meChoose between prompt versions
Calibration24Replies from 3 prompt versionsJudge against a person
Seeds10Replies from the first versionsReal generator failures
This page's selector156 written by me and 9 illustrativeShow cases; not a sample
For technical reviewers