불만 메일의 요구를 빠짐없이 답한 회신 초안 받기
화난 고객 메일에 AI 초안을 시키면 사과만 길고 요구 하나가 빠집니다. 요구를 원문 인용으로 먼저 뽑게 하고, 요구마다 답했는지와 약속이 허용 목록 안인지 스크립트로 대조합니다.
사과는 정중한데 고객이 적은 「3일 안에 답이 없으면」 에는 답이 없는 초안이 나옵니다. 요구를 먼저 목록으로 뽑고 회신 문장마다 어느 요구에 대한 답인지 꼬리표를 달게 하면 빠진 곳이 보입니다. 인용 대조 방식은 요약 인용 검증 과 같습니다.
[1] 입력은 가상 가구 쇼핑몰의 불만 메일과 회사가 할 수 있는 조치 목록입니다. 메일의 요구는 문단 속에 섞여 있습니다(앞뒤 사정 줄은 …로 줄였습니다).
제목: 주문 1주일 넘게 안 오더니 온 건 다리 부러진 책장입니다 … 배송 예정일은 10월 2일이었는데 열흘이나 늦었고, 문의 게시판에 글을 두 번 남겼지만 답이 없었습니다. 열어 보니 오른쪽 아래 다리가 부러져 있었고 사진은 첨부합니다. … 요구 사항은 세 가지입니다. 첫째, 부러진 책장 말고 멀쩡한 새 제품을 보내 주세요. 둘째, 열흘 늦은 데 대해 상품 값의 절반을 돌려 주세요. 셋째, 반품이든 교환이든 택배 회수는 회사가 알아서 해 주시고 저는 한 번도 집을 비우지 않을 겁니다. 방문 날짜는 미리 문자로 알려 주세요. … 이 메일에 3일 안에 답이 없으면 카드사에 결제 취소를 요청하겠습니다.「세 가지」라고 했지만 문자 안내와 3일 기한까지 세면 요구는 다섯 줄입니다. 조치 목록은 일부러 고객 요구와 어긋나게 짰습니다.
[가능한 조치 — 이 목록 밖의 약속은 하지 않는다]1. 파손 확인 시: 새 제품 재배송. 출고는 접수 후 영업일 기준 5일 이내.2. 배송 지연 보상: 상품 금액의 10% 를 적립금으로 지급(현금 환불 아님). 지연 일수와 무관하게 상한 10%.3. 부분 환불: 상품에 흠이 있으나 고객이 그대로 쓰겠다고 할 때만, 상품 금액의 최대 30%.4. 회수: 재배송 시 파손품은 새 제품 배송 기사가 같은 날 회수. 별도 방문 회수는 평일 오전·오후 시간대 선택 방식이라 고객의 부재가 없어야 함(시간 약속 불가).5. 회신 기한: 이 건의 담당자 직통 회신은 영업일 기준 1일 이내.6. 카드 결제 취소는 고객과 카드사 사이의 절차이며 회사는 막지 않는다.[2] 프롬프트는 두 단계입니다. 요구를 원문 인용으로 뽑고, 회신의 각 답 끝에 [R1] 꼬리표를 답니다. 아래는 고친 최종본이고, 처음 돌린 첫 판은 ★ 줄 넷을 뺀 것입니다.
You are a customer-support writer for an online furniture shop. Work in two stages, in this order. Stage 1 - DEMANDS. List every request or demand the customer makes in <mail>. One line each: `R1 | "<quote>"`. The quote must be copied character for character from the mail. ★Include any deadline or ultimatum the customer sets. Do not list complaints that ask for nothing. Stage 2 - REPLY. Write the reply in Korean, polite 합쇼체, to the customer. Rules:- Answer each demand Rn in at least one sentence that ends with the tag [Rn]. If the answer is "we cannot", say so plainly and offer what the list allows.- Every action, amount, percentage or deadline you promise must come from <allowed>. Do not invent anything else (no compensation, no phone call, no time slot).- ★Use an item of <allowed> only if it fits the customer's situation as written (for example, a partial refund only if the customer says they keep the product).- ★If a demand is not covered by <allowed> at all, do not say yes or no. Write that the person in charge will confirm it in the reply within the deadline given in <allowed>.- ★Do not speak as an individual (no "제가"); use "저희" or "담당자".- One short apology, at the top. Do not repeat it.- Tone: calm, specific. No promises about what you "will try". Output exactly two parts: a line `== DEMANDS ==` with the list, then a line `== REPLY ==` with the reply. No other text.[3] 틀 뒤에 조치 목록과 메일을 붙여 실행합니다. 프롬프트는 표준 입력으로 줍니다.
$ (cat head.txt; echo "<allowed>"; cat policy.ko.txt; echo "</allowed>"; echo "<mail>"; cat mail.ko.txt; echo "</mail>") > prompt.ko.txt$ claude -p --model claude-sonnet-5-5 --max-turns 1 --tools "" --setting-sources "" --strict-mcp-config < prompt.ko.txt > out.ko.txt[4] 대조 스크립트입니다. 인용이 메일에 한 번 있는지, 목록의 요구마다 [Rn] 문장이 있는지, 회신의 N%·N일 이 조치 목록에도 있는지 셉니다.
import re, sysnorm = lambda s: " ".join(s.split())mail, policy = (norm(open(p, encoding="utf-8").read()) for p in sys.argv[1:3])dem, _, rep = open(sys.argv[3], encoding="utf-8").read().partition("== REPLY ==")bad, ids = 0, []for m in re.finditer(r'^(R\d+)\s*\|\s*"(.+)"\s*$', dem, re.M): ids.append(m.group(1)); n = mail.count(norm(m.group(2))); bad += n != 1 print("QUOTE", m.group(1), "OK" if n == 1 else f"FAIL({n}x)")for rid in ids: n = rep.count(f"[{rid}]"); bad += n == 0 print("REPLY", rid, "OK" if n else "EMPTY")for x in sorted(set(re.findall(r"\d+\s*(?:%|일|영업일|시간)", rep))): ok = x.replace(" ", "") in policy.replace(" ", ""); bad += not ok print("PROMISE", x, "in policy" if ok else "NOT IN POLICY")print("problems:", bad); sys.exit(1 if bad else 0)[5] 첫 판으로 돌린 결과의 요구 목록은 네 줄입니다.
== DEMANDS ==R1 | "부러진 책장 말고 멀쩡한 새 제품을 보내 주세요"R2 | "열흘 늦은 데 대해 상품 값의 절반을 돌려 주세요"R3 | "반품이든 교환이든 택배 회수는 회사가 알아서 해 주시고"R4 | "방문 날짜는 미리 문자로 알려 주세요"3일 기한 줄이 없는데도 check.py 는 problems: 0 입니다. 스크립트는 AI 가 뽑은 목록 안에서만 보기 때문에 빠진 줄은 못 잡습니다. 메일을 읽고 요구 개수를 사람이 먼저 세어 두십시오. 같은 첫 판을 한 번 더 돌려도 4줄이었고, 3일 기한은 두 번 다 빠졌습니다.
회신에도 스크립트가 못 보는 약점이 있었습니다. 첫 실행은 새 제품을 요구한 고객에게 「현재 상품을 그대로 사용하시겠다면 최대 30% 부분 환불이 가능합니다」 까지 안내했습니다(30% 는 목록에 있어 통과). 두 실행 모두 목록에 없는 「문자로 미리 알려 드리는 것은 약속드릴 수 없습니다」 라는 거절을 만들었습니다. 「제가」 는 이번에는 나오지 않았습니다.
[6] ★ 넷을 넣은 고친 판으로 2회 돌렸고 둘 다 5개를 뽑았습니다. 3일 기한이 R5 로 들어왔고, 두 회신 모두 문자 안내를 거절하지 않고 담당자 확인으로 돌렸습니다. 첫 실행의 R2·R4 답입니다.
지연 보상으로 상품 금액의 절반을 돌려드리는 것은 불가능합니다. [R2]지연 보상은 상품 금액의 10%를 적립금으로 지급해 드리며, 현금 환불은 아니고 지연 일수와 무관하게 10%가 상한입니다. [R2]부분 환불은 상품을 그대로 사용하시는 경우에만 가능한데, 고객님은 새 제품을 요청하셨으므로 해당하지 않습니다. [R2] 방문 날짜를 문자로 미리 알려 달라는 요청은 담당자가 확인한 뒤 영업일 기준 1일 이내 회신으로 안내드립니다. [R4][7] 대조가 걸러 내는지 손으로 만든 불량 파일로 봅니다. 고친 판 결과에서 R3 문장을 지우고 「5일」 을 「2일」 로, 인용의 「새 제품」 을 「제품」 으로 바꿨습니다(모델이 만든 오류가 아닙니다).
$ python3 -I check.py mail.ko.txt policy.ko.txt out-bad.txt | grep -E 'FAIL|EMPTY|NOT|problems'; echo "exit ${PIPESTATUS[0]}"QUOTE R1 FAIL(0x)REPLY R3 EMPTYPROMISE 2일 NOT IN POLICYproblems: 3exit 1통과하면 꼬리표를 지우고 보냅니다: sed 's/ *\[R[0-9]*\]//g' out.ko.txt.