AI 업무 활용

AI 요약을 원문과 문장별로 대조하기

AI 요약을 개별 주장 단위로 나누고 각 문장을 구체적인 원문 문장과 연결해 근거가 없는 내용이나 과장된 내용을 찾습니다. 작은 합성 문서로 자연스러운 문장도 근거 검토가 필요한 이유를 확인합니다.

목차 보기

이런 분께 맞아요AI로 보고서, 회의록, 연구 자료를 요약한 뒤 문장별 근거를 추적해 검토하려는 사람을 위한 안내입니다.

준비할 것
  • Python 3.12와 해당 버전을 실행하는 터미널 명령이 준비되어 있어야 합니다.
  • UTF-8 텍스트와 CSV 파일을 저장할 수 있는 텍스트 편집기가 필요합니다.
  • 스크립트가 outputs 아래에 새 폴더를 만들 수 있는 작업 폴더가 필요합니다.
  • Python 표준 라이브러리인 csv와 pathlib만 사용합니다.

01요약 문장 하나를 하나의 주장으로 보기

AI 요약은 문법적으로 자연스러워도 원인의 의미를 바꾸거나, 제한 조건을 빼거나, 원문에 없는 결론을 추가할 수 있습니다. 따라서 각 요약 문장마다 어떤 원문이 이 문장을 실제로 뒷받침하는지 확인해야 합니다.

이 글에서는 세 가지 판정을 사용합니다. SUPPORTED는 원문이 문장을 직접 뒷받침하는 경우, PARTIAL은 문장의 일부만 근거가 있거나 중요한 조건이 빠진 경우, UNSUPPORTED는 원문이 해당 주장을 뒷받침하지 않거나 오히려 반대되는 경우입니다.

02합성 원문과 AI 요약 만들기

아래 원문과 요약은 이 글을 위해 작성한 합성 데이터입니다. 첫 번째 블록은 source.txt, 두 번째 블록은 ai_summary.txt로 저장하세요.

text
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.
text
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문장입니다. 소프트웨어 없이도 손으로 대조할 수 있도록 숫자와 문장을 작게 구성했습니다.

03요약 문장을 원문과 직접 비교하기

A1은 S1과 S2가 직접 뒷받침합니다. A2는 S3과 맞지 않습니다. 원문에는 두 사례가 solver 실행 전에 입력 검증 단계에서 중단되었다고 되어 있으며 solver crash라고 하지 않습니다. A3은 S4가 직접 뒷받침합니다. A4는 S6에서 production deployment와 long-term reliability test를 하지 않았다고 명시했으므로 근거 범위를 넘어섭니다.

요약 ID판정근거이유
A1SUPPORTEDS1;S2전체 사례 수와 성공 사례 수가 원문과 일치합니다.
A2UNSUPPORTEDS3원문은 solver 실행 전 input validation 단계에서 중단되었다고 합니다.
A3SUPPORTEDS4완료된 18개 사례의 median runtime이 42초라고 명시되어 있습니다.
A4UNSUPPORTEDS6production readiness는 시험하지 않았습니다.

따라서 예상 검토 결과는 SUPPORTED 2문장, UNSUPPORTED 2문장입니다. 이 합성 예제에서는 PARTIAL 판정이 필요한 문장이 없습니다.

04추적 가능한 검토 CSV 작성하기

아래 내용을 summary_review.csv로 저장하세요. evidence 열에는 원문 전체를 복사하지 않고 원문 문장 ID를 기록합니다. 검토 기록을 간결하게 유지하면서도 근거 위치를 추적할 수 있습니다.

csv
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가 실제로 존재하는지를 검사합니다.

05모든 문장이 검토되었는지 자동 확인하기

다음 스크립트를 check_summary_review.py로 저장하세요. 원문, AI 요약, 작성된 검토 CSV를 읽어 ID와 판정을 확인하고 새로운 검증 결과 파일을 만듭니다. 입력 파일은 수정하지 않습니다.

python
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()

06예상 검증 결과 확인하기

위 합성 검토에서는 요약 4문장 모두 유효한 판정과 존재하는 원문 ID를 갖고 있습니다. 아래 콘솔 출력은 손으로 도출한 예상값이며 실제 실행 로그가 아닙니다.

text
Summary sentences checked: 4.
Supported: 2; partial: 0; unsupported: 2.
Output: outputs/summary_source_check/validated_review.csv
  • 모든 A-ID가 검토 CSV에 정확히 한 번씩 있는지 확인합니다.
  • 인용된 모든 S-ID가 source.txt에 실제로 존재하는지 확인합니다.
  • UNSUPPORTED 문장을 조용히 SUPPORTED로 바꾸지 않았는지 확인합니다.
  • 검토 추적성을 유지하기 위해 원래 AI 요약은 수정하지 않고 보관합니다.
  • OUTPUT_DIR을 바꾸지 않고 다시 실행합니다. 이전 검증 결과를 덮어쓰지 않고 FileExistsError로 중단되어야 합니다.

07자주 발생하는 오류와 한계 이해하기

문제처리 방법
한 문장에 서로 다른 주장 두 개가 있음문장을 주장 단위로 나누거나 각 주장을 별도로 검토하세요.
근거가 문장의 일부만 지원함PARTIAL로 표시하고 빠진 제한 조건을 reason에 적으세요.
원문에 결론에 대한 내용이 없음그럴듯한 추론을 출처가 있는 사실처럼 처리하지 말고 UNSUPPORTED로 표시하세요.
존재하는 source ID를 적었지만 실제 의미가 맞지 않음사람의 검토를 수정해야 합니다. 유효한 ID 자체는 의미적 근거를 증명하지 않습니다.
여러 출처가 서로 충돌함하나의 결론으로 억지로 합치지 말고 충돌 사실을 명시적으로 기록하세요.

Python 스크립트가 검증하는 것은 검토의 완전성과 참조 ID의 유효성이지 사실 여부가 아닙니다. 의미적 근거는 여전히 사람이 읽거나 별도의 근거 평가 절차를 통해 판단해야 합니다. 사람이 잘못 판정하면 구조 검사는 통과할 수 있습니다.

긴 보고서에서는 임의의 문장 번호 대신 페이지, 문단 ID, 표 번호, 인용 위치 등을 사용하세요. 또한 원문의 직접 진술, 계산 결과, 해석, 외부 지식을 구분해야 합니다. 요약이 이 범주들을 조용히 서로 바꾸어서는 안 됩니다.

실행·검증 기록

2026-09-20 · 예제 수동 검토 · 대상: Python 3.12 · 표준 라이브러리: csv, pathlib · 실행하지 않음

  • 합성 원문 6문장과 합성 AI 요약 4문장을 수동으로 대조했습니다.
  • A1과 A3을 SUPPORTED, A2와 A4를 UNSUPPORTED로 분류했습니다.
  • A2가 solver crash라고 주장하지만 S3은 solver 실행 전 input validation 단계에서 중단되었다고 하므로 맞지 않음을 확인했습니다.
  • A4가 production use 가능성을 주장하지만 S6은 production deployment와 long-term reliability를 시험하지 않았다고 하므로 근거 범위를 넘는다고 확인했습니다.
  • 예상 판정 개수를 SUPPORTED 2, PARTIAL 0, UNSUPPORTED 2로 수동 계산했습니다.
  • 누락 검토, 중복 ID, 잘못된 판정값, 존재하지 않는 source ID, 출력 충돌 방지, 보고서 생성 로직을 코드로 검토했습니다.
검증 한계
  • 이 응답의 작성자는 코드를 실행하지 않았으며, 검증 CSV를 실제로 만들지 않았습니다.
  • 구조 검증 스크립트는 인용된 원문이 실제로 주장을 의미적으로 뒷받침하는지 판단하지 않습니다.
  • 여기 제시한 합성 원문과 요약만 검토했으며 긴 문서, 표, 그림, 상충하는 출처는 시험하지 않았습니다.
  • 공식 문서 URL은 알려진 문서 위치를 사용했지만 실시간으로 접속해 확인하지 않았습니다.

사이트 전체 작성·검증 원칙

참고 출처

설명과 예제는 직접 작성했습니다. 관련 동작과 개념은 아래 공식 자료에서 확인할 수 있습니다.