Ask your documents. Get a cited answer — or an honest "not in the source." And verify it.
Most doc-chat tools always answer, even when the documents don't say anything, and ask you to trust that the citations are real. Sourcerer does two things they don't: it refuses when the sources don't support an answer, and it can check its own answer against those sources before you trust it.
import { ask } from 'sourcerer';
const passages = [
{ text: 'Standard shipping takes 3 to 5 business days within the continental United States.', source: 'Shipping Policy' },
{ text: 'Returns are accepted within 30 days of delivery for a full refund on unused items.', source: 'Returns Policy' },
];
await ask('How long does standard shipping take?', { passages });
// → { answer: 'Standard shipping takes 3 to 5 business days… [1]', refused: false, citations: [{ label: 'Shipping Policy' }] }
await ask('Do you offer a lifetime warranty?', { passages });
// → { answer: "I can't answer that from the provided sources — nothing supports it.",
// refused: true, reason: 'not_in_sources' }cite-or-refuse is a prompt instruction until something checks it. Turn on verification and Sourcerer
breaks its own answer into claims and confirms each is actually supported by the retrieved passages:
const res = await ask('How long does standard shipping take?', { passages, verify: true });
res.faithfulness; // → { faithful: true, unsupported: [], score: 1 }
// 'strict' turns an unfaithful answer into a refusal automatically
await ask(question, { passages, verify: 'strict' });Verification checks the answer against the passage text it was drawn from, so it needs
{ passages } or { retriever }. A grounding { endpoint } returns citations without passage
text, so verify on that path throws rather than silently pretending the answer was checked.
Passages (zero-infrastructure) — hand it { text, source }[] and it retrieves + answers:
await ask(question, { passages, k: 5, minScore: 0.2 });k must be a positive integer. minScore (default 0.2) is a retrieval floor: if the best passage
scores below it, Sourcerer refuses (reason: 'below_threshold') rather than answer off a single
common-word overlap. And a cited answer is enforced, not requested — the model must both list the
passages it used and carry a matching inline [n] marker, or the answer is refused
(reason: 'no_citation'). Citations are never fabricated from the top passages.
Your own retriever — anything async (question, k) => [{ text, source, score }]:
await ask(question, { retriever: myVectorSearch });A grounding endpoint — point at an existing retrieval/verification service that returns
{ text, citations, abstained }:
await ask(question, { endpoint: 'http://localhost:7700' }); // or SOURCERER_GROUNDING_URLShips with a zero-dependency keyword retriever (the default) and a semantic one backed by any
OpenAI-compatible /embeddings endpoint — embeddings are computed once and cached, and it falls back
to keyword if embeddings are unavailable. The keyword tokenizer is Unicode-aware, so Arabic, CJK, and
accented-Latin sources retrieve correctly:
import { embedRetriever } from 'sourcerer';
const retriever = embedRetriever({ passages, model: 'text-embedding-3-small' });
await ask(question, { retriever });Pass prior turns and follow-ups stay grounded:
const history = [
{ role: 'user', text: 'How long does standard shipping take?' },
{ role: 'assistant', text: 'Standard shipping takes 3 to 5 business days. [1]' },
];
await ask('And what about returns?', { passages, history });reason names why an answer was refused: not_in_sources (nothing retrieved), below_threshold
(top match under minScore), no_citation (the model gave no valid, in-range, inline-marked
citation), unsupported (the model declined), unfaithful (failed strict verification),
no_question, bad_k, or no_grounding.
npm install sourcerer
export SOURCERER_API_KEY=... # any OpenAI-compatible key (OpenRouter by default)
node example/demo.mjs
npm test # retrieval + refusal logic is unit-tested (no key needed)| Variable | Default |
|---|---|
SOURCERER_API_KEY |
(OPENROUTER_API_KEY also accepted) |
SOURCERER_ENDPOINT |
https://openrouter.ai/api/v1/chat/completions |
SOURCERER_MODEL |
google/gemini-3-flash-preview |
SOURCERER_EMBED_ENDPOINT / SOURCERER_EMBED_MODEL |
for embedRetriever |
SOURCERER_GROUNDING_URL |
optional external grounding service |
For anything where a wrong answer is worse than no answer — policy, doctrine, medicine, law — a model that confidently fills gaps is a liability. Sourcerer draws a hard line at the edge of what your sources say, and gives you the receipts to prove it stayed inside it.
Apache-2.0.
{ "answer": "…", // grounded answer with [1]-style inline cites "refused": false, // true when the sources don't support an answer "reason": null, "citations": [{ "label": "…", "source": "…" }], "faithfulness": { "faithful": true, "unsupported": [], "score": 1 }, // when verify is on "via": "retriever" }