ai-answer-citations 0.1.1

Extract the cited sources from an AI answer: HTML anchors, numbered markers, Markdown links, footnote definitions and bare URLs, then canonicalise and tally them per host.
Documentation
  • Coverage
  • 100%
    22 out of 22 items documented6 out of 10 items with examples
  • Size
  • Source code size: 48.1 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 273.8 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 6s Average build duration of successful builds.
  • all releases: 3s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • seeninaianswers/ai-answer-citations
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • theluckystrike

ai-answer-citations

Pull the cited sources out of an AI answer.

ChatGPT, Perplexity, Claude and Google AI Overviews all attach sources to the answers they write, but they attach them differently: an HTML anchor with a site-name chip, a numbered marker like [3] that links out, a Markdown link in a copied answer, a footnote definition at the bottom, or a bare URL in plain text. Anyone measuring which pages AI answers cite has to read all of those forms before counting anything. This crate reads them.

use ai_answer_citations::{extract, canonical, tally, chatgpt_tagged};

let answer = r#"<p>Boil the kettle first.<a href="https://example.com/tea?utm_source=chatgpt.com">[1]</a>
Also see [the guide](https://guide.example.org/tea) and https://example.com/tea.</p>"#;

let cites = extract(answer);
assert_eq!(cites.len(), 3);
assert_eq!(cites[0].marker, Some(1));
assert!(chatgpt_tagged(&cites[0].url));
assert_eq!(canonical(&cites[0].url), "https://example.com/tea");

let counts = tally(&cites);
assert_eq!(counts[0].host, "example.com");
assert_eq!(counts[0].citations, 2);

What it does

  • extract(text) returns every absolute http(s) link in document order, tagged with the form it was found in (HtmlAnchor, MarkdownLink, Footnote, BareUrl), its visible label, and the numeric marker when the label is one (1, [1], [^1], (1)). A URL inside an anchor or Markdown link is reported once, in the richer form.
  • canonical(url) lower-cases scheme and host, drops a default port and the fragment, and removes click-tracking query keys (utm_*, fbclid, gclid, srsltid, ...) so two citations of one page compare equal. Path case, the remaining query, and the trailing slash are kept: on a real server those can name different pages.
  • host(url) gives the lower-cased host without port, userinfo or a leading www..
  • chatgpt_tagged(url) reports the utm_source=chatgpt.com tag ChatGPT appends to the links it cites.
  • tally(&citations) counts citations per host; distinct_pages(&citations) lists canonical URLs in first-seen order.

What it does not do

It does not fetch anything, resolve relative links (an answer copied out of a chat has no base URL, and a relative link in one is almost never a citation), or carry a public-suffix list. It is not an HTML parser: it reads <a> tags and Markdown link syntax out of whatever string you hand it, which is what an exported answer is.

No dependencies, no unsafe.

Where it comes from

Written as the measurement side of Seen in AI Answers, a page engine built around the question of what AI answers cite. The engine shapes pages to be cited; this crate reads what an answer actually cited, so the two can be compared once a citation sample exists.

License

MIT OR Apache-2.0