// What an answer's citations owe: every sentence that states something cites the passage it rests on, // and every ref it cites is one a search gave. Pure, so the reading of an answer is tested. // Headings, table rows, code, and short fragments state nothing to cite; a sentence saying the passages // do not hold something is what the prompt asks for when they do not, and cites nothing. const CITE = /\[(p\d+)\]/g const SAYS_NOT_FOUND = /\b(no passage|not (?:find|found)|couldn['’]?t find|could not find|did not find|didn['’]?t find|do(?:es)? not (?:say|hold|mention|contain)|not in the (?:passages|filings|store))\b/i const CLAIM_WORDS = 6 /** The sentences of an answer that state something and cite nothing. */ export function uncited(answer: string): string[] { const out: string[] = [] let fenced = false for (const raw of answer.split('\n')) { const line = raw.trim() if (line.startsWith('```')) { fenced = !fenced continue } if (fenced || !line || line.startsWith('#') || line.startsWith('|') || line.startsWith('>')) continue const body = line.replace(/^(?:[-*+]|\d+[.)])\s+/, '') for (const sentence of body.split(/(?<=[.!?])\s+(?=["“(*\p{Lu}\d])/u)) { const plain = sentence.replace(/\*\*?|__?|`/g, '').trim() if (CITE.test(sentence) || SAYS_NOT_FOUND.test(plain)) { CITE.lastIndex = 0 continue } CITE.lastIndex = 0 const words = plain.split(/\s+/).filter(Boolean).length if (words >= CLAIM_WORDS || (/\d/.test(plain) && words >= 3)) out.push(plain) } } return out } /** The refs an answer cites that no search gave. */ export function unknownRefs(answer: string, known: Set): string[] { const out = new Set() for (const match of answer.matchAll(CITE)) if (!known.has(match[1]!)) out.add(match[1]!) return [...out] } /** What the model is told when its answer owes citations. */ export function repairText(missing: string[], unknown: string[], refs: string[]): string { const lines = [ 'Every sentence that states a fact must cite the passage it rests on, like [p2]. This answer does not yet:', ...missing.map((s) => `- Not cited: "${s}"`), ...unknown.map((r) => `- [${r}] is not a passage you were given.`), `Write the whole answer again. Cite each of these from the passages you have (${refs.join(', ') || 'none yet'}), search for what they rest on, or leave them out.`, 'Reply with the answer alone, as the user will read it: say nothing about this rewrite, what you changed, or what you left out.', ] return lines.join('\n') } /** * An answer as it is copied: its words, then the passages it cites in the order it first cites them, * each with where it is from and its address, so a ref still says something once pasted elsewhere. */ export function withSources(answer: string, passages: { ref: string; label: string; url: string | null }[]): string { const byRef = new Map(passages.map((p) => [p.ref, p])) const cited = [...new Set([...answer.matchAll(CITE)].map((m) => m[1]!))].flatMap((ref) => byRef.get(ref) ?? []) if (cited.length === 0) return answer.trim() const lines = cited.map((p) => `[${p.ref}] ${p.label}${p.url ? ` ${p.url}` : ''}`) return `${answer.trim()}\n\nSources\n${lines.join('\n')}` }