AI引用と回答の正確性・適合性を評価する
サービスと質問条件を固定し、回答原文と引用URLを保存する。引用の有無と事実・時点・条件の正確さを分け、回数と分母を一緒に比較する。
AI観測の質問を三種類に分ける
AI回答に図書館リンクがあっても、第一日曜に来館するよう案内すれば利用者は誤った判断をする。GEO・AEOで引用を数えるのは資料が回答に使われたかを見るためで、正確性と条件を評価するのは質問を正しく解決したかを見るためだ。両方を読んで、発見経路を直すか、原稿の例外説明を直すかを決める。
自然発見質問はサイト名やURLを知らせず実際の利用要求を聞く。指定出典質問は特定サイトの参照を明示し、アクセスと解釈を確認する。特定文書質問はタイトルや主題を絞り、その記事へ届くかを見る。条件が違うので引用率を別々に計算する。
指定出典質問はURLを先に与えるので自然発見を飛ばす。こちらは正解でも自然発見ではサイトが出ないなら、解釈と発見を分けて調査できる。両方で同じ例外を落とすなら、引用箇所と条件文の配置を先に読む。種類を分ける理由はこの比較だ。
同じ条件で実行し原文を残す
各サービスのウェブ検索可否と選択モードを記録する。新しい会話で一問を実行し、サイトを教えた以前の会話の文脈を混ぜない。地域、言語、ログイン、個人化を完全固定できなければ観測時の値を記す。モデル名が分からない画面では推測で埋めない。
反復回数は質問ごとに実行前に決める。三回なら引用のよい回答だけ選ばず三つとも保存する。回ごとに大きく変わるなら観測回数と期間を増やし変動幅を見る。サービスエラー、制限、応答失敗は実行失敗とし、正常回答の引用なしと区別する。
応答全文、実際の引用リンク、実行時刻を保存する。リンクを開いて意図した文書か確認する。URLの言及、実際の出典引用、名前だけの言及を分ける。ブラウザーのサービス結果とAPIモデル結果は別集合にする。検索ツール、モデル、個人化設定が異なり、答えも変わり得るからだ。
引用と回答適合性を別に判定する
各回答の核心主張を文単位で分け、引用ページと照合する。正確性は事実の正しさ、最新性は質問基準時点での有効性、適合性は対象・条件・意図の充足、十分性は主要な下位質問の欠落を読む。リンクがあっても四つの値は異なり得る。
例えば今度の日曜の時間に平日表を正確に引用すれば、事実転記は正しくても質問適合性は低い。申請書類と提出方法に書類だけ並べれば不十分。変更前の案内を現行として答えれば最新性の誤りだ。判定の横に、どの文と根拠が違ったかを書く。
回数と分母を一緒に集計する
観測引用率は、関連文書の引用を最後まで確認した正常応答を分母にする。正常でも出典欄が切れたり未確認なら別扱いにする。実行失敗数、完了数、判定可能数を一緒に示す。名前言及率、関連文書引用率、正確回答率は別々に計算する。
観測引用率=関連文書を引用した正常応答数/引用有無を判定した正常応答数
正確回答率=核心事実がすべて正しい応答数/正確性を判定した応答数
適合回答率=質問の対象・条件・意図に合う応答数/適合性を判定した応答数
併記する値=全試行数、実行失敗数、未確認数
例えば十二回中二回失敗し、正常十回答のうち四つが引用したなら4/10と失敗二回を併記する。未確認を正誤に入れず、判定可能な分母と示す。小標本では百分率だけでなく実際の回数を書く。
変更前後は同じURL、質問、サービス、条件で収集する。同時に広告や外部報道があれば日付も示す。引用増加時点と本文、サービス、流入条件の変更時点を並べれば、次に比較する変数を選べる。
一つの回答を主張・出典・条件に分けて判定する
架空図書館の二回答を比べる。質問は今度の日曜日にいつ利用できるかで、基準日は第一日曜。現行案内には第一日曜休館が明記されている。
回答Aが日曜10〜17時に利用可能と言い現行案内へリンクすれば、引用はあるが休館例外を落としている。回答Bが根拠とともに今度の日曜は休館、次の開館日は別日程で確認すべきと言えば、条件を反映している。実際の判定では暦の基準日と引用原文の有効時点を併せて確認する。
無関係な外部サイトを対象サイトの引用に数えない。サイト全体と対象詳細文書の引用を分け、主張を支える箇所のないリンクは根拠不足とする。評価できない応答は理由を添えて分母から区別する。
出典による主張の裏づけと事実の正解を別集計する
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時だったと限定すれば、その過去主張は両方trueになり得る。ただし現在の答えがないのでtimely、relevant、completeはfalse。正しい文と十分な答えを分ける理由だ。
metrics.mjs、以下のassessment-fixture.mjs、metrics.test.mjsを同じフォルダーに保存しnode --test metrics.test.mjsを実行する。六テストは分母0、失敗・未確認、過去事実、一部主張だけの裏づけ、項目欠落、集計条件の分離を検査する。仮想入力は四試行、三完了、一失敗。裏づけは判定可能二つのうち一つ、未確認は一つだ。
// 回答評価用の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の分離値は、失敗を引用率一つに隠さず次の調査へつなぐ。
研究の可視性指標と運用指標を区別する
2024年GEO論文のGEO-benchは一万質問を訓練八千、検証千、試験千に分ける。基本実験はGoogle上位五出典から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
回答単位引用率は別計算だ。十回答中四つが文書を引用すれば40%。四つが末尾に一行ずつ引用しても、冒頭から複数文を引用しても率は同じ。量と位置に重みを付ける研究指標では得点が変わり得る。
サイトの引用文追加の効果を見るには、前後で同じ質問を実行する。引用回答数、引用文の量、質問に必要な例外の反映を一緒に比べる。モデルや検索モードが変われば別グループにする。
現在の役割と動画の例外条件を質問で検査する
組織の役割評価では、現在の図書館運営主体と2025年の開館準備組織を両方聞く。期待根拠は前者が現行紹介、後者が当時の議事録だ。現在の問いに準備委員会と答えれば、実在引用URLがあっても時点の誤り。過去の問いに運営チームと答えても同じだ。
誤りが出たら取得文書のID、適用期間、引用箇所を読む。現行紹介自体が旧役割なら原文を直す。原文は新役割で検索資料が旧公開版なら再クロールを直す。両方最新でも現在の問いに過去文書を選べば、時間条件と検索順位を調べる。回答への日付追加だけで原因調査を終えない。
動画実習の毎週日曜10〜17時に利用可能という答えは、0〜4秒の時間を読み、4〜8秒の休館例外を落とした。時間、反復条件、休館例外を主張別に分け、支える区間を記す。リンクが該当区間へ移動するか、区間原文が答えを支えるか、質問日が休館日かを順に照合する。