000%

수정

NOTES · SEO / GEO / AEO16 · 2026.10

AI 인용과 답변의 정확성·적합성을 평가하기

서비스와 질문 조건을 고정하고 답변 원문·인용 주소를 저장한다. 인용 여부와 사실·시점·조건의 정확성을 나누어 평가하고 횟수와 분모를 함께 비교한다.

§ 01SEO / GEO / AEO

AI 관측 질문을 세 종류로 나눈다

AI 답변에 도서관 링크가 생겨도 첫째 일요일에 방문하라고 안내하면 이용자는 잘못된 결정을 할 수 있다. GEO·AEO에서 인용을 세는 이유는 자료가 답변에 사용됐는지 보기 위해서이고, 정확성과 조건을 평가하는 이유는 그 사용이 질문을 제대로 해결했는지 보기 위해서다. 두 결과를 함께 읽어야 발견 경로를 고칠지 원고의 예외 설명을 고칠지 정할 수 있다.

자연 발견 질문은 사이트 이름이나 URL을 알려주지 않고 실제 이용자의 요구를 묻는다. 지정 출처 질문은 특정 사이트를 참고하라고 명시해 접근과 해석을 확인한다. 특정 문서 질문은 제목이나 주제를 좁혀 해당 글의 도달 가능성을 확인한다. 세 종류는 조건이 다르므로 인용률을 따로 계산한다.

지정 출처 질문은 사이트 주소를 이미 제공하므로 자연스럽게 발견하는 단계를 건너뛴다. 여기서는 답이 맞는데 자연 발견 질문에는 사이트가 나오지 않는다면, 문서 해석과 발견을 나눠 조사할 수 있다. 두 질문에서 모두 같은 예외를 놓친다면 인용된 부분과 조건 문장의 배치를 먼저 읽는다. 질문 유형을 나누는 이유가 이 비교에 있다.

가상 안내 사이트의 질문 설계
자연 발견
질문 예시
이번 일요일에 이용할 수 있는 공공 도서관은 어디인가?
읽을 결과
사이트나 페이지를 스스로 발견하는가
지정 출처
질문 예시
example.com의 안내를 참고해 대출 신청 서류를 설명해 줘.
읽을 결과
지정 자료를 읽고 조건을 정확히 설명하는가
특정 문서
질문 예시
“대출 신청 안내”에서 미성년자 신청 조건은 무엇인가?
읽을 결과
특정 문서의 해당 내용을 찾아 답하는가
§ 02SEO / GEO / AEO

동일한 조건으로 실행하고 원문을 남긴다

관측 대상 서비스마다 웹 검색 가능 여부와 선택한 모드를 기록한다. 새 대화에서 질문 하나를 실행하고, 이미 사이트를 알려준 이전 대화의 맥락을 섞지 않는다. 지역·언어·로그인·개인화 조건을 완전히 고정하기 어려우면 관측 당시 값을 적는다. 모델 이름을 확인할 수 없는 화면에서는 추정해서 채우지 않는다.

질문마다 반복 횟수를 실행 전에 정한다. 예를 들어 질문당 3회로 정했다면 인용이 잘 나온 응답만 고르지 않고 세 응답을 모두 저장한다. 응답이 회차마다 크게 달라지면 관측 횟수와 기간을 늘려 변동 폭을 확인한다. 서비스 오류·제한·응답 실패는 실행 실패로 기록하고 정상 응답의 미인용과 구분한다.

응답 전문, 실제 인용 링크, 실행 시각을 저장한다. 링크는 직접 열어 의도한 문서인지 확인한다. URL만 언급한 경우, 실제 출처로 인용한 경우, 이름만 언급한 경우를 나누어 기록한다. 브라우저에서 본 서비스 결과와 API 모델 결과는 별도 집합으로 유지한다. 두 경로는 검색 도구·모델·개인화 설정이 달라 서로 다른 답을 낼 수 있다.

§ 03SEO / GEO / AEO

인용과 답변 적합성을 별도로 판정한다

각 답변의 핵심 주장을 문장 단위로 나누고 인용 페이지와 대조한다. 정확성은 사실이 맞는지, 최신성은 질문의 기준 시점에 맞는지, 적합성은 질문의 대상·조건·의도를 충족하는지, 충분성은 주요 하위 질문을 빠뜨리지 않았는지로 읽는다. 링크를 붙였더라도 이 네 값은 서로 다를 수 있다.

예를 들어 “이번 일요일 이용 시간”에 평일 시간표를 정확히 인용했다면 사실의 전사는 맞아도 질문 적합성이 낮다. “신청 서류와 제출 방법”에 서류만 나열했다면 답이 불충분하다. 변경 전 안내를 현재 기준으로 답하면 최신성 오류다. 판정 옆에는 어느 문장과 어느 근거가 달랐는지 적는다.

응답 평가 기록
인용
판정 기준
대상 사이트의 관련 문서를 실제 출처로 연결
저장할 근거
인용 URL·응답 내 위치
정확성
판정 기준
핵심 주장과 근거 자료의 사실이 일치
저장할 근거
주장 문장·대응 원문
최신성
판정 기준
질문의 기준일에 유효한 자료 사용
저장할 근거
적용일·개정일·현재 안내
적합성
판정 기준
질문의 대상·조건·목적을 충족
저장할 근거
질문 요구와 답의 대응
충분성
판정 기준
필요한 주요 항목을 빠뜨리지 않음
저장할 근거
미답변 항목과 영향
미확인
판정 기준
판단할 근거 또는 접근 권한이 부족
저장할 근거
부족한 자료·재확인 조건
§ 04SEO / GEO / AEO

횟수와 분모를 함께 집계한다

관측 인용률은 관련 문서의 인용 여부를 끝까지 확인한 정상 응답을 분모로 계산한다. 정상 응답이라도 출처 영역이 잘렸거나 확인되지 않으면 미확인으로 분리한다. 실행 실패 수와 완료 응답 수, 판정 가능한 수를 함께 제시한다. 이름 언급률·관련 문서 인용률·정확한 답변 비율은 각각 계산한다.

관측 인용률 = 관련 문서 인용이 있는 정상 응답 수 / 인용 여부를 판정한 정상 응답 수 정확 응답 비율 = 핵심 사실이 모두 맞는 응답 수 / 정확성을 판정한 응답 수 적합 응답 비율 = 질문의 대상·조건·의도에 맞는 응답 수 / 적합성을 판정한 응답 수 함께 보고할 값 = 전체 시도 수, 실행 실패 수, 미확인 수

가령 12회 시도 중 2회 실패하고 10개의 정상 응답 가운데 4개가 인용했다면 4/10과 실패 2회를 함께 쓴다. 미확인을 정답이나 오답에 넣지 말고 판정 가능한 분모와 함께 표시한다. 작은 표본에서는 퍼센트만 쓰지 않고 실제 횟수를 적는다.

변경 전후에는 같은 URL·질문·서비스·조건으로 응답을 모은다. 동시에 광고를 시작했거나 외부 보도가 있었다면 그 날짜도 표시한다. 인용이 늘어난 시점과 본문·서비스·유입 조건이 바뀐 시점을 함께 보면 다음에 비교할 변수를 고를 수 있다.

§ 05SEO / GEO / AEO

한 답변을 주장·출처·조건으로 나누어 판정한다

가상 도서관의 두 답변을 비교한다. 질문은 “이번 일요일 도서관을 언제 이용할 수 있는가”이며, 기준일은 첫째 일요일이다. 현재 운영 안내에는 첫째 일요일 휴관이 명시돼 있다.

답변 A가 “일요일 10시부터 17시까지 이용할 수 있다”고 말하며 현재 안내를 연결했다면 인용은 있지만 휴관 예외를 빠뜨렸다. 답변 B가 “이번 일요일은 휴관이며 다음 운영일은 별도 일정에서 확인해야 한다”고 근거와 함께 말했다면 질문의 조건을 반영했다. 실제 판정에서는 달력 기준일과 인용 원문의 유효 시점을 함께 확인한다.

인용 수를 셀 때는 관련 없는 외부 사이트까지 목표 사이트 인용으로 넣지 않는다. 사이트 인용과 목표 상세 문서 인용을 나누고, 해당 주장을 지지하는 부분이 없는 링크는 근거 불충분으로 기록한다. 평가할 수 없는 응답은 그 이유와 함께 분모에서 구분한다.

한 답변을 주장·출처·조건으로 나누어 판정한다
현재 안내 인용 + 휴관 예외 누락
관측할 내용
인용 있음, 적합성·충분성 부족
다음 조치
조건 문장과 실제 답변을 대조
과거 시간표를 현재 기준으로 사용
관측할 내용
인용 있음, 최신성 오류
다음 조치
문서 기간과 현재 안내 연결 확인
이름만 언급하고 근거 링크 없음
관측할 내용
언급 있음, 확인한 인용 없음
다음 조치
출처 선택과 명칭·본문 관계 조사
출처 화면 접근 실패
관측할 내용
인용 주소 있음, 근거 대조 미확인
다음 조치
접근 실패 사유를 남기고 재확인
§ 06SEO / GEO / AEO

출처의 주장 지지와 사실의 정답을 별도로 집계한다

metrics.mjs는 cited·supported·accurate·timely·relevant·complete를 집계한다. 차례로 관련 문서 인용, 주장 근거, 사실 정확성, 질문 시점에 맞는 자료 사용, 질문 의도와 조건 대응, 필요한 답의 충분성을 뜻한다. 마지막 complete는 응답 완료 상태인 status: complete와 별도 판정이다. 판정값 true·false·null마다 이유를 기록하며 null은 평가 불가능을 뜻한다.

질문을 “2026년 11월 1일 이용할 수 있는가”로 고정한다. 출처가 “2025년 일요일 11–16시”인데 답변이 “지금도 11–16시 이용 가능”이라고 쓰면 출처는 현재 주장을 지지하지 않는다. supported와 accurate는 모두 false다. 답변이 “2025년에는 11–16시였다”고 한정하면 그 과거 주장은 supported와 accurate가 true일 수 있다. 그러나 현재 질문에 필요한 답이 없어 timely·relevant·complete는 false다. 사실이 맞는 문장과 질문에 충분한 답을 구분하는 이유다.

metrics.mjs, 아래 assessment-fixture.mjs, metrics.test.mjs를 같은 폴더에 저장하고 node --test metrics.test.mjs를 실행한다. 6개 테스트는 0분모, 실패·미확인, 과거 사실, 일부 주장만의 지지, 필드 누락, 집계 조건 분리를 검사한다. 가상 입력은 4회 시도·3회 완료·1회 실패이며, 근거 지지는 판정 가능한 2개 중 1개이고 미확인은 1개다.

// 답변 평가에 사용하는 fixture 데이터. const common = { questionId: 'sunday-hours', questionVersion: '2', service: 'fixture-assistant', mode: 'offline', model: null, question: '2026년 11월 1일 일요일에 가상 도서관을 이용할 수 있나요?', reference: '2026년 10월 적용 운영 시간 안내: 첫째 일요일과 공휴일 휴관.', rawPath: 'raw/answer-1.json', status: 'complete' }; export const examples = [ { ...common, answer: '일요일 10–17시입니다. 11월 1일에도 이용할 수 있습니다.', cited: true, supported: false, accurate: false, timely: true, relevant: true, complete: false, reasons: { cited: '현재 운영 시간 안내를 출처로 연결', supported: '두 번째 주장을 원문이 지지하지 않음', accurate: '첫째 일요일 휴관과 충돌', timely: '2026년 10월 적용 자료 사용', relevant: '질문의 날짜와 시설을 다룸', complete: '휴관 예외 누락' }, claims: [ { text: '일반 일요일 운영 시간은 10–17시', sourceUrl: 'https://example.com/policies/opening-hours', quote: '일요일 10–17시. 첫째 일요일과 공휴일 휴관.', supported: true, accurate: true, reason: '일반 운영 시간으로 한정한 주장은 일치' }, { text: '11월 1일에도 이용할 수 있음', sourceUrl: 'https://example.com/policies/opening-hours', quote: '첫째 일요일 휴관', supported: false, accurate: false, reason: '2026-11-01은 첫째 일요일' }, ] }, { ...common, rawPath: 'raw/answer-2.json', answer: '2025년에는 일요일 11–16시였습니다.', cited: true, supported: true, accurate: true, timely: false, relevant: false, complete: false, reasons: { cited: '2025년 기록 연결', supported: '연도를 명시한 과거 사실은 출처와 일치', accurate: '가상 기준 자료의 과거 사실과 일치', timely: '질문의 2026년 적용 자료가 아님', relevant: '2026-11-01 방문 가능 여부에 답하지 않음', complete: '현재 휴관 여부 누락' }, claims: [{ text: '2025년 일요일 11–16시 운영', sourceUrl: 'https://example.com/archive/opening-2025', quote: '2025년 일요일 11–16시 운영', supported: true, accurate: true, reason: '문장과 출처 모두 2025년으로 한정됨' }] }, { ...common, rawPath: 'raw/answer-3.json', answer: '저장 중 응답이 잘렸습니다.', cited: null, supported: null, accurate: null, timely: null, relevant: null, complete: null, reasons: Object.fromEntries(['cited','supported','accurate','timely','relevant','complete'] .map(key => [key, '저장된 원문이 잘려 판정할 수 없음'])), claims: [{ text: '미확인', sourceUrl: null, quote: null, supported: null, accurate: null, reason: '원문 불완전' }] }, { ...common, rawPath: 'raw/attempt-4.json', status: 'failed', failure: 'HTTP 429', answer: null }, ];

import test from 'node:test'; import assert from 'node:assert/strict'; import { ratio, evaluate, grouped } from './metrics.mjs'; import { examples } from './assessment-fixture.mjs'; test('0분모는 null이며 잘못된 횟수는 거절한다', () => { assert.equal(ratio(0, 0).rate, null); for (const pair of [[2, 1], [-1, 1], [0.5, 1]]) assert.throws(() => ratio(...pair)); }); test('실행 실패와 미확인은 각각의 분모에서 분리한다', () => { const r = evaluate(examples); assert.equal(r.attempts, 4); assert.equal(r.completed, 3); assert.equal(r.failed, 1); assert.deepEqual(r.metrics.supported, { numerator: 1, denominator: 2, rate: 0.5, unknown: 1 }); assert.equal(r.metrics.complete.numerator, 0); }); test('과거 사실이 맞아도 현재 질문의 최신성·적합성은 실패할 수 있다', () => { const r = evaluate([examples[1]]).metrics; assert.equal(r.accurate.rate, 1); assert.equal(r.timely.rate, 0); assert.equal(r.relevant.rate, 0); }); test('일부 주장만 지지되는 답을 전체 지지로 집계할 수 없다', () => { assert.throws(() => evaluate([{ ...examples[0], supported: true }]), /summary mismatch/); }); test('판정·이유·원문·주장 근거 누락을 거절한다', () => { for (const key of ['timely', 'reasons', 'claims', 'rawPath']) { const broken = structuredClone(examples[0]); delete broken[key]; assert.throws(() => evaluate([broken])); } const broken = structuredClone(examples[1]); broken.claims[0].quote = null; assert.throws(() => evaluate([broken])); }); test('질문 버전·서비스·모드가 바뀌면 별도 집계한다', () => { const rows = [examples[0], { ...examples[0], questionVersion: '3' }, { ...examples[0], service: 'another' }, { ...examples[0], mode: 'browser' }]; rows.push({ ...examples[0], model: 'model-b' }); assert.equal(grouped(rows).length, 5); }); console.log(JSON.stringify(grouped(examples), null, 2));

이 함수는 사람이 기록한 true·false·null 판정과 응답 상태를 입력받아 횟수와 비율을 계산한다. 질문 원문·기준일·기대 사실·응답 원문·근거 구절을 먼저 저장하고 같은 판정 기준으로 입력한다. ALCE 연구의 인용 평가와 실무의 응답 단위 요약 지표는 평가 단위가 다르므로 같은 점수 이름으로 합치지 않는다. ALCE 연구

evaluate는 완료 행마다 원문 경로·질문 버전·서비스·모드·판정 이유를 요구한다. claims에는 답의 핵심 주장을 빠짐없이 분해해 출처 구절과 판정을 적는다. 하나라도 지지되지 않은 주장이 있으면 supported 전체값은 false다. 모두 true일 때만 true이며 미확인이 남으면 null이다. grouped는 질문·질문 버전·서비스·모드·확인된 모델이 다른 응답을 따로 집계한다. 모델을 확인할 수 없으면 null로 남기고, 확인된 모델은 같은 집계 키에 포함해 모델 변경 전후를 섞지 않는다.

supported와 accurate를 따로 두면 출처 선택의 오류와 사실 전달의 오류를 나눌 수 있다. 정확한 휴관 사실을 말하면서 그 내용을 담지 않은 페이지를 인용했다면 사실과 근거 연결의 판정이 달라진다. 반대로 과거 자료의 시간은 정확히 옮겼어도 현재 질문에는 맞지 않을 수 있다. metrics.mjs의 분리된 값은 이런 실패를 인용률 한 값에 숨기지 않고 다음 조사로 연결한다.

§ 07SEO / GEO / AEO

연구의 가시성 지표와 운영 지표를 구분한다

2024년 GEO 논문의 GEO-bench는 1만 질문을 훈련 8천·검증 1천·시험 1천으로 나눈다. 기본 실험은 Google 상위 5개 출처를 가져와 gpt-3.5-turbo로 답을 생성하며 temperature 0.7에서 다섯 응답을 샘플링한다. 한 출처를 골라 인용문·통계·출처 추가 등의 수정 방법을 비교했다. GEO 논문 §3

논문 표 1의 Position-Adjusted Word Count Overall은 수정 전 19.3, Quotation Addition 27.2다. (27.2−19.3)÷19.3으로 계산하면 약 40.9% 증가했다. 이 지표는 출처에 연결된 답변 내용의 양과 위치를 반영한다. Subjective Impression은 별도 평가 지표로, 같은 표에서 수정 전 19.3, 인용문 추가 24.7이다. GEO 논문 표 1

응답 단위 인용률은 다른 방식으로 계산한다. 예를 들어 답변 10개 중 4개가 문서를 인용했다면 40%다. 그 네 답변이 문서를 끝에 한 줄씩 인용한 경우와 처음부터 여러 문장을 인용한 경우의 인용률은 같다. 양과 위치를 가중하는 연구 지표에서는 두 경우의 점수가 달라질 수 있다.

사이트에서 인용문을 보강한 효과를 보려면 수정 전후에 같은 질문을 실행한다. 인용한 응답 수와 인용된 문장의 양, 질문에 필요한 예외가 반영됐는지를 함께 비교한다. 모델이나 검색 모드가 바뀌었다면 그 조건의 응답을 따로 묶는다.

§ 08SEO / GEO / AEO

현재 역할과 영상의 예외 조건을 질문으로 검사한다

기관 역할의 평가에는 “현재 도서관 운영 주체는 누구인가”와 “2025년 개관을 준비한 조직은 누구인가”를 함께 둔다. 첫 질문의 기대 근거는 현행 소개, 둘째 질문의 기대 근거는 당시 회의록이다. 현재 질문에 준비위원회를 답하면 인용 주소가 실제로 있어도 시점이 틀린 답이다. 과거 질문에 운영팀을 답해도 같은 오류다.

오류가 나면 가져온 문서의 ID·적용 기간·인용 구절을 읽는다. 현행 소개 자체가 옛 역할이면 원문을 고친다. 원문은 새 역할인데 검색 자료가 옛 게시본이면 재수집을 고친다. 두 자료가 모두 최신인데 현재 질문에 과거 문서를 선택했다면 시간 조건과 검색 순위를 조사한다. 답변에 날짜를 덧붙이는 것으로 원인 조사를 끝내지 않는다.

영상 실습의 답 “매주 일요일 10~17시에 이용할 수 있다”는 0~4초의 시간은 읽었지만 4~8초의 휴관 예외를 빠뜨렸다. 주장별로 시간·반복 조건·휴관 예외를 나누고 각각 지지하는 구간을 기록한다. 답이 연결한 주소에서 해당 구간으로 이동하는지, 그 구간의 원문이 답을 지지하는지, 질문의 날짜가 휴관일인지 순서대로 대조한다.

— FIN —