Skip to content

Ask & abstain

Strict by default: the answer is generated only from retrieved passages, quoted verbatim in the source language, and every claim is checked against its citation. When nothing supports the question, CiteNexus refuses instead of guessing.

from citenexus.answer.result import Decision
response = rag.ask("Can the employee disclose this?") # mode=strict by default
if response.evidence.decision is Decision.answered:
src = response.sources[0]
print(response.answer, "", src.document, "p.", src.page)
else:
print("abstained:", response.answer) # the pinned refusal, not a fabrication
ask(question, *, mode=TrustMode.strict, k=None, answer_language=None,
conversation_id=None, strategy="strict", search_languages=("en",))
  • mode — the trust mode (below). Default TrustMode.strict.
  • k — per-call top_k override; falls back to the client’s top_k.
  • answer_language — force the answer’s language (see Languages).
  • conversation_id — thread turns together (see below).
  • strategy"strict" (default) is the single-passage flow. "deep" runs the bounded agentic loop, and raises UnsupportedSearchLanguageError if search_languages is anything other than the default, because the loop writes its own follow-up queries. Any other value raises ValueError.
  • search_languages — the languages the question is searched in; default ("en",). See Cross-lingual corpus.

Today the flow emits answered or refused (partial is a reserved Decision value). See The Result object for the full return shape.

When the decision is refused, missing_evidence says which of the six refusal paths you hit — thin retrieval, the faithfulness gate, the authority floor, an unclaimed script on the question or on the corpus, or a conflict. Read them off in Why did it abstain?.

Import: from citenexus.domain.trust import TrustMode

All three modes run the same extractive, per-claim-gated flow: the answer is quoted from one retrieved passage, every claim is verified against it, and an unsupported claim is dropped in every mode. Citations are never optional, and no mode summarises ungrounded evidence. What the mode actually changes is two things — the authority floor, and what happens when the retrieved passages contradict each other.

Mode Authority Conflicts
strict (default) Enforces the floor. Candidates below minimum_tier are dropped outright; if nothing survives, the call refuses for lack of standing. Abstains on a conflict touching the cited passage, and cites both sides.
normal Tie-break only — a stable sort by descending tier, nothing dropped. Answers, and lists the conflicts on result.conflicts.
exploratory Ignored — candidates pass through untouched. Answers, and records conflicts_detected only.

Conflicts are never resolved by rank (ADR-0007).

from citenexus.domain.trust import TrustMode
response = rag.ask("Summarize the risk factors.", mode=TrustMode.exploratory)

Pass a conversation_id and prior turns are folded into the retrieval query, and the new turn is remembered (bounded by memory_max_turns, default 20):

rag.ask("What notice does termination require?", conversation_id="thread-42")
rag.ask("And for a fixed-term contract?", conversation_id="thread-42") # follow-up in context

The same cite-or-abstain flow in all three ports. Python’s ask is the store-backed facade (the corpus was ingested first with rag.ingest(...)); Go and JavaScript take the corpus inline.

res := answer.Ask(corpus, "Can the employee disclose confidential information?", answer.DefaultTopK)
if res.Evidence.Decision == result.DecisionAnswered {
fmt.Println(res.Answer, "", res.Sources[0].Passage)
} else {
fmt.Println("abstained:", res.Answer) // the pinned refusal
}