Replying to reviews shows customers that someone listens, and future buyers read those replies too. Writing hundreds of thoughtful replies in several languages, however, takes hours every week. This guide uses a local language model to draft a reply to each review in the reviewer's language, following your brand notes and a strict set of rules. It flags the reviews that need a person, such as low ratings, safety problems, legal threats or suspected fraud, and writes everything into a sheet for approval. Nothing is posted automatically.
Use the /opt/reviews environment with Ollama from analysing customer reviews with a local LLM. The same review CSV works here; the script needs the columns id and text, and uses rating when it is present.
The model uses these notes for tone, contact details and the facts it may mention. Keep them short and factual:
cat > /data/reviews/brand.txt <<'EOF2'
Business: ACME Home, sells bamboo office accessories online.
Tone: warm and simple, no slang, no emoji.
Support contact: the "Contact seller" button on the order page, or [email protected].
Facts you may mention: 12-month warranty on all stands.
Sign off as: The ACME Home team
EOF2
cat > /opt/reviews/replies.py <<'EOF2'
import csv, json, os, re, sys
import ollama
MODEL = os.environ.get("MODEL", "qwen2.5:7b")
BRAND = open(os.environ.get("BRAND_FILE", "brand.txt"), encoding="utf-8").read()
ESCALATE = re.compile(os.environ.get("ESCALATE",
r"refund|chargeback|injur|burn|fire|smoke|allerg|lawyer|lawsuit|scam|fraud|fake|police|退款|投诉|受伤|起火|过敏|律师|骗"), re.I)
SCHEMA = {"type": "object", "properties": {
"reply": {"type": "string"}, "needs_human": {"type": "boolean"}, "reason": {"type": "string"}},
"required": ["reply", "needs_human", "reason"]}
SYSTEM = """You draft public replies to customer reviews for the business described in the brand notes.
Rules:
- Reply in the same language as the review, in 2 to 4 sentences.
- Thank the customer and respond to the specific point they made. Vary your wording.
- Never admit legal liability. Never promise refunds, replacements, discounts or compensation.
- Never ask the customer to change or remove their review, and never offer anything in return for a review.
- Never include order numbers, names or other personal data.
- For problems, apologise briefly and invite the customer to contact support as described in the brand notes.
- Set needs_human to true for safety issues, legal threats, suspected fraud, abuse, or if you are unsure,
and give the reason. Otherwise leave reason empty.
Brand notes:
""" + BRAND
src, out = sys.argv[1], "replies.csv"
done = set()
if os.path.exists(out):
with open(out, encoding="utf-8-sig") as f:
done = {r["id"] for r in csv.DictReader(f)}
with open(src, encoding="utf-8-sig") as f:
reviews = [r for r in csv.DictReader(f) if r.get("text", "").strip()]
new_file = not os.path.exists(out)
with open(out, "a", newline="", encoding="utf-8-sig") as f:
w = csv.writer(f)
if new_file:
w.writerow(["id", "rating", "text", "draft_reply", "needs_human", "reason", "approved", "final_reply"])
for r in reviews:
if r["id"] in done:
continue
res = ollama.chat(model=MODEL, format=SCHEMA, options={"temperature": 0.5}, messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": f"Rating: {r.get('rating', '?')} of 5\nReview: {r['text'][:3000]}"}])
d = json.loads(res.message.content)
reasons = [d["reason"]] if d["needs_human"] and d["reason"] else []
if r.get("rating", "").isdigit() and int(r["rating"]) <= 2:
reasons.append("low rating")
if ESCALATE.search(r["text"]):
reasons.append("keyword: " + ESCALATE.search(r["text"]).group(0))
w.writerow([r["id"], r.get("rating", ""), r["text"], d["reply"].strip(),
"yes" if reasons or d["needs_human"] else "no", "; ".join(reasons), "", ""])
f.flush()
print(r["id"], "NEEDS HUMAN" if reasons or d["needs_human"] else "draft ok", flush=True)
print("done: replies.csv")
EOF2
A review is marked for a person when the model asks for it, when the rating is 2 or lower, or when the text matches the ESCALATE keyword list. Finished reviews are saved immediately and skipped on the next run.
cd /data/reviews
/opt/reviews/bin/python /opt/reviews/replies.py reviews.csv
The result is replies.csv with the columns id, rating, text, draft_reply, needs_human, reason, approved and final_reply.
replies.csv in Excel or Google Sheets and filter needs_human = yes first. Handle these yourself: contact the customer privately through the platform, solve the problem, then reply publicly.final_reply with any changes, and set approved to yes.ESCALATE with words in the languages your customers use, for example in Thai, Spanish or Japanese.Technically yes, through platform APIs, but we recommend human approval. A wrong public reply is hard to undo, and many platforms hold sellers responsible for every word.
The rules ask the model to vary its wording, and the temperature of 0.5 adds variety. Example replies with different structures in the brand notes help most.
Prioritise negative and detailed reviews, where a good reply can change a buyer's mind. Short positive reviews need at most a brief thank-you. To understand what customers complain about overall, see finding what customers talk about with embeddings.
Still stuck after following these steps? Open a support ticket and the IMIDC 24/7 technical team will help. Include the server IP, the model you use and the error output so we can pinpoint the issue faster.