GrepBoard/매뉴얼
84 편 ·

리뷰 답글 AI 로 쓰고 보상·개인정보 걸러내기

·고침 ·claude-sonnet-5-5 · 2026-10-20 실행 (ChatGPT 는 돌리지 못해 Claude 로 실행)·6 단계

별점 1점 리뷰에 AI 로 답글을 쓰면 보상 약속이 끼어듭니다. 넣으면 안 되는 것을 규칙으로 주고, 전화번호·보상 단어와 지적 항목별 응답을 스크립트로 검사합니다.

「답글 써 줘」 한 줄만 주면 AI 는 답글 밑에 사장님께 드리는 조언까지 붙여 줍니다. 문제는 답글 본문입니다. 세 개 중 두 개가 공개 답글 안에서 「보상 방법을 상의드리겠습니다」 「환불이나 보상은 주문 내역을 확인한 뒤 안내드리겠습니다」 라고 약속했습니다. 두 번 돌려 두 번 다 그랬습니다.

[1] 별점 1점짜리 가상 리뷰 세 개입니다. 주문번호와 전화번호는 일부러 넣었고, 「지적」 줄은 나중에 대조하려고 항목마다 번호를 붙였습니다.

reviews.ko.txt
[리뷰 1] 별점 1배달 시킨 지 70분 만에 왔습니다. 도착했을 땐 이미 다 식었고 면이 불어 있었어요. 주문번호 2410-0731 확인해 보세요. 연락은 010-0000-0000 으로 주세요.지적: 1-1 배달이 너무 늦음 / 1-2 음식이 식고 면이 불음 [리뷰 2] 별점 1떡볶이 2인분 시켰는데 1인분 같아요. 예전엔 이렇지 않았는데 양을 줄인 건가요? 이 가격이면 환불해 주셔야 하는 거 아닌가요.지적: 2-1 양이 적음 / 2-2 양을 줄였는지 의문 [리뷰 3] 별점 1전화로 배달이 언제 오냐고 물었더니 "바쁘니까 기다리세요" 하고 끊으셨어요. 말투가 너무 퉁명스러웠습니다.지적: 3-1 전화 응대 말투 / 3-2 도착 시간을 문의했는데 답을 못 받음

[2] 먼저 짧은 프롬프트로 시킵니다. p0.ko.txt 는 「아래 리뷰 3개에 사장님 답글을 써 줘.」 한 줄 뒤에 위 리뷰를 붙인 것입니다.

console
$ claude -p --model claude-sonnet-5-5 --max-turns 1 --tools "" --setting-sources "" --strict-mcp-config < p0.ko.txt > out0.ko.1.txt$ grep -oE '.{12}(보상|환불|쿠폰).{12}' out0.ko.1.txt | head -2락처로 따로 연락드려 보상 방법을 상의드리겠습 점 사과드립니다.] 환불이나 보상은 주문 내역

전화번호와 주문번호는 두 번 모두 답글에 쓰지 않았습니다. 첫 결과는 리뷰 2 답글에 [양을 조정한 적이 없다면: … / 조정한 적이 있다면: …] 선택지를 그대로 남겼고, 리뷰 3 답글에는 메모에 없는 「직원 교육을 다시 하겠습니다」 가 들어 있었습니다. 두 번째 결과는 아무도 알려 주지 않은 「저희는 의도적으로 양을 줄이지 않았습니다」 를 단정해 적었습니다.

[3] 고친 프롬프트입니다. 가게 사정은 <facts> 에, 리뷰는 <reviews> 에 따로 넣고 규칙은 맨 위에 둡니다. 앱이나 서비스의 규칙은 지시문 쪽에 두라는 OpenAI 의 프롬프트 엔지니어링 안내 구성을 따랐고, 첫 줄의 역할 지정은 Google 의 Workspace 프롬프트 가이드가 권하는 방식입니다. 출력은 JSON 으로 받아 스크립트가 읽게 합니다.

p1.head.txt
You write public replies from a small restaurant owner to negative reviews. Replies are read by other customers, not only the reviewer. Rules:- Write one reply per review, in Korean, polite, 3~5 sentences, no excuses and no blaming the customer or the delivery rider.- Use only the facts in <facts>. Do not invent causes, numbers, or promises.- Never put in a public reply: order numbers, phone numbers, names, or anything copied from the review's personal details; any compensation (refund, coupon, discount, free item, 보상, 환불, 쿠폰, 할인, 서비스); the number of staff or any person.- If the review demands compensation, do not answer that part publicly; say individual checks are handled separately through the store's phone or app inquiry, without giving a number.- Every item listed under "지적" (ids like 1-1) must be answered by at least one sentence in that reply. Output only JSON, no code fence:{"replies":[{"review":1,"reply":"...","map":[{"id":"1-1","sentence":"<one sentence copied exactly from reply>"}]}]}
facts.ko.txt
- 리뷰 1 주문은 배달앱 기록상 접수부터 도착까지 68분. 평소 평균은 35~40분.- 그날 저녁 비가 와서 주문이 몰렸고, 배달 기사 배정이 늦어졌다.- 떡볶이 2인분 기준 중량은 올해 바뀐 적 없다. 리뷰 날짜의 개별 포장 중량 기록은 없다.- 피크 시간(18~20시)에 전화가 겹치면 응대가 짧아질 수 있었다. 이번 주부터 전화 응대 첫 문장을 「도착 예정 시간부터 말한다」로 정했다.- 보상·환불·쿠폰 이야기는 공개 답글에 쓰지 않는다. 개별 확인은 가게 전화나 앱 문의로 따로 받는다(번호는 답글에 쓰지 않는다).

[4] head 뒤에 <facts>…</facts> 와 <reviews>…</reviews> 를 이어 붙여 p1.ko.txt 를 만들고 실행합니다. 첫 번째 결과의 리뷰 2 답글입니다. 환불 요구에는 환불이라는 말 없이 답했습니다.

console
$ claude -p --model claude-sonnet-5-5 --max-turns 1 --tools "" --setting-sources "" --strict-mcp-config < p1.ko.txt > out1.ko.1.txt$ python3 -c "import json;print(json.load(open('out1.ko.1.txt'))['replies'][1]['reply'])"기대하신 만큼 양이 넉넉하지 않다고 느끼셨다니 죄송합니다. 떡볶이 2인분 기준 중량은 올해 바뀐 적이 없습니다. 다만 리뷰 날짜의 개별 포장 중량 기록은 남아 있지 않아, 그날의 양을 이 자리에서 확인해 드리기는 어렵습니다. 개별 확인은 가게 전화나 앱 문의로 따로 받고 있으니 편하게 문의해 주세요.

[5] 검사 스크립트입니다. 답글에서 전화번호·주문번호 꼴·보상 단어를 정규식으로 찾고, map 의 문장이 답글에 실제로 있는지, 리뷰의 지적 번호가 빠짐없이 답해졌는지 봅니다.

check.py
import json, re, sysrv = open(sys.argv[1], encoding="utf-8").read()ids = re.findall(r"(\d-\d) ", " ".join(l for l in rv.splitlines() if l.startswith("지적")))data = json.load(open(sys.argv[2], encoding="utf-8"))BAN = {"phone": r"0\d{1,2}[- .]?\d{3,4}[- .]?\d{4}", "order-no": r"\d{4}-\d{3,}|주문\s*번호",       "compensation": r"보상|쿠폰|환불|할인"}bad, seen = 0, set()for r in data["replies"]:    for name, pat in BAN.items():        if m := re.search(pat, r["reply"]):            bad += 1; print(f"BAN  review {r['review']}: {name} {m.group(0)!r}")    seen |= {m["id"] for m in r["map"] if m["sentence"] in r["reply"]}for i in ids:    if i not in seen: bad += 1; print(f"GAP  {i}: no matching sentence")print(f"{len(data['replies'])} replies, {len(ids)} points, {bad} problems")sys.exit(1 if bad else 0)
console
$ python3 -I check.py reviews.ko.txt out1.ko.1.txt; echo "exit $?"3 replies, 6 points, 0 problemsexit 0

두 번째 결과도 통과했습니다. 걸러지는지는 첫 결과의 리뷰 1 답글 끝에 「연락 주시면 쿠폰을 드리겠습니다. 010-0000-0000」 을 손으로 붙이고 리뷰 3 의 map 한 줄을 지운 out-bad.ko.json 으로 확인했습니다(모델이 만든 오류가 아닙니다).

console
$ python3 -I check.py reviews.ko.txt out-bad.ko.json; echo "exit $?"BAN  review 1: phone '010-0000-0000'BAN  review 1: order-no '0000-0000'BAN  review 1: compensation '쿠폰'GAP  3-2: no matching sentence3 replies, 6 points, 4 problemsexit 1

전화번호 하나가 두 규칙에 같이 걸렸습니다.

[6] 올리기 전에 사람이 볼 것이 남습니다. 스크립트는 「지적 번호마다 문장이 하나 있다」만 압니다. 두 번째 결과에서 2-1(양이 적다) 의 대응 문장으로 고른 것이 사과가 아니라 「해당 날짜의 개별 포장 중량 기록이 남아 있지 않아…」 라는 설명이었습니다. 문장이 있다고 해서 지적에 맞게 답한 것은 아닙니다.

또 하나, 사실 메모를 문장째 옮깁니다. 「기사 배정이 늦어진 것이 그대로 배달 지연으로 이어졌습니다」 「배달 기사 배정이 늦어진 점이 확인되었습니다」 가 두 결과에 각각 들어갔습니다. 기사를 탓하지 말라는 규칙이 있어도 원인 설명은 변명처럼 읽히니, 메모에는 사과에 필요한 사실만 두십시오. 「전화 응대 첫 문장을 정했다」 같은 내부 조치를 공개할지도 사장님이 정합니다.

#review-reply#prompt#리뷰답글#claude