LangChain: check a quote before citing it
Last updated: 2026-10-10
A LangChain agent with two AttestPage tools: verify_quote (US$0.01 a call) and check_links (US$0.005 per batch of up to 10 URLs). The client below uses only the Python standard library. It calls the free trial; when a call needs payment it returns the price to the agent instead of raising, so the agent can say what the call would cost. To pay per call, see fetch-x402.md or claude-agent-sdk.md. For JavaScript, see LangChain in JavaScript below: its client can pay per call.
Install
Python 3.10 or later.
pip install "langchain>=1.0" langchain-anthropic
export ANTHROPIC_API_KEY=...
Any chat model LangChain supports works; set MODEL (for example openai:gpt-5) and install its package.
attestpage.py
"""AttestPage client for agent tools (Python standard library only).
Sends the free-trial header (Quoteproof-Trial: 1). When the trial does not cover a call (a batch over
3 URLs gets 402; a trial used up for the day gets 429, and this asks again without it for the 402), this
returns {"payment_required": True, "price_usd": ..., "network": ...} instead of raising.
"""
import base64
import json
import os
import urllib.error
import urllib.request
BASE_URL = os.environ.get("ATTESTPAGE_URL", "https://vehcdj664efetfrsolne5umanq.srv.us").rstrip("/")
def _offer(header):
# PAYMENT-REQUIRED is base64 JSON; amounts are USDC atomic units (6 decimals).
try:
first = json.loads(base64.b64decode(header))["accepts"][0]
return {"price_usd": int(first["amount"]) / 1_000_000, "network": first["network"]}
except (ValueError, KeyError, IndexError, TypeError):
return {}
def call(path, body, trial=True, timeout=60):
"""POST body to path; returns the JSON answer, a payment_required dict or an error dict."""
headers = {"content-type": "application/json", "accept": "application/json"}
if trial:
headers["quoteproof-trial"] = "1"
req = urllib.request.Request(BASE_URL + path, data=json.dumps(body).encode(), headers=headers, method="POST")
try:
with urllib.request.urlopen(req, timeout=timeout) as res:
return json.loads(res.read())
except urllib.error.HTTPError as e:
try:
err = json.loads(e.read()).get("error", {})
except ValueError:
err = {}
if e.code == 429 and trial and err.get("code") == "trial_exhausted":
return call(path, body, trial=False, timeout=timeout) # ask again without the trial, for the price
if e.code == 402:
return {"payment_required": True, **_offer(e.headers.get("payment-required", "")),
"message": "This call needs an x402 payment; see /docs/payments."}
return {"error": err.get("code", "http_%d" % e.code), "status": e.code, "message": err.get("message", "")}
def verify_quote(url, quote, match="fuzzy"):
return call("/v1/verify/quote", {"url": url, "quote": quote, "match": match})
def check_links(urls):
return call("/v1/check/links", {"urls": list(urls)})
def verify_receipt(receipt):
return call("/v1/receipt/verify", {"receipt": receipt}, trial=False)
agent.py
"""LangChain agent that checks a quote with AttestPage before it cites it."""
import os
import sys
from langchain.agents import create_agent
from langchain.tools import tool
import attestpage
@tool
def verify_quote(url: str, quote: str) -> dict:
"""Use before quoting a web page: is this exact or near-exact quote on the page at url?
Returns result.match (exact, fuzzy or none), page.content_kind and a signed receipt."""
return attestpage.verify_quote(url, quote)
@tool
def check_links(urls: list[str]) -> dict:
"""Use before putting links in an answer: status, final URL and content_kind for each URL (up to 3 on the trial)."""
return attestpage.check_links(urls)
agent = create_agent(
model=os.environ.get("MODEL", "anthropic:claude-sonnet-5-5"),
tools=[verify_quote, check_links],
system_prompt=(
"Before you quote a web page, call verify_quote. Cite a quote only when result.match is exact or fuzzy "
"and page.content_kind is real. If a tool returns payment_required, give the price and stop."
),
)
if __name__ == "__main__":
question = " ".join(sys.argv[1:]) or (
'Does https://www.iana.org/help/example-domains say '
'"These domains may be used as illustrative examples in documents"?'
)
result = agent.invoke({"messages": [{"role": "user", "content": question}]})
print(result["messages"][-1].content)
Run
python agent.py
python agent.py 'Is "Example Domain" the title of https://example.com?'
What comes back
verify_quote returns the route's JSON. The parts an agent needs:
result.match:exact,fuzzyornone, withresult.scorefrom 0 to 1.page.content_kind: read it before trustingmatch. Anoneon abot_wallorjs_shellpage means the page could not be read, not that the quote is missing.receipt: keep it with the answer.attestpage.verify_receipt(receipt)checks it for free.
On a paraphrased claim, call attestpage.verify_quote(url, claim, match="passages"): it returns the 3 passages that share most of the claim's words, as evidence to read, not a verdict.
LangChain in JavaScript
The same agent in JavaScript. It uses the attestpage.mjs client from openai-agents-sdk.md: with no key it calls the free trial, and with ATTESTPAGE_X402_KEY set it pays each call with x402 on Base Sepolia, a test network, in test USDC.
Node.js 20 or later. Save the client as attestpage.mjs next to langchain.mjs.
npm init -y && npm pkg set type=module
npm install langchain @langchain/core @langchain/anthropic zod
npm install @x402/fetch @x402/evm viem # only needed to pay
export ANTHROPIC_API_KEY=...
langchain.mjs
// LangChain.js agent that checks a quote with AttestPage before it cites it.
import { createAgent, tool } from 'langchain';
import * as z from 'zod';
import { createClient } from './attestpage.mjs';
const attestpage = await createClient();
// Tools return the route's JSON as text.
const verifyQuote = tool(async ({ url, quote }) => JSON.stringify(await attestpage.verifyQuote({ url, quote })), {
name: 'verify_quote',
description: 'Use before quoting a web page: is this exact or near-exact quote on the page at url? '
+ 'Returns result.match (exact, fuzzy or none), page.content_kind and a signed receipt. US$0.01.',
schema: z.object({ url: z.string(), quote: z.string() }),
});
const checkLinks = tool(async ({ urls }) => JSON.stringify(await attestpage.checkLinks({ urls })), {
name: 'check_links',
description: 'Use before putting links in an answer: status, final URL and content_kind for each URL. '
+ 'Up to 10 URLs (3 on the free trial). US$0.005 per batch.',
schema: z.object({ urls: z.array(z.string()).min(1).max(10) }),
});
const agent = createAgent({
model: process.env.MODEL || 'anthropic:claude-sonnet-5-5',
tools: [verifyQuote, checkLinks],
systemPrompt: 'Before you quote a web page, call verify_quote. Cite a quote only when result.match is exact or fuzzy '
+ 'and page.content_kind is real. If a tool returns payment_required, give the price and stop.',
});
const question = process.argv.slice(2).join(' ')
|| 'Does https://www.iana.org/help/example-domains say "These domains may be used as illustrative examples in documents"?';
const result = await agent.invoke({ messages: [{ role: 'user', content: question }] });
console.log(result.messages.at(-1).content);
node langchain.mjs # free trial
ATTESTPAGE_X402_KEY=0x... node langchain.mjs # pays US$0.01 per verify_quote in test USDC