Wauldo verification as NeMo Guardrails rails (fact-check + citations)
Project description
wauldo-nemo
Independent answer verification as NeMo Guardrails output rails, powered by Wauldo. Every bot message is checked against its retrieved context claim-by-claim: unsupported answers are refused, weakly-supported ones are annotated — with the evidence for each verdict.
This is a thin adapter. All verdict logic lives in the
wauldo SDK; this repo only maps a
verification result to a rail decision and never re-implements parsing.
Why not NeMo's built-in self check facts?
NeMo's built-in self-check rail asks the same generation LLM "is this true?" — same-model bias, no sources, no granularity. wauldo-nemo calls an independent verification service that returns:
- claim-level verdicts (which specific claim failed),
- the evidence in your sources behind each verdict,
- a numeric hallucination rate and per-claim confidence,
- a fast rule-based
lexicalmode (sub-second) plushybrid/semantic.
The policy is honesty-bound: thresholds may only make the rail stricter, never override the verification service into a more lenient verdict.
Use case
A RAG support bot retrieves policy docs and answers "Can I return this after
60 days?". The model says "Yes, returns are accepted within 60 days." but
the sources say 14 days. The output rail fact-checks the answer against the
retrieved context, sees a numerical_mismatch, and refuses the answer
before the user ever sees it — surfacing the exact claim and the contradicting
evidence to your logs.
[refuse] verdict=rejected halluc=1.00
claim : "returns are accepted within 60 days" supported=false
evidence: "Our return policy: 14 days." reason=numerical_mismatch
Install
pip install 'wauldo-nemo[nemo]'
export WAULDO_API_KEY=tig_live_...
Pulls
wauldo[async]>=0.19.1automatically (the rails use the async client so they never block NeMo's event loop).
⚠️ Wauldo is the output rail, not the main LLM
Keep your generation model (type: main) a standard provider — OpenAI,
Anthropic, a local model. Do not set Wauldo as the main LLM. Wauldo is a
guarded API: its prompt-injection classifier rejects NeMo's internal Colang
dialog meta-prompts with 403 security_violation. Wauldo's role is
verification (the output rail), not generation.
How it works
bot message ──▶ wauldo_fact_check action ──▶ AsyncHttpClient.fact_check(text, context)
│
▼
policy.decide(result, thresholds)
│
allow ──▶ pass review ──▶ annotate block ──▶ refuse
Two rails are available:
| Rail | Action | Checks |
|---|---|---|
| Fact-check | wauldo_fact_check |
claims are supported by the context |
| Citations | wauldo_verify_citations |
the answer actually cites its sources |
Quickstart — verify against the live API (no generation LLM)
The fastest way to confirm the rail works, driven through NeMo's real runtime:
export WAULDO_API_KEY=tig_live_...
python examples/run_example.py
# [hallucinated] decision=refuse verdict=rejected halluc=1.00
# [grounded] decision=allow verdict=verified halluc=0.00
Production usage
from nemoguardrails import LLMRails, RailsConfig
from wauldo_nemo import register, RailConfig, PolicyThresholds, RailDecision
rails = LLMRails(RailsConfig.from_path("examples/config")) # main: openai etc.
register(
rails,
config=RailConfig(
thresholds=PolicyThresholds(strict=True, max_hallucination_rate=0.4),
on_missing_context=RailDecision.ANNOTATE, # no context → can't verify
on_error=RailDecision.PASS, # Wauldo outage → fail-open
timeout=8.0,
),
verify_citations=True, # also enable the citation rail
)
Configuration (RailConfig)
| Field | Default | Effect |
|---|---|---|
thresholds.min_confidence |
0.0 |
Below it, an allow is downgraded to annotate. |
thresholds.max_hallucination_rate |
1.0 |
Above it, the response is refused. |
thresholds.strict |
False |
A server review becomes a refusal. |
mode |
"lexical" |
lexical (fast) / hybrid / semantic. |
on_missing_context |
ANNOTATE |
No context to verify against → can't fact-check. |
on_error |
PASS |
Wauldo unreachable → fail-open (flag) vs REFUSE (fail-closed). |
timeout |
8.0 |
Timeout (seconds) on a single verification attempt. |
max_retries |
1 |
Attempts before applying on_error. Default 1 = fail fast: a guardrail is in the hot path, so retrying a down Wauldo with backoff would add latency to every response. With the default, timeout is the real latency bound. |
min_citation_ratio |
0.5 |
Citation rail: minimum cited-sentence ratio. |
on_insufficient_citations |
ANNOTATE |
Citation rail decision when under-cited. |
Environment: WAULDO_API_KEY (required), WAULDO_BASE_URL (defaults to
https://api.wauldo.com).
Troubleshooting
403 security_violationduring generation — you set Wauldo as themainmodel. Use a standard provider for generation; Wauldo is the output rail.- Answers always
annotatewithnote=no_context— the rail received no context. Passsource_context=$relevant_chunksin the flow, or set your retriever's$relevant_chunks(the action auto-reads it). NB: the argument issource_context, notcontext—contextis reserved by NeMo.
Develop
pip install -e '.[dev]'
pytest # policy + action tests, no guardrails runtime needed
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wauldo_nemo-0.2.1.tar.gz.
File metadata
- Download URL: wauldo_nemo-0.2.1.tar.gz
- Upload date:
- Size: 13.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8f69cb69bc036828bda6e236053f0f94827ab04c32a4ffa61059443815239777
|
|
| MD5 |
b7e41ef7e9b1f539d08524abeefb0293
|
|
| BLAKE2b-256 |
629c986fb1a7ad4115d3fe5238d08d27851469ed786b580b98159f63fff65755
|
Provenance
The following attestation bundles were made for wauldo_nemo-0.2.1.tar.gz:
Publisher:
publish.yml on wauldoai/wauldo-nemo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wauldo_nemo-0.2.1.tar.gz -
Subject digest:
8f69cb69bc036828bda6e236053f0f94827ab04c32a4ffa61059443815239777 - Sigstore transparency entry: 1757944439
- Sigstore integration time:
-
Permalink:
wauldoai/wauldo-nemo@e18fe9e0683578c6b9fc07528acfad3653746543 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/wauldoai
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e18fe9e0683578c6b9fc07528acfad3653746543 -
Trigger Event:
push
-
Statement type:
File details
Details for the file wauldo_nemo-0.2.1-py3-none-any.whl.
File metadata
- Download URL: wauldo_nemo-0.2.1-py3-none-any.whl
- Upload date:
- Size: 11.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c390f3a1b7a3ed1b0b37235c8ffa3a92b02ab137885353aa12b3175dfd77817
|
|
| MD5 |
850bd572801a02f217255ab1fc96a831
|
|
| BLAKE2b-256 |
61ffcb163b9a74a195b5bbe441847672c62c1cf37f0d049fda3c5bf415dc8bd5
|
Provenance
The following attestation bundles were made for wauldo_nemo-0.2.1-py3-none-any.whl:
Publisher:
publish.yml on wauldoai/wauldo-nemo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wauldo_nemo-0.2.1-py3-none-any.whl -
Subject digest:
6c390f3a1b7a3ed1b0b37235c8ffa3a92b02ab137885353aa12b3175dfd77817 - Sigstore transparency entry: 1757944546
- Sigstore integration time:
-
Permalink:
wauldoai/wauldo-nemo@e18fe9e0683578c6b9fc07528acfad3653746543 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/wauldoai
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e18fe9e0683578c6b9fc07528acfad3653746543 -
Trigger Event:
push
-
Statement type: