Paste this into almost any article about leadership and nobody blinks:
"Be the change you wish to see in the world." — Mahatma Gandhi
There's no reliable source that Gandhi said it. The line isn't in his Collected Works; it's a modern condensation of his ideas. "Insanity is doing the same thing over and over again and expecting different results" has no Einstein source either; the earliest known printing is a 1981 Narcotics Anonymous text.
A language model answering from memory has a specific problem here. It has seen the popular credit far more often than the correction. I tested this on 22 October 2026: I gave Claude Sonnet (through the Claude Code CLI, with no tools) five well-known lines and asked "Who said each of these? Answer with the name only." It answered:
1. Ralph Waldo Emerson ("Do not follow where the path may lead…")
2. Mahatma Gandhi ("Be the change you wish to see in the world.")
3. Albert Einstein ("Insanity is doing the same thing over and over…")
4. Seneca ("Every new beginning comes from some other beginning's end.")
5. Peter Drucker ("The best way to predict the future is to create it.")
That's the popular credit for all five, and none of them holds up. To its credit, the model then added a note saying several were "commonly misattributed". But the note didn't name the real author of #1 (Muriel Strode) or #4 (Dan Wilson, who wrote it for Semisonic's "Closing Time"), and it called #4 a "Seneca-like paraphrase", which is wrong. The model could tell that something was off. In this run it couldn't say what was right, or show a source.
Now picture that inside an agent that writes captions or summarises articles, where only the name ends up in the output. That's where you want a lookup, not a memory.
I run GraciousQuotes, where every quote has a fact-checked record: a verdict, the reason for it, the source it was traced to, and a public correction log when we get one wrong. So I wrapped those records as LangChain tools, so the agent can look the quote up instead of remembering it. The package was listed in LangChain's integrations directory on 20 October 2026.
This post covers what the tools return, how to use them in an agent and in CI, and three design decisions that took longer than the code.
Install (no key, no sign-up)
pip install langchain-graciousquotes
from langchain_graciousquotes import QuoteAttributionCheck
check = QuoteAttributionCheck()
print(check.invoke({"quote": "Be the change you wish to see in the world"}))
Output, from the live API on 22 October 2026:
Match: exact wording
Verdict: Uncertain. It is often credited to Mahatma Gandhi, but there is no reliable source that Mahatma Gandhi said it.
Status: Disputed
Quote as checked: "Be the change you wish to see in the world."
Credit it as: Often attributed to Mahatma Gandhi
Why: Not in Gandhi's Collected Works; a modern condensation of his ideas.
Traced to: https://en.wikiquote.org/wiki/Mahatma_Gandhi#Misattributed
Record: https://graciousquotes.com/quotes-with-sources/mahatma-gandhi/#q3684
Source: Gracious Quotes (graciousquotes.com). Link the record above when you repeat this.
The answer comes from a reviewed record, so if a verdict changes, the output changes with it.
The three tools
| Tool | Name the model sees | What it does |
|---|---|---|
QuoteAttributionCheck |
check_quote_attribution |
Verdict for one quote: said by the credited person, uncertain, or credited to the wrong person. Includes the reason, the source and the record link. |
QuoteSearch |
search_checked_quotes |
Checked quotes by wording, author or verdict (verified, plausible, disputed, misattributed). |
QuoteCitation |
cite_quote |
MLA 9, APA 7 or Chicago 17 citation for a checked quote, with a warning when it's usually credited to someone else. |
In an agent:
from langchain.agents import create_agent
from langchain_graciousquotes import QuoteAttributionCheck, QuoteCitation, QuoteSearch
agent = create_agent(
"anthropic:claude-sonnet-5-5", # any tool-calling model
tools=[QuoteAttributionCheck(), QuoteSearch(), QuoteCitation()],
)
agent.invoke({"messages": [{"role": "user",
"content": "Did Einstein say the definition of insanity is doing the same thing over and over?"}]})
This needs LangChain 1.0+ and Python 3.10+. The tools work on their own from Python 3.9. Every tool also supports ainvoke.
Check every quote in a draft, with no model at all
You don't need an agent to use these. The more useful thing for me turned out to be a plain script: examples/check_draft.py finds the quotes in a Markdown file or a live URL, reads the name the article gives each one ("Emerson told us", "— Seneca"), and checks both.
Here's the run on the repo's sample draft, 22 October 2026 (trimmed):
4 to fix · 1 unverified · 2 OK
FIX "Do not follow where the path may lead. Go instead where there is no path and leave a trail."
Your draft credits: Ralph Waldo Emerson
Record: Re-credited. No: Ralph Waldo Emerson did not say it. It comes from Muriel Strode.
Fix: Credit Muriel Strode instead of Ralph Waldo Emerson.
FIX "The only thing we have to fear is fear itself."
Your draft credits: Mark Twain
Record: Verified. Yes: President Franklin D. Roosevelt said it.
Fix: Credit President Franklin D. Roosevelt instead of Mark Twain. Source: First Inaugural Address (1933).
FIX "The best way to predict the future is to create it."
Your draft credits: Peter Drucker
Fix: No reliable source shows Peter Drucker said it. Write "often attributed to Peter Drucker", or cut it.
UNVERIFIED "The quiet hours before dawn belong to people who keep their own promises."
Fix: No checked record. Keep the quote only if you can cite where it first appeared.
OK "insanity is doing the same thing over and over again and expecting different results."
Your draft credits: Albert Einstein (hedged)
Note: Fine as written: you already present it as an attribution, which matches the record.
The Emerson line is a good example of why this is worth automating: it is widely credited to Emerson, but it comes from Muriel Strode's poem "Wind-Wafted Wild Flowers", printed in The Open Court in August 1903.
The script exits with code 1 when something needs fixing (--strict also fails on CHECK and UNVERIFIED), so it can block a publish step:
python examples/check_draft.py content/posts/new-post.md || exit 1
Note the last OK. The draft says "often attributed to Einstein", which matches the record, so it passes. A checker that flags every hedged attribution gets switched off within a week.
Three design decisions
1. "No record" is not a verdict
The easy failure is a tool that returns nothing, after which the model fills the gap from memory and sounds just as confident. When there's no checked record, the tool says so explicitly and tells the model not to present a remembered author as confirmed. You can see it in the UNVERIFIED line above. It's the same reason a missing value in a spreadsheet shouldn't be read as zero.
2. The tool description is the model's prompt, so be honest about the limits
The author filter in search_checked_quotes matches the person who really said the quote. That means "show me quotes wrongly credited to Mark Twain" can't be answered by searching for author="Mark Twain"; those records are filed under the real author. I found this while testing, and the tool description now says it directly:
The author filter matches the person who really said it, so it cannot list quotes wrongly credited to someone; use check_quote_attribution for a specific quote instead.
If you only document a limit in the README, the model never reads it. Put it in the description.
3. Every result carries its own credit line
Each result ends with Source: Gracious Quotes … Link the record above when you repeat this. That's a terms of use condition, and it also works in the user's favour: a quote verdict without a link to its evidence is just another claim. With the record link, the reader can check the source themselves.
Testing against LangChain's own conformance suite
The package passes langchain-tests (ToolsUnitTests and ToolsIntegrationTests, sync and async) along with its own behaviour tests. Two things worth knowing if you build a tool package:
-
Set
asyncio_mode = "auto"in your pytest config. Without it, pytest skips the async cases and the run still looks like a pass. - CI runs with no API key on purpose. The API is keyless, so a green build shows that the advertised zero-setup path works for a stranger, not just on my machine. A scheduled run also catches the live API drifting.
A separate CI job builds the create_agent example on Python 3.13 so the guide's code can't quietly break.
Getting listed: file through the web form
LangChain doesn't accept integration PRs. You file an "Integration listing" issue on langchain-ai/docs, a maintainer applies a label, and a bot opens the docs PR. One trap from my first listing (a sister project): an issue created with gh issue create never triggers the automation. It doesn't get the template's labels, so the bot never sees it. Filed again through the web form, it went through in days. For this package: issue filed 9 October, approved and merged on 20 October.
Limits, stated plainly
- Coverage is what we've checked. Anything outside the records comes back UNVERIFIED rather than guessed.
-
Name detection in
check_draft.pyis a heuristic. When it isn't sure who a draft credits, it reports CHECK, not a firm verdict. - Verdicts can change. When new evidence turns up we update the record and log it on the public corrections page. The full method is on How we source quotes.
- Rate limit: 40 requests per 10 seconds per IP. A tool that hits it says so in its result instead of raising.
- Bulk copying or training on the records needs a licence. Fetching records because a user asked is fine, with the credit.
Links
- Guide and API reference: graciousquotes.com/developers/langchain/
- Source (MIT): github.com/graciousquotes/langchain-graciousquotes
- PyPI: langchain-graciousquotes
- OpenAPI 3.1: graciousquotes.com/developers/openapi.json
- Cite it: Say, J. (2026). langchain-graciousquotes [Computer software]. Zenodo. doi.org/10.5281/zenodo.23258870
If you paste in a quote and think our verdict is wrong, open an issue with your source. Corrections with a primary source are the most useful thing anyone can send us.
I'm Jeremiah Say, founder and editor of GraciousQuotes. I research quote attributions against original books, speeches and letters, and I maintain this package. I wrote earlier about how the site matches misremembered quotes.













