AI가 작성한 정규표현식을 일치·불일치 예제로 검증하기
AI가 만든 정규표현식을 완성된 규칙이 아니라 초안으로 취급합니다. 작은 합성 테스트 표를 만들고 예상 결과와 실제 매칭 결과를 비교한 뒤, 수정된 패턴과 검토 보고서를 확인합니다.
AI 요약을 개별 주장 단위로 나누고 각 문장을 구체적인 원문 문장과 연결해 근거가 없는 내용이나 과장된 내용을 찾습니다. 작은 합성 문서로 자연스러운 문장도 근거 검토가 필요한 이유를 확인합니다.
이런 분께 맞아요AI로 보고서, 회의록, 연구 자료를 요약한 뒤 문장별 근거를 추적해 검토하려는 사람을 위한 안내입니다.
AI 요약은 문법적으로 자연스러워도 원인의 의미를 바꾸거나, 제한 조건을 빼거나, 원문에 없는 결론을 추가할 수 있습니다. 따라서 각 요약 문장마다 어떤 원문이 이 문장을 실제로 뒷받침하는지 확인해야 합니다.
이 글에서는 세 가지 판정을 사용합니다. SUPPORTED는 원문이 문장을 직접 뒷받침하는 경우, PARTIAL은 문장의 일부만 근거가 있거나 중요한 조건이 빠진 경우, UNSUPPORTED는 원문이 해당 주장을 뒷받침하지 않거나 오히려 반대되는 경우입니다.
아래 원문과 요약은 이 글을 위해 작성한 합성 데이터입니다. 첫 번째 블록은 source.txt, 두 번째 블록은 ai_summary.txt로 저장하세요.
S1|The pilot processed 20 test cases.
S2|Eighteen cases completed successfully.
S3|Two cases stopped during input validation before the solver started.
S4|Among the 18 completed cases, the median runtime was 42 seconds.
S5|The test was performed on one workstation under the same software configuration.
S6|No production deployment or long-term reliability test was performed.
A1|The pilot processed 20 cases, and 18 completed successfully.
A2|Two cases failed because the solver crashed.
A3|The median runtime for completed cases was 42 seconds.
A4|The results show that the automation is reliable enough for production use.
원문은 6문장이고 AI 요약은 4문장입니다. 소프트웨어 없이도 손으로 대조할 수 있도록 숫자와 문장을 작게 구성했습니다.
A1은 S1과 S2가 직접 뒷받침합니다. A2는 S3과 맞지 않습니다. 원문에는 두 사례가 solver 실행 전에 입력 검증 단계에서 중단되었다고 되어 있으며 solver crash라고 하지 않습니다. A3은 S4가 직접 뒷받침합니다. A4는 S6에서 production deployment와 long-term reliability test를 하지 않았다고 명시했으므로 근거 범위를 넘어섭니다.
| 요약 ID | 판정 | 근거 | 이유 |
|---|---|---|---|
| A1 | SUPPORTED | S1;S2 | 전체 사례 수와 성공 사례 수가 원문과 일치합니다. |
| A2 | UNSUPPORTED | S3 | 원문은 solver 실행 전 input validation 단계에서 중단되었다고 합니다. |
| A3 | SUPPORTED | S4 | 완료된 18개 사례의 median runtime이 42초라고 명시되어 있습니다. |
| A4 | UNSUPPORTED | S6 | production readiness는 시험하지 않았습니다. |
따라서 예상 검토 결과는 SUPPORTED 2문장, UNSUPPORTED 2문장입니다. 이 합성 예제에서는 PARTIAL 판정이 필요한 문장이 없습니다.
아래 내용을 summary_review.csv로 저장하세요. evidence 열에는 원문 전체를 복사하지 않고 원문 문장 ID를 기록합니다. 검토 기록을 간결하게 유지하면서도 근거 위치를 추적할 수 있습니다.
summary_id,verdict,evidence,reason
A1,SUPPORTED,S1;S2,The case count and successful completions match the source.
A2,UNSUPPORTED,S3,The source attributes the stops to input validation before solver start.
A3,SUPPORTED,S4,The 42-second median is stated for the 18 completed cases.
A4,UNSUPPORTED,S6,Production readiness was not tested.
이 판정 자체는 사람의 검토 결과입니다. 다음 스크립트는 주장의 사실 여부를 판단하지 않습니다. 모든 AI 요약 문장에 유효한 판정이 있는지, 그리고 인용한 원문 ID가 실제로 존재하는지를 검사합니다.
다음 스크립트를 check_summary_review.py로 저장하세요. 원문, AI 요약, 작성된 검토 CSV를 읽어 ID와 판정을 확인하고 새로운 검증 결과 파일을 만듭니다. 입력 파일은 수정하지 않습니다.
import csv
from pathlib import Path
SOURCE = Path("source.txt")
SUMMARY = Path("ai_summary.txt")
REVIEW = Path("summary_review.csv")
OUTPUT_DIR = Path("outputs") / "summary_source_check"
OUTPUT = OUTPUT_DIR / "validated_review.csv"
VALID_VERDICTS = {"SUPPORTED", "PARTIAL", "UNSUPPORTED"}
def read_id_text(path: Path) -> dict[str, str]:
result = {}
with path.open("r", encoding="utf-8") as stream:
for line_number, line in enumerate(stream, start=1):
line = line.rstrip("\n")
if not line:
continue
item_id, separator, text = line.partition("|")
if not separator or not item_id or not text:
raise ValueError(f"Invalid line {line_number} in {path}.")
if item_id in result:
raise ValueError(f"Duplicate ID {item_id} in {path}.")
result[item_id] = text
return result
def main() -> None:
for path in (SOURCE, SUMMARY, REVIEW):
if not path.is_file():
raise FileNotFoundError(f"Missing input: {path}")
if OUTPUT_DIR.exists():
raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")
source = read_id_text(SOURCE)
summary = read_id_text(SUMMARY)
reviewed = {}
with REVIEW.open("r", encoding="utf-8-sig", newline="") as stream:
reader = csv.DictReader(stream)
required = {"summary_id", "verdict", "evidence", "reason"}
if reader.fieldnames is None or not required.issubset(reader.fieldnames):
raise ValueError("Review CSV is missing a required column.")
for row in reader:
summary_id = row["summary_id"].strip()
verdict = row["verdict"].strip()
evidence_ids = [x.strip() for x in row["evidence"].split(";") if x.strip()]
if summary_id not in summary:
raise ValueError(f"Unknown summary ID: {summary_id}")
if summary_id in reviewed:
raise ValueError(f"Duplicate review: {summary_id}")
if verdict not in VALID_VERDICTS:
raise ValueError(f"Invalid verdict for {summary_id}: {verdict}")
if not evidence_ids:
raise ValueError(f"No evidence listed for {summary_id}.")
unknown = [item for item in evidence_ids if item not in source]
if unknown:
raise ValueError(f"Unknown source IDs for {summary_id}: {unknown}")
if not row["reason"].strip():
raise ValueError(f"Missing reason for {summary_id}.")
reviewed[summary_id] = {
"summary_id": summary_id,
"summary_text": summary[summary_id],
"verdict": verdict,
"evidence": ";".join(evidence_ids),
"reason": row["reason"].strip(),
}
missing = [item for item in summary if item not in reviewed]
if missing:
raise ValueError(f"Summary sentences not reviewed: {missing}")
OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_DIR.mkdir()
fields = ["summary_id", "summary_text", "verdict", "evidence", "reason"]
with OUTPUT.open("x", encoding="utf-8", newline="") as stream:
writer = csv.DictWriter(stream, fieldnames=fields)
writer.writeheader()
for summary_id in summary:
writer.writerow(reviewed[summary_id])
counts = {verdict: 0 for verdict in VALID_VERDICTS}
for row in reviewed.values():
counts[row["verdict"]] += 1
print(f"Summary sentences checked: {len(summary)}.")
print(
f"Supported: {counts['SUPPORTED']}; partial: {counts['PARTIAL']}; "
f"unsupported: {counts['UNSUPPORTED']}."
)
print(f"Output: {OUTPUT.as_posix()}")
if __name__ == "__main__":
main()
위 합성 검토에서는 요약 4문장 모두 유효한 판정과 존재하는 원문 ID를 갖고 있습니다. 아래 콘솔 출력은 손으로 도출한 예상값이며 실제 실행 로그가 아닙니다.
Summary sentences checked: 4.
Supported: 2; partial: 0; unsupported: 2.
Output: outputs/summary_source_check/validated_review.csv| 문제 | 처리 방법 |
|---|---|
| 한 문장에 서로 다른 주장 두 개가 있음 | 문장을 주장 단위로 나누거나 각 주장을 별도로 검토하세요. |
| 근거가 문장의 일부만 지원함 | PARTIAL로 표시하고 빠진 제한 조건을 reason에 적으세요. |
| 원문에 결론에 대한 내용이 없음 | 그럴듯한 추론을 출처가 있는 사실처럼 처리하지 말고 UNSUPPORTED로 표시하세요. |
| 존재하는 source ID를 적었지만 실제 의미가 맞지 않음 | 사람의 검토를 수정해야 합니다. 유효한 ID 자체는 의미적 근거를 증명하지 않습니다. |
| 여러 출처가 서로 충돌함 | 하나의 결론으로 억지로 합치지 말고 충돌 사실을 명시적으로 기록하세요. |
Python 스크립트가 검증하는 것은 검토의 완전성과 참조 ID의 유효성이지 사실 여부가 아닙니다. 의미적 근거는 여전히 사람이 읽거나 별도의 근거 평가 절차를 통해 판단해야 합니다. 사람이 잘못 판정하면 구조 검사는 통과할 수 있습니다.
긴 보고서에서는 임의의 문장 번호 대신 페이지, 문단 ID, 표 번호, 인용 위치 등을 사용하세요. 또한 원문의 직접 진술, 계산 결과, 해석, 외부 지식을 구분해야 합니다. 요약이 이 범주들을 조용히 서로 바꾸어서는 안 됩니다.
2026-09-20 · 예제 수동 검토 · 대상: Python 3.12 · 표준 라이브러리: csv, pathlib · 실행하지 않음
설명과 예제는 직접 작성했습니다. 관련 동작과 개념은 아래 공식 자료에서 확인할 수 있습니다.